Merge remote-tracking branch 'origin/et_surface'
This commit is contained in:
commit
6a1a91ed1a
93
resources/icons/measure.svg
Normal file
93
resources/icons/measure.svg
Normal file
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.0"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 128 128"
|
||||
enable-background="new 0 0 128 128"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="measure2.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs976">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</defs><sodipodi:namedview
|
||||
id="namedview974"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.34375"
|
||||
inkscape:cx="63.921182"
|
||||
inkscape:cy="64.078818"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1001"
|
||||
inkscape:window-x="3191"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<g
|
||||
id="g1872"><path
|
||||
fill="#ed6b21"
|
||||
d="m 26.966044,54.457929 0.07558,-9.999714 c 0.0063,-0.829976 0.681318,-1.494893 1.511294,-1.48862 0.829976,0.0063 1.494893,0.681318 1.48862,1.511294 l -0.07558,9.999714 c -0.0063,0.829977 -0.681318,1.494894 -1.511294,1.48862 -0.829976,-0.0063 -1.494893,-0.681317 -1.48862,-1.511294 z"
|
||||
id="path958-8"
|
||||
style="fill:#ed6b21;fill-opacity:1;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /><path
|
||||
fill="#ed6b21"
|
||||
d="m 36.966044,54.458378 0.07558,-9.999714 c 0.0063,-0.829976 0.681318,-1.494893 1.511294,-1.48862 0.829976,0.0063 1.494893,0.681318 1.48862,1.511294 l -0.07558,9.999714 c -0.0063,0.829977 -0.681318,1.494894 -1.511294,1.48862 -0.829976,-0.0063 -1.494893,-0.681317 -1.48862,-1.511294 z"
|
||||
id="path958-8-1"
|
||||
style="fill:#ed6b21;fill-opacity:1;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /><path
|
||||
fill="#ed6b21"
|
||||
d="m 46.966202,60.202938 0.07558,-14.999888 c 0.0063,-1.244993 0.681318,-2.242399 1.511294,-2.232988 0.829976,0.0087 1.494893,1.021998 1.48862,2.266999 l -0.07558,14.999887 c -0.0063,1.244995 -0.681318,2.242401 -1.511294,2.232988 -0.829976,-0.0087 -1.494893,-1.021997 -1.48862,-2.266998 z"
|
||||
id="path958-8-9"
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:1.22476;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /><path
|
||||
fill="#ed6b21"
|
||||
d="m 56.966044,54.458378 0.07558,-9.999714 c 0.0063,-0.829976 0.681318,-1.494893 1.511294,-1.48862 0.829976,0.0063 1.494893,0.681318 1.48862,1.511294 l -0.07558,9.999714 c -0.0063,0.829977 -0.681318,1.494894 -1.511294,1.48862 -0.829976,-0.0063 -1.494893,-0.681317 -1.48862,-1.511294 z"
|
||||
id="path958-8-9-3"
|
||||
style="fill:#ed6b21;fill-opacity:1;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /><path
|
||||
fill="#ed6b21"
|
||||
d="m 66.966044,54.458378 0.07558,-9.999714 c 0.0063,-0.829976 0.681318,-1.494893 1.511294,-1.48862 0.829976,0.0063 1.494893,0.681318 1.48862,1.511294 l -0.07558,9.999714 c -0.0063,0.829977 -0.681318,1.494894 -1.511294,1.48862 -0.829976,-0.0063 -1.494893,-0.681317 -1.48862,-1.511294 z"
|
||||
id="path958-8-9-3-0"
|
||||
style="fill:#ed6b21;fill-opacity:1;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /><path
|
||||
fill="#ed6b21"
|
||||
d="m 76.966202,60.202932 0.07558,-14.999881 c 0.0063,-1.244994 0.681318,-2.2424 1.511294,-2.23299 0.829976,0.0085 1.494893,1.021998 1.48862,2.266999 l -0.07558,14.999882 c -0.0063,1.244995 -0.681318,2.2424 -1.511294,2.232987 -0.829976,-0.0085 -1.494893,-1.021996 -1.48862,-2.266997 z"
|
||||
id="path958-8-9-3-1"
|
||||
style="fill:#ed6b21;fill-opacity:1;stroke-width:1.22474;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /><path
|
||||
fill="#ed6b21"
|
||||
d="m 86.966044,54.458378 0.07558,-9.999714 c 0.0063,-0.829976 0.681318,-1.494893 1.511294,-1.48862 0.829976,0.0063 1.494893,0.681318 1.48862,1.511294 l -0.07558,9.999714 c -0.0063,0.829977 -0.681318,1.494894 -1.511294,1.48862 -0.829976,-0.0063 -1.494893,-0.681317 -1.48862,-1.511294 z"
|
||||
id="path958-8-9-3-1-4"
|
||||
style="fill:#ed6b21;fill-opacity:1;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /><path
|
||||
fill="#ed6b21"
|
||||
d="m 96.966044,54.458378 0.07558,-9.999714 c 0.0063,-0.829976 0.681318,-1.494893 1.511294,-1.48862 0.829976,0.0063 1.494892,0.681318 1.488622,1.511294 l -0.07558,9.999714 c -0.0063,0.829977 -0.681318,1.494894 -1.511294,1.48862 -0.829976,-0.0063 -1.494893,-0.681317 -1.48862,-1.511294 z"
|
||||
id="path958-8-9-3-1-4-2"
|
||||
style="fill:#ed6b21;fill-opacity:1;paint-order:stroke fill markers"
|
||||
sodipodi:nodetypes="sccsccs" /></g><g
|
||||
id="g964"
|
||||
transform="translate(0,34.9)">
|
||||
<path
|
||||
fill="#ffffff"
|
||||
d="M 108.79,51.6 H 19.21 c -1.93,0 -3.5,-1.57 -3.5,-3.5 V 10.12 c 0,-1.93 1.57,-3.5 3.5,-3.5 h 89.57 c 1.93,0 3.5,1.57 3.5,3.5 V 48.1 c 0.01,1.93 -1.57,3.5 -3.49,3.5 z M 19.21,9.62 c -0.27,0 -0.5,0.23 -0.5,0.5 V 48.1 c 0,0.27 0.23,0.5 0.5,0.5 h 89.57 c 0.27,0 0.5,-0.23 0.5,-0.5 V 10.12 c 0,-0.27 -0.23,-0.5 -0.5,-0.5 z"
|
||||
id="path962" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
@ -157,23 +157,25 @@ namespace ImGui
|
||||
const wchar_t InfoMarker = 0x2603;
|
||||
const wchar_t SliderFloatEditBtnIcon = 0x2604;
|
||||
const wchar_t SliderFloatEditBtnPressedIcon = 0x2605;
|
||||
const wchar_t LegendTravel = 0x2606;
|
||||
const wchar_t LegendWipe = 0x2607;
|
||||
const wchar_t LegendRetract = 0x2608;
|
||||
const wchar_t LegendDeretract = 0x2609;
|
||||
const wchar_t LegendSeams = 0x2610;
|
||||
const wchar_t LegendToolChanges = 0x2611;
|
||||
const wchar_t LegendColorChanges = 0x2612;
|
||||
const wchar_t LegendPausePrints = 0x2613;
|
||||
const wchar_t LegendCustomGCodes = 0x2614;
|
||||
const wchar_t LegendCOG = 0x2615;
|
||||
const wchar_t LegendShells = 0x2616;
|
||||
const wchar_t LegendToolMarker = 0x2617;
|
||||
const wchar_t WarningMarkerSmall = 0x2618;
|
||||
const wchar_t ExpandBtn = 0x2619;
|
||||
const wchar_t CollapseBtn = 0x2620;
|
||||
const wchar_t InfoMarkerSmall = 0x2621;
|
||||
const wchar_t ClipboardBtnIcon = 0x2606;
|
||||
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
const wchar_t LegendTravel = 0x2701;
|
||||
const wchar_t LegendWipe = 0x2702;
|
||||
const wchar_t LegendRetract = 0x2703;
|
||||
const wchar_t LegendDeretract = 0x2704;
|
||||
const wchar_t LegendSeams = 0x2705;
|
||||
const wchar_t LegendToolChanges = 0x2706;
|
||||
const wchar_t LegendColorChanges = 0x2707;
|
||||
const wchar_t LegendPausePrints = 0x2708;
|
||||
const wchar_t LegendCustomGCodes = 0x2709;
|
||||
const wchar_t LegendCOG = 0x2710;
|
||||
const wchar_t LegendShells = 0x2711;
|
||||
const wchar_t LegendToolMarker = 0x2712;
|
||||
const wchar_t WarningMarkerSmall = 0x2713;
|
||||
const wchar_t ExpandBtn = 0x2714;
|
||||
const wchar_t CollapseBtn = 0x2715;
|
||||
const wchar_t InfoMarkerSmall = 0x2716;
|
||||
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
}
|
||||
|
||||
|
@ -180,6 +180,9 @@ set(SLIC3R_SOURCES
|
||||
MultiMaterialSegmentation.hpp
|
||||
MeshNormals.hpp
|
||||
MeshNormals.cpp
|
||||
Measure.hpp
|
||||
Measure.cpp
|
||||
MeasureUtils.hpp
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
Arrange.hpp
|
||||
@ -253,6 +256,7 @@ set(SLIC3R_SOURCES
|
||||
Surface.hpp
|
||||
SurfaceCollection.cpp
|
||||
SurfaceCollection.hpp
|
||||
SurfaceMesh.hpp
|
||||
SVG.cpp
|
||||
SVG.hpp
|
||||
Technologies.hpp
|
||||
|
1082
src/libslic3r/Measure.cpp
Normal file
1082
src/libslic3r/Measure.cpp
Normal file
File diff suppressed because it is too large
Load Diff
197
src/libslic3r/Measure.hpp
Normal file
197
src/libslic3r/Measure.hpp
Normal file
@ -0,0 +1,197 @@
|
||||
#ifndef Slic3r_Measure_hpp_
|
||||
#define Slic3r_Measure_hpp_
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
|
||||
#include "Point.hpp"
|
||||
|
||||
|
||||
struct indexed_triangle_set;
|
||||
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
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<Vec3d> 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<Vec3d, Vec3d> 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<Vec3d, double, Vec3d> 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<int, Vec3d, Vec3d> 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<Vec3d> 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<Vec3d> m_pt3;
|
||||
double m_value{ 0.0 };
|
||||
};
|
||||
|
||||
|
||||
|
||||
class MeasuringImpl;
|
||||
|
||||
|
||||
class Measuring {
|
||||
public:
|
||||
// Construct the measurement object on a given its. The its must remain
|
||||
// valid and unchanged during the whole lifetime of the object.
|
||||
explicit Measuring(const indexed_triangle_set& its);
|
||||
~Measuring();
|
||||
|
||||
// Return a reference to a list of all features identified on the its.
|
||||
// Use only for debugging. Expensive, do not call often.
|
||||
std::vector<SurfaceFeature> get_all_features() const;
|
||||
|
||||
// Given a face_idx where the mouse cursor points, return a feature that
|
||||
// should be highlighted (if any).
|
||||
std::optional<SurfaceFeature> get_feature(size_t face_idx, const Vec3d& point) const;
|
||||
|
||||
// Returns a list of triangle indices for each identified plane. Each
|
||||
// Plane object contains an index into this vector. Expensive, do not
|
||||
// call too often.
|
||||
std::vector<std::vector<int>> get_planes_triangle_indices() const;
|
||||
|
||||
// Returns the surface features of the plane with the given index
|
||||
const std::vector<SurfaceFeature>& get_plane_features(unsigned int plane_id) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<MeasuringImpl> priv;
|
||||
};
|
||||
|
||||
|
||||
struct DistAndPoints {
|
||||
DistAndPoints(double dist_, Vec3d from_, Vec3d to_) : dist(dist_), from(from_), to(to_) {}
|
||||
double dist;
|
||||
Vec3d from;
|
||||
Vec3d to;
|
||||
|
||||
void transform(const Transform3d& trafo);
|
||||
};
|
||||
|
||||
struct AngleAndEdges {
|
||||
AngleAndEdges(double angle_, const Vec3d& center_, const std::pair<Vec3d, Vec3d>& e1_, const std::pair<Vec3d, Vec3d>& e2_, double radius_, bool coplanar_)
|
||||
: angle(angle_), center(center_), e1(e1_), e2(e2_), radius(radius_), coplanar(coplanar_) {}
|
||||
double angle;
|
||||
Vec3d center;
|
||||
std::pair<Vec3d, Vec3d> e1;
|
||||
std::pair<Vec3d, Vec3d> e2;
|
||||
double radius;
|
||||
bool coplanar;
|
||||
|
||||
void transform(const Transform3d& trafo);
|
||||
|
||||
static const AngleAndEdges Dummy;
|
||||
};
|
||||
|
||||
struct MeasurementResult {
|
||||
std::optional<AngleAndEdges> angle;
|
||||
std::optional<DistAndPoints> distance_infinite;
|
||||
std::optional<DistAndPoints> distance_strict;
|
||||
std::optional<Vec3d> 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<Vec3d, Vec3d>& 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<Vec3d, Vec3d>& e1, const std::pair<Vec3d, Vec3d>& 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_
|
386
src/libslic3r/MeasureUtils.hpp
Normal file
386
src/libslic3r/MeasureUtils.hpp
Normal file
@ -0,0 +1,386 @@
|
||||
#ifndef Slic3r_MeasureUtils_hpp_
|
||||
#define Slic3r_MeasureUtils_hpp_
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
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<double> values)
|
||||
{
|
||||
// C++ 11 will call the default constructor for
|
||||
// Polynomial1<Real> 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<size_t>(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<int32_t>(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<uint32_t>(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<int32_t>(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<double> 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<double> derivCoeff(static_cast<size_t>(derivDegree) + 1);
|
||||
std::vector<double> 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<size_t>(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_
|
154
src/libslic3r/SurfaceMesh.hpp
Normal file
154
src/libslic3r/SurfaceMesh.hpp
Normal file
@ -0,0 +1,154 @@
|
||||
#ifndef slic3r_SurfaceMesh_hpp_
|
||||
#define slic3r_SurfaceMesh_hpp_
|
||||
|
||||
#include <admesh/stl.h>
|
||||
#include <libslic3r/TriangleMesh.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
|
||||
{
|
||||
Halfedge_index h_first = halfedge(v);
|
||||
Halfedge_index h = next_around_target(h_first);
|
||||
size_t degree = 2;
|
||||
while (! h.is_invalid() && h != h_first) {
|
||||
h = next_around_target(h);
|
||||
++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<Vec3i> m_face_neighbors;
|
||||
const indexed_triangle_set& m_its;
|
||||
};
|
||||
|
||||
} //namespace Slic3r
|
||||
|
||||
#endif // slic3r_SurfaceMesh_hpp_
|
@ -26,6 +26,8 @@
|
||||
#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0
|
||||
// Disable using instanced models to render options in gcode preview
|
||||
#define DISABLE_GCODEVIEWER_INSTANCED_MODELS 1
|
||||
// Enable Measure Gizmo debug window
|
||||
#define ENABLE_MEASURE_GIZMO_DEBUG 0
|
||||
|
||||
|
||||
// Enable rendering of objects using environment map
|
||||
|
@ -63,6 +63,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Gizmos/GLGizmoSimplify.hpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
|
||||
GUI/Gizmos/GLGizmoMeasure.cpp
|
||||
GUI/Gizmos/GLGizmoMeasure.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
GUI/GLSelectionRectangle.hpp
|
||||
GUI/GLModel.hpp
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
#include "SceneRaycaster.hpp"
|
||||
#endif // ENABLE_RAYCAST_PICKING
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
#include "libslic3r/Slicing.hpp"
|
||||
|
||||
@ -135,16 +136,6 @@ private:
|
||||
wxTimer* m_timer;
|
||||
};
|
||||
|
||||
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; }
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
|
||||
|
||||
using Vec2dEvent = Event<Vec2d>;
|
||||
@ -680,7 +671,7 @@ public:
|
||||
|
||||
#if ENABLE_RAYCAST_PICKING
|
||||
std::shared_ptr<SceneRaycasterItem> add_raycaster_for_picking(SceneRaycaster::EType type, int id, const MeshRaycaster& raycaster,
|
||||
const Transform3d& trafo, bool use_back_faces = false) {
|
||||
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) {
|
||||
|
@ -257,6 +257,21 @@ void GLModel::Geometry::remove_vertex(size_t id)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -2253,6 +2268,128 @@ GLModel::Geometry smooth_sphere(unsigned int resolution, float radius)
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height)
|
||||
{
|
||||
resolution = std::max<unsigned int>(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<Vec3f> 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<Vec3f> 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<unsigned int>(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<unsigned int>(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;
|
||||
}
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
|
||||
} // namespace GUI
|
||||
|
@ -140,6 +140,8 @@ namespace GUI {
|
||||
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); }
|
||||
|
||||
@ -374,9 +376,17 @@ namespace GUI {
|
||||
GLModel::Geometry diamond(unsigned int resolution);
|
||||
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
// create a sphere with the given resolution and smooth normals
|
||||
// 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);
|
||||
#endif // ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
|
||||
} // namespace GUI
|
||||
|
@ -416,6 +416,16 @@ public:
|
||||
~TaskTimer();
|
||||
};
|
||||
|
||||
class KeyAutoRepeatFilter
|
||||
{
|
||||
size_t m_count{ 0 };
|
||||
|
||||
public:
|
||||
void increase_count() { ++m_count; }
|
||||
void reset_count() { m_count = 0; }
|
||||
bool is_first() const { return m_count == 0; }
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
|
@ -25,6 +25,8 @@ class GLGizmoFlatten : public GLGizmoBase
|
||||
|
||||
private:
|
||||
|
||||
GLModel arrow;
|
||||
|
||||
struct PlaneData {
|
||||
std::vector<Vec3d> vertices; // should be in fact local in update_planes()
|
||||
#if ENABLE_LEGACY_OPENGL_REMOVAL
|
||||
|
@ -114,7 +114,7 @@ void GLGizmoHollow::on_register_raycasters_for_picking()
|
||||
if (info != nullptr && !info->model_object()->sla_drain_holes.empty()) {
|
||||
const sla::DrainHoles& drain_holes = info->model_object()->sla_drain_holes;
|
||||
for (int i = 0; i < (int)drain_holes.size(); ++i) {
|
||||
m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cylinder.mesh_raycaster, Transform3d::Identity()));
|
||||
m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cylinder.mesh_raycaster));
|
||||
}
|
||||
update_raycasters_for_picking_transform();
|
||||
}
|
||||
|
1691
src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
Normal file
1691
src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp
Normal file
File diff suppressed because it is too large
Load Diff
163
src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
Normal file
163
src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp
Normal file
@ -0,0 +1,163 @@
|
||||
#ifndef slic3r_GLGizmoMeasure_hpp_
|
||||
#define slic3r_GLGizmoMeasure_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||
#include "libslic3r/Measure.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelVolume;
|
||||
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
namespace Measure { class Measuring; }
|
||||
|
||||
|
||||
namespace GUI {
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
||||
class GLGizmoMeasure : public GLGizmoBase
|
||||
{
|
||||
enum class EMode : unsigned char
|
||||
{
|
||||
BasicSelection,
|
||||
ExtendedSelection
|
||||
};
|
||||
|
||||
struct SelectedFeatures
|
||||
{
|
||||
struct Item
|
||||
{
|
||||
std::string source;
|
||||
std::optional<Measure::SurfaceFeature> feature;
|
||||
|
||||
bool operator == (const Item& other) const {
|
||||
if (this->source != other.source) return false;
|
||||
return this->feature == other.feature;
|
||||
}
|
||||
|
||||
bool operator != (const Item& other) const {
|
||||
return !operator == (other);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
source.clear();
|
||||
feature.reset();
|
||||
}
|
||||
};
|
||||
|
||||
Item first;
|
||||
Item second;
|
||||
|
||||
void reset() {
|
||||
first.reset();
|
||||
second.reset();
|
||||
}
|
||||
|
||||
bool operator == (const SelectedFeatures & other) const {
|
||||
if (this->first != other.first) return false;
|
||||
return this->second == other.second;
|
||||
}
|
||||
|
||||
bool operator != (const SelectedFeatures & other) const {
|
||||
return !operator == (other);
|
||||
}
|
||||
};
|
||||
|
||||
EMode m_mode{ EMode::BasicSelection };
|
||||
Measure::MeasurementResult m_measurement_result;
|
||||
|
||||
std::unique_ptr<Measure::Measuring> m_measuring; // PIMPL
|
||||
|
||||
PickingModel m_sphere;
|
||||
PickingModel m_cylinder;
|
||||
PickingModel m_circle;
|
||||
PickingModel m_plane;
|
||||
struct Dimensioning
|
||||
{
|
||||
GLModel line;
|
||||
GLModel triangle;
|
||||
GLModel arc;
|
||||
};
|
||||
Dimensioning m_dimensioning;
|
||||
|
||||
Transform3d m_volume_matrix{ Transform3d::Identity() };
|
||||
std::vector<GLModel> m_plane_models_cache;
|
||||
std::map<int, std::shared_ptr<SceneRaycasterItem>> m_raycasters;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_selection_raycasters;
|
||||
std::optional<Measure::SurfaceFeature> m_curr_feature;
|
||||
std::optional<Vec3d> m_curr_point_on_feature_position;
|
||||
struct SceneRaycasterState
|
||||
{
|
||||
std::shared_ptr<SceneRaycasterItem> raycaster{ nullptr };
|
||||
bool state{true};
|
||||
|
||||
};
|
||||
std::vector<SceneRaycasterState> m_scene_raycasters;
|
||||
|
||||
// These hold information to decide whether recalculation is necessary:
|
||||
std::vector<Transform3d> m_volumes_matrices;
|
||||
std::vector<ModelVolumeType> m_volumes_types;
|
||||
Vec3d m_first_instance_scale{ Vec3d::Ones() };
|
||||
Vec3d m_first_instance_mirror{ Vec3d::Ones() };
|
||||
float m_last_inv_zoom{ 0.0f };
|
||||
std::optional<Measure::SurfaceFeature> m_last_circle;
|
||||
int m_last_plane_idx{ -1 };
|
||||
|
||||
bool m_mouse_left_down{ false }; // for detection left_up of this gizmo
|
||||
const ModelObject* m_old_model_object{ nullptr };
|
||||
const ModelVolume* m_old_model_volume{ nullptr };
|
||||
|
||||
Vec2d m_mouse_pos{ Vec2d::Zero() };
|
||||
|
||||
KeyAutoRepeatFilter m_ctrl_kar_filter;
|
||||
|
||||
SelectedFeatures m_selected_features;
|
||||
bool m_editing_distance{ false };
|
||||
bool m_is_editing_distance_first_frame{ true };
|
||||
|
||||
void update_if_needed();
|
||||
|
||||
void disable_scene_raycasters();
|
||||
void restore_scene_raycasters_state();
|
||||
|
||||
void render_dimensioning();
|
||||
|
||||
#if ENABLE_MEASURE_GIZMO_DEBUG
|
||||
void render_debug_dialog();
|
||||
#endif // ENABLE_MEASURE_GIZMO_DEBUG
|
||||
|
||||
public:
|
||||
GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
/// <summary>
|
||||
/// Apply rotation on select plane
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void data_changed() override;
|
||||
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
void on_render() override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoMeasure_hpp_
|
@ -148,8 +148,8 @@ void GLGizmoSlaSupports::on_register_raycasters_for_picking()
|
||||
|
||||
if (m_editing_mode && !m_editing_cache.empty()) {
|
||||
for (size_t i = 0; i < m_editing_cache.size(); ++i) {
|
||||
m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_sphere.mesh_raycaster, Transform3d::Identity()),
|
||||
m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cone.mesh_raycaster, Transform3d::Identity()));
|
||||
m_raycasters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_sphere.mesh_raycaster),
|
||||
m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_cone.mesh_raycaster));
|
||||
}
|
||||
update_raycasters_for_picking_transform();
|
||||
}
|
||||
|
@ -119,21 +119,25 @@ void SelectionInfo::on_update()
|
||||
const Selection& selection = get_pool()->get_canvas()->get_selection();
|
||||
if (selection.is_single_full_instance()) {
|
||||
m_model_object = selection.get_model()->objects[selection.get_object_idx()];
|
||||
m_model_volume = nullptr;
|
||||
m_z_shift = selection.get_first_volume()->get_sla_shift_z();
|
||||
}
|
||||
else
|
||||
else {
|
||||
m_model_object = nullptr;
|
||||
if (selection.is_single_volume())
|
||||
m_model_volume = selection.get_model()->objects[selection.get_object_idx()]->volumes[selection.get_first_volume()->volume_idx()];
|
||||
}
|
||||
}
|
||||
|
||||
void SelectionInfo::on_release()
|
||||
{
|
||||
m_model_object = nullptr;
|
||||
m_model_volume = nullptr;
|
||||
}
|
||||
|
||||
int SelectionInfo::get_active_instance() const
|
||||
{
|
||||
const Selection& selection = get_pool()->get_canvas()->get_selection();
|
||||
return selection.get_instance_idx();
|
||||
return get_pool()->get_canvas()->get_selection().get_instance_idx();
|
||||
}
|
||||
|
||||
|
||||
@ -329,12 +333,18 @@ void Raycaster::on_update()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
const ModelObject* mo = get_pool()->selection_info()->model_object();
|
||||
const ModelVolume* mv = get_pool()->selection_info()->model_volume();
|
||||
|
||||
if (! mo)
|
||||
if (mo == nullptr && mv == nullptr)
|
||||
return;
|
||||
|
||||
std::vector<ModelVolume*> mvs;
|
||||
if (mv != nullptr)
|
||||
mvs.push_back(const_cast<ModelVolume*>(mv));
|
||||
else
|
||||
mvs = mo->volumes;
|
||||
|
||||
std::vector<const TriangleMesh*> meshes;
|
||||
const std::vector<ModelVolume*>& mvs = mo->volumes;
|
||||
if (mvs.size() == 1) {
|
||||
assert(mvs.front()->is_model_part());
|
||||
const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh();
|
||||
@ -342,9 +352,9 @@ void Raycaster::on_update()
|
||||
meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh());
|
||||
}
|
||||
if (meshes.empty()) {
|
||||
for (const ModelVolume* mv : mvs) {
|
||||
if (mv->is_model_part())
|
||||
meshes.push_back(&mv->mesh());
|
||||
for (const ModelVolume* v : mvs) {
|
||||
if (v->is_model_part())
|
||||
meshes.push_back(&v->mesh());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelObject;
|
||||
|
||||
class ModelVolume;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
@ -24,6 +24,8 @@ enum class SLAGizmoEventType : unsigned char {
|
||||
Dragging,
|
||||
Delete,
|
||||
SelectAll,
|
||||
CtrlDown,
|
||||
CtrlUp,
|
||||
ShiftUp,
|
||||
AltUp,
|
||||
ApplyChanges,
|
||||
@ -153,7 +155,10 @@ public:
|
||||
explicit SelectionInfo(CommonGizmosDataPool* cgdp)
|
||||
: CommonGizmosDataBase(cgdp) {}
|
||||
|
||||
// Returns a non-null pointer if the selection is a single full instance
|
||||
ModelObject* model_object() const { return m_model_object; }
|
||||
// Returns a non-null pointer if the selection is a single volume
|
||||
ModelVolume* model_volume() const { return m_model_volume; }
|
||||
int get_active_instance() const;
|
||||
float get_sla_shift() const { return m_z_shift; }
|
||||
|
||||
@ -163,6 +168,7 @@ protected:
|
||||
|
||||
private:
|
||||
ModelObject* m_model_object = nullptr;
|
||||
ModelVolume* m_model_volume = nullptr;
|
||||
// int m_active_inst = -1;
|
||||
float m_z_shift = 0.f;
|
||||
};
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp"
|
||||
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
@ -106,6 +107,7 @@ bool GLGizmosManager::init()
|
||||
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
|
||||
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "cut.svg", 10));
|
||||
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, "measure.svg", 11));
|
||||
|
||||
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
|
||||
|
||||
@ -288,6 +290,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
|
||||
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == MmuSegmentation)
|
||||
return dynamic_cast<GLGizmoMmuSegmentation*>(m_gizmos[MmuSegmentation].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Measure)
|
||||
return dynamic_cast<GLGizmoMeasure*>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == Cut)
|
||||
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else
|
||||
@ -495,8 +499,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||
|
||||
bool processed = false;
|
||||
|
||||
if ((evt.GetModifiers() & ctrlMask) != 0)
|
||||
{
|
||||
if ((evt.GetModifiers() & ctrlMask) != 0) {
|
||||
switch (keyCode)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
@ -514,15 +517,13 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!evt.HasModifiers())
|
||||
{
|
||||
else if (!evt.HasModifiers()) {
|
||||
switch (keyCode)
|
||||
{
|
||||
// key ESC
|
||||
case WXK_ESCAPE:
|
||||
{
|
||||
if (m_current != Undefined)
|
||||
{
|
||||
if (m_current != Undefined) {
|
||||
if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges))
|
||||
reset_all_states();
|
||||
|
||||
@ -559,8 +560,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||
case 'A':
|
||||
case 'a':
|
||||
{
|
||||
if (m_current == SlaSupports)
|
||||
{
|
||||
if (m_current == SlaSupports) {
|
||||
gizmo_event(SLAGizmoEventType::AutomaticGeneration);
|
||||
// set as processed no matter what's returned by gizmo_event() to avoid the calling canvas to process 'A' as arrange
|
||||
processed = true;
|
||||
@ -578,8 +578,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||
case 'F':
|
||||
case 'f':
|
||||
{
|
||||
if (m_current == Scale)
|
||||
{
|
||||
if (m_current == Scale) {
|
||||
if (!is_dragging())
|
||||
wxGetApp().plater()->scale_selection_to_fit_print_volume();
|
||||
|
||||
@ -591,8 +590,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||
}
|
||||
}
|
||||
|
||||
if (!processed && !evt.HasModifiers())
|
||||
{
|
||||
if (!processed && !evt.HasModifiers()) {
|
||||
if (handle_shortcut(keyCode))
|
||||
processed = true;
|
||||
}
|
||||
@ -616,19 +614,20 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||
const bool is_editing = m_current == Hollow ? true : gizmo->is_in_editing_mode();
|
||||
const bool is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
|
||||
|
||||
if (keyCode == WXK_SHIFT)
|
||||
{
|
||||
if (keyCode == WXK_SHIFT) {
|
||||
// shift has been just released - SLA gizmo might want to close rectangular selection.
|
||||
if (gizmo_event(SLAGizmoEventType::ShiftUp) || (is_editing && is_rectangle_dragging))
|
||||
processed = true;
|
||||
}
|
||||
else if (keyCode == WXK_ALT)
|
||||
{
|
||||
else if (keyCode == WXK_ALT) {
|
||||
// alt has been just released - SLA gizmo might want to close rectangular selection.
|
||||
if (gizmo_event(SLAGizmoEventType::AltUp) || (is_editing && is_rectangle_dragging))
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
else if (m_current == Measure && keyCode == WXK_CONTROL) {
|
||||
gizmo_event(SLAGizmoEventType::CtrlUp, Vec2d::Zero(), false);
|
||||
}
|
||||
|
||||
// if (processed)
|
||||
// m_parent.set_cursor(GLCanvas3D::Standard);
|
||||
@ -641,8 +640,7 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||
// m_parent.set_cursor(GLCanvas3D::Cross);
|
||||
processed = true;
|
||||
}
|
||||
else if (m_current == Cut)
|
||||
{
|
||||
else if (m_current == Cut) {
|
||||
auto do_move = [this, &processed](double delta_z) {
|
||||
GLGizmoCut3D* cut = dynamic_cast<GLGizmoCut3D*>(get_current());
|
||||
cut->shift_cut_z(delta_z);
|
||||
@ -658,11 +656,15 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||
}
|
||||
default: { break; }
|
||||
}
|
||||
} else if (m_current == Simplify && keyCode == WXK_ESCAPE) {
|
||||
}
|
||||
else if (m_current == Simplify && keyCode == WXK_ESCAPE) {
|
||||
GLGizmoSimplify *simplify = dynamic_cast<GLGizmoSimplify *>(get_current());
|
||||
if (simplify != nullptr)
|
||||
processed = simplify->on_esc_key_down();
|
||||
}
|
||||
else if (m_current == Measure && keyCode == WXK_CONTROL) {
|
||||
gizmo_event(SLAGizmoEventType::CtrlDown, Vec2d::Zero(), true);
|
||||
}
|
||||
}
|
||||
|
||||
if (processed)
|
||||
|
@ -80,6 +80,7 @@ public:
|
||||
Seam,
|
||||
MmuSegmentation,
|
||||
Simplify,
|
||||
Measure,
|
||||
Undefined
|
||||
};
|
||||
|
||||
|
@ -56,6 +56,7 @@ static const std::map<const wchar_t, std::string> font_icons = {
|
||||
{ImGui::PreferencesHoverButton, "notification_preferences_hover"},
|
||||
{ImGui::SliderFloatEditBtnIcon, "edit_button" },
|
||||
{ImGui::SliderFloatEditBtnPressedIcon, "edit_button_pressed" },
|
||||
{ImGui::ClipboardBtnIcon , "copy_menu" },
|
||||
{ImGui::ExpandBtn , "expand_btn" },
|
||||
{ImGui::CollapseBtn , "collapse_btn" },
|
||||
{ImGui::RevertButton , "undo" },
|
||||
@ -428,36 +429,6 @@ bool ImGuiWrapper::draw_radio_button(const std::string& name, float size, bool a
|
||||
return pressed;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
|
||||
{
|
||||
return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_double(const wxString &label, const double &value, const std::string &format)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
return input_double(label_utf8, value, format);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format)
|
||||
{
|
||||
bool value_changed = false;
|
||||
|
||||
ImGui::BeginGroup();
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
std::string item_label = (i == 0) ? "X" : ((i == 1) ? "Y" : "Z");
|
||||
ImGui::PushID(i);
|
||||
ImGui::PushItemWidth(width);
|
||||
value_changed |= ImGui::InputDouble(item_label.c_str(), const_cast<double*>(&value(i)), 0.0f, 0.0f, format.c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
return value_changed;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::checkbox(const wxString &label, bool &value)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
@ -516,20 +487,20 @@ void ImGuiWrapper::text_wrapped(const wxString &label, float wrap_width)
|
||||
|
||||
void ImGuiWrapper::tooltip(const char *label, float wrap_width)
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 4.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 8.0f, 8.0f });
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(wrap_width);
|
||||
ImGui::TextUnformatted(label);
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleVar(3);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::tooltip(const wxString &label, float wrap_width)
|
||||
{
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(wrap_width);
|
||||
ImGui::TextUnformatted(label.ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
tooltip(label.ToUTF8().data(), wrap_width);
|
||||
}
|
||||
|
||||
ImVec2 ImGuiWrapper::get_slider_icon_size() const
|
||||
@ -671,6 +642,29 @@ bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size,
|
||||
return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::image_button(const wchar_t icon, const wxString& tooltip)
|
||||
{
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
const ImTextureID tex_id = io.Fonts->TexID;
|
||||
assert(io.Fonts->TexWidth > 0 && io.Fonts->TexHeight > 0);
|
||||
const float inv_tex_w = 1.0f / float(io.Fonts->TexWidth);
|
||||
const float inv_tex_h = 1.0f / float(io.Fonts->TexHeight);
|
||||
const ImFontAtlasCustomRect* const rect = GetTextureCustomRect(icon);
|
||||
const ImVec2 size = { float(rect->Width), float(rect->Height) };
|
||||
const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h);
|
||||
const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f });
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f });
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.25f, 0.25f, 0.25f, 1.0f });
|
||||
const bool res = image_button(tex_id, size, uv0, uv1);
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
if (!tooltip.empty() && ImGui::IsItemHovered())
|
||||
this->tooltip(tooltip, ImGui::GetFontSize() * 20.0f);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags)
|
||||
{
|
||||
// this is to force the label to the left of the widget:
|
||||
@ -1133,6 +1127,11 @@ ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id)
|
||||
return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr;
|
||||
}
|
||||
|
||||
void ImGuiWrapper::disable_background_fadeout_animation()
|
||||
{
|
||||
GImGui->DimBgRatio = 1.0f;
|
||||
}
|
||||
|
||||
ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA& color)
|
||||
{
|
||||
return ImGui::GetColorU32({ color.r(), color.g(), color.b(), color.a() });
|
||||
|
@ -89,9 +89,6 @@ public:
|
||||
bool button(const wxString& label, float width, float height);
|
||||
bool radio_button(const wxString &label, bool active);
|
||||
bool draw_radio_button(const std::string& name, float size, bool active, std::function<void(ImGuiWindow& window, const ImVec2& pos, float size)> draw_callback);
|
||||
bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
|
||||
bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f");
|
||||
bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");
|
||||
bool checkbox(const wxString &label, bool &value);
|
||||
void text(const char *label);
|
||||
void text(const std::string &label);
|
||||
@ -112,6 +109,7 @@ public:
|
||||
bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true);
|
||||
|
||||
bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0);
|
||||
bool image_button(const wchar_t icon, const wxString& tooltip = L"");
|
||||
|
||||
// Use selection = -1 to not mark any option as selected
|
||||
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags = 0);
|
||||
@ -132,6 +130,8 @@ public:
|
||||
void set_requires_extra_frame() { m_requires_extra_frame = true; }
|
||||
void reset_requires_extra_frame() { m_requires_extra_frame = false; }
|
||||
|
||||
void disable_background_fadeout_animation();
|
||||
|
||||
static ImU32 to_ImU32(const ColorRGBA& color);
|
||||
static ImVec4 to_ImVec4(const ColorRGBA& color);
|
||||
static ColorRGBA from_ImU32(const ImU32& color);
|
||||
|
@ -3034,6 +3034,7 @@ void Plater::priv::delete_all_objects_from_model()
|
||||
gcode_result.reset();
|
||||
|
||||
view3D->get_canvas3d()->reset_sequential_print_clearance();
|
||||
view3D->get_canvas3d()->reset_all_gizmos();
|
||||
|
||||
m_worker.cancel_all();
|
||||
|
||||
|
@ -37,10 +37,10 @@ std::shared_ptr<SceneRaycasterItem> SceneRaycaster::add_raycaster(EType type, in
|
||||
const Transform3d& trafo, bool use_back_faces)
|
||||
{
|
||||
switch (type) {
|
||||
case EType::Bed: { return m_bed.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
case EType::Bed: { return m_bed.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
case EType::Volume: { return m_volumes.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
default: { assert(false); return nullptr; }
|
||||
case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
|
||||
default: { assert(false); return nullptr; }
|
||||
};
|
||||
}
|
||||
|
||||
@ -173,6 +173,31 @@ void SceneRaycaster::render_hit(const Camera& camera)
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
size_t SceneRaycaster::active_beds_count() const {
|
||||
size_t count = 0;
|
||||
for (const auto& b : m_bed) {
|
||||
if (b->is_active())
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
size_t SceneRaycaster::active_volumes_count() const {
|
||||
size_t count = 0;
|
||||
for (const auto& v : m_volumes) {
|
||||
if (v->is_active())
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
size_t SceneRaycaster::active_gizmos_count() const {
|
||||
size_t count = 0;
|
||||
for (const auto& g : m_gizmos) {
|
||||
if (g->is_active())
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_raycasters(EType type)
|
||||
|
@ -105,9 +105,10 @@ public:
|
||||
size_t active_gizmos_count() const;
|
||||
#endif // ENABLE_RAYCAST_PICKING_DEBUG
|
||||
|
||||
static int decode_id(EType type, int id);
|
||||
|
||||
private:
|
||||
static int encode_id(EType type, int id);
|
||||
static int decode_id(EType type, int id);
|
||||
static int base_id(EType type);
|
||||
};
|
||||
|
||||
|
@ -330,6 +330,7 @@ public:
|
||||
#if ENABLE_WORLD_COORDINATE
|
||||
bool is_single_volume_or_modifier() const { return is_single_volume() || is_single_modifier(); }
|
||||
#endif // ENABLE_WORLD_COORDINATE
|
||||
bool is_single_volume_instance() const { return is_single_full_instance() && m_list.size() == 1; }
|
||||
|
||||
bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); }
|
||||
// returns true if the selection contains all the given indices
|
||||
|
@ -26,6 +26,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_voronoi.cpp
|
||||
test_optimizers.cpp
|
||||
test_png_io.cpp
|
||||
test_surface_mesh.cpp
|
||||
test_timeutils.cpp
|
||||
test_indexed_triangle_set.cpp
|
||||
test_astar.cpp
|
||||
|
122
tests/libslic3r/test_surface_mesh.cpp
Normal file
122
tests/libslic3r/test_surface_mesh.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <test_utils.hpp>
|
||||
|
||||
|
||||
#include <libslic3r/SurfaceMesh.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
|
||||
// Generate a broken cube mesh. Face 8 is inverted, face 11 is missing.
|
||||
indexed_triangle_set its_make_cube_broken(double xd, double yd, double zd)
|
||||
{
|
||||
auto x = float(xd), y = float(yd), z = float(zd);
|
||||
return {
|
||||
{ {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7},
|
||||
{0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2},
|
||||
{2, 5, 6}, {2, 5, 3}, {4, 0, 3} /*missing face*/ },
|
||||
{ {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0},
|
||||
{x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_CASE("SurfaceMesh on a cube", "[SurfaceMesh]") {
|
||||
indexed_triangle_set cube = its_make_cube(1., 1., 1.);
|
||||
SurfaceMesh sm(cube);
|
||||
const Halfedge_index hi_first = sm.halfedge(Face_index(0));
|
||||
Halfedge_index hi = hi_first;
|
||||
|
||||
REQUIRE(! hi_first.is_invalid());
|
||||
|
||||
SECTION("next / prev halfedge") {
|
||||
hi = sm.next(hi);
|
||||
REQUIRE(hi != hi_first);
|
||||
hi = sm.next(hi);
|
||||
hi = sm.next(hi);
|
||||
REQUIRE(hi == hi_first);
|
||||
hi = sm.prev(hi);
|
||||
REQUIRE(hi != hi_first);
|
||||
hi = sm.prev(hi);
|
||||
hi = sm.prev(hi);
|
||||
REQUIRE(hi == hi_first);
|
||||
}
|
||||
|
||||
SECTION("next_around_target") {
|
||||
// Check that we get to the same halfedge after applying next_around_target
|
||||
// four times.
|
||||
const Vertex_index target_vert = sm.target(hi_first);
|
||||
for (int i=0; i<4;++i) {
|
||||
hi = sm.next_around_target(hi);
|
||||
REQUIRE((hi == hi_first) == (i == 3));
|
||||
REQUIRE(sm.is_same_vertex(sm.target(hi), target_vert));
|
||||
REQUIRE(! sm.is_border(hi));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("iterate around target and source") {
|
||||
hi = sm.next_around_target(hi);
|
||||
hi = sm.prev_around_target(hi);
|
||||
hi = sm.prev_around_source(hi);
|
||||
hi = sm.next_around_source(hi);
|
||||
REQUIRE(hi == hi_first);
|
||||
}
|
||||
|
||||
SECTION("opposite") {
|
||||
const Vertex_index target = sm.target(hi);
|
||||
const Vertex_index source = sm.source(hi);
|
||||
hi = sm.opposite(hi);
|
||||
REQUIRE(sm.is_same_vertex(target, sm.source(hi)));
|
||||
REQUIRE(sm.is_same_vertex(source, sm.target(hi)));
|
||||
hi = sm.opposite(hi);
|
||||
REQUIRE(hi == hi_first);
|
||||
}
|
||||
|
||||
SECTION("halfedges walk") {
|
||||
for (int i=0; i<4; ++i) {
|
||||
hi = sm.next(hi);
|
||||
hi = sm.opposite(hi);
|
||||
}
|
||||
REQUIRE(hi == hi_first);
|
||||
}
|
||||
|
||||
SECTION("point accessor") {
|
||||
Halfedge_index hi = sm.halfedge(Face_index(0));
|
||||
hi = sm.opposite(hi);
|
||||
hi = sm.prev(hi);
|
||||
hi = sm.opposite(hi);
|
||||
REQUIRE(hi.face() == Face_index(6));
|
||||
REQUIRE(sm.point(sm.target(hi)).isApprox(cube.vertices[7]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
TEST_CASE("SurfaceMesh on a broken cube", "[SurfaceMesh]") {
|
||||
indexed_triangle_set cube = its_make_cube_broken(1., 1., 1.);
|
||||
SurfaceMesh sm(cube);
|
||||
|
||||
SECTION("Check inverted face") {
|
||||
Halfedge_index hi = sm.halfedge(Face_index(8));
|
||||
for (int i=0; i<3; ++i) {
|
||||
REQUIRE(! hi.is_invalid());
|
||||
REQUIRE(sm.is_border(hi));
|
||||
}
|
||||
REQUIRE(hi == sm.halfedge(Face_index(8)));
|
||||
hi = sm.opposite(hi);
|
||||
REQUIRE(hi.is_invalid());
|
||||
}
|
||||
|
||||
SECTION("missing face") {
|
||||
Halfedge_index hi = sm.halfedge(Face_index(0));
|
||||
for (int i=0; i<3; ++i)
|
||||
hi = sm.next_around_source(hi);
|
||||
hi = sm.next(hi);
|
||||
REQUIRE(sm.is_border(hi));
|
||||
REQUIRE(! hi.is_invalid());
|
||||
hi = sm.opposite(hi);
|
||||
REQUIRE(hi.is_invalid());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user