From 1cf9c8a7682ab78dddf8749abe43401b14f504a3 Mon Sep 17 00:00:00 2001
From: SoftFever <103989404+SoftFever@users.noreply.github.com>
Date: Mon, 9 Oct 2023 22:57:40 +0800
Subject: [PATCH 01/18] measure tool init port
---
resources/images/copy_menu.svg | 37 +
resources/images/measure.svg | 93 +
src/imgui/imconfig.h | 3 +
src/libslic3r/BuildVolume.hpp | 4 +
src/libslic3r/CMakeLists.txt | 6 +
src/libslic3r/Color.cpp | 408 ++++
src/libslic3r/Color.hpp | 193 ++
src/libslic3r/Geometry.cpp | 47 +
src/libslic3r/Geometry.hpp | 19 +
src/libslic3r/Geometry/Circle.cpp | 4 +-
src/libslic3r/Geometry/Circle.hpp | 2 +-
src/libslic3r/Measure.cpp | 1241 ++++++++++
src/libslic3r/Measure.hpp | 196 ++
src/libslic3r/MeasureUtils.hpp | 386 +++
src/libslic3r/Point.hpp | 1 +
src/libslic3r/SurfaceMesh.hpp | 163 ++
src/slic3r/CMakeLists.txt | 6 +
src/slic3r/GUI/GLCanvas3D.cpp | 13 +-
src/slic3r/GUI/GLCanvas3D.hpp | 23 +
src/slic3r/GUI/GLModel.cpp | 614 +++++
src/slic3r/GUI/GLModel.hpp | 149 ++
src/slic3r/GUI/GLShader.cpp | 137 ++
src/slic3r/GUI/GLShader.hpp | 26 +
src/slic3r/GUI/GUI_Geometry.cpp | 9 +
src/slic3r/GUI/GUI_Geometry.hpp | 78 +
src/slic3r/GUI/GUI_ObjectManipulation.cpp | 1300 ++++++++++
src/slic3r/GUI/GUI_ObjectManipulation.hpp | 250 ++
src/slic3r/GUI/GUI_Utils.hpp | 10 +
src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 26 +
src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 32 +-
src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp | 2 +-
src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp | 2142 +++++++++++++++++
src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp | 188 ++
.../GUI/Gizmos/GLGizmoMmuSegmentation.hpp | 2 +-
src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp | 2 +-
src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp | 4 +
src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 37 +-
src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 +
src/slic3r/GUI/ImGuiWrapper.cpp | 98 +-
src/slic3r/GUI/ImGuiWrapper.hpp | 11 +-
src/slic3r/GUI/MeshUtils.cpp | 37 +-
src/slic3r/GUI/MeshUtils.hpp | 28 +-
src/slic3r/GUI/SceneRaycaster.cpp | 292 +++
src/slic3r/GUI/SceneRaycaster.hpp | 117 +
src/slic3r/GUI/Selection.cpp | 19 +
src/slic3r/GUI/Selection.hpp | 58 +-
46 files changed, 8433 insertions(+), 81 deletions(-)
create mode 100644 resources/images/copy_menu.svg
create mode 100644 resources/images/measure.svg
create mode 100644 src/libslic3r/Color.cpp
create mode 100644 src/libslic3r/Color.hpp
create mode 100644 src/libslic3r/Measure.cpp
create mode 100644 src/libslic3r/Measure.hpp
create mode 100644 src/libslic3r/MeasureUtils.hpp
create mode 100644 src/libslic3r/SurfaceMesh.hpp
create mode 100644 src/slic3r/GUI/GUI_Geometry.cpp
create mode 100644 src/slic3r/GUI/GUI_Geometry.hpp
create mode 100644 src/slic3r/GUI/GUI_ObjectManipulation.cpp
create mode 100644 src/slic3r/GUI/GUI_ObjectManipulation.hpp
create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
create mode 100644 src/slic3r/GUI/SceneRaycaster.cpp
create mode 100644 src/slic3r/GUI/SceneRaycaster.hpp
diff --git a/resources/images/copy_menu.svg b/resources/images/copy_menu.svg
new file mode 100644
index 00000000000..0d1af6a0a7c
--- /dev/null
+++ b/resources/images/copy_menu.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/resources/images/measure.svg b/resources/images/measure.svg
new file mode 100644
index 00000000000..3ea137a1ed9
--- /dev/null
+++ b/resources/images/measure.svg
@@ -0,0 +1,93 @@
+
+
+
+
diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h
index 1774eb28ac5..2bd30184a56 100644
--- a/src/imgui/imconfig.h
+++ b/src/imgui/imconfig.h
@@ -197,6 +197,9 @@ namespace ImGui
const wchar_t CloseBlockNotifHoverButton = 0x0834;
const wchar_t BlockNotifErrorIcon = 0x0835;
+ const wchar_t ClipboardBtnIcon = 0x2606;
+
+
// void MyFunction(const char* name, const MyMatrix44& v);
}
diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp
index 4ab007f8b8c..cfb45371529 100644
--- a/src/libslic3r/BuildVolume.hpp
+++ b/src/libslic3r/BuildVolume.hpp
@@ -93,6 +93,10 @@ class BuildVolume
// Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices.
bool all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& bbox, bool ignore_bottom = true) const;
+
+ const std::pair, std::vector>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; }
+ const std::pair, std::vector>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; }
+
private:
// Source definition of the print bed geometry (PrintConfig::printable_area)
std::vector m_bed_shape;
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 3365a3e6bea..8498692a0e0 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -47,6 +47,8 @@ set(lisbslic3r_sources
Clipper2Utils.hpp
Config.cpp
Config.hpp
+ Color.hpp
+ Color.cpp
CurveAnalyzer.cpp
CurveAnalyzer.hpp
EdgeGrid.cpp
@@ -195,6 +197,9 @@ set(lisbslic3r_sources
ModelArrange.cpp
MultiMaterialSegmentation.cpp
MultiMaterialSegmentation.hpp
+ Measure.hpp
+ Measure.cpp
+ MeasureUtils.hpp
CustomGCode.cpp
CustomGCode.hpp
Arrange.hpp
@@ -294,6 +299,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 00000000000..ed4441dbfd3
--- /dev/null
+++ b/src/libslic3r/Color.cpp
@@ -0,0 +1,408 @@
+#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(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) })
+{
+}
+ColorRGBA::ColorRGBA(const std::array &data) : m_data(data) {}
+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] = 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() == '#'; }
+
+bool decode_color(const std::string& color_in, ColorRGB& 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 = ColorRGB::BLACK();
+ if (can_decode_color(color_in)) {
+ const char* c = color_in.data() + 1;
+ 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
+ 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);
+ return true;
+}
+
+bool decode_color(const std::string& color_in, ColorRGBA& color_out)
+{
+ ColorRGB rgb;
+ if (!decode_color(color_in, rgb))
+ return false;
+
+ color_out = to_rgba(rgb, color_out.a());
+ 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 00000000000..60b6af9f7c5
--- /dev/null
+++ b/src/libslic3r/Color.hpp
@@ -0,0 +1,193 @@
+#ifndef slic3r_Color_hpp_
+#define slic3r_Color_hpp_
+
+#include
+#include
+
+#include "Point.hpp"
+
+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 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(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+ ColorRGBA(const std::array& data);
+ 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& data_array() 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 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);
+
+// Color mapping of a value into RGB false colors.
+inline Vec3f value_to_rgbf(float minimum, float maximum, float value)
+{
+ float ratio = 2.0f * (value - minimum) / (maximum - minimum);
+ float b = std::max(0.0f, (1.0f - ratio));
+ float r = std::max(0.0f, (ratio - 1.0f));
+ float g = 1.0f - b - r;
+ return Vec3f { r, g, b };
+}
+
+// Color mapping of a value into RGB false colors.
+inline Vec3i value_to_rgbi(float minimum, float maximum, float value)
+{
+ return (value_to_rgbf(minimum, maximum, value) * 255).cast();
+}
+
+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.cpp b/src/libslic3r/Geometry.cpp
index a2c59ec17d5..5a17e7a0afe 100644
--- a/src/libslic3r/Geometry.cpp
+++ b/src/libslic3r/Geometry.cpp
@@ -409,6 +409,20 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, doubl
}
}
+static Transform3d extract_rotation_matrix(const Transform3d& trafo)
+{
+ Matrix3d rotation;
+ Matrix3d scale;
+ trafo.computeRotationScaling(&rotation, &scale);
+ return Transform3d(rotation);
+}
+
+void translation_transform(Transform3d& transform, const Vec3d& translation)
+{
+ transform = Transform3d::Identity();
+ transform.translate(translation);
+}
+
Transform3d translation_transform(const Vec3d &translation)
{
Transform3d transform = Transform3d::Identity();
@@ -416,6 +430,12 @@ Transform3d translation_transform(const Vec3d &translation)
return transform;
}
+void rotation_transform(Transform3d& transform, const Vec3d& rotation)
+{
+ transform = Transform3d::Identity();
+ transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX()));
+}
+
Transform3d rotation_transform(const Vec3d& rotation)
{
Transform3d transform = Transform3d::Identity();
@@ -423,6 +443,29 @@ Transform3d rotation_transform(const Vec3d& rotation)
return transform;
}
+void scale_transform(Transform3d& transform, double scale)
+{
+ return scale_transform(transform, scale * Vec3d::Ones());
+}
+
+void scale_transform(Transform3d& transform, const Vec3d& scale)
+{
+ transform = Transform3d::Identity();
+ transform.scale(scale);
+}
+
+Transform3d scale_transform(double scale)
+{
+ return scale_transform(scale * Vec3d::Ones());
+}
+
+Transform3d scale_transform(const Vec3d& scale)
+{
+ Transform3d transform;
+ scale_transform(transform, scale);
+ return transform;
+}
+
Transformation::Flags::Flags()
: dont_translate(true)
, dont_rotate(true)
@@ -469,6 +512,10 @@ void Transformation::set_offset(Axis axis, double offset)
m_dirty = true;
}
}
+Transform3d Transformation::get_rotation_matrix() const
+{
+ return extract_rotation_matrix(m_matrix);
+}
void Transformation::set_rotation(const Vec3d& rotation)
{
diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp
index 8eb6195a107..4d75217182a 100644
--- a/src/libslic3r/Geometry.hpp
+++ b/src/libslic3r/Geometry.hpp
@@ -348,15 +348,32 @@ Vec3d extract_euler_angles(const Transform3d& transform);
// Euler angles can be obtained by extract_euler_angles()
void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix = nullptr);
+// Sets the given transform by assembling the given translation
+void translation_transform(Transform3d& transform, const Vec3d& translation);
+
// Returns the transform obtained by assembling the given translation
Transform3d translation_transform(const Vec3d &translation);
+// Sets the given transform by assembling the given rotations in the following order:
+// 1) rotate X
+// 2) rotate Y
+// 3) rotate Z
+void rotation_transform(Transform3d& transform, const Vec3d& rotation);
+
// Returns the transform obtained by assembling the given rotations in the following order:
// 1) rotate X
// 2) rotate Y
// 3) rotate Z
Transform3d rotation_transform(const Vec3d &rotation);
+// Sets the given transform by assembling the given scale factors
+void scale_transform(Transform3d& transform, double scale);
+void scale_transform(Transform3d& transform, const Vec3d& scale);
+
+// Returns the transform obtained by assembling the given scale factors
+Transform3d scale_transform(double scale);
+Transform3d scale_transform(const Vec3d& scale);
+
class Transformation
{
struct Flags
@@ -397,6 +414,8 @@ class Transformation
const Vec3d& get_rotation() const { return m_rotation; }
double get_rotation(Axis axis) const { return m_rotation(axis); }
+ Transform3d get_rotation_matrix() const;
+
void set_rotation(const Vec3d& rotation);
void set_rotation(Axis axis, double rotation);
diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp
index 4d7c38ccc23..67966719549 100644
--- a/src/libslic3r/Geometry/Circle.cpp
+++ b/src/libslic3r/Geometry/Circle.cpp
@@ -108,7 +108,7 @@ 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();
@@ -132,6 +132,8 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations)
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 39973d916d9..653102e2abd 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 00000000000..08c17df47be
--- /dev/null
+++ b/src/libslic3r/Measure.cpp
@@ -0,0 +1,1241 @@
+#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 TriangleMesh& get_mesh() const;
+
+private:
+ void update_planes();
+ void extract_features(int plane_idx);
+
+ std::vector m_planes;
+ std::vector m_face_to_plane;
+ TriangleMesh m_mesh;
+};
+
+
+
+
+
+
+MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its)
+: m_mesh(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_mesh.its);
+ const std::vector face_neighbors = its_face_neighbors(m_mesh.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);
+ };
+
+ // 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.
+ SurfaceMesh sm(m_mesh.its);
+ for (int plane_id=0; plane_id < int(m_planes.size()); ++plane_id) {
+ const auto& facets = m_planes[plane_id].facets;
+ m_planes[plane_id].borders.clear();
+ std::vector> visited(facets.size(), {false, false, false});
+
+
+
+ for (int face_id=0; face_id& last_border = m_planes[plane_id].borders.back();
+ 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 ( (int)m_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)
+ m_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:
+ m_planes[plane_id].borders.clear();
+ }
+}
+
+
+
+
+
+
+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 TriangleMesh& MeasuringImpl::get_mesh() const
+{
+ return this->m_mesh;
+}
+
+
+
+
+
+
+
+
+
+
+
+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 TriangleMesh& Measuring::get_mesh() const
+{
+ return priv->get_mesh();
+}
+
+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;
+}
+
+
+
+
+
+
+
+
+} // namespace Measure
+} // namespace Slic3r
+
diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp
new file mode 100644
index 00000000000..a273135ca8b
--- /dev/null
+++ b/src/libslic3r/Measure.hpp
@@ -0,0 +1,196 @@
+#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} {}
+
+ // 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 TriangleMesh& get_mesh() 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 00000000000..0ab4ac121d8
--- /dev/null
+++ b/src/libslic3r/MeasureUtils.hpp
@@ -0,0 +1,386 @@
+#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 dacf49b0803..47750d9e688 100644
--- a/src/libslic3r/Point.hpp
+++ b/src/libslic3r/Point.hpp
@@ -42,6 +42,7 @@ using Vec3i64 = Eigen::Matrix;
// Vector types with a double coordinate base type.
using Vec2f = Eigen::Matrix;
using Vec3f = Eigen::Matrix;
+using Vec4f = Eigen::Matrix;
using Vec2d = Eigen::Matrix;
using Vec3d = Eigen::Matrix;
// BBS
diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp
new file mode 100644
index 00000000000..93eb9fdaa6e
--- /dev/null
+++ b/src/libslic3r/SurfaceMesh.hpp
@@ -0,0 +1,163 @@
+#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/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 61dc8c9491f..331e89128bd 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -99,6 +99,8 @@ set(SLIC3R_GUI_SOURCES
GUI/GLShader.hpp
GUI/GLCanvas3D.hpp
GUI/GLCanvas3D.cpp
+ GUI/SceneRaycaster.hpp
+ GUI/SceneRaycaster.cpp
GUI/OpenGLManager.hpp
GUI/OpenGLManager.cpp
GUI/Selection.hpp
@@ -139,6 +141,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoText.hpp
GUI/Gizmos/GLGizmoMeshBoolean.cpp
GUI/Gizmos/GLGizmoMeshBoolean.hpp
+ GUI/Gizmos/GLGizmoMeasure.cpp
+ GUI/Gizmos/GLGizmoMeasure.hpp
GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp
GUI/Gizmos/GizmoObjectManipulation.cpp
@@ -205,6 +209,8 @@ set(SLIC3R_GUI_SOURCES
GUI/GUI_AuxiliaryList.hpp
GUI/GUI_ObjectSettings.cpp
GUI/GUI_ObjectSettings.hpp
+ GUI/GUI_Geometry.cpp
+ GUI/GUI_Geometry.hpp
GUI/GUI_ObjectTable.cpp
GUI/GUI_ObjectTable.hpp
GUI/GUI_ObjectTableSettings.cpp
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index e68df3e3829..284398c8f86 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -4044,7 +4044,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_layers_editing.state = LayersEditing::Editing;
_perform_layer_editing_action(&evt);
}
-
else {
// BBS: define Alt key to enable volume selection mode
m_selection.set_volume_selection_mode(evt.AltDown() ? Selection::Volume : Selection::Instance);
@@ -4052,6 +4051,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
&& m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
+ && m_gizmos.get_current_type() != GLGizmosManager::Measure
&& m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) {
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
m_dirty = true;
@@ -4327,7 +4327,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
// if right clicking on volume, propagate event through callback (shows context menu)
int volume_idx = get_first_hover_volume_idx();
if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower
- && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open
+ && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open
{
// forces the selection of the volume
/* m_selection.add(volume_idx); // #et_FIXME_if_needed
@@ -4368,7 +4368,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
else {
// do not post the event if the user is panning the scene
// or if right click was done over the wipe tower
- bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower;
+ bool post_right_click_event = (m_hover_volume_idxs.empty() ||
+ !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower) &&
+ m_gizmos.get_current_type() != GLGizmosManager::Measure;
if (post_right_click_event)
post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() }));
}
@@ -6953,6 +6955,7 @@ void GLCanvas3D::_render_sequential_clearance()
{
case GLGizmosManager::EType::Flatten:
case GLGizmosManager::EType::Cut:
+ case GLGizmosManager::EType::Measure:
case GLGizmosManager::EType::Hollow:
case GLGizmosManager::EType::SlaSupports:
case GLGizmosManager::EType::FdmSupports:
@@ -8224,6 +8227,10 @@ void GLCanvas3D::_render_selection_sidebar_hints() const
void GLCanvas3D::_update_volumes_hover_state()
{
+ // skip update if the Gizmo Measure is active
+ if (m_gizmos.get_current_type() == GLGizmosManager::Measure)
+ return;
+
for (GLVolume* v : m_volumes.volumes) {
v->hover = GLVolume::HS_None;
}
diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp
index 303a2d04b4c..14d657bc573 100644
--- a/src/slic3r/GUI/GLCanvas3D.hpp
+++ b/src/slic3r/GUI/GLCanvas3D.hpp
@@ -19,6 +19,7 @@
#include "IMToolbar.hpp"
#include "libslic3r/Slicing.hpp"
+#include "slic3r/GUI/SceneRaycaster.hpp"
#include
@@ -506,6 +507,8 @@ class GLCanvas3D
bool m_is_dark = false;
wxGLCanvas* m_canvas;
wxGLContext* m_context;
+ SceneRaycaster m_scene_raycaster;
+
Bed3D &m_bed;
#if ENABLE_RETINA_GL
std::unique_ptr m_retina_helper;
@@ -724,6 +727,26 @@ class GLCanvas3D
bool init();
void post_event(wxEvent &&event);
+ std::shared_ptr add_raycaster_for_picking(SceneRaycaster::EType type, int id, const MeshRaycaster& raycaster,
+ const Transform3d& trafo = Transform3d::Identity(), bool use_back_faces = false) {
+ return m_scene_raycaster.add_raycaster(type, id, raycaster, trafo, use_back_faces);
+ }
+ void remove_raycasters_for_picking(SceneRaycaster::EType type, int id) {
+ m_scene_raycaster.remove_raycasters(type, id);
+ }
+ void remove_raycasters_for_picking(SceneRaycaster::EType type) {
+ m_scene_raycaster.remove_raycasters(type);
+ }
+
+ std::vector>* get_raycasters_for_picking(SceneRaycaster::EType type) {
+ return m_scene_raycaster.get_raycasters(type);
+ }
+
+ void set_raycaster_gizmos_on_top(bool value) {
+ m_scene_raycaster.set_gizmos_on_top(value);
+ }
+
+
void reset_explosion_ratio() { m_explosion_ratio = 1.0; }
void on_change_color_mode(bool is_dark, bool reinit = true);
const bool get_dark_mode_status() { return m_is_dark; }
diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp
index 422b6540807..dc93009cc9f 100644
--- a/src/slic3r/GUI/GLModel.cpp
+++ b/src/slic3r/GUI/GLModel.cpp
@@ -35,6 +35,429 @@ size_t GLModel::InitializationData::indices_count() const
return ret;
}
+
+void GLModel::Geometry::add_vertex(const Vec2f& position)
+{
+ assert(format.vertex_layout == EVertexLayout::P2);
+ vertices.emplace_back(position.x());
+ vertices.emplace_back(position.y());
+}
+
+void GLModel::Geometry::add_vertex(const Vec2f& position, const Vec2f& tex_coord)
+{
+ assert(format.vertex_layout == EVertexLayout::P2T2);
+ vertices.emplace_back(position.x());
+ vertices.emplace_back(position.y());
+ vertices.emplace_back(tex_coord.x());
+ vertices.emplace_back(tex_coord.y());
+}
+
+void GLModel::Geometry::add_vertex(const Vec3f& position)
+{
+ assert(format.vertex_layout == EVertexLayout::P3);
+ vertices.emplace_back(position.x());
+ vertices.emplace_back(position.y());
+ vertices.emplace_back(position.z());
+}
+
+void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec2f& tex_coord)
+{
+ assert(format.vertex_layout == EVertexLayout::P3T2);
+ vertices.insert(vertices.end(), position.data(), position.data() + 3);
+ vertices.insert(vertices.end(), tex_coord.data(), tex_coord.data() + 2);
+}
+
+void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord)
+{
+ assert(format.vertex_layout == EVertexLayout::P3N3T2);
+ vertices.emplace_back(position.x());
+ vertices.emplace_back(position.y());
+ vertices.emplace_back(position.z());
+ vertices.emplace_back(normal.x());
+ vertices.emplace_back(normal.y());
+ vertices.emplace_back(normal.z());
+ vertices.emplace_back(tex_coord.x());
+ vertices.emplace_back(tex_coord.y());
+}
+
+#if ENABLE_OPENGL_ES
+void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, const Vec3f& extra)
+{
+ assert(format.vertex_layout == EVertexLayout::P3N3E3);
+ vertices.emplace_back(position.x());
+ vertices.emplace_back(position.y());
+ vertices.emplace_back(position.z());
+ vertices.emplace_back(normal.x());
+ vertices.emplace_back(normal.y());
+ vertices.emplace_back(normal.z());
+ vertices.emplace_back(extra.x());
+ vertices.emplace_back(extra.y());
+ vertices.emplace_back(extra.z());
+}
+#endif // ENABLE_OPENGL_ES
+
+void GLModel::Geometry::add_vertex(const Vec4f& position)
+{
+ assert(format.vertex_layout == EVertexLayout::P4);
+ vertices.emplace_back(position.x());
+ vertices.emplace_back(position.y());
+ vertices.emplace_back(position.z());
+ vertices.emplace_back(position.w());
+}
+
+void GLModel::Geometry::add_index(unsigned int id)
+{
+ indices.emplace_back(id);
+}
+
+void GLModel::Geometry::add_line(unsigned int id1, unsigned int id2)
+{
+ indices.emplace_back(id1);
+ indices.emplace_back(id2);
+}
+
+Vec2f GLModel::Geometry::extract_position_2(size_t id) const
+{
+ const size_t p_stride = position_stride_floats(format);
+ if (p_stride != 2) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX };
+ }
+
+ if (vertices_count() <= id) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX };
+ }
+
+ const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)];
+ return { *(start + 0), *(start + 1) };
+}
+
+Vec3f GLModel::Geometry::extract_position_3(size_t id) const
+{
+ const size_t p_stride = position_stride_floats(format);
+ if (p_stride != 3) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX, FLT_MAX };
+ }
+
+ if (vertices_count() <= id) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX, FLT_MAX };
+ }
+
+ const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)];
+ return { *(start + 0), *(start + 1), *(start + 2) };
+}
+
+Vec3f GLModel::Geometry::extract_normal_3(size_t id) const
+{
+ const size_t n_stride = normal_stride_floats(format);
+ if (n_stride != 3) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX, FLT_MAX };
+ }
+
+ if (vertices_count() <= id) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX, FLT_MAX };
+ }
+
+ const float* start = &vertices[id * vertex_stride_floats(format) + normal_offset_floats(format)];
+ return { *(start + 0), *(start + 1), *(start + 2) };
+}
+
+Vec2f GLModel::Geometry::extract_tex_coord_2(size_t id) const
+{
+ const size_t t_stride = tex_coord_stride_floats(format);
+ if (t_stride != 2) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX };
+ }
+
+ if (vertices_count() <= id) {
+ assert(false);
+ return { FLT_MAX, FLT_MAX };
+ }
+
+ const float* start = &vertices[id * vertex_stride_floats(format) + tex_coord_offset_floats(format)];
+ return { *(start + 0), *(start + 1) };
+}
+
+void GLModel::Geometry::set_vertex(size_t id, const Vec3f& position, const Vec3f& normal)
+{
+ assert(format.vertex_layout == EVertexLayout::P3N3);
+ assert(id < vertices_count());
+ if (id < vertices_count()) {
+ float* start = &vertices[id * vertex_stride_floats(format)];
+ *(start + 0) = position.x();
+ *(start + 1) = position.y();
+ *(start + 2) = position.z();
+ *(start + 3) = normal.x();
+ *(start + 4) = normal.y();
+ *(start + 5) = normal.z();
+ }
+}
+
+void GLModel::Geometry::set_index(size_t id, unsigned int index)
+{
+ assert(id < indices_count());
+ if (id < indices_count())
+ indices[id] = index;
+}
+
+unsigned int GLModel::Geometry::extract_index(size_t id) const
+{
+ if (indices_count() <= id) {
+ assert(false);
+ return -1;
+ }
+
+ return indices[id];
+}
+
+void GLModel::Geometry::remove_vertex(size_t id)
+{
+ assert(id < vertices_count());
+ if (id < vertices_count()) {
+ const size_t stride = vertex_stride_floats(format);
+ std::vector::const_iterator it = vertices.begin() + id * stride;
+ vertices.erase(it, it + stride);
+ }
+}
+
+indexed_triangle_set GLModel::Geometry::get_as_indexed_triangle_set() const
+{
+ indexed_triangle_set its;
+ its.vertices.reserve(vertices_count());
+ for (size_t i = 0; i < vertices_count(); ++i) {
+ its.vertices.emplace_back(extract_position_3(i));
+ }
+ its.indices.reserve(indices_count() / 3);
+ for (size_t i = 0; i < indices_count() / 3; ++i) {
+ const size_t tri_id = i * 3;
+ its.indices.emplace_back(extract_index(tri_id), extract_index(tri_id + 1), extract_index(tri_id + 2));
+ }
+ return its;
+}
+
+size_t GLModel::Geometry::vertex_stride_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2: { return 2; }
+ case EVertexLayout::P2T2: { return 4; }
+ case EVertexLayout::P3: { return 3; }
+ case EVertexLayout::P3T2: { return 5; }
+ case EVertexLayout::P3N3: { return 6; }
+ case EVertexLayout::P3N3T2: { return 8; }
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3E3: { return 9; }
+#endif // ENABLE_OPENGL_ES
+ case EVertexLayout::P4: { return 4; }
+ default: { assert(false); return 0; }
+ };
+}
+
+size_t GLModel::Geometry::position_stride_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2:
+ case EVertexLayout::P2T2: { return 2; }
+ case EVertexLayout::P3:
+ case EVertexLayout::P3T2:
+ case EVertexLayout::P3N3:
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3T2:
+ case EVertexLayout::P3N3E3: { return 3; }
+#else
+ case EVertexLayout::P3N3T2: { return 3; }
+#endif // ENABLE_OPENGL_ES
+ case EVertexLayout::P4: { return 4; }
+ default: { assert(false); return 0; }
+ };
+}
+
+size_t GLModel::Geometry::position_offset_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2:
+ case EVertexLayout::P2T2:
+ case EVertexLayout::P3:
+ case EVertexLayout::P3T2:
+ case EVertexLayout::P3N3:
+ case EVertexLayout::P3N3T2:
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3E3:
+#endif // ENABLE_OPENGL_ES
+ case EVertexLayout::P4: { return 0; }
+ default: { assert(false); return 0; }
+ };
+}
+
+size_t GLModel::Geometry::normal_stride_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P3N3:
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3T2:
+ case EVertexLayout::P3N3E3: { return 3; }
+#else
+ case EVertexLayout::P3N3T2: { return 3; }
+#endif // ENABLE_OPENGL_ES
+ default: { assert(false); return 0; }
+ };
+}
+
+size_t GLModel::Geometry::normal_offset_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P3N3:
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3T2:
+ case EVertexLayout::P3N3E3: { return 3; }
+#else
+ case EVertexLayout::P3N3T2: { return 3; }
+#endif // ENABLE_OPENGL_ES
+ default: { assert(false); return 0; }
+ };
+}
+
+size_t GLModel::Geometry::tex_coord_stride_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2T2:
+ case EVertexLayout::P3T2:
+ case EVertexLayout::P3N3T2: { return 2; }
+ default: { assert(false); return 0; }
+ };
+}
+
+size_t GLModel::Geometry::tex_coord_offset_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2T2: { return 2; }
+ case EVertexLayout::P3T2: { return 3; }
+ case EVertexLayout::P3N3T2: { return 6; }
+ default: { assert(false); return 0; }
+ };
+}
+
+#if ENABLE_OPENGL_ES
+size_t GLModel::Geometry::extra_stride_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P3N3E3: { return 3; }
+ default: { assert(false); return 0; }
+ };
+}
+
+size_t GLModel::Geometry::extra_offset_floats(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P3N3E3: { return 6; }
+ default: { assert(false); return 0; }
+ };
+}
+#endif // ENABLE_OPENGL_ES
+
+size_t GLModel::Geometry::index_stride_bytes(const Geometry& data)
+{
+ switch (data.index_type)
+ {
+ case EIndexType::UINT: { return sizeof(unsigned int); }
+ case EIndexType::USHORT: { return sizeof(unsigned short); }
+ case EIndexType::UBYTE: { return sizeof(unsigned char); }
+ default: { assert(false); return 0; }
+ };
+}
+
+bool GLModel::Geometry::has_position(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2:
+ case EVertexLayout::P2T2:
+ case EVertexLayout::P3:
+ case EVertexLayout::P3T2:
+ case EVertexLayout::P3N3:
+ case EVertexLayout::P3N3T2:
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3E3:
+#endif // ENABLE_OPENGL_ES
+ case EVertexLayout::P4: { return true; }
+ default: { assert(false); return false; }
+ };
+}
+
+bool GLModel::Geometry::has_normal(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2:
+ case EVertexLayout::P2T2:
+ case EVertexLayout::P3:
+ case EVertexLayout::P3T2:
+ case EVertexLayout::P4: { return false; }
+ case EVertexLayout::P3N3:
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3T2:
+ case EVertexLayout::P3N3E3: { return true; }
+#else
+ case EVertexLayout::P3N3T2: { return true; }
+#endif // ENABLE_OPENGL_ES
+ default: { assert(false); return false; }
+ };
+}
+
+bool GLModel::Geometry::has_tex_coord(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P2T2:
+ case EVertexLayout::P3T2:
+ case EVertexLayout::P3N3T2: { return true; }
+ case EVertexLayout::P2:
+ case EVertexLayout::P3:
+ case EVertexLayout::P3N3:
+#if ENABLE_OPENGL_ES
+ case EVertexLayout::P3N3E3:
+#endif // ENABLE_OPENGL_ES
+ case EVertexLayout::P4: { return false; }
+ default: { assert(false); return false; }
+ };
+}
+
+#if ENABLE_OPENGL_ES
+bool GLModel::Geometry::has_extra(const Format& format)
+{
+ switch (format.vertex_layout)
+ {
+ case EVertexLayout::P3N3E3: { return true; }
+ case EVertexLayout::P2:
+ case EVertexLayout::P2T2:
+ case EVertexLayout::P3:
+ case EVertexLayout::P3T2:
+ case EVertexLayout::P3N3:
+ case EVertexLayout::P3N3T2:
+ case EVertexLayout::P4: { return false; }
+ default: { assert(false); return false; }
+ };
+}
+#endif // ENABLE_OPENGL_ES
+
+#if ENABLE_GLMODEL_STATISTICS
+GLModel::Statistics GLModel::s_statistics;
+#endif // ENABLE_GLMODEL_STATISTICS
+
void GLModel::init_from(const InitializationData& data)
{
if (!m_render_data.empty()) // call reset() if you want to reuse this model
@@ -74,6 +497,11 @@ void GLModel::init_from(const InitializationData& data)
}
}
+void GLModel::init_from(Geometry& data)
+{
+ init_from(data.get_as_indexed_triangle_set());
+}
+
void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bbox)
{
if (!m_render_data.empty()) // call reset() if you want to reuse this model
@@ -744,5 +1172,191 @@ GLModel::InitializationData diamond(int resolution)
return data;
}
+
+GLModel::Geometry smooth_sphere(unsigned int resolution, float radius)
+{
+ resolution = std::max(4, resolution);
+
+ const unsigned int sectorCount = resolution;
+ const unsigned int stackCount = resolution;
+
+ const float sectorStep = float(2.0 * M_PI / sectorCount);
+ const float stackStep = float(M_PI / stackCount);
+
+ GLModel::Geometry data;
+ data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
+ data.reserve_vertices((stackCount - 1) * sectorCount + 2);
+ data.reserve_indices((2 * (stackCount - 1) * sectorCount) * 3);
+
+ // vertices
+ for (unsigned int i = 0; i <= stackCount; ++i) {
+ // from pi/2 to -pi/2
+ const double stackAngle = 0.5 * M_PI - stackStep * i;
+ const double xy = double(radius) * ::cos(stackAngle);
+ const double z = double(radius) * ::sin(stackAngle);
+ if (i == 0 || i == stackCount) {
+ const Vec3f v(float(xy), 0.0f, float(z));
+ data.add_vertex(v, (Vec3f)v.normalized());
+ }
+ else {
+ for (unsigned int j = 0; j < sectorCount; ++j) {
+ // from 0 to 2pi
+ const double sectorAngle = sectorStep * j;
+ const Vec3f v(float(xy * std::cos(sectorAngle)), float(xy * std::sin(sectorAngle)), float(z));
+ data.add_vertex(v, (Vec3f)v.normalized());
+ }
+ }
+ }
+
+ // triangles
+ for (unsigned int i = 0; i < stackCount; ++i) {
+ // Beginning of current stack.
+ unsigned int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount);
+ const unsigned int k1_first = k1;
+ // Beginning of next stack.
+ unsigned int k2 = (i == 0) ? 1 : (k1 + sectorCount);
+ const unsigned int k2_first = k2;
+ for (unsigned int j = 0; j < sectorCount; ++j) {
+ // 2 triangles per sector excluding first and last stacks
+ unsigned int k1_next = k1;
+ unsigned int k2_next = k2;
+ if (i != 0) {
+ k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1);
+ data.add_triangle(k1, k2, k1_next);
+ }
+ if (i + 1 != stackCount) {
+ k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1);
+ data.add_triangle(k1_next, k2, k2_next);
+ }
+ k1 = k1_next;
+ k2 = k2_next;
+ }
+ }
+
+ return data;
+}
+
+GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height)
+{
+ resolution = std::max(4, resolution);
+
+ const unsigned int sectorCount = resolution;
+ const float sectorStep = 2.0f * float(M_PI) / float(sectorCount);
+
+ GLModel::Geometry data;
+ data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
+ data.reserve_vertices(sectorCount * 4 + 2);
+ data.reserve_indices(sectorCount * 4 * 3);
+
+ auto generate_vertices_on_circle = [sectorCount, sectorStep](float radius) {
+ std::vector ret;
+ ret.reserve(sectorCount);
+ for (unsigned int i = 0; i < sectorCount; ++i) {
+ // from 0 to 2pi
+ const float sectorAngle = sectorStep * i;
+ ret.emplace_back(radius * std::cos(sectorAngle), radius * std::sin(sectorAngle), 0.0f);
+ }
+ return ret;
+ };
+
+ const std::vector base_vertices = generate_vertices_on_circle(radius);
+ const Vec3f h = height * Vec3f::UnitZ();
+
+ // stem vertices
+ for (unsigned int i = 0; i < sectorCount; ++i) {
+ const Vec3f& v = base_vertices[i];
+ const Vec3f n = v.normalized();
+ data.add_vertex(v, n);
+ data.add_vertex(v + h, n);
+ }
+
+ // stem triangles
+ for (unsigned int i = 0; i < sectorCount; ++i) {
+ unsigned int v1 = i * 2;
+ unsigned int v2 = (i < sectorCount - 1) ? v1 + 2 : 0;
+ unsigned int v3 = v2 + 1;
+ unsigned int v4 = v1 + 1;
+ data.add_triangle(v1, v2, v3);
+ data.add_triangle(v1, v3, v4);
+ }
+
+ // bottom cap vertices
+ Vec3f cap_center = Vec3f::Zero();
+ unsigned int cap_center_id = data.vertices_count();
+ Vec3f normal = -Vec3f::UnitZ();
+
+ data.add_vertex(cap_center, normal);
+ for (unsigned int i = 0; i < sectorCount; ++i) {
+ data.add_vertex(base_vertices[i], normal);
+ }
+
+ // bottom cap triangles
+ for (unsigned int i = 0; i < sectorCount; ++i) {
+ data.add_triangle(cap_center_id, (i < sectorCount - 1) ? cap_center_id + i + 2 : cap_center_id + 1, cap_center_id + i + 1);
+ }
+
+ // top cap vertices
+ cap_center += h;
+ cap_center_id = data.vertices_count();
+ normal = -normal;
+
+ data.add_vertex(cap_center, normal);
+ for (unsigned int i = 0; i < sectorCount; ++i) {
+ data.add_vertex(base_vertices[i] + h, normal);
+ }
+
+ // top cap triangles
+ for (unsigned int i = 0; i < sectorCount; ++i) {
+ data.add_triangle(cap_center_id, cap_center_id + i + 1, (i < sectorCount - 1) ? cap_center_id + i + 2 : cap_center_id + 1);
+ }
+
+ return data;
+}
+
+GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness)
+{
+ const unsigned int torus_sector_count = std::max(4, primary_resolution);
+ const float torus_sector_step = 2.0f * float(M_PI) / float(torus_sector_count);
+ const unsigned int section_sector_count = std::max(4, secondary_resolution);
+ const float section_sector_step = 2.0f * float(M_PI) / float(section_sector_count);
+
+ GLModel::Geometry data;
+ data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 };
+ data.reserve_vertices(torus_sector_count * section_sector_count);
+ data.reserve_indices(torus_sector_count * section_sector_count * 2 * 3);
+
+ // vertices
+ for (unsigned int i = 0; i < torus_sector_count; ++i) {
+ const float section_angle = torus_sector_step * i;
+ const float csa = std::cos(section_angle);
+ const float ssa = std::sin(section_angle);
+ const Vec3f section_center(radius * csa, radius * ssa, 0.0f);
+ for (unsigned int j = 0; j < section_sector_count; ++j) {
+ const float circle_angle = section_sector_step * j;
+ const float thickness_xy = thickness * std::cos(circle_angle);
+ const float thickness_z = thickness * std::sin(circle_angle);
+ const Vec3f v(thickness_xy * csa, thickness_xy * ssa, thickness_z);
+ data.add_vertex(section_center + v, (Vec3f)v.normalized());
+ }
+ }
+
+ // triangles
+ for (unsigned int i = 0; i < torus_sector_count; ++i) {
+ const unsigned int ii = i * section_sector_count;
+ const unsigned int ii_next = ((i + 1) % torus_sector_count) * section_sector_count;
+ for (unsigned int j = 0; j < section_sector_count; ++j) {
+ const unsigned int j_next = (j + 1) % section_sector_count;
+ const unsigned int i0 = ii + j;
+ const unsigned int i1 = ii_next + j;
+ const unsigned int i2 = ii_next + j_next;
+ const unsigned int i3 = ii + j_next;
+ data.add_triangle(i0, i1, i2);
+ data.add_triangle(i0, i2, i3);
+ }
+ }
+
+ return data;
+}
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp
index d47c56fd93c..1e218df1463 100644
--- a/src/slic3r/GUI/GLModel.hpp
+++ b/src/slic3r/GUI/GLModel.hpp
@@ -3,6 +3,8 @@
#include "libslic3r/Point.hpp"
#include "libslic3r/BoundingBox.hpp"
+#include "libslic3r/Color.hpp"
+#include "libslic3r/Utils.hpp"
#include
#include
@@ -27,6 +29,139 @@ namespace GUI {
LineLoop
};
+ struct Geometry
+ {
+ enum class EPrimitiveType : unsigned char
+ {
+ Points,
+ Triangles,
+ TriangleStrip,
+ TriangleFan,
+ Lines,
+ LineStrip,
+ LineLoop
+ };
+
+ enum class EVertexLayout : unsigned char
+ {
+ P2, // position 2 floats
+ P2T2, // position 2 floats + texture coords 2 floats
+ P3, // position 3 floats
+ P3T2, // position 3 floats + texture coords 2 floats
+ P3N3, // position 3 floats + normal 3 floats
+ P3N3T2, // position 3 floats + normal 3 floats + texture coords 2 floats
+#if ENABLE_OPENGL_ES
+ P3N3E3, // position 3 floats + normal 3 floats + extra 3 floats
+#endif // ENABLE_OPENGL_ES
+ P4, // position 4 floats
+ };
+
+ enum class EIndexType : unsigned char
+ {
+ UINT, // unsigned int
+ USHORT, // unsigned short
+ UBYTE // unsigned byte
+ };
+
+ struct Format
+ {
+ EPrimitiveType type{ EPrimitiveType::Triangles };
+ EVertexLayout vertex_layout{ EVertexLayout::P3N3 };
+ };
+
+ Format format;
+ std::vector vertices;
+ std::vector indices;
+ EIndexType index_type{ EIndexType::UINT };
+ ColorRGBA color{ ColorRGBA::BLACK() };
+
+ void reserve_vertices(size_t vertices_count) { vertices.reserve(vertices_count * vertex_stride_floats(format)); }
+ void reserve_more_vertices(size_t vertices_count) { vertices.reserve(next_highest_power_of_2(vertices.size() + vertices_count * vertex_stride_floats(format))); }
+ void reserve_indices(size_t indices_count) { indices.reserve(indices_count); }
+ void reserve_more_indices(size_t indices_count) { indices.reserve(next_highest_power_of_2(indices.size() + indices_count)); }
+
+ void add_vertex(const Vec2f& position); // EVertexLayout::P2
+ void add_vertex(const Vec2f& position, const Vec2f& tex_coord); // EVertexLayout::P2T2
+ void add_vertex(const Vec3f& position); // EVertexLayout::P3
+ void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2
+ void add_vertex(const Vec3f& position, const Vec3f& normal) { // EVertexLayout::P3N3
+ assert(format.vertex_layout == EVertexLayout::P3N3);
+ vertices.insert(vertices.end(), position.data(), position.data() + 3);
+ vertices.insert(vertices.end(), normal.data(), normal.data() + 3);
+ }
+ void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord); // EVertexLayout::P3N3T2
+#if ENABLE_OPENGL_ES
+ void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec3f& extra); // EVertexLayout::P3N3E3
+#endif // ENABLE_OPENGL_ES
+ void add_vertex(const Vec4f& position); // EVertexLayout::P4
+
+ void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3
+
+ void set_index(size_t id, unsigned int index);
+
+ void add_index(unsigned int id);
+ void add_line(unsigned int id1, unsigned int id2);
+ void add_triangle(unsigned int id1, unsigned int id2, unsigned int id3){
+ indices.emplace_back(id1);
+ indices.emplace_back(id2);
+ indices.emplace_back(id3);
+ }
+
+ Vec2f extract_position_2(size_t id) const;
+ Vec3f extract_position_3(size_t id) const;
+ Vec3f extract_normal_3(size_t id) const;
+ Vec2f extract_tex_coord_2(size_t id) const;
+
+ unsigned int extract_index(size_t id) const;
+
+ void remove_vertex(size_t id);
+
+ bool is_empty() const { return vertices_count() == 0 || indices_count() == 0; }
+
+ size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); }
+ size_t indices_count() const { return indices.size(); }
+
+ size_t vertices_size_floats() const { return vertices.size(); }
+ size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); }
+ size_t indices_size_bytes() const { return indices.size() * index_stride_bytes(*this); }
+
+ indexed_triangle_set get_as_indexed_triangle_set() const;
+
+ static size_t vertex_stride_floats(const Format& format);
+ static size_t vertex_stride_bytes(const Format& format) { return vertex_stride_floats(format) * sizeof(float); }
+
+ static size_t position_stride_floats(const Format& format);
+ static size_t position_stride_bytes(const Format& format) { return position_stride_floats(format) * sizeof(float); }
+ static size_t position_offset_floats(const Format& format);
+ static size_t position_offset_bytes(const Format& format) { return position_offset_floats(format) * sizeof(float); }
+
+ static size_t normal_stride_floats(const Format& format);
+ static size_t normal_stride_bytes(const Format& format) { return normal_stride_floats(format) * sizeof(float); }
+ static size_t normal_offset_floats(const Format& format);
+ static size_t normal_offset_bytes(const Format& format) { return normal_offset_floats(format) * sizeof(float); }
+
+ static size_t tex_coord_stride_floats(const Format& format);
+ static size_t tex_coord_stride_bytes(const Format& format) { return tex_coord_stride_floats(format) * sizeof(float); }
+ static size_t tex_coord_offset_floats(const Format& format);
+ static size_t tex_coord_offset_bytes(const Format& format) { return tex_coord_offset_floats(format) * sizeof(float); }
+
+#if ENABLE_OPENGL_ES
+ static size_t extra_stride_floats(const Format& format);
+ static size_t extra_stride_bytes(const Format& format) { return extra_stride_floats(format) * sizeof(float); }
+ static size_t extra_offset_floats(const Format& format);
+ static size_t extra_offset_bytes(const Format& format) { return extra_offset_floats(format) * sizeof(float); }
+#endif // ENABLE_OPENGL_ES
+
+ static size_t index_stride_bytes(const Geometry& data);
+
+ static bool has_position(const Format& format);
+ static bool has_normal(const Format& format);
+ static bool has_tex_coord(const Format& format);
+#if ENABLE_OPENGL_ES
+ static bool has_extra(const Format& format);
+#endif // ENABLE_OPENGL_ES
+ };
+
struct RenderData
{
PrimitiveType type;
@@ -69,12 +204,14 @@ namespace GUI {
void init_from(const InitializationData& data);
void init_from(const indexed_triangle_set& its, const BoundingBoxf3& bbox);
+ void init_from(Geometry& data);
void init_from(const indexed_triangle_set& its);
void init_from(const Polygons& polygons, float z);
bool init_from_file(const std::string& filename);
// if entity_id == -1 set the color of all entities
void set_color(int entity_id, const std::array& color);
+ void set_color(const ColorRGBA& color) { set_color(-1, color.data_array()); }
void reset();
void render() const;
@@ -112,6 +249,18 @@ namespace GUI {
// the diamond is contained into a box with size [1, 1, 1]
GLModel::InitializationData diamond(int resolution);
+ // create a sphere with smooth normals
+ // the origin of the sphere is in its center
+ GLModel::Geometry smooth_sphere(unsigned int resolution, float radius);
+ // create a cylinder with smooth normals
+ // the axis of the cylinder is the Z axis
+ // the origin of the cylinder is the center of its bottom cap face
+ GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height);
+ // create a torus with smooth normals
+ // the axis of the torus is the Z axis
+ // the origin of the torus is in its center
+ GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness);
+
} // namespace GUI
} // namespace Slic3r
diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp
index 9c1e936525e..4bd2264ab73 100644
--- a/src/slic3r/GUI/GLShader.cpp
+++ b/src/slic3r/GUI/GLShader.cpp
@@ -206,6 +206,140 @@ void GLShaderProgram::stop_using() const
glsafe(::glUseProgram(0));
}
+
+void GLShaderProgram::set_uniform(int id, int value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform1i(id, value));
+}
+
+void GLShaderProgram::set_uniform(int id, bool value) const
+{
+ set_uniform(id, value ? 1 : 0);
+}
+
+void GLShaderProgram::set_uniform(int id, float value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform1f(id, value));
+}
+
+void GLShaderProgram::set_uniform(int id, double value) const
+{
+ set_uniform(id, static_cast(value));
+}
+
+void GLShaderProgram::set_uniform(int id, const std::array& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform2iv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const std::array& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform3iv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const std::array& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform4iv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const std::array& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform2fv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const std::array& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform3fv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const std::array& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform4fv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const std::array& value) const
+{
+ const std::array f_value = { float(value[0]), float(value[1]), float(value[2]), float(value[3]) };
+ set_uniform(id, f_value);
+}
+
+void GLShaderProgram::set_uniform(int id, const float* value, size_t size) const
+{
+ if (id >= 0) {
+ if (size == 1)
+ set_uniform(id, value[0]);
+ else if (size == 2)
+ glsafe(::glUniform2fv(id, 1, static_cast(value)));
+ else if (size == 3)
+ glsafe(::glUniform3fv(id, 1, static_cast(value)));
+ else if (size == 4)
+ glsafe(::glUniform4fv(id, 1, static_cast(value)));
+ }
+}
+
+void GLShaderProgram::set_uniform(int id, const Transform3f& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.matrix().data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const Transform3d& value) const
+{
+ set_uniform(id, value.cast());
+}
+
+void GLShaderProgram::set_uniform(int id, const Matrix3f& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const Matrix3d& value) const
+{
+ set_uniform(id, (Matrix3f)value.cast());
+}
+
+void GLShaderProgram::set_uniform(int id, const Matrix4f& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const Matrix4d& value) const
+{
+ set_uniform(id, (Matrix4f)value.cast());
+}
+
+void GLShaderProgram::set_uniform(int id, const Vec2f& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform2fv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const Vec2d& value) const
+{
+ set_uniform(id, static_cast(value.cast()));
+}
+
+void GLShaderProgram::set_uniform(int id, const Vec3f& value) const
+{
+ if (id >= 0)
+ glsafe(::glUniform3fv(id, 1, static_cast(value.data())));
+}
+
+void GLShaderProgram::set_uniform(int id, const Vec3d& value) const
+{
+ set_uniform(id, static_cast(value.cast()));
+}
+
bool GLShaderProgram::set_uniform(const char* name, int value) const
{
int id = get_uniform_location(name);
@@ -340,6 +474,9 @@ bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const
}
return false;
}
+void GLShaderProgram::set_uniform(const char *name, const Matrix3d &value) const {
+ set_uniform(get_uniform_location(name), value);
+}
bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const
{
diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp
index d7b92000dfc..8e26fd8050c 100644
--- a/src/slic3r/GUI/GLShader.hpp
+++ b/src/slic3r/GUI/GLShader.hpp
@@ -58,9 +58,35 @@ class GLShaderProgram
bool set_uniform(const char* name, const Transform3f& value) const;
bool set_uniform(const char* name, const Transform3d& value) const;
bool set_uniform(const char* name, const Matrix3f& value) const;
+ void set_uniform(const char* name, const Matrix3d& value) const;
bool set_uniform(const char* name, const Vec3f& value) const;
bool set_uniform(const char* name, const Vec3d& value) const;
+ void set_uniform(int id, int value) const;
+ void set_uniform(int id, bool value) const;
+ void set_uniform(int id, float value) const;
+ void set_uniform(int id, double value) const;
+ void set_uniform(int id, const std::array& value) const;
+ void set_uniform(int id, const std::array& value) const;
+ void set_uniform(int id, const std::array& value) const;
+ void set_uniform(int id, const std::array& value) const;
+ void set_uniform(int id, const std::array& value) const;
+ void set_uniform(int id, const std::array& value) const;
+ void set_uniform(int id, const std::array& value) const;
+ void set_uniform(int id, const float* value, size_t size) const;
+ void set_uniform(int id, const Transform3f& value) const;
+ void set_uniform(int id, const Transform3d& value) const;
+ void set_uniform(int id, const Matrix3f& value) const;
+ void set_uniform(int id, const Matrix3d& value) const;
+ void set_uniform(int id, const Matrix4f& value) const;
+ void set_uniform(int id, const Matrix4d& value) const;
+ void set_uniform(int id, const Vec2f& value) const;
+ void set_uniform(int id, const Vec2d& value) const;
+ void set_uniform(int id, const Vec3f& value) const;
+ void set_uniform(int id, const Vec3d& value) const;
+ // void set_uniform(int id, const ColorRGB& value) const;
+ // void set_uniform(int id, const ColorRGBA& value) const;
+
// returns -1 if not found
int get_attrib_location(const char* name) const;
// returns -1 if not found
diff --git a/src/slic3r/GUI/GUI_Geometry.cpp b/src/slic3r/GUI/GUI_Geometry.cpp
new file mode 100644
index 00000000000..b0ed0e04fc5
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Geometry.cpp
@@ -0,0 +1,9 @@
+#include "libslic3r/libslic3r.h"
+#include "GUI_Geometry.hpp"
+
+namespace Slic3r {
+namespace GUI {
+
+
+} // namespace Slic3r
+} // namespace GUI
diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp
new file mode 100644
index 00000000000..b18e4ae5a53
--- /dev/null
+++ b/src/slic3r/GUI/GUI_Geometry.hpp
@@ -0,0 +1,78 @@
+#ifndef slic3r_GUI_Geometry_hpp_
+#define slic3r_GUI_Geometry_hpp_
+
+namespace Slic3r {
+namespace GUI {
+
+enum class ECoordinatesType : unsigned char
+{
+ World,
+ Instance,
+ Local
+};
+
+class TransformationType
+{
+public:
+ enum Enum {
+ // Transforming in a world coordinate system
+ World = 0,
+ // Transforming in a instance coordinate system
+ Instance = 1,
+ // Transforming in a local coordinate system
+ Local = 2,
+ // Absolute transformations, allowed in local coordinate system only.
+ Absolute = 0,
+ // Relative transformations, allowed in both local and world coordinate system.
+ Relative = 4,
+ // For group selection, the transformation is performed as if the group made a single solid body.
+ Joint = 0,
+ // For group selection, the transformation is performed on each object independently.
+ Independent = 8,
+
+ World_Relative_Joint = World | Relative | Joint,
+ World_Relative_Independent = World | Relative | Independent,
+ Instance_Absolute_Joint = Instance | Absolute | Joint,
+ Instance_Absolute_Independent = Instance | Absolute | Independent,
+ Instance_Relative_Joint = Instance | Relative | Joint,
+ Instance_Relative_Independent = Instance | Relative | Independent,
+ Local_Absolute_Joint = Local | Absolute | Joint,
+ Local_Absolute_Independent = Local | Absolute | Independent,
+ Local_Relative_Joint = Local | Relative | Joint,
+ Local_Relative_Independent = Local | Relative | Independent,
+ };
+
+ TransformationType() : m_value(World) {}
+ TransformationType(Enum value) : m_value(value) {}
+ TransformationType& operator=(Enum value) { m_value = value; return *this; }
+
+ Enum operator()() const { return m_value; }
+ bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; }
+
+ void set_world() { this->remove(Instance); this->remove(Local); }
+ void set_instance() { this->remove(Local); this->add(Instance); }
+ void set_local() { this->remove(Instance); this->add(Local); }
+ void set_absolute() { this->remove(Relative); }
+ void set_relative() { this->add(Relative); }
+ void set_joint() { this->remove(Independent); }
+ void set_independent() { this->add(Independent); }
+
+ bool world() const { return !this->has(Instance) && !this->has(Local); }
+ bool instance() const { return this->has(Instance); }
+ bool local() const { return this->has(Local); }
+ bool absolute() const { return !this->has(Relative); }
+ bool relative() const { return this->has(Relative); }
+ bool joint() const { return !this->has(Independent); }
+ bool independent() const { return this->has(Independent); }
+
+private:
+ void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); }
+ void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); }
+
+ Enum m_value;
+};
+
+} // namespace Slic3r
+} // namespace GUI
+
+#endif // slic3r_GUI_Geometry_hpp_
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
new file mode 100644
index 00000000000..0cc6416a84c
--- /dev/null
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp
@@ -0,0 +1,1300 @@
+#include "GUI_ObjectManipulation.hpp"
+#include "I18N.hpp"
+#include "format.hpp"
+#include "BitmapComboBox.hpp"
+
+#include "GLCanvas3D.hpp"
+#include "OptionsGroup.hpp"
+#include "GUI_App.hpp"
+#include "wxExtensions.hpp"
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/Model.hpp"
+#include "libslic3r/Geometry.hpp"
+#include "Selection.hpp"
+#include "Plater.hpp"
+#include "MainFrame.hpp"
+#include "MsgDialog.hpp"
+
+#include
+
+#include
+#include "slic3r/Utils/FixModelByWin10.hpp"
+
+namespace Slic3r
+{
+namespace GUI
+{
+
+const double ObjectManipulation::in_to_mm = 25.4;
+const double ObjectManipulation::mm_to_in = 1 / ObjectManipulation::in_to_mm;
+
+// Helper function to be used by drop to bed button. Returns lowest point of this
+// volume in world coordinate system.
+static double get_volume_min_z(const GLVolume& volume)
+{
+ return volume.transformed_convex_hull_bounding_box().min.z();
+}
+
+static choice_ctrl* create_word_local_combo(wxWindow *parent)
+{
+ wxSize size(15 * wxGetApp().em_unit(), -1);
+
+ choice_ctrl* temp = nullptr;
+#ifdef __WXOSX__
+ /* wxBitmapComboBox with wxCB_READONLY style return NULL for GetTextCtrl(),
+ * so ToolTip doesn't shown.
+ * Next workaround helps to solve this problem
+ */
+ temp = new wxBitmapComboBox();
+ temp->SetTextCtrlStyle(wxTE_READONLY);
+ temp->Create(parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr);
+#else
+ temp = new choice_ctrl(parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY | wxBORDER_SIMPLE);
+#endif //__WXOSX__
+
+ temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
+ if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
+
+ temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World));
+ temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance));
+ temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local));
+ temp->Select((int)ECoordinatesType::World);
+
+ temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed."));
+ return temp;
+}
+
+void msw_rescale_word_local_combo(choice_ctrl* combo)
+{
+#ifdef __WXOSX__
+ const wxString selection = combo->GetString(combo->GetSelection());
+
+ /* To correct scaling (set new controll size) of a wxBitmapCombobox
+ * we need to refill control with new bitmaps. So, in our case :
+ * 1. clear control
+ * 2. add content
+ * 3. add scaled "empty" bitmap to the at least one item
+ */
+ combo->Clear();
+ wxSize size(wxDefaultSize);
+ size.SetWidth(15 * wxGetApp().em_unit());
+
+ // Set rescaled min height to correct layout
+ combo->SetMinSize(wxSize(-1, int(1.5f*combo->GetFont().GetPixelSize().y + 0.5f)));
+ // Set rescaled size
+ combo->SetSize(size);
+
+ combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World));
+ combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance));
+ combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local));
+
+ combo->SetValue(selection);
+#else
+#ifdef _WIN32
+ combo->Rescale();
+#endif
+ combo->SetMinSize(wxSize(15 * wxGetApp().em_unit(), -1));
+#endif
+}
+
+static void set_font_and_background_style(wxWindow* win, const wxFont& font)
+{
+ win->SetFont(font);
+ win->SetBackgroundStyle(wxBG_STYLE_PAINT);
+}
+
+static const wxString axes_color_text[] = { "#990000", "#009900", "#000099" };
+static const wxString axes_color_back[] = { "#f5dcdc", "#dcf5dc", "#dcdcf5" };
+
+ObjectManipulation::ObjectManipulation(wxWindow* parent) :
+ OG_Settings(parent, true)
+{
+ m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
+ m_use_colors = wxGetApp().app_config->get("color_mapinulation_panel") == "1";
+
+ m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation");
+
+ // Load bitmaps to be used for the mirroring buttons:
+ m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on");
+
+ const int border = wxOSX ? 0 : 4;
+ const int em = wxGetApp().em_unit();
+ m_main_grid_sizer = new wxFlexGridSizer(2, 3, 3); // "Name/label", "String name / Editors"
+ m_main_grid_sizer->SetFlexibleDirection(wxBOTH);
+
+ // Add "Name" label with warning icon
+ auto sizer = new wxBoxSizer(wxHORIZONTAL);
+
+ m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
+ if (is_windows10())
+ m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent& e)
+ {
+ // if object/sub-object has no errors
+ if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData())
+ return;
+
+ wxGetApp().obj_list()->fix_through_netfabb();
+ update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_info());
+ });
+
+ sizer->Add(m_fix_throught_netfab_bitmap);
+
+ auto name_label = new wxStaticText(m_parent, wxID_ANY, _L("Name")+":");
+ set_font_and_background_style(name_label, wxGetApp().normal_font());
+ name_label->SetToolTip(_L("Object name"));
+ sizer->Add(name_label);
+
+ m_main_grid_sizer->Add(sizer);
+
+ // Add name of the item
+ const wxSize name_size = wxSize(20 * em, wxDefaultCoord);
+ m_item_name = new wxStaticText(m_parent, wxID_ANY, "", wxDefaultPosition, name_size, wxST_ELLIPSIZE_MIDDLE);
+ set_font_and_background_style(m_item_name, wxGetApp().bold_font());
+
+ m_main_grid_sizer->Add(m_item_name, 0, wxEXPAND);
+
+ // Add labels grid sizer
+ m_labels_grid_sizer = new wxFlexGridSizer(1, 3, 3); // "Name/label", "String name / Editors"
+ m_labels_grid_sizer->SetFlexibleDirection(wxBOTH);
+
+ // Add world local combobox
+ m_word_local_combo = create_word_local_combo(parent);
+ m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) { this->set_coordinates_type(evt.GetString()); }), m_word_local_combo->GetId());
+
+ // Small trick to correct layouting in different view_mode :
+ // Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden
+ m_word_local_combo_sizer = new wxBoxSizer(wxHORIZONTAL);
+ m_empty_str = new wxStaticText(parent, wxID_ANY, "");
+ m_word_local_combo_sizer->Add(m_word_local_combo);
+ m_word_local_combo_sizer->Add(m_empty_str);
+ m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1)));
+ m_labels_grid_sizer->Add(m_word_local_combo_sizer);
+
+ // Text trick to grid sizer layout:
+ // Height of labels should be equivalent to the edit boxes
+ int height = wxTextCtrl(parent, wxID_ANY, "Br").GetBestHeight(-1);
+#ifdef __WXGTK__
+ // On Linux button with bitmap has bigger height then regular button or regular TextCtrl
+ // It can cause a wrong alignment on show/hide of a reset buttons
+ const int bmp_btn_height = ScalableButton(parent, wxID_ANY, "undo") .GetBestHeight(-1);
+ if (bmp_btn_height > height)
+ height = bmp_btn_height;
+#endif //__WXGTK__
+
+ auto add_label = [this, height](wxStaticText** label, const std::string& name, wxSizer* reciver = nullptr)
+ {
+ *label = new wxStaticText(m_parent, wxID_ANY, _(name) + ":");
+ set_font_and_background_style(*label, wxGetApp().normal_font());
+
+ wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
+ sizer->SetMinSize(wxSize(-1, height));
+ sizer->Add(*label, 0, wxALIGN_CENTER_VERTICAL);
+
+ if (reciver)
+ reciver->Add(sizer);
+ else
+ m_labels_grid_sizer->Add(sizer);
+
+ m_rescalable_sizers.push_back(sizer);
+ };
+
+ // Add labels
+ add_label(&m_move_Label, L("Position"));
+ add_label(&m_rotate_Label, L("Rotation"));
+
+ // additional sizer for lock and labels "Scale" & "Size"
+ sizer = new wxBoxSizer(wxHORIZONTAL);
+
+ m_lock_bnt = new LockButton(parent, wxID_ANY);
+ m_lock_bnt->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
+ event.Skip();
+ wxTheApp->CallAfter([this]() { set_uniform_scaling(m_lock_bnt->IsLocked()); });
+ });
+ sizer->Add(m_lock_bnt, 0, wxALIGN_CENTER_VERTICAL);
+
+ auto v_sizer = new wxGridSizer(1, 3, 3);
+
+ add_label(&m_scale_Label, L("Scale"), v_sizer);
+ wxStaticText* size_Label {nullptr};
+ add_label(&size_Label, L("Size [World]"), v_sizer);
+ if (wxOSX) set_font_and_background_style(size_Label, wxGetApp().normal_font());
+
+ sizer->Add(v_sizer, 0, wxLEFT, border);
+ m_labels_grid_sizer->Add(sizer);
+ m_main_grid_sizer->Add(m_labels_grid_sizer, 0, wxEXPAND);
+
+
+ // Add editors grid sizer
+ wxFlexGridSizer* editors_grid_sizer = new wxFlexGridSizer(5, 3, 3); // "Name/label", "String name / Editors"
+ editors_grid_sizer->SetFlexibleDirection(wxBOTH);
+
+ // Add Axes labels with icons
+ static const char axes[] = { 'X', 'Y', 'Z' };
+// std::vector axes_color = {"#EE0000", "#00EE00", "#0000EE"};
+ for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
+ const char label = axes[axis_idx];
+
+ wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label));
+ set_font_and_background_style(axis_name, wxGetApp().bold_font());
+ //if (m_use_colors)
+ // axis_name->SetForegroundColour(wxColour(axes_color_text[axis_idx]));
+
+ sizer = new wxBoxSizer(wxHORIZONTAL);
+ // Under OSX or Linux with GTK3 we use font, smaller than default font, so
+ // there is a next trick for an equivalent layout of coordinates combobox and axes labels in they own sizers
+ // if (wxOSX || wxGTK3)
+ sizer->SetMinSize(-1, m_word_local_combo->GetBestHeight(-1));
+ sizer->Add(axis_name, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
+
+ // We will add a button to toggle mirroring to each axis:
+ auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
+ btn->SetToolTip(format_wxstr(_L("Mirror along %1% axis"), label));
+ m_mirror_buttons[axis_idx] = btn;
+
+ sizer->AddStretchSpacer(2);
+ sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL);
+
+ btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) {
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+ TransformationType transformation_type;
+ if (is_local_coordinates())
+ transformation_type.set_local();
+ else if (is_instance_coordinates())
+ transformation_type.set_instance();
+
+ transformation_type.set_relative();
+
+ selection.setup_cache();
+ selection.mirror((Axis)axis_idx);
+
+ // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
+ canvas->do_mirror(L("Set Mirror"));
+ UpdateAndShow(true);
+ });
+
+ editors_grid_sizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL);
+ }
+
+ m_mirror_warning_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
+ editors_grid_sizer->Add(m_mirror_warning_bitmap, 0, wxALIGN_CENTER_VERTICAL);
+ editors_grid_sizer->AddStretchSpacer(1);
+
+ // add EditBoxes
+ auto add_edit_boxes = [this, editors_grid_sizer](const std::string& opt_key, int axis)
+ {
+ ManipulationEditor* editor = new ManipulationEditor(this, opt_key, axis);
+ m_editors.push_back(editor);
+
+ editors_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL);
+ };
+
+ // add Units
+ auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit, wxStaticText** unit_text)
+ {
+ *unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
+ set_font_and_background_style(*unit_text, wxGetApp().normal_font());
+
+ // Unit text should be the same height as labels
+ wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
+ sizer->SetMinSize(wxSize(-1, height));
+ sizer->Add(*unit_text, 0, wxALIGN_CENTER_VERTICAL);
+
+ editors_grid_sizer->Add(sizer);
+ m_rescalable_sizers.push_back(sizer);
+ };
+
+ for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
+ add_edit_boxes("position", axis_idx);
+ add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_position_unit);
+
+ // Add drop to bed button
+ m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
+ m_drop_to_bed_button->SetToolTip(_L("Drop to bed"));
+ m_drop_to_bed_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
+ // ???
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+
+ if (selection.is_single_volume_or_modifier()) {
+ const GLVolume* volume = selection.get_first_volume();
+ const double min_z = get_volume_min_z(*volume);
+ if (!is_world_coordinates()) {
+ const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
+ change_position_value(0, diff.x());
+ change_position_value(1, diff.y());
+ change_position_value(2, diff.z());
+ }
+ else {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
+ change_position_value(2, m_cache.position.z() - min_z);
+ }
+ }
+ else if (selection.is_single_full_instance()) {
+ const double min_z = selection.get_scaled_instance_bounding_box().min.z();
+ if (!is_world_coordinates()) {
+ const GLVolume* volume = selection.get_first_volume();
+ const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ());
+
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
+ change_position_value(0, diff.x());
+ change_position_value(1, diff.y());
+ change_position_value(2, diff.z());
+ }
+ else {
+ Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
+ change_position_value(2, m_cache.position.z() - min_z);
+ }
+ }
+ });
+ editors_grid_sizer->Add(m_drop_to_bed_button);
+
+ for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
+ add_edit_boxes("rotation", axis_idx);
+ wxStaticText* rotation_unit{ nullptr };
+ add_unit_text("°", &rotation_unit);
+
+ // Add reset rotation button
+ m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
+ m_reset_rotation_button->SetToolTip(_L("Reset rotation"));
+ m_reset_rotation_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+ selection.setup_cache();
+ if (selection.is_single_volume_or_modifier()) {
+ GLVolume* vol = const_cast(selection.get_first_volume());
+ Geometry::Transformation trafo = vol->get_volume_transformation();
+ trafo.reset_rotation();
+ vol->set_volume_transformation(trafo);
+ }
+ else if (selection.is_single_full_instance()) {
+ Geometry::Transformation trafo = selection.get_first_volume()->get_instance_transformation();
+ trafo.reset_rotation();
+ for (unsigned int idx : selection.get_volume_idxs()) {
+ const_cast(selection.get_volume(idx))->set_instance_transformation(trafo);
+ }
+ }
+ else
+ return;
+
+ // Synchronize instances/volumes.
+
+ selection.synchronize_unselected_instances(Selection::SyncRotationType::RESET);
+ selection.synchronize_unselected_volumes();
+
+ // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
+ canvas->do_rotate(L("Reset Rotation"));
+
+ UpdateAndShow(true);
+ });
+ editors_grid_sizer->Add(m_reset_rotation_button);
+
+ for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
+ add_edit_boxes("scale", axis_idx);
+ wxStaticText* scale_unit{ nullptr };
+ add_unit_text("%", &scale_unit);
+
+ // Add reset scale button
+ m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
+ m_reset_scale_button->SetToolTip(_L("Reset scale"));
+ m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+ selection.setup_cache();
+ if (selection.is_single_volume_or_modifier()) {
+ GLVolume* vol = const_cast(selection.get_first_volume());
+ Geometry::Transformation trafo = vol->get_volume_transformation();
+ trafo.reset_scaling_factor();
+ vol->set_volume_transformation(trafo);
+ }
+ else if (selection.is_single_full_instance()) {
+ Geometry::Transformation trafo = selection.get_first_volume()->get_instance_transformation();
+ trafo.reset_scaling_factor();
+ for (unsigned int idx : selection.get_volume_idxs()) {
+ const_cast(selection.get_volume(idx))->set_instance_transformation(trafo);
+ }
+ }
+ else
+ return;
+
+ // Synchronize instances/volumes.
+ selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL);
+ selection.synchronize_unselected_volumes();
+
+ canvas->do_scale(L("Reset scale"));
+ UpdateAndShow(true);
+ });
+ editors_grid_sizer->Add(m_reset_scale_button);
+
+ for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
+ add_edit_boxes("size", axis_idx);
+ add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_size_unit);
+ editors_grid_sizer->AddStretchSpacer(1);
+
+ m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND);
+
+ m_skew_label = new wxStaticText(parent, wxID_ANY, _L("Skew [World]"));
+ m_main_grid_sizer->Add(m_skew_label, 1, wxEXPAND);
+
+ m_reset_skew_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
+ m_reset_skew_button->SetToolTip(_L("Reset skew"));
+ m_reset_skew_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+ if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
+ selection.setup_cache();
+ selection.reset_skew();
+ canvas->do_reset_skew(L("Reset skew"));
+ UpdateAndShow(true);
+ }
+ });
+ m_main_grid_sizer->Add(m_reset_skew_button);
+
+ m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches"));
+ m_check_inch->SetFont(wxGetApp().normal_font());
+
+ m_check_inch->SetValue(m_imperial_units);
+ m_check_inch->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
+ wxGetApp().app_config->set("use_inches", m_check_inch->GetValue() ? "1" : "0");
+ wxGetApp().sidebar().update_ui_from_settings();
+ });
+
+ m_main_grid_sizer->Add(m_check_inch, 1, wxEXPAND);
+
+ m_og->activate();
+ m_og->sizer->Clear(true);
+ m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border);
+}
+
+void ObjectManipulation::Show(const bool show)
+{
+ if (show != IsShown()) {
+ // Show all lines of the panel. Some of these lines will be hidden in the lines below.
+ m_og->Show(show);
+
+ if (show && wxGetApp().get_mode() != comSimple) {
+ // Show the label and the name of the STL in simple mode only.
+ // Label "Name: "
+ m_main_grid_sizer->Show(size_t(0), false);
+ // The actual name of the STL.
+ m_main_grid_sizer->Show(size_t(1), false);
+ }
+ }
+
+ if (show) {
+ ECoordinatesType coordinates_type = m_coordinates_type;
+
+ // Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only.
+ const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier());
+ if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) {
+#ifdef __linux__
+ m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Local), 2);
+#else
+ m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Local), wxNullBitmap, 2);
+#endif // __linux__
+ }
+ else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) {
+ m_word_local_combo->Delete(2);
+ if (coordinates_type > ECoordinatesType::Instance)
+ coordinates_type = ECoordinatesType::World;
+ }
+ m_word_local_combo->Show(show_world_local_combo);
+ m_empty_str->Show(!show_world_local_combo);
+ }
+}
+
+bool ObjectManipulation::IsShown()
+{
+ return dynamic_cast(m_og->sizer)->GetStaticBox()->IsShown(); // m_og->get_grid_sizer()->IsShown(2);
+}
+
+void ObjectManipulation::UpdateAndShow(const bool show)
+{
+ if (show) {
+ this->set_dirty();
+ this->update_if_dirty();
+ }
+
+ OG_Settings::UpdateAndShow(show);
+}
+
+void ObjectManipulation::Enable(const bool enable)
+{
+ m_is_enabled = m_is_enabled_size_and_scale = enable;
+ for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt
+ , m_reset_skew_button })
+ win->Enable(enable);
+}
+
+void ObjectManipulation::DisableScale()
+{
+ m_is_enabled = true;
+ m_is_enabled_size_and_scale = false;
+ for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_lock_bnt, m_reset_skew_button })
+ win->Enable(false);
+}
+
+void ObjectManipulation::DisableUnuniformScale()
+{
+ m_lock_bnt->Enable(false);
+}
+
+void ObjectManipulation::update_ui_from_settings()
+{
+ if (m_imperial_units != wxGetApp().app_config->get_bool("use_inches")) {
+ m_imperial_units = wxGetApp().app_config->get_bool("use_inches");
+
+ auto update_unit_text = [](const wxString& new_unit_text, wxStaticText* widget) {
+ widget->SetLabel(new_unit_text);
+ if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font());
+ };
+ update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_position_unit);
+ update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_size_unit);
+
+ for (int i = 0; i < 3; ++i) {
+ auto update = [this, i](/*ManipulationEditorKey*/int key_id, const Vec3d& new_value) {
+ double value = new_value(i);
+ if (m_imperial_units)
+ value *= mm_to_in;
+ wxString new_text = double_to_string(value, m_imperial_units && key_id == 3/*meSize*/ ? 4 : 2);
+ const int id = key_id * 3 + i;
+ if (id >= 0) m_editors[id]->set_value(new_text);
+ };
+ update(0/*mePosition*/, m_new_position);
+ update(3/*meSize*/, m_new_size);
+ }
+ }
+ m_check_inch->SetValue(m_imperial_units);
+
+ if (m_use_colors != wxGetApp().app_config->get_bool("color_mapinulation_panel")) {
+ m_use_colors = wxGetApp().app_config->get_bool("color_mapinulation_panel");
+ // update colors for edit-boxes
+ int axis_id = 0;
+ for (ManipulationEditor* editor : m_editors) {
+// editor->SetForegroundColour(m_use_colors ? wxColour(axes_color_text[axis_id]) : wxGetApp().get_label_clr_default());
+ if (m_use_colors) {
+ editor->SetBackgroundColour(wxColour(axes_color_back[axis_id]));
+ if (wxGetApp().dark_mode())
+ editor->SetForegroundColour(*wxBLACK);
+ }
+ else {
+#ifdef _WIN32
+ wxGetApp().UpdateDarkUI(editor);
+#else
+ editor->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
+ editor->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
+#endif /* _WIN32 */
+ }
+ editor->Refresh();
+ if (++axis_id == 3)
+ axis_id = 0;
+ }
+ }
+}
+
+void ObjectManipulation::update_settings_value(const Selection& selection)
+{
+ if (selection.is_empty()) {
+ // No selection, reset the cache.
+ reset_settings_value();
+ return;
+ }
+
+ m_new_move_label_string = L("Position");
+ m_new_rotate_label_string = L("Rotation");
+ m_new_scale_label_string = L("Scale factors");
+
+ ObjectList* obj_list = wxGetApp().obj_list();
+ if (selection.is_single_full_instance()) {
+ // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
+ const GLVolume* volume = selection.get_first_volume();
+
+ if (is_world_coordinates()) {
+ m_new_position = volume->get_instance_offset();
+ m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
+ m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0;
+ m_new_rotate_label_string = L("Rotate (relative)");
+ m_new_rotation = Vec3d::Zero();
+ }
+ else {
+ m_new_move_label_string = L("Translate (relative) [World]");
+ m_new_rotate_label_string = L("Rotate (relative)");
+ m_new_position = Vec3d::Zero();
+ m_new_rotation = Vec3d::Zero();
+ m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
+ m_new_scale = m_new_size.cwiseQuotient(selection.get_full_unscaled_instance_local_bounding_box().size()) * 100.0;
+ }
+
+ m_new_enabled = true;
+ }
+ else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) {
+ const BoundingBoxf3& box = selection.get_bounding_box();
+ m_new_position = box.center();
+ m_new_rotation = Vec3d::Zero();
+ m_new_scale = Vec3d(100.0, 100.0, 100.0);
+ m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
+ m_new_rotate_label_string = L("Rotate");
+ m_new_scale_label_string = L("Scale");
+ m_new_enabled = true;
+ }
+ else if (selection.is_single_volume_or_modifier()) {
+ // the selection contains a single volume
+ const GLVolume* volume = selection.get_first_volume();
+ if (is_world_coordinates()) {
+ const Geometry::Transformation trafo(volume->world_matrix());
+
+ const Vec3d& offset = trafo.get_offset();
+
+ m_new_position = offset;
+ m_new_rotate_label_string = L("Rotate (relative)");
+ m_new_scale_label_string = L("Scale");
+ m_new_scale = Vec3d(100.0, 100.0, 100.0);
+ m_new_rotation = Vec3d::Zero();
+ m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
+ }
+ else if (is_local_coordinates()) {
+ m_new_move_label_string = L("Translate (relative) [World]");
+ m_new_rotate_label_string = L("Rotate (relative)");
+ m_new_position = Vec3d::Zero();
+ m_new_rotation = Vec3d::Zero();
+ m_new_scale = volume->get_volume_scaling_factor() * 100.0;
+ m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
+ }
+ else {
+ m_new_position = volume->get_volume_offset();
+ m_new_rotate_label_string = L("Rotate (relative)");
+ m_new_rotation = Vec3d::Zero();
+ m_new_scale_label_string = L("Scale");
+ m_new_scale = Vec3d(100.0, 100.0, 100.0);
+ m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
+ }
+ m_new_enabled = true;
+ }
+ else if (obj_list->is_connectors_item_selected() || obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) {
+ reset_settings_value();
+ m_new_move_label_string = L("Translate");
+ m_new_rotate_label_string = L("Rotate");
+ m_new_scale_label_string = L("Scale");
+ m_new_size = selection.get_bounding_box_in_current_reference_system().first.size();
+ m_new_enabled = true;
+ }
+}
+
+void ObjectManipulation::update_if_dirty()
+{
+ if (! m_dirty)
+ return;
+
+ const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection();
+ this->update_settings_value(selection);
+
+ auto update_label = [](wxString &label_cache, const std::string &new_label, wxStaticText *widget) {
+ wxString new_label_localized = _(new_label) + ":";
+ if (label_cache != new_label_localized) {
+ label_cache = new_label_localized;
+ widget->SetLabel(new_label_localized);
+ if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font());
+ }
+ };
+ update_label(m_cache.move_label_string, m_new_move_label_string, m_move_Label);
+ update_label(m_cache.rotate_label_string, m_new_rotate_label_string, m_rotate_Label);
+ update_label(m_cache.scale_label_string, m_new_scale_label_string, m_scale_Label);
+
+ enum ManipulationEditorKey
+ {
+ mePosition = 0,
+ meRotation,
+ meScale,
+ meSize
+ };
+
+ for (int i = 0; i < 3; ++ i) {
+ auto update = [this, i](Vec3d &cached, Vec3d &cached_rounded, ManipulationEditorKey key_id, const Vec3d &new_value) {
+ wxString new_text = double_to_string(new_value(i), m_imperial_units && key_id == meSize ? 4 : 2);
+ double new_rounded;
+ new_text.ToDouble(&new_rounded);
+ if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
+ cached_rounded(i) = new_rounded;
+ const int id = key_id*3+i;
+ if (m_imperial_units) {
+ double inch_value = new_value(i) * mm_to_in;
+ if (key_id == mePosition)
+ new_text = double_to_string(inch_value, 2);
+ if (key_id == meSize) {
+ if(std::abs(m_cache.size_inches(i) - inch_value) > EPSILON)
+ m_cache.size_inches(i) = inch_value;
+ new_text = double_to_string(inch_value, 4);
+ }
+ }
+ if (id >= 0) m_editors[id]->set_value(new_text);
+ }
+ cached(i) = new_value(i);
+ };
+ update(m_cache.position, m_cache.position_rounded, mePosition, m_new_position);
+ update(m_cache.scale, m_cache.scale_rounded, meScale, m_new_scale);
+ update(m_cache.size, m_cache.size_rounded, meSize, m_new_size);
+ update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation);
+ }
+
+ m_lock_bnt->SetLock(m_uniform_scale);
+ m_lock_bnt->SetToolTip(wxEmptyString);
+ m_lock_bnt->enable();
+
+ if (m_new_enabled)
+ m_og->enable();
+ else
+ m_og->disable();
+
+ if (!wxGetApp().plater()->canvas3D()->is_dragging()) {
+ update_reset_buttons_visibility();
+ update_mirror_buttons_visibility();
+ }
+
+ m_dirty = false;
+}
+
+void ObjectManipulation::update_reset_buttons_visibility()
+{
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ if (!canvas)
+ return;
+
+ bool show_drop_to_bed = false;
+ bool show_rotation = false;
+ bool show_scale = false;
+ bool show_mirror = false;
+ bool show_skew = false;
+
+ const Selection& selection = canvas->get_selection();
+ if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
+ const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() :
+ get_volume_min_z(*selection.get_first_volume());
+
+ show_drop_to_bed = std::abs(min_z) > EPSILON;
+ const GLVolume* volume = selection.get_first_volume();
+ const Geometry::Transformation trafo = selection.is_single_full_instance() ? volume->get_instance_transformation() : volume->get_volume_transformation();
+
+ const Geometry::TransformationSVD trafo_svd(trafo);
+ show_rotation = trafo_svd.rotation;
+ show_scale = trafo_svd.scale;
+ show_mirror = trafo_svd.mirror;
+ show_skew = Geometry::TransformationSVD(volume->world_matrix()).skew;
+ }
+
+ wxGetApp().CallAfter([this, show_drop_to_bed, show_rotation, show_scale, show_mirror, show_skew] {
+ // There is a case (under OSX), when this function is called after the Manipulation panel is hidden
+ // So, let check if Manipulation panel is still shown for this moment
+ if (!this->IsShown())
+ return;
+ m_drop_to_bed_button->Show(show_drop_to_bed);
+ m_reset_rotation_button->Show(show_rotation);
+ m_reset_scale_button->Show(show_scale);
+ m_mirror_warning_bitmap->SetBitmap(show_mirror ? m_manifold_warning_bmp.bmp() : wxNullBitmap);
+ m_mirror_warning_bitmap->SetMinSize(show_mirror ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0));
+ m_mirror_warning_bitmap->SetToolTip(show_mirror ? _L("Left handed") : "");
+ m_reset_skew_button->Show(show_skew);
+ m_skew_label->Show(show_skew);
+
+ // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
+ Sidebar& panel = wxGetApp().sidebar();
+ if (!panel.IsFrozen()) {
+ panel.Freeze();
+ panel.Layout();
+ panel.Thaw();
+ }
+ });
+}
+
+void ObjectManipulation::update_mirror_buttons_visibility()
+{
+ const bool can_mirror = wxGetApp().plater()->can_mirror();
+ for (ScalableButton* button : m_mirror_buttons) {
+ button->Enable(can_mirror);
+ }
+}
+
+
+
+
+#ifndef __APPLE__
+void ObjectManipulation::emulate_kill_focus()
+{
+ if (!m_focused_editor)
+ return;
+
+ m_focused_editor->kill_focus(this);
+}
+#endif // __APPLE__
+
+void ObjectManipulation::update_item_name(const wxString& item_name)
+{
+ m_item_name->SetLabel(item_name);
+}
+
+void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning)
+{
+ if (const std::string& warning_icon_name = warning.warning_icon_name;
+ !warning_icon_name.empty())
+ m_manifold_warning_bmp = ScalableBitmap(m_parent, warning_icon_name);
+ const wxString& tooltip = warning.tooltip;
+ m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp());
+ m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.GetSize());
+ m_fix_throught_netfab_bitmap->SetToolTip(tooltip);
+}
+
+wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type)
+{
+ switch (type)
+ {
+ case ECoordinatesType::World: { return _L("World coordinates"); }
+ case ECoordinatesType::Instance: { return _L("Object coordinates"); }
+ case ECoordinatesType::Local: { return _L("Part coordinates"); }
+ default: { assert(false); return _L("Unknown"); }
+ }
+}
+
+#if ENABLE_OBJECT_MANIPULATION_DEBUG
+void ObjectManipulation::render_debug_window()
+{
+ ImGuiWrapper& imgui = *wxGetApp().imgui();
+// ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once);
+ imgui.begin(std::string("ObjectManipulation"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
+ imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Coordinates type");
+ ImGui::SameLine();
+ imgui.text(coordinate_type_str(m_coordinates_type));
+ imgui.end();
+}
+#endif // ENABLE_OBJECT_MANIPULATION_DEBUG
+
+void ObjectManipulation::reset_settings_value()
+{
+ m_new_position = Vec3d::Zero();
+ m_new_rotation = Vec3d::Zero();
+ m_new_scale = Vec3d::Ones() * 100.;
+ m_new_size = Vec3d::Zero();
+ m_new_enabled = false;
+ // no need to set the dirty flag here as this method is called from update_settings_value(),
+ // which is called from update_if_dirty(), which resets the dirty flag anyways.
+// m_dirty = true;
+}
+
+void ObjectManipulation::change_position_value(int axis, double value)
+{
+ if (std::abs(m_cache.position_rounded(axis) - value) < EPSILON)
+ return;
+
+ Vec3d position = m_cache.position;
+ position(axis) = value;
+
+ auto canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+ selection.setup_cache();
+ TransformationType trafo_type;
+ trafo_type.set_relative();
+ switch (get_coordinates_type())
+ {
+ case ECoordinatesType::Instance: { trafo_type.set_instance(); break; }
+ case ECoordinatesType::Local: { trafo_type.set_local(); break; }
+ default: { break; }
+ }
+ selection.translate(position - m_cache.position, trafo_type);
+ canvas->do_move(L("Set Position"));
+
+ m_cache.position = position;
+ m_cache.position_rounded(axis) = DBL_MAX;
+ this->UpdateAndShow(true);
+}
+
+void ObjectManipulation::change_rotation_value(int axis, double value)
+{
+ if (std::abs(m_cache.rotation_rounded(axis) - value) < EPSILON)
+ return;
+
+ Vec3d rotation = m_cache.rotation;
+ rotation(axis) = value;
+
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ Selection& selection = canvas->get_selection();
+
+ TransformationType transformation_type;
+ transformation_type.set_relative();
+ if (selection.is_single_full_instance())
+ transformation_type.set_independent();
+
+ if (is_local_coordinates())
+ transformation_type.set_local();
+
+ if (is_instance_coordinates())
+ transformation_type.set_instance();
+
+ selection.setup_cache();
+ selection.rotate(
+ (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation),
+ transformation_type);
+ canvas->do_rotate(L("Set Orientation"));
+
+ m_cache.rotation = rotation;
+ m_cache.rotation_rounded(axis) = DBL_MAX;
+ this->UpdateAndShow(true);
+}
+
+void ObjectManipulation::change_scale_value(int axis, double value)
+{
+ if (value <= 0.0)
+ return;
+
+ if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON)
+ return;
+
+ Vec3d scale = m_cache.scale;
+ scale(axis) = value;
+
+ const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ Vec3d ref_scale = m_cache.scale;
+ if (selection.is_single_volume_or_modifier()) {
+ scale = scale.cwiseQuotient(ref_scale);
+ ref_scale = Vec3d::Ones();
+ }
+ else if (selection.is_single_full_instance())
+ ref_scale = 100.0 * Vec3d::Ones();
+
+ this->do_scale(axis, scale.cwiseQuotient(ref_scale));
+
+ m_cache.scale = scale;
+ m_cache.scale_rounded(axis) = DBL_MAX;
+ this->UpdateAndShow(true);
+}
+
+
+void ObjectManipulation::change_size_value(int axis, double value)
+{
+ if (value <= 0.0)
+ return;
+
+ if (m_imperial_units) {
+ if (std::abs(m_cache.size_inches(axis) - value) < EPSILON)
+ return;
+ m_cache.size_inches(axis) = value;
+ value *= in_to_mm;
+ }
+
+ if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON)
+ return;
+
+ Vec3d size = m_cache.size;
+ size(axis) = value;
+
+ const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+
+ Vec3d ref_size = m_cache.size;
+ if (selection.is_single_volume_or_modifier()) {
+ size = size.cwiseQuotient(ref_size);
+ ref_size = Vec3d::Ones();
+ }
+ else if (selection.is_single_full_instance()) {
+ if (is_world_coordinates())
+ ref_size = selection.get_full_unscaled_instance_bounding_box().size();
+ else
+ ref_size = selection.get_full_unscaled_instance_local_bounding_box().size();
+ }
+
+ this->do_size(axis, size.cwiseQuotient(ref_size));
+
+ m_cache.size = size;
+ m_cache.size_rounded(axis) = DBL_MAX;
+ this->UpdateAndShow(true);
+}
+
+void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
+{
+ TransformationType transformation_type;
+ if (is_local_coordinates())
+ transformation_type.set_local();
+ else if (is_instance_coordinates())
+ transformation_type.set_instance();
+
+ Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+ if (selection.is_single_volume_or_modifier() && !is_local_coordinates())
+ transformation_type.set_relative();
+
+ const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale;
+ selection.setup_cache();
+ selection.scale(scaling_factor, transformation_type);
+ wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale"));
+}
+
+void ObjectManipulation::do_size(int axis, const Vec3d& scale) const
+{
+ Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
+
+ TransformationType transformation_type;
+ if (is_local_coordinates())
+ transformation_type.set_local();
+ else if (is_instance_coordinates())
+ transformation_type.set_instance();
+
+ const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale;
+ selection.setup_cache();
+ selection.scale(scaling_factor, transformation_type);
+ wxGetApp().plater()->canvas3D()->do_scale(L("Set Size"));
+}
+
+void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value)
+{
+ if (!m_cache.is_valid())
+ return;
+
+ if (opt_key == "position") {
+ if (m_imperial_units)
+ new_value *= in_to_mm;
+ change_position_value(axis, new_value);
+ }
+ else if (opt_key == "rotation")
+ change_rotation_value(axis, new_value);
+ else if (opt_key == "scale") {
+ if (new_value > 0.0)
+ change_scale_value(axis, new_value);
+ else {
+ new_value = m_cache.scale(axis);
+ m_cache.scale(axis) = 0.0;
+ m_cache.scale_rounded(axis) = DBL_MAX;
+ change_scale_value(axis, new_value);
+ }
+ }
+ else if (opt_key == "size") {
+ if (new_value > 0.0)
+ change_size_value(axis, new_value);
+ else {
+ Vec3d& size = m_imperial_units ? m_cache.size_inches : m_cache.size;
+ new_value = size(axis);
+ size(axis) = 0.0;
+ m_cache.size_rounded(axis) = DBL_MAX;
+ change_size_value(axis, new_value);
+ }
+ }
+}
+
+void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale)
+{
+ if (!use_uniform_scale)
+ // Recalculate cached values at this panel, refresh the screen.
+ this->UpdateAndShow(true);
+
+ m_uniform_scale = use_uniform_scale;
+ set_dirty();
+}
+
+void ObjectManipulation::set_coordinates_type(ECoordinatesType type)
+{
+ if (wxGetApp().get_mode() == comSimple)
+ type = ECoordinatesType::World;
+
+ if (m_coordinates_type == type)
+ return;
+
+ m_coordinates_type = type;
+ m_word_local_combo->SetSelection((int)m_coordinates_type);
+ this->UpdateAndShow(true);
+ GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
+ canvas->get_gizmos_manager().update_data();
+ canvas->set_as_dirty();
+ canvas->request_extra_frame();
+}
+
+ECoordinatesType ObjectManipulation::get_coordinates_type() const
+{
+ return m_coordinates_type;
+}
+
+void ObjectManipulation::msw_rescale()
+{
+ const int em = wxGetApp().em_unit();
+ m_item_name->SetMinSize(wxSize(20*em, wxDefaultCoord));
+ msw_rescale_word_local_combo(m_word_local_combo);
+ m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1)));
+
+ const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText();
+ m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp());
+ m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.GetSize());
+
+ // rescale label-heights
+ // Text trick to grid sizer layout:
+ // Height of labels should be equivalent to the edit boxes
+ const int height = wxTextCtrl(parent(), wxID_ANY, "Br").GetBestHeight(-1);
+ for (wxBoxSizer* sizer : m_rescalable_sizers)
+ sizer->SetMinSize(wxSize(-1, height));
+
+ // rescale edit-boxes
+ for (ManipulationEditor* editor : m_editors)
+ editor->msw_rescale();
+
+ // rescale "inches" checkbox
+ m_check_inch->SetInitialSize(m_check_inch->GetBestSize());
+
+ get_og()->msw_rescale();
+}
+
+void ObjectManipulation::sys_color_changed()
+{
+#ifdef _WIN32
+ get_og()->sys_color_changed();
+ wxGetApp().UpdateDarkUI(m_word_local_combo);
+ wxGetApp().UpdateDarkUI(m_check_inch);
+#endif
+ for (ManipulationEditor* editor : m_editors)
+ editor->sys_color_changed(this);
+
+ m_mirror_bitmap_on.sys_color_changed();
+ m_reset_scale_button->sys_color_changed();
+ m_reset_rotation_button->sys_color_changed();
+ m_drop_to_bed_button->sys_color_changed();
+ m_lock_bnt->sys_color_changed();
+
+ for (int id = 0; id < 3; ++id) {
+ m_mirror_buttons[id]->sys_color_changed();
+ }
+}
+
+void ObjectManipulation::set_coordinates_type(const wxString& type_string)
+{
+ if (type_string == coordinate_type_str(ECoordinatesType::Instance))
+ this->set_coordinates_type(ECoordinatesType::Instance);
+ else if (type_string == coordinate_type_str(ECoordinatesType::Local))
+ this->set_coordinates_type(ECoordinatesType::Local);
+ else
+ this->set_coordinates_type(ECoordinatesType::World);
+}
+
+static const char axes[] = { 'x', 'y', 'z' };
+ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
+ const std::string& opt_key,
+ int axis) :
+ wxTextCtrl(parent->parent(), wxID_ANY, wxEmptyString, wxDefaultPosition,
+ wxSize((wxOSX ? 5 : 6)*int(wxGetApp().em_unit()), wxDefaultCoord), wxTE_PROCESS_ENTER
+#ifdef _WIN32
+ | wxBORDER_SIMPLE
+#endif
+ ),
+ m_opt_key(opt_key),
+ m_axis(axis)
+{
+ set_font_and_background_style(this, wxGetApp().normal_font());
+#ifdef __WXOSX__
+ this->OSXDisableAllSmartSubstitutions();
+#endif // __WXOSX__
+ if (parent->use_colors()) {
+ this->SetBackgroundColour(wxColour(axes_color_back[axis]));
+ this->SetForegroundColour(*wxBLACK);
+ } else {
+ wxGetApp().UpdateDarkUI(this);
+ }
+
+ // A name used to call handle_sidebar_focus_event()
+ m_full_opt_name = m_opt_key+"_"+axes[axis];
+
+ // Reset m_enter_pressed flag to _false_, when value is editing
+ this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId());
+
+ this->Bind(wxEVT_TEXT_ENTER, [this, parent](wxEvent&)
+ {
+ m_enter_pressed = true;
+ parent->on_change(m_opt_key, m_axis, get_value());
+ }, this->GetId());
+
+ this->Bind(wxEVT_KILL_FOCUS, [this, parent](wxFocusEvent& e)
+ {
+ parent->set_focused_editor(nullptr);
+
+ if (!m_enter_pressed)
+ kill_focus(parent);
+
+ e.Skip();
+ }, this->GetId());
+
+ this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e)
+ {
+ parent->set_focused_editor(this);
+
+ // needed to show the visual hints in 3D scene
+ wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, true);
+ e.Skip();
+ }, this->GetId());
+
+ this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event)
+ {
+ // select all text using Ctrl+A
+ if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
+ this->SetSelection(-1, -1); //select all
+ event.Skip();
+ }));
+
+ this->Bind(wxEVT_UPDATE_UI, [parent, this](wxUpdateUIEvent& evt) {
+ const bool is_gizmo_in_editing_mode = wxGetApp().plater()->canvas3D()->get_gizmos_manager().is_in_editing_mode();
+ const bool is_enabled_editing = has_opt_key("scale") || has_opt_key("size") ? parent->is_enabled_size_and_scale() : true;
+ evt.Enable(!is_gizmo_in_editing_mode && parent->is_enabled() && is_enabled_editing);
+ });
+}
+
+void ManipulationEditor::msw_rescale()
+{
+ const int em = wxGetApp().em_unit();
+ SetMinSize(wxSize(5 * em, wxDefaultCoord));
+}
+
+void ManipulationEditor::sys_color_changed(ObjectManipulation* parent)
+{
+ if (parent->use_colors())
+ SetForegroundColour(*wxBLACK);
+ else
+#ifdef _WIN32
+ wxGetApp().UpdateDarkUI(this);
+#else
+ SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
+#endif // _WIN32
+}
+
+double ManipulationEditor::get_value()
+{
+ wxString str = GetValue();
+
+ double value;
+ const char dec_sep = is_decimal_separator_point() ? '.' : ',';
+ const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
+ // Replace the first incorrect separator in decimal number.
+ if (str.Replace(dec_sep_alt, dec_sep, false) != 0)
+ SetValue(str);
+
+ if (str == ".")
+ value = 0.0;
+
+ if ((str.IsEmpty() || !str.ToDouble(&value)) && !m_valid_value.IsEmpty()) {
+ str = m_valid_value;
+ SetValue(str);
+ str.ToDouble(&value);
+ }
+
+ return value;
+}
+
+void ManipulationEditor::set_value(const wxString& new_value)
+{
+ if (new_value.IsEmpty())
+ return;
+ m_valid_value = new_value;
+ SetValue(m_valid_value);
+}
+
+void ManipulationEditor::kill_focus(ObjectManipulation* parent)
+{
+ parent->on_change(m_opt_key, m_axis, get_value());
+
+ // if the change does not come from the user pressing the ENTER key
+ // we need to hide the visual hints in 3D scene
+ wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, false);
+}
+
+} //namespace GUI
+} //namespace Slic3r
diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
new file mode 100644
index 00000000000..139171d999a
--- /dev/null
+++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp
@@ -0,0 +1,250 @@
+#ifndef slic3r_GUI_ObjectManipulation_hpp_
+#define slic3r_GUI_ObjectManipulation_hpp_
+
+#include
+
+#include "GUI_ObjectSettings.hpp"
+#include "GUI_ObjectList.hpp"
+#include "GUI_Geometry.hpp"
+#include "libslic3r/Point.hpp"
+#include
+
+#ifdef __WXOSX__
+class wxBitmapComboBox;
+#else
+class wxComboBox;
+#endif // __WXOSX__
+class wxStaticText;
+class LockButton;
+class wxStaticBitmap;
+class wxCheckBox;
+
+namespace Slic3r {
+namespace GUI {
+
+#ifdef _WIN32
+class BitmapComboBox;
+#endif
+
+#ifdef __WXOSX__
+ static_assert(wxMAJOR_VERSION >= 3, "Use of wxBitmapComboBox on Manipulation panel requires wxWidgets 3.0 and newer");
+ using choice_ctrl = wxBitmapComboBox;
+#else
+#ifdef _WIN32
+ using choice_ctrl = BitmapComboBox;
+#else
+ using choice_ctrl = wxComboBox;
+#endif
+#endif // __WXOSX__
+
+class Selection;
+
+class ObjectManipulation;
+class ManipulationEditor : public wxTextCtrl
+{
+ std::string m_opt_key;
+ int m_axis;
+ bool m_enter_pressed { false };
+ wxString m_valid_value {wxEmptyString};
+
+ std::string m_full_opt_name;
+
+public:
+ ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, int axis);
+ ~ManipulationEditor() {}
+
+ void msw_rescale();
+ void sys_color_changed(ObjectManipulation* parent);
+ void set_value(const wxString& new_value);
+ void kill_focus(ObjectManipulation *parent);
+
+ const std::string& get_full_opt_name() const { return m_full_opt_name; }
+
+ bool has_opt_key(const std::string& key) { return m_opt_key == key; }
+
+private:
+ double get_value();
+};
+
+
+class ObjectManipulation : public OG_Settings
+{
+public:
+ static const double in_to_mm;
+ static const double mm_to_in;
+
+private:
+ struct Cache
+ {
+ Vec3d position;
+ Vec3d position_rounded;
+ Vec3d rotation;
+ Vec3d rotation_rounded;
+ Vec3d scale;
+ Vec3d scale_rounded;
+ Vec3d size;
+ Vec3d size_inches;
+ Vec3d size_rounded;
+
+ wxString move_label_string;
+ wxString rotate_label_string;
+ wxString scale_label_string;
+
+ Cache() { reset(); }
+ void reset()
+ {
+ position = position_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
+ rotation = rotation_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
+ scale = scale_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
+ size = size_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
+ move_label_string = wxString();
+ rotate_label_string = wxString();
+ scale_label_string = wxString();
+ }
+ bool is_valid() const { return position != Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); }
+ };
+
+ Cache m_cache;
+
+ wxStaticText* m_move_Label = nullptr;
+ wxStaticText* m_scale_Label = nullptr;
+ wxStaticText* m_rotate_Label = nullptr;
+
+ bool m_imperial_units { false };
+ bool m_use_colors { false };
+ wxStaticText* m_position_unit { nullptr };
+ wxStaticText* m_size_unit { nullptr };
+
+ wxStaticText* m_item_name = nullptr;
+ wxStaticText* m_empty_str = nullptr;
+
+ // Non-owning pointers to the reset buttons, so we can hide and show them.
+ ScalableButton* m_reset_scale_button{ nullptr };
+ ScalableButton* m_reset_rotation_button{ nullptr };
+ ScalableButton* m_reset_skew_button{ nullptr };
+ ScalableButton* m_drop_to_bed_button{ nullptr };
+
+ wxCheckBox* m_check_inch {nullptr};
+
+ std::array m_mirror_buttons;
+
+ // Bitmaps for the mirroring buttons.
+ ScalableBitmap m_mirror_bitmap_on;
+
+ // Needs to be updated from OnIdle?
+ bool m_dirty = false;
+ // Cached labels for the delayed update, not localized!
+ std::string m_new_move_label_string;
+ std::string m_new_rotate_label_string;
+ std::string m_new_scale_label_string;
+ Vec3d m_new_position;
+ Vec3d m_new_rotation;
+ Vec3d m_new_scale;
+ Vec3d m_new_size;
+ bool m_new_enabled {true};
+ bool m_uniform_scale {true};
+ ECoordinatesType m_coordinates_type{ ECoordinatesType::World };
+ LockButton* m_lock_bnt{ nullptr };
+ choice_ctrl* m_word_local_combo { nullptr };
+
+ ScalableBitmap m_manifold_warning_bmp;
+ wxStaticBitmap* m_fix_throught_netfab_bitmap{ nullptr };
+ wxStaticBitmap* m_mirror_warning_bitmap{ nullptr };
+
+ // Currently focused editor (nullptr if none)
+ ManipulationEditor* m_focused_editor{ nullptr };
+
+ wxFlexGridSizer* m_main_grid_sizer;
+ wxFlexGridSizer* m_labels_grid_sizer;
+
+ wxStaticText* m_skew_label{ nullptr };
+
+ // sizers, used for msw_rescale
+ wxBoxSizer* m_word_local_combo_sizer;
+ std::vector m_rescalable_sizers;
+
+ std::vector m_editors;
+
+ // parameters for enabling/disabling of editors
+ bool m_is_enabled { true };
+ bool m_is_enabled_size_and_scale { true };
+
+
+public:
+ ObjectManipulation(wxWindow* parent);
+ ~ObjectManipulation() {}
+
+ void Show(const bool show) override;
+ bool IsShown() override;
+ void UpdateAndShow(const bool show) override;
+ void Enable(const bool enadle = true);
+ void Disable() { Enable(false); }
+ void DisableScale();
+ void DisableUnuniformScale();
+ void update_ui_from_settings();
+ bool use_colors() { return m_use_colors; }
+
+ void set_dirty() { m_dirty = true; }
+ // Called from the App to update the UI if dirty.
+ void update_if_dirty();
+
+ void set_uniform_scaling(const bool use_uniform_scale);
+ bool get_uniform_scaling() const { return m_uniform_scale; }
+
+ void set_coordinates_type(ECoordinatesType type);
+ ECoordinatesType get_coordinates_type() const;
+ bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; }
+ bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; }
+ bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; }
+
+ void reset_cache() { m_cache.reset(); }
+#ifndef __APPLE__
+ // On Windows and Linux, emulates a kill focus event on the currently focused option (if any)
+ // Used only in ObjectList wxEVT_DATAVIEW_SELECTION_CHANGED handler which is called before the regular kill focus event
+ // bound to this class when changing selection in the objects list
+ void emulate_kill_focus();
+#endif // __APPLE__
+
+ void update_item_name(const wxString &item_name);
+ void update_warning_icon_state(const MeshErrorsInfo& warning);
+ void msw_rescale();
+ void sys_color_changed();
+ void on_change(const std::string& opt_key, int axis, double new_value);
+ void set_focused_editor(ManipulationEditor* focused_editor) {
+ m_focused_editor = focused_editor;
+ }
+
+ ManipulationEditor* get_focused_editor() { return m_focused_editor; }
+
+ static wxString coordinate_type_str(ECoordinatesType type);
+
+ bool is_enabled() const { return m_is_enabled; }
+ bool is_enabled_size_and_scale()const { return m_is_enabled_size_and_scale; }
+
+#if ENABLE_OBJECT_MANIPULATION_DEBUG
+ void render_debug_window();
+#endif // ENABLE_OBJECT_MANIPULATION_DEBUG
+
+private:
+ void reset_settings_value();
+ void update_settings_value(const Selection& selection);
+
+ // Show or hide scale/rotation reset buttons if needed
+ void update_reset_buttons_visibility();
+ //Show or hide mirror buttons
+ void update_mirror_buttons_visibility();
+
+ // change values
+ void change_position_value(int axis, double value);
+ void change_rotation_value(int axis, double value);
+ void change_scale_value(int axis, double value);
+ void change_size_value(int axis, double value);
+ void do_scale(int axis, const Vec3d &scale) const;
+ void do_size(int axis, const Vec3d& scale) const;
+
+ void set_coordinates_type(const wxString& type_string);
+};
+
+}}
+
+#endif // slic3r_GUI_ObjectManipulation_hpp_
diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp
index bf2c4277b88..f0c3d30a6fb 100644
--- a/src/slic3r/GUI/GUI_Utils.hpp
+++ b/src/slic3r/GUI/GUI_Utils.hpp
@@ -501,6 +501,16 @@ bool generate_image(const std::string &filename, wxImage &image, wxSize img_size
int get_dpi_for_window(const wxWindow *window);
+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; }
+};
+
}}
#endif
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
index b1d98d3dc2f..fd678f303e1 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
@@ -5,6 +5,7 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI_Colors.hpp"
+#include "slic3r/GUI/Plater.hpp"
// TODO: Display tooltips quicker on Linux
@@ -99,6 +100,18 @@ float GLGizmoBase::Grabber::get_dragging_half_size(float size) const
{
return get_half_size(size) * DraggingScaleFactor;
}
+void GLGizmoBase::Grabber::register_raycasters_for_picking(int id)
+{
+ picking_id = id;
+ // registration will happen on next call to render()
+}
+
+void GLGizmoBase::Grabber::unregister_raycasters_for_picking()
+{
+ wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, picking_id);
+ picking_id = -1;
+ raycasters = { nullptr };
+}
const GLModel& GLGizmoBase::Grabber::get_cube() const
{
@@ -238,7 +251,19 @@ bool GLGizmoBase::update_items_state()
m_dirty = false;
return res;
};
+void GLGizmoBase::register_grabbers_for_picking()
+{
+ for (size_t i = 0; i < m_grabbers.size(); ++i) {
+ m_grabbers[i].register_raycasters_for_picking((m_group_id >= 0) ? m_group_id : i);
+ }
+}
+void GLGizmoBase::unregister_grabbers_for_picking()
+{
+ for (size_t i = 0; i < m_grabbers.size(); ++i) {
+ m_grabbers[i].unregister_raycasters_for_picking();
+ }
+}
bool GLGizmoBase::GizmoImguiBegin(const std::string &name, int flags)
{
return m_imgui->begin(name, flags);
@@ -283,6 +308,7 @@ std::array GLGizmoBase::picking_color_component(unsigned int id) const
};
}
+
void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const
{
#if ENABLE_FIXED_GRABBER
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
index c725809bbae..5bfac2a9fb7 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
@@ -5,6 +5,7 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GLModel.hpp"
+#include "slic3r/GUI/SceneRaycaster.hpp"
#include
@@ -34,6 +35,7 @@ class GLGizmoBase
// Starting value for ids to avoid clashing with ids used by GLVolumes
// (254 is choosen to leave some space for forward compatibility)
static const unsigned int BASE_ID = 255 * 255 * 254;
+ static const unsigned int GRABBER_ELEMENTS_MAX_COUNT = 7;
static float INV_ZOOM;
@@ -69,6 +71,8 @@ class GLGizmoBase
std::array hover_color;
bool enabled;
bool dragging;
+ int picking_id{ -1 };
+ std::array, GRABBER_ELEMENTS_MAX_COUNT> raycasters = { nullptr };
Grabber();
@@ -79,6 +83,9 @@ class GLGizmoBase
float get_dragging_half_size(float size) const;
const GLModel& get_cube() const;
+ void register_raycasters_for_picking(int id);
+ void unregister_raycasters_for_picking();
+
private:
void render(float size, const std::array& render_color, bool picking) const;
@@ -159,7 +166,7 @@ class GLGizmoBase
virtual bool wants_enter_leave_snapshots() const { return false; }
virtual std::string get_gizmo_entering_text() const { assert(false); return ""; }
virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; }
- virtual std::string get_action_snapshot_name() { return "Gizmo action"; }
+ virtual std::string get_action_snapshot_name() const { return "Gizmo action"; }
void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
virtual bool apply_clipping_plane() { return true; }
@@ -189,8 +196,27 @@ class GLGizmoBase
void render_input_window(float x, float y, float bottom_limit);
virtual void on_change_color_mode(bool is_dark) { m_is_dark_mode = is_dark; }
+ ///
+ /// Mouse tooltip text
+ ///
+ /// Text to be visible in mouse tooltip
virtual std::string get_tooltip() const { return ""; }
+ ///
+ /// Is called when data (Selection) is changed
+ ///
+ virtual void data_changed(bool is_serializing){};
+
+ ///
+ /// Implement when want to process mouse events in gizmo
+ /// Click, Right click, move, drag, ...
+ ///
+ /// Keep information about mouse click
+ /// Return True when use the information and don't want to propagate it otherwise False.
+ virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; }
+ void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); }
+ void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
+
int get_count() { return ++count; }
std::string get_gizmo_name() { return on_get_name(); }
@@ -213,6 +239,10 @@ class GLGizmoBase
virtual void on_render() = 0;
virtual void on_render_for_picking() = 0;
virtual void on_render_input_window(float x, float y, float bottom_limit) {}
+ void register_grabbers_for_picking();
+ void unregister_grabbers_for_picking();
+ virtual void on_register_raycasters_for_picking() {}
+ virtual void on_unregister_raycasters_for_picking() {}
bool GizmoImguiBegin(const std::string& name, int flags);
void GizmoImguiEnd();
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
index 6960a81dc62..5651d247a68 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp
@@ -39,7 +39,7 @@ class GLGizmoFdmSupports : public GLGizmoPainterBase
std::string get_gizmo_entering_text() const override { return "Entering Paint-on supports"; }
std::string get_gizmo_leaving_text() const override { return "Leaving Paint-on supports"; }
- std::string get_action_snapshot_name() override { return "Paint-on supports editing"; }
+ std::string get_action_snapshot_name() const override { return "Paint-on supports editing"; }
// BBS
wchar_t m_current_tool = 0;
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
new file mode 100644
index 00000000000..7fd85d6729f
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
@@ -0,0 +1,2142 @@
+#include "GLGizmoMeasure.hpp"
+#include "libslic3r/Color.hpp"
+#include "libslic3r/Format/STL.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/Plater.hpp"
+#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp"
+
+
+#include "libslic3r/PresetBundle.hpp"
+#include "libslic3r/MeasureUtils.hpp"
+
+#include
+
+#include
+
+#include