mirror of https://github.com/CGAL/cgal
Update offset meshing plugin
- fixed not working for polygon soups - fixed broken formula for both giving Mesh_3 assertions and inverted meshes - fixed applicable() allowing wrong combinations - fixed inflate() needlessly normalizing normals - fixed crashing for non triangulated inputs - fixed some memory leaks - fixed dialog name - removed useless sqrts - avoid recomputing bbox 4 times - cleaned UI - etc.
This commit is contained in:
parent
f85b839858
commit
efd68a5f92
|
|
@ -116,3 +116,16 @@ if(TBB_FOUND)
|
|||
target_link_libraries(c3t3_io_plugin PUBLIC CGAL::TBB_support)
|
||||
target_link_libraries(c3t3_rib_exporter_plugin PUBLIC CGAL::TBB_support)
|
||||
endif()
|
||||
|
||||
qt5_wrap_ui(offsetMeshingUI_FILES Offset_meshing_dialog.ui)
|
||||
polyhedron_demo_plugin(offset_meshing_plugin Offset_meshing_plugin
|
||||
${offsetMeshingUI_FILES})
|
||||
target_link_libraries(offset_meshing_plugin PUBLIC scene_surface_mesh_item
|
||||
scene_polygon_soup_item
|
||||
scene_polylines_item)
|
||||
if(TARGET CGAL::Eigen3_support)
|
||||
target_link_libraries(offset_meshing_plugin PUBLIC CGAL::Eigen3_support)
|
||||
endif()
|
||||
if(TARGET CGAL::TBB_support)
|
||||
target_link_libraries(offset_meshing_plugin PUBLIC CGAL::TBB_support)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -1,64 +1,70 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Remeshing_dialog</class>
|
||||
<widget class="QDialog" name="Remeshing_dialog">
|
||||
<class>Offset_meshing_dialog</class>
|
||||
<widget class="QDialog" name="Offset_meshing_dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>376</width>
|
||||
<height>216</height>
|
||||
<width>413</width>
|
||||
<height>294</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Meshing criteria</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="2" colspan="2">
|
||||
<item row="1" column="2" colspan="2">
|
||||
<widget class="DoubleEdit" name="angle">
|
||||
<property name="text">
|
||||
<string>25.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" colspan="2">
|
||||
<widget class="DoubleEdit" name="sizing">
|
||||
<property name="text">
|
||||
<string>0.00</string>
|
||||
<item row="7" column="3">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="approxLabel">
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="sizingLabel_2">
|
||||
<property name="text">
|
||||
<string>Approximation &error:</string>
|
||||
<string>Edge size</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="DoubleEdit" name="approx">
|
||||
<property name="text">
|
||||
<string>0.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Topological criterion:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>tags</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2" colspan="2">
|
||||
<widget class="DoubleEdit" name="edge_sizing">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="2" colspan="2">
|
||||
<widget class="QComboBox" name="tags">
|
||||
<item>
|
||||
<property name="text">
|
||||
|
|
@ -77,43 +83,47 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Topological criterion</string>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="6" column="3">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<property name="buddy">
|
||||
<cstring>tags</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="2">
|
||||
<widget class="DoubleEdit" name="edge_sizing">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="DoubleEdit" name="approx">
|
||||
<property name="text">
|
||||
<string>0.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="sizingLabel_2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLabel" name="approxLabel">
|
||||
<property name="text">
|
||||
<string>&Edge size:</string>
|
||||
<string>Approximation error</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="2">
|
||||
<widget class="DoubleEdit" name="sizing">
|
||||
<property name="text">
|
||||
<string>0.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="sizingLabel">
|
||||
<property name="text">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
|
@ -121,22 +131,24 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="sizingLabel">
|
||||
<widget class="QLabel" name="angleLabel">
|
||||
<property name="text">
|
||||
<string>&Size:</string>
|
||||
<string>Angle</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="angleLabel">
|
||||
<property name="text">
|
||||
<string>&Angle:</string>
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="objectName">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>15</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
<property name="text">
|
||||
<string>Meshing Criteria</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -154,7 +166,7 @@
|
|||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Remeshing_dialog</receiver>
|
||||
<receiver>Offset_meshing_dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
|
|
@ -170,7 +182,7 @@
|
|||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Remeshing_dialog</receiver>
|
||||
<receiver>Offset_meshing_dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
|
|
@ -0,0 +1,765 @@
|
|||
#include "config.h"
|
||||
|
||||
#ifdef CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER
|
||||
|
||||
#include <CGAL/Three/Polyhedron_demo_plugin_interface.h>
|
||||
#include "ui_Offset_meshing_dialog.h"
|
||||
|
||||
#include "C3t3_type.h"
|
||||
|
||||
#include <CGAL/AABB_tree.h>
|
||||
#include <CGAL/AABB_traits.h>
|
||||
#include <CGAL/AABB_face_graph_triangle_primitive.h>
|
||||
|
||||
#include <CGAL/Side_of_triangle_mesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/bbox.h>
|
||||
#include <CGAL/Polygon_mesh_processing/orientation.h>
|
||||
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
|
||||
|
||||
#include <CGAL/Timer.h>
|
||||
#include <CGAL/make_mesh_3.h>
|
||||
#include <CGAL/Labeled_mesh_domain_3.h>
|
||||
#include <CGAL/Mesh_domain_with_polyline_features_3.h>
|
||||
#include <CGAL/Mesh_criteria_3.h>
|
||||
#include <CGAL/facets_in_complex_3_to_triangle_mesh.h>
|
||||
|
||||
#include <CGAL/Three/Three.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include <QtPlugin>
|
||||
#include <QThread>
|
||||
#include "Scene_surface_mesh_item.h"
|
||||
#include "Scene_polygon_soup_item.h"
|
||||
#include "Scene_polylines_item.h"
|
||||
#include <QInputDialog>
|
||||
#include <QStringList>
|
||||
#include <QMessageBox>
|
||||
#include <QAbstractButton>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
||||
using namespace CGAL::Three;
|
||||
|
||||
namespace CGAL {
|
||||
|
||||
template <class TriangleMesh, class GeomTraits>
|
||||
class Offset_function
|
||||
{
|
||||
using Primitive = AABB_face_graph_triangle_primitive<TriangleMesh>;
|
||||
using Traits = AABB_traits<GeomTraits, Primitive>;
|
||||
using Tree = AABB_tree<Traits>;
|
||||
using Side_of = Side_of_triangle_mesh<TriangleMesh, GeomTraits>;
|
||||
|
||||
using FT = typename GeomTraits::FT;
|
||||
using Point_3 = typename GeomTraits::Point_3;
|
||||
|
||||
public:
|
||||
Offset_function(const TriangleMesh& tm,
|
||||
double offset_distance)
|
||||
: m_tree_ptr(std::make_shared<Tree>(std::begin(faces(tm)), std::end(faces(tm)), tm)),
|
||||
m_side_of_ptr(std::make_shared<Side_of>(*m_tree_ptr)),
|
||||
m_is_inset(offset_distance < 0),
|
||||
m_sq_offset_distance(CGAL::square(offset_distance)),
|
||||
m_is_closed(is_closed(tm))
|
||||
{
|
||||
CGAL_assertion(!m_tree_ptr->empty());
|
||||
}
|
||||
|
||||
// we only need negative inside, and positive outside, so we can compare square roots
|
||||
double operator()(const Point_3& p) const
|
||||
{
|
||||
const Bounded_side side = m_is_closed ? m_side_of_ptr->operator()(p) : ON_UNBOUNDED_SIDE;
|
||||
|
||||
if(m_is_inset) // also means that the mesh is closed
|
||||
{
|
||||
// - ON_UNBOUNDED_SIDE is outside the offset since we are insetting
|
||||
// - ON_BOUNDARY is outside the offset since we are insetting
|
||||
if(side != ON_BOUNDED_SIDE)
|
||||
return 1;
|
||||
|
||||
// inside the offset if the distance to the input mesh is greater than the offset distance
|
||||
const FT sq_distance = m_tree_ptr->squared_distance(p);
|
||||
return (sq_distance > m_sq_offset_distance) ? -1 : 1;
|
||||
}
|
||||
else // outset
|
||||
{
|
||||
// - ON_BOUNDED_SIDE can only happen if it's a closed mesh, and in that case, being inside
|
||||
// the mesh is being inside the offset
|
||||
// - ON_BOUNDARY is in the offset whether the mesh is open or closed
|
||||
if(side != ON_UNBOUNDED_SIDE)
|
||||
return - 1;
|
||||
|
||||
// inside the offset if the distance to the input mesh is smaller than the offset distance
|
||||
const FT sq_distance = m_tree_ptr->squared_distance(p);
|
||||
return (sq_distance < m_sq_offset_distance) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Tree> m_tree_ptr;
|
||||
std::shared_ptr<Side_of> m_side_of_ptr;
|
||||
const bool m_is_inset;
|
||||
const double m_sq_offset_distance;
|
||||
const bool m_is_closed;
|
||||
};
|
||||
|
||||
template <typename Points, typename Polygons>
|
||||
class Polygon_soup_offset_function
|
||||
{
|
||||
using Polygon_iterator = typename Polygons::const_iterator;
|
||||
|
||||
class Polygon_soup_point_property_map
|
||||
{
|
||||
const Points* points_vector_ptr;
|
||||
|
||||
public:
|
||||
using key_type = Polygon_iterator;
|
||||
using value_type = EPICK::Point_3;
|
||||
using reference = const value_type&;
|
||||
using category = boost::readable_property_map_tag;
|
||||
|
||||
Polygon_soup_point_property_map() = default;
|
||||
Polygon_soup_point_property_map(const Points* ptr) : points_vector_ptr(ptr) { }
|
||||
|
||||
friend reference get(Polygon_soup_point_property_map map,
|
||||
key_type polygon_it)
|
||||
{
|
||||
return (*map.points_vector_ptr)[*polygon_it->begin()];
|
||||
}
|
||||
};
|
||||
|
||||
class Polygon_soup_triangle_property_map
|
||||
{
|
||||
const Points* points_vector_ptr;
|
||||
|
||||
public:
|
||||
using key_type = Polygon_iterator;
|
||||
using value_type = EPICK::Triangle_3;
|
||||
using reference = value_type;
|
||||
using category = boost::readable_property_map_tag;
|
||||
|
||||
Polygon_soup_triangle_property_map() = default;
|
||||
Polygon_soup_triangle_property_map(const Points* ptr) : points_vector_ptr(ptr) { }
|
||||
|
||||
friend value_type get(Polygon_soup_triangle_property_map map,
|
||||
key_type polygon_it)
|
||||
{
|
||||
auto it = polygon_it->begin();
|
||||
CGAL_assertion(it != polygon_it->end());
|
||||
const auto id0 = *it++;
|
||||
CGAL_assertion(it != polygon_it->end());
|
||||
const auto id1 = *it++;
|
||||
CGAL_assertion(it != polygon_it->end());
|
||||
const auto id2 = *it++;
|
||||
CGAL_assertion(it == polygon_it->end());
|
||||
|
||||
return value_type((*map.points_vector_ptr)[id0],
|
||||
(*map.points_vector_ptr)[id1],
|
||||
(*map.points_vector_ptr)[id2]);
|
||||
}
|
||||
};
|
||||
|
||||
struct AABB_polygon_soup_triangle_primitive
|
||||
: public CGAL::AABB_primitive<Polygon_iterator,
|
||||
Polygon_soup_triangle_property_map,
|
||||
Polygon_soup_point_property_map,
|
||||
CGAL::Tag_true /*ExternalPropertyMaps*/,
|
||||
CGAL::Tag_false /*CacheDatum*/>
|
||||
{
|
||||
using Base = CGAL::AABB_primitive<Polygon_iterator,
|
||||
Polygon_soup_triangle_property_map,
|
||||
Polygon_soup_point_property_map,
|
||||
CGAL::Tag_true,
|
||||
CGAL::Tag_false>;
|
||||
|
||||
using Id = Polygon_iterator;
|
||||
|
||||
template <typename ObjectPmap, typename PointPmap>
|
||||
AABB_polygon_soup_triangle_primitive(Id id,
|
||||
ObjectPmap&& opmap,
|
||||
PointPmap&& ppmap)
|
||||
: Base(id, std::forward<ObjectPmap>(opmap), std::forward<PointPmap>(ppmap))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Iterator, typename ObjectPmap, typename PointPmap>
|
||||
AABB_polygon_soup_triangle_primitive(Iterator it,
|
||||
ObjectPmap&& opmap,
|
||||
PointPmap&& ppmap)
|
||||
: Base(*it, std::forward<ObjectPmap>(opmap), std::forward<PointPmap>(ppmap))
|
||||
{
|
||||
}
|
||||
}; // struct template Polygon_soup_primitive
|
||||
|
||||
using AABB_traits = CGAL::AABB_traits<EPICK, AABB_polygon_soup_triangle_primitive>;
|
||||
using AABB_tree = CGAL::AABB_tree<AABB_traits>;
|
||||
|
||||
std::shared_ptr<AABB_tree> m_tree_ptr;
|
||||
double m_sq_offset_distance;
|
||||
|
||||
public:
|
||||
Polygon_soup_offset_function(const Points& points,
|
||||
const Polygons& polygons,
|
||||
const double offset_distance)
|
||||
: m_tree_ptr(std::make_shared<AABB_tree>(std::begin(polygons),
|
||||
std::end(polygons),
|
||||
Polygon_soup_triangle_property_map(&points),
|
||||
Polygon_soup_point_property_map(&points))),
|
||||
m_sq_offset_distance(square(offset_distance))
|
||||
{
|
||||
CGAL_assertion(!m_tree_ptr->empty());
|
||||
}
|
||||
|
||||
// we only need negative inside, and positive outside, so we can compare square roots
|
||||
double operator()(const EPICK::Point_3& p) const
|
||||
{
|
||||
// it's a soup so it's open by definition ==> treat inset and outset identically
|
||||
const double sq_distance = m_tree_ptr->squared_distance(p);
|
||||
return sq_distance - m_sq_offset_distance;
|
||||
}
|
||||
|
||||
}; // class Polygon_soup_offset_function
|
||||
|
||||
} // namespace CGAL
|
||||
|
||||
CGAL::Offset_function<SMesh, EPICK>
|
||||
offset_function(Scene_surface_mesh_item* item, double offset_value)
|
||||
{
|
||||
return { *(item->face_graph()), offset_value };
|
||||
}
|
||||
|
||||
CGAL::Polygon_soup_offset_function<Scene_polygon_soup_item::Points,
|
||||
Scene_polygon_soup_item::Polygons>
|
||||
offset_function(Scene_polygon_soup_item* item, double offset_value)
|
||||
{
|
||||
return { item->points(), item->polygons(), offset_value };
|
||||
}
|
||||
|
||||
class MeshGuard
|
||||
{
|
||||
SMesh* mesh;
|
||||
bool done;
|
||||
|
||||
public:
|
||||
MeshGuard(SMesh* mesh) : mesh(mesh), done(false) { }
|
||||
void setDone() { done = true; }
|
||||
~MeshGuard()
|
||||
{
|
||||
if(!done)
|
||||
delete mesh;
|
||||
}
|
||||
};
|
||||
|
||||
// declare the CGAL function
|
||||
template<class SourceItem>
|
||||
SMesh* cgal_off_meshing(QWidget*,
|
||||
SourceItem* source_item,
|
||||
Scene_polylines_item* polylines_item,
|
||||
const double offset_value,
|
||||
const double angle,
|
||||
const double sizing,
|
||||
const double approx,
|
||||
const double edge_size,
|
||||
int tag)
|
||||
{
|
||||
using GT = EPICK;
|
||||
using Sphere_3 = GT::Sphere_3;
|
||||
|
||||
using Mesh_domain_base = CGAL::Labeled_mesh_domain_3<GT, int, int>;
|
||||
using Mesh_domain = CGAL::Mesh_domain_with_polyline_features_3<Mesh_domain_base>;
|
||||
using Tr = C3t3::Triangulation;
|
||||
using Mesh_criteria = CGAL::Mesh_criteria_3<Tr>;
|
||||
|
||||
const CGAL::Bbox_3 bbox = source_item->bbox();
|
||||
|
||||
const GT::Point_3 center((bbox.xmax() + bbox.xmin()) / 2,
|
||||
(bbox.ymax() + bbox.ymin()) / 2,
|
||||
(bbox.zmax() + bbox.zmin()) / 2);
|
||||
const double rad = 0.6 * std::sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) +
|
||||
CGAL::square(bbox.ymax() - bbox.ymin()) +
|
||||
CGAL::square(bbox.zmax() - bbox.zmin()))
|
||||
+ offset_value;
|
||||
const double sqrad = CGAL::square(rad);
|
||||
|
||||
CGAL::Timer timer;
|
||||
timer.start();
|
||||
|
||||
namespace p = CGAL::parameters;
|
||||
|
||||
Mesh_domain domain =
|
||||
Mesh_domain::create_implicit_mesh_domain
|
||||
(p::function = offset_function(source_item, offset_value),
|
||||
p::bounding_object = Sphere_3(center, sqrad),
|
||||
p::relative_error_bound = 1e-7,
|
||||
p::construct_surface_patch_index = [](int i, int j) { return (i * 1000 + j); });
|
||||
|
||||
const CGAL::Mesh_facet_topology topology = CGAL::FACET_VERTICES_ON_SAME_SURFACE_PATCH;
|
||||
auto manifold_option = p::non_manifold();
|
||||
if(tag == 1)
|
||||
manifold_option = p::manifold_with_boundary();
|
||||
if(tag == 2)
|
||||
manifold_option = p::manifold();
|
||||
|
||||
Mesh_criteria criteria(p::facet_angle = angle,
|
||||
p::facet_size = sizing,
|
||||
p::facet_distance = approx,
|
||||
p::facet_topology = topology,
|
||||
p::edge_size = edge_size);
|
||||
|
||||
if(polylines_item != nullptr)
|
||||
{
|
||||
typedef std::vector<Mesh_domain::Surface_patch_index> Surface_patch_ids;
|
||||
std::vector<Mesh_domain::Surface_patch_index> surface_patch_ids;
|
||||
|
||||
domain.add_features_and_incidences(polylines_item->polylines.begin(),
|
||||
polylines_item->polylines.end(),
|
||||
CGAL::Identity_property_map<Scene_polylines_item::Polyline>(),
|
||||
CGAL::Constant_property_map<Scene_polylines_item::Polyline, Surface_patch_ids>(surface_patch_ids));
|
||||
}
|
||||
|
||||
C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria,
|
||||
p::no_perturb(),
|
||||
p::no_exude(),
|
||||
manifold_option);
|
||||
|
||||
timer.stop();
|
||||
std::cerr << "done (" << timer.time() << " ms, " << c3t3.triangulation().number_of_vertices() << " vertices)" << std::endl;
|
||||
|
||||
if(c3t3.number_of_facets_in_complex() > 0)
|
||||
{
|
||||
SMesh* pRemesh = new SMesh();
|
||||
|
||||
// if the thread is interrupted before the mesh is returned, delete it.
|
||||
MeshGuard guard(pRemesh);
|
||||
CGAL::facets_in_complex_3_to_triangle_mesh(c3t3, *pRemesh);
|
||||
guard.setDone();
|
||||
|
||||
CGAL_postcondition(CGAL::Polygon_mesh_processing::is_outward_oriented(*pRemesh));
|
||||
|
||||
return pRemesh;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
struct Mesher_thread
|
||||
: public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Scene_surface_mesh_item* sm_item;
|
||||
Scene_polygon_soup_item* soup_item;
|
||||
Scene_polylines_item* polylines_item;
|
||||
|
||||
const double offset_value;
|
||||
const double angle;
|
||||
const double sizing;
|
||||
const double approx;
|
||||
const double edge_size;
|
||||
int tag_index;
|
||||
|
||||
public:
|
||||
Mesher_thread(Scene_surface_mesh_item* sm_item,
|
||||
Scene_polygon_soup_item* soup_item,
|
||||
Scene_polylines_item* polylines_item,
|
||||
const double offset_value,
|
||||
const double angle,
|
||||
const double sizing,
|
||||
const double approx,
|
||||
const double edge_size,
|
||||
int tag)
|
||||
: sm_item(sm_item), soup_item(soup_item), polylines_item(polylines_item),
|
||||
offset_value(offset_value),
|
||||
angle(angle), sizing(sizing), approx(approx), edge_size(edge_size), tag_index(tag)
|
||||
{
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
SMesh* offset_mesh = nullptr;
|
||||
|
||||
if(soup_item)
|
||||
{
|
||||
offset_mesh = cgal_off_meshing(Three::mainWindow(),
|
||||
soup_item, polylines_item,
|
||||
offset_value,
|
||||
angle, sizing, approx, edge_size, tag_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
offset_mesh = cgal_off_meshing(Three::mainWindow(),
|
||||
sm_item, polylines_item,
|
||||
offset_value,
|
||||
angle, sizing, approx, edge_size, tag_index);
|
||||
}
|
||||
|
||||
Three::getMutex()->lock();
|
||||
Three::getWaitCondition()->wakeAll();
|
||||
Three::getMutex()->unlock();
|
||||
|
||||
Q_EMIT resultReady(offset_mesh);
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void resultReady(SMesh *offset_mesh);
|
||||
};
|
||||
|
||||
class Polyhedron_demo_offset_meshing_plugin
|
||||
: public QObject,
|
||||
protected Polyhedron_demo_plugin_interface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface)
|
||||
Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0")
|
||||
|
||||
private:
|
||||
QAction* actionOffsetMeshing;
|
||||
QAction* actionInflateMesh;
|
||||
|
||||
Scene_interface *scene;
|
||||
QMainWindow *mw;
|
||||
|
||||
public:
|
||||
void init(QMainWindow* mainWindow,
|
||||
Scene_interface* scene_interface,
|
||||
Messages_interface*)
|
||||
{
|
||||
this->scene = scene_interface;
|
||||
this->mw = mainWindow;
|
||||
|
||||
actionOffsetMeshing = new QAction(tr("Offset Meshing"), mw);
|
||||
actionOffsetMeshing->setProperty("subMenuName", "3D Surface Mesh Generation");
|
||||
connect(actionOffsetMeshing, SIGNAL(triggered()),
|
||||
this, SLOT(offset_meshing()));
|
||||
|
||||
actionInflateMesh = new QAction(tr("Inflate Mesh"), mw);
|
||||
actionInflateMesh->setProperty("subMenuName", "Operations on Polyhedra");
|
||||
connect(actionInflateMesh, SIGNAL(triggered()),
|
||||
this, SLOT(inflate_mesh()));
|
||||
}
|
||||
|
||||
bool applicable(QAction* action) const
|
||||
{
|
||||
if(action == actionOffsetMeshing)
|
||||
{
|
||||
if(scene->selectionIndices().size() == 1)
|
||||
{
|
||||
const int index = scene->mainSelectionIndex();
|
||||
return (qobject_cast<Scene_surface_mesh_item*>(scene->item(index)) ||
|
||||
qobject_cast<Scene_polygon_soup_item*>(scene->item(index)));
|
||||
}
|
||||
|
||||
// Can provide a polyline item for feature protection
|
||||
if(scene->selectionIndices().size() != 2)
|
||||
return false;
|
||||
|
||||
// One needs to be a surface mesh or polygon soup item, and the other a polyline item
|
||||
const int index1 = scene->selectionIndices().at(0);
|
||||
const int index2 = scene->selectionIndices().at(1);
|
||||
Scene_item* item1 = scene->item(index1);
|
||||
Scene_item* item2 = scene->item(index2);
|
||||
|
||||
if((qobject_cast<Scene_surface_mesh_item*>(item1) ||
|
||||
qobject_cast<Scene_polygon_soup_item*>(item1)) &&
|
||||
qobject_cast<Scene_polylines_item*>(item2))
|
||||
return true;
|
||||
|
||||
if((qobject_cast<Scene_surface_mesh_item*>(item2) ||
|
||||
qobject_cast<Scene_polygon_soup_item*>(item2)) &&
|
||||
qobject_cast<Scene_polylines_item*>(item1))
|
||||
return true;
|
||||
}
|
||||
else if(action == actionInflateMesh)
|
||||
{
|
||||
if(scene->selectionIndices().size() == 1)
|
||||
{
|
||||
const int index = scene->mainSelectionIndex();
|
||||
return qobject_cast<Scene_surface_mesh_item*>(scene->item(index));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<QAction*> actions() const
|
||||
{
|
||||
return QList<QAction*>() << actionOffsetMeshing
|
||||
<< actionInflateMesh;
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
void offset_meshing();
|
||||
void inflate_mesh();
|
||||
}; // class Polyhedron_demo_offset_meshing_plugin
|
||||
|
||||
void
|
||||
Polyhedron_demo_offset_meshing_plugin::
|
||||
offset_meshing()
|
||||
{
|
||||
Scene_item* item = nullptr;
|
||||
Scene_surface_mesh_item* sm_item = nullptr;
|
||||
Scene_polygon_soup_item* soup_item = nullptr;
|
||||
Scene_polylines_item* polylines_item = nullptr;
|
||||
|
||||
bool mesh_or_soup_item_found = false;
|
||||
Q_FOREACH(Scene_interface::Item_id index, scene->selectionIndices())
|
||||
{
|
||||
if(!mesh_or_soup_item_found)
|
||||
{
|
||||
sm_item = qobject_cast<Scene_surface_mesh_item*>(scene->item(index));
|
||||
if(sm_item == nullptr)
|
||||
{
|
||||
soup_item = qobject_cast<Scene_polygon_soup_item*>(scene->item(index));
|
||||
if(soup_item != nullptr)
|
||||
{
|
||||
item = scene->item(index);
|
||||
mesh_or_soup_item_found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item = scene->item(index);
|
||||
mesh_or_soup_item_found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
polylines_item = qobject_cast<Scene_polylines_item*>(scene->item(index));
|
||||
}
|
||||
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
|
||||
if(!mesh_or_soup_item_found)
|
||||
return;
|
||||
|
||||
if(sm_item)
|
||||
{
|
||||
if(!is_triangle_mesh(*(sm_item->face_graph())))
|
||||
{
|
||||
QMessageBox::critical(mw,
|
||||
tr("Offset Meshing"),
|
||||
tr("The selected mesh is not a triangle mesh."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(const auto& p : soup_item->polygons())
|
||||
{
|
||||
if(p.size() != 3)
|
||||
{
|
||||
QMessageBox::critical(mw,
|
||||
tr("Offset Meshing"),
|
||||
tr("The selected polygon soup is not a triangle soup."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double diag;
|
||||
if(sm_item)
|
||||
diag = sm_item->bboxDiagonal();
|
||||
else
|
||||
diag = soup_item->bboxDiagonal();
|
||||
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
bool ok = true;
|
||||
double offset_value = QInputDialog::getDouble(mw,
|
||||
QString("Choose Offset Value"),
|
||||
QString("Offset Value (use a negative number to compute the inset of a closed mesh)"),
|
||||
0.1 * diag,
|
||||
- (std::numeric_limits<double>::max)(),
|
||||
(std::numeric_limits<double>::max)(), 10, &ok);
|
||||
if(!ok)
|
||||
return;
|
||||
|
||||
if(offset_value < 0 && (!sm_item || !is_closed(*(sm_item->face_graph()))))
|
||||
{
|
||||
QMessageBox::critical(mw,
|
||||
tr("Offset Meshing"),
|
||||
tr("Insetting is only possible for closed polygon meshes."));
|
||||
return;
|
||||
}
|
||||
|
||||
QDialog dialog(mw);
|
||||
Ui::Offset_meshing_dialog ui;
|
||||
ui.setupUi(&dialog);
|
||||
ui.angle->setRange(1.0, 30.0);
|
||||
|
||||
connect(ui.buttonBox, SIGNAL(accepted()),
|
||||
&dialog, SLOT(accept()));
|
||||
connect(ui.buttonBox, SIGNAL(rejected()),
|
||||
&dialog, SLOT(reject()));
|
||||
|
||||
ui.sizing->setRange(diag * 10e-6, diag);
|
||||
ui.sizing->setValue(diag * 0.05); // default value
|
||||
ui.approx->setRange(diag * 10e-7, diag);
|
||||
ui.approx->setValue(diag * 0.005);
|
||||
|
||||
if(polylines_item != nullptr)
|
||||
{
|
||||
ui.edge_sizing->setRange(diag * 10e-6, diag);
|
||||
ui.edge_sizing->setValue(diag * 0.05); // default value
|
||||
}
|
||||
else
|
||||
{
|
||||
ui.edge_sizing->setEnabled(false);
|
||||
}
|
||||
|
||||
int i = dialog.exec();
|
||||
if(i == QDialog::Rejected)
|
||||
return;
|
||||
|
||||
const double angle = ui.angle->value();
|
||||
const double approx = ui.approx->value();
|
||||
const double sizing = ui.sizing->value();
|
||||
const double edge_size = (polylines_item != nullptr) ? ui.edge_sizing->value() : 0;
|
||||
const int tag_index = ui.tags->currentIndex();
|
||||
|
||||
if(tag_index < 0)
|
||||
return;
|
||||
|
||||
QApplication::setOverrideCursor(Qt::BusyCursor);
|
||||
|
||||
std::cerr << "mesh with:"
|
||||
<< "\n angle= " << angle
|
||||
<< "\n sizing= " << sizing
|
||||
<< "\n approx= " << approx
|
||||
<< "\n tag= " << tag_index
|
||||
<< std::boolalpha
|
||||
<< std::endl;
|
||||
|
||||
Mesher_thread* worker = nullptr;
|
||||
if(soup_item)
|
||||
{
|
||||
worker = new Mesher_thread(nullptr, soup_item, polylines_item,
|
||||
offset_value,
|
||||
angle, sizing, approx, edge_size, tag_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
worker = new Mesher_thread(sm_item, nullptr, polylines_item,
|
||||
offset_value,
|
||||
angle, sizing, approx, edge_size, tag_index);
|
||||
}
|
||||
|
||||
connect(worker, &QThread::finished,
|
||||
worker, &QObject::deleteLater);
|
||||
|
||||
connect(worker, &Mesher_thread::resultReady,
|
||||
this, [item, angle, sizing, approx, offset_value/* , index */](SMesh* offset_mesh)
|
||||
{
|
||||
if(!offset_mesh)
|
||||
{
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
Three::getMutex()->lock();
|
||||
Three::isLocked() = false;
|
||||
Three::getMutex()->unlock();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Scene_surface_mesh_item* offset_item = new Scene_surface_mesh_item(offset_mesh);
|
||||
offset_item->setName(tr("%1 offset %5 (%2 %3 %4)").arg(item->name())
|
||||
.arg(angle)
|
||||
.arg(sizing)
|
||||
.arg(approx)
|
||||
.arg(offset_value));
|
||||
offset_item->setColor(Qt::magenta);
|
||||
offset_item->setRenderingMode(Wireframe);
|
||||
Three::scene()->addItem(offset_item);
|
||||
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
Three::getMutex()->lock();
|
||||
Three::isLocked() = false;
|
||||
Three::getMutex()->unlock();
|
||||
});
|
||||
|
||||
QMessageBox* message_box = new QMessageBox(QMessageBox::NoIcon,
|
||||
"Meshing",
|
||||
"Offset meshing in progress...",
|
||||
QMessageBox::Cancel,
|
||||
mw);
|
||||
message_box->setDefaultButton(QMessageBox::Cancel);
|
||||
QAbstractButton* cancelButton = message_box->button(QMessageBox::Cancel);
|
||||
cancelButton->setText(tr("Stop"));
|
||||
|
||||
connect(cancelButton, &QAbstractButton::clicked,
|
||||
this, [worker](){ worker->terminate(); });
|
||||
connect(worker, &Mesher_thread::finished,
|
||||
message_box, &QMessageBox::close);
|
||||
|
||||
Three::getMutex()->lock();
|
||||
Three::isLocked() = true;
|
||||
Three::getMutex()->unlock();
|
||||
|
||||
message_box->open();
|
||||
worker->start();
|
||||
}
|
||||
|
||||
void
|
||||
Polyhedron_demo_offset_meshing_plugin::
|
||||
inflate_mesh()
|
||||
{
|
||||
const Scene_interface::Item_id index = scene->mainSelectionIndex();
|
||||
Scene_item* item = scene->item(index);
|
||||
if(item == nullptr)
|
||||
return;
|
||||
|
||||
Scene_surface_mesh_item* sm_item = qobject_cast<Scene_surface_mesh_item*>(item);
|
||||
if(sm_item == nullptr)
|
||||
return;
|
||||
|
||||
SMesh* sMesh = sm_item->face_graph();
|
||||
if(sMesh == nullptr)
|
||||
return;
|
||||
|
||||
const double diag = sm_item->bboxDiagonal();
|
||||
|
||||
bool ok = true;
|
||||
const double offset_value = QInputDialog::getDouble(mw,
|
||||
QString("Choose Inflate Distance"),
|
||||
QString("Inflate Distance (use a negative number to deflate)"),
|
||||
0.1 * diag,
|
||||
-(std::numeric_limits<double>::max)(),
|
||||
(std::numeric_limits<double>::max)(),
|
||||
10,
|
||||
&ok);
|
||||
if(!ok)
|
||||
return;
|
||||
|
||||
auto vpm = get(CGAL::vertex_point, *sMesh);
|
||||
auto vnm = sMesh->property_map<vertex_descriptor, EPICK::Vector_3 >("v:normal").first;
|
||||
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
|
||||
for(const auto& v : vertices(*sMesh))
|
||||
{
|
||||
const EPICK::Vector_3& n = get(vnm, v);
|
||||
put(vpm, v, get(vpm, v) + offset_value * n);
|
||||
}
|
||||
|
||||
sm_item->invalidateOpenGLBuffers();
|
||||
sm_item->itemChanged();
|
||||
sm_item->itemVisibilityChanged();
|
||||
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
#include "Offset_meshing_plugin.moc"
|
||||
|
||||
#endif // CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER
|
||||
|
|
@ -41,18 +41,6 @@ if(NOT CGAL_DISABLE_GMP)
|
|||
polyhedron_demo_plugin(mesh_simplification_plugin Mesh_simplification_plugin ${mesh_simplificationUI_FILES})
|
||||
target_link_libraries(mesh_simplification_plugin PUBLIC scene_surface_mesh_item scene_selection_item)
|
||||
|
||||
qt5_wrap_ui(remeshingUI_FILES Remeshing_dialog.ui)
|
||||
polyhedron_demo_plugin(offset_meshing_plugin Offset_meshing_plugin
|
||||
${remeshingUI_FILES})
|
||||
target_link_libraries(offset_meshing_plugin PUBLIC scene_surface_mesh_item
|
||||
scene_polygon_soup_item
|
||||
scene_polylines_item)
|
||||
if(TARGET CGAL::Eigen3_support)
|
||||
target_link_libraries(offset_meshing_plugin PUBLIC CGAL::Eigen3_support)
|
||||
endif()
|
||||
if(TARGET CGAL::TBB_support)
|
||||
target_link_libraries(offset_meshing_plugin PUBLIC CGAL::TBB_support)
|
||||
endif()
|
||||
|
||||
qt5_wrap_ui(shortestPathUI_FILES Shortest_path_widget.ui)
|
||||
polyhedron_demo_plugin(shortest_path_plugin Shortest_path_plugin
|
||||
|
|
|
|||
|
|
@ -1,691 +0,0 @@
|
|||
#include "config.h"
|
||||
#ifdef CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER
|
||||
#include <CGAL/Three/Polyhedron_demo_plugin_interface.h>
|
||||
#include "ui_Remeshing_dialog.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include <QtPlugin>
|
||||
#include <QThread>
|
||||
#include "Scene_surface_mesh_item.h"
|
||||
#include "Scene_polygon_soup_item.h"
|
||||
#include "Scene_polylines_item.h"
|
||||
#include <QInputDialog>
|
||||
#include <QStringList>
|
||||
#include <QMessageBox>
|
||||
#include <QAbstractButton>
|
||||
|
||||
#include "C3t3_type.h"
|
||||
|
||||
#include <CGAL/AABB_tree.h>
|
||||
#include <CGAL/AABB_traits.h>
|
||||
#include <CGAL/AABB_face_graph_triangle_primitive.h>
|
||||
|
||||
#include <CGAL/Side_of_triangle_mesh.h>
|
||||
#include <CGAL/Polygon_mesh_processing/bbox.h>
|
||||
#include <CGAL/Polygon_mesh_processing/orientation.h>
|
||||
|
||||
#include <CGAL/Timer.h>
|
||||
#include <CGAL/make_mesh_3.h>
|
||||
#include <CGAL/Labeled_mesh_domain_3.h>
|
||||
#include <CGAL/Mesh_domain_with_polyline_features_3.h>
|
||||
#include <CGAL/Mesh_criteria_3.h>
|
||||
#include <CGAL/Three/Three.h>
|
||||
|
||||
#include <CGAL/facets_in_complex_3_to_triangle_mesh.h>
|
||||
|
||||
#include <memory> // std::shared_ptr
|
||||
|
||||
namespace CGAL{
|
||||
|
||||
template <class TriangleMesh, class GeomTraits>
|
||||
class Offset_function
|
||||
{
|
||||
typedef AABB_face_graph_triangle_primitive<TriangleMesh> Primitive;
|
||||
typedef AABB_traits<GeomTraits, Primitive> Traits;
|
||||
typedef AABB_tree<Traits> Tree;
|
||||
typedef Side_of_triangle_mesh<TriangleMesh, GeomTraits> Side_of;
|
||||
|
||||
public:
|
||||
|
||||
Offset_function(TriangleMesh& tm, double offset_distance)
|
||||
: m_tree_ptr(new Tree(boost::begin(faces(tm)),
|
||||
boost::end(faces(tm)),
|
||||
tm) )
|
||||
, m_side_of_ptr( new Side_of(*m_tree_ptr) )
|
||||
, m_offset_distance(offset_distance)
|
||||
, m_is_closed( is_closed(tm) )
|
||||
{
|
||||
CGAL_assertion(!m_tree_ptr->empty());
|
||||
}
|
||||
|
||||
double operator()(const typename GeomTraits::Point_3& p) const
|
||||
{
|
||||
using CGAL::sqrt;
|
||||
|
||||
Bounded_side side = m_is_closed?m_side_of_ptr->operator()(p):ON_UNBOUNDED_SIDE;
|
||||
if (side==ON_BOUNDARY) return m_offset_distance;
|
||||
|
||||
typename GeomTraits::Point_3 closest_point = m_tree_ptr->closest_point(p);
|
||||
double distance = sqrt(squared_distance(p, closest_point));
|
||||
|
||||
return (side == ON_UNBOUNDED_SIDE ? -distance : distance) + m_offset_distance;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Tree> m_tree_ptr;
|
||||
std::shared_ptr<Side_of> m_side_of_ptr;
|
||||
double m_offset_distance;
|
||||
bool m_is_closed;
|
||||
|
||||
};
|
||||
|
||||
class Polygon_soup_offset_function {
|
||||
typedef Scene_polygon_soup_item::Points Points;
|
||||
typedef Scene_polygon_soup_item::Polygons Polygons;
|
||||
|
||||
typedef Polygons::const_iterator Polygon_iterator;
|
||||
|
||||
|
||||
class Polygon_soup_point_property_map {
|
||||
const Points* points_vector_ptr;
|
||||
public:
|
||||
typedef Polygon_iterator key_type;
|
||||
typedef EPICK::Point_3 value_type;
|
||||
typedef const value_type& reference;
|
||||
typedef boost::readable_property_map_tag category;
|
||||
|
||||
Polygon_soup_point_property_map() = default;
|
||||
Polygon_soup_point_property_map(const Points* ptr)
|
||||
: points_vector_ptr(ptr)
|
||||
{}
|
||||
|
||||
friend reference get(Polygon_soup_point_property_map map,
|
||||
key_type polygon_it)
|
||||
{
|
||||
return (*map.points_vector_ptr)[*polygon_it->begin()];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Polygon_soup_triangle_property_map {
|
||||
const Points* points_vector_ptr;
|
||||
public:
|
||||
typedef Polygon_iterator key_type;
|
||||
typedef EPICK::Triangle_3 value_type;
|
||||
typedef value_type reference;
|
||||
typedef boost::readable_property_map_tag category;
|
||||
|
||||
Polygon_soup_triangle_property_map() = default;
|
||||
Polygon_soup_triangle_property_map(const Points* ptr)
|
||||
: points_vector_ptr(ptr)
|
||||
{}
|
||||
|
||||
friend value_type get(Polygon_soup_triangle_property_map map,
|
||||
key_type polygon_it)
|
||||
{
|
||||
auto it = polygon_it->begin();
|
||||
CGAL_assertion(it != polygon_it->end());
|
||||
const auto id0 = *it++;
|
||||
CGAL_assertion(it != polygon_it->end());
|
||||
const auto id1 = *it++;
|
||||
CGAL_assertion(it != polygon_it->end());
|
||||
const auto id2 = *it++;
|
||||
CGAL_assertion(it == polygon_it->end());
|
||||
|
||||
return value_type( (*map.points_vector_ptr)[id0],
|
||||
(*map.points_vector_ptr)[id1],
|
||||
(*map.points_vector_ptr)[id2] );
|
||||
}
|
||||
};
|
||||
|
||||
struct AABB_primitive :
|
||||
public CGAL::AABB_primitive<Polygon_iterator,
|
||||
Polygon_soup_triangle_property_map,
|
||||
Polygon_soup_point_property_map,
|
||||
CGAL::Tag_true,
|
||||
CGAL::Tag_false>
|
||||
{
|
||||
typedef CGAL::AABB_primitive<Polygon_iterator,
|
||||
Polygon_soup_triangle_property_map,
|
||||
Polygon_soup_point_property_map,
|
||||
CGAL::Tag_true,
|
||||
CGAL::Tag_false> Base;
|
||||
|
||||
typedef Polygon_iterator Id;
|
||||
|
||||
template <typename ObjectPmap, typename PointPmap>
|
||||
AABB_primitive(Id id, ObjectPmap&& opmap, PointPmap&& ppmap)
|
||||
: Base(id, std::forward<ObjectPmap>(opmap), std::forward<PointPmap>(ppmap))
|
||||
{}
|
||||
|
||||
template <typename Iterator, typename ObjectPmap, typename PointPmap>
|
||||
AABB_primitive(Iterator it, ObjectPmap&& opmap, PointPmap&& ppmap)
|
||||
: Base(*it, std::forward<ObjectPmap>(opmap), std::forward<PointPmap>(ppmap))
|
||||
{}
|
||||
}; // end struct template AABB_primitive
|
||||
|
||||
|
||||
typedef CGAL::AABB_traits<EPICK, AABB_primitive> AABB_traits;
|
||||
typedef CGAL::AABB_tree<AABB_traits> AABB_tree;
|
||||
|
||||
std::shared_ptr<AABB_tree> m_tree_ptr;
|
||||
double m_offset_distance;
|
||||
|
||||
typedef Polygon_soup_triangle_property_map ObjectPmap;
|
||||
typedef Polygon_soup_point_property_map PointPmap;
|
||||
public:
|
||||
Polygon_soup_offset_function(const Scene_polygon_soup_item* soup,
|
||||
const double offset_distance)
|
||||
: m_tree_ptr
|
||||
(std::make_shared<AABB_tree>(begin(soup->polygons()),
|
||||
end(soup->polygons()),
|
||||
ObjectPmap(&soup->points()),
|
||||
PointPmap(&soup->points()))
|
||||
)
|
||||
, m_offset_distance(offset_distance)
|
||||
{
|
||||
CGAL_assertion(! m_tree_ptr->empty() );
|
||||
}
|
||||
|
||||
double operator()(const EPICK::Point_3& p) const
|
||||
{
|
||||
using CGAL::sqrt;
|
||||
|
||||
EPICK::Point_3 closest_point = m_tree_ptr->closest_point(p);
|
||||
double distance = sqrt(squared_distance(p, closest_point));
|
||||
|
||||
return m_offset_distance - distance;
|
||||
}
|
||||
|
||||
}; // end class Polygon_soup_offset_function
|
||||
|
||||
} //end of CGAL namespace
|
||||
|
||||
Scene_surface_mesh_item* make_item(SMesh* sm)
|
||||
{
|
||||
return new Scene_surface_mesh_item(sm);
|
||||
}
|
||||
|
||||
CGAL::Offset_function<SMesh, EPICK>
|
||||
offset_function(SMesh* surface_mesh_ptr, double offset_value) {
|
||||
return { *surface_mesh_ptr, offset_value };
|
||||
}
|
||||
|
||||
CGAL::Polygon_soup_offset_function
|
||||
offset_function(Scene_polygon_soup_item* item, double offset_value) {
|
||||
return { item, offset_value };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct Result_type {
|
||||
typedef T type;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Result_type<Scene_polygon_soup_item> {
|
||||
typedef SMesh type;
|
||||
};
|
||||
|
||||
template <typename Mesh>
|
||||
CGAL::Bbox_3 bbox(Mesh* mesh_ptr) {
|
||||
return CGAL::Polygon_mesh_processing::bbox(*mesh_ptr);
|
||||
}
|
||||
|
||||
CGAL::Bbox_3 bbox(Scene_polygon_soup_item* item) {
|
||||
return item->bbox();
|
||||
}
|
||||
class MeshGuard{
|
||||
SMesh* mesh;
|
||||
bool done;
|
||||
public:
|
||||
MeshGuard(SMesh* mesh):mesh(mesh), done(false){}
|
||||
void setDone(){done = true;}
|
||||
~MeshGuard(){
|
||||
if(!done)
|
||||
delete mesh;
|
||||
}
|
||||
};
|
||||
// declare the CGAL function
|
||||
template<class Mesh>
|
||||
SMesh* cgal_off_meshing(QWidget*,
|
||||
Mesh* tm_ptr,
|
||||
Scene_polylines_item* polylines_item,
|
||||
const double offset_value,
|
||||
const double angle,
|
||||
const double sizing,
|
||||
const double approx,
|
||||
const double edge_size,
|
||||
int tag)
|
||||
{
|
||||
typedef EPICK GT;
|
||||
typedef CGAL::Labeled_mesh_domain_3<GT, int, int> Mesh_domain_base;
|
||||
typedef CGAL::Mesh_domain_with_polyline_features_3<Mesh_domain_base> Mesh_domain;
|
||||
typedef C3t3::Triangulation Tr;
|
||||
typedef CGAL::Mesh_criteria_3<Tr> Mesh_criteria;
|
||||
typedef GT::Sphere_3 Sphere_3;
|
||||
|
||||
CGAL::Bbox_3 bbox = ::bbox(tm_ptr);
|
||||
|
||||
GT::Point_3 center((bbox.xmax()+bbox.xmin())/2,
|
||||
(bbox.ymax()+bbox.ymin())/2,
|
||||
(bbox.zmax()+bbox.zmin())/2);
|
||||
double sqrad = 0.6 * std::sqrt( CGAL::square(bbox.xmax()-bbox.xmin())+
|
||||
CGAL::square(bbox.ymax()-bbox.ymin())+
|
||||
CGAL::square(bbox.zmax()-bbox.zmin()) )
|
||||
+ offset_value;
|
||||
sqrad=CGAL::square(sqrad);
|
||||
|
||||
CGAL::Timer timer;
|
||||
timer.start();
|
||||
|
||||
namespace p = CGAL::parameters;
|
||||
|
||||
Mesh_domain domain =
|
||||
Mesh_domain::create_implicit_mesh_domain
|
||||
(p::function = offset_function(tm_ptr, offset_value),
|
||||
p::bounding_object = Sphere_3(center, sqrad),
|
||||
p::relative_error_bound = 1e-7,
|
||||
p::construct_surface_patch_index = [](int i, int j) { return (i * 1000 + j); });
|
||||
|
||||
const CGAL::Mesh_facet_topology topology = CGAL::FACET_VERTICES_ON_SAME_SURFACE_PATCH;
|
||||
auto manifold_option = p::non_manifold();
|
||||
if(tag == 1) manifold_option = p::manifold_with_boundary();
|
||||
if(tag == 2) manifold_option = p::manifold();
|
||||
Mesh_criteria criteria(p::facet_angle = angle,
|
||||
p::facet_size = sizing,
|
||||
p::facet_distance = approx,
|
||||
p::facet_topology = topology,
|
||||
p::edge_size = edge_size);
|
||||
|
||||
if (polylines_item!=nullptr)
|
||||
{
|
||||
typedef std::vector<Mesh_domain::Surface_patch_index> Surface_patch_ids;
|
||||
std::vector<Mesh_domain::Surface_patch_index> surface_patch_ids;
|
||||
|
||||
domain.add_features_and_incidences(polylines_item->polylines.begin(),
|
||||
polylines_item->polylines.end(),
|
||||
CGAL::Identity_property_map<Scene_polylines_item::Polyline>(),
|
||||
CGAL::Constant_property_map<Scene_polylines_item::Polyline, Surface_patch_ids>(
|
||||
surface_patch_ids));
|
||||
}
|
||||
|
||||
C3t3 c3t3 = CGAL::make_mesh_3<C3t3>(domain, criteria,
|
||||
p::no_perturb(),
|
||||
p::no_exude(),
|
||||
manifold_option);
|
||||
|
||||
const Tr& tr = c3t3.triangulation();
|
||||
|
||||
timer.stop();
|
||||
std::cerr << "done (" << timer.time() << " ms, " << tr.number_of_vertices() << " vertices)" << std::endl;
|
||||
|
||||
if(tr.number_of_vertices() > 0)
|
||||
{
|
||||
typedef typename Result_type<Mesh>::type Result_mesh;
|
||||
// add remesh as new polyhedron
|
||||
Result_mesh *pRemesh = new Result_mesh;
|
||||
//if the thread is interrupted before the mesh is returned, delete it.
|
||||
MeshGuard guard(pRemesh);
|
||||
CGAL::facets_in_complex_3_to_triangle_mesh(c3t3, *pRemesh);
|
||||
guard.setDone();
|
||||
if(CGAL::is_closed(*pRemesh)
|
||||
&& ! CGAL::Polygon_mesh_processing::is_outward_oriented(*pRemesh))
|
||||
{
|
||||
CGAL::Polygon_mesh_processing::reverse_face_orientations(*pRemesh);
|
||||
}
|
||||
|
||||
return pRemesh;
|
||||
}
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct Mesher_thread:public QThread{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
SMesh* sMesh;
|
||||
Scene_polygon_soup_item* soup_item;
|
||||
Scene_polylines_item* polylines_item;
|
||||
const double offset_value;
|
||||
const double angle;
|
||||
const double sizing;
|
||||
const double approx;
|
||||
const double edge_size;
|
||||
int tag_index;
|
||||
public:
|
||||
Mesher_thread( SMesh* tm_ptr,
|
||||
Scene_polygon_soup_item* soup_item,
|
||||
Scene_polylines_item* polylines_item,
|
||||
const double offset_value,
|
||||
const double angle,
|
||||
const double sizing,
|
||||
const double approx,
|
||||
const double edge_size,
|
||||
int tag)
|
||||
:sMesh(tm_ptr), soup_item(soup_item), polylines_item(polylines_item),
|
||||
offset_value(offset_value), angle(angle),
|
||||
sizing(sizing), approx(approx), edge_size(edge_size), tag_index(tag){
|
||||
}
|
||||
void run() override {
|
||||
SMesh* new_mesh= nullptr;
|
||||
if(soup_item)
|
||||
new_mesh = cgal_off_meshing(CGAL::Three::Three::mainWindow(),
|
||||
soup_item,
|
||||
polylines_item,
|
||||
offset_value,
|
||||
angle,
|
||||
sizing,
|
||||
approx,
|
||||
edge_size,
|
||||
tag_index);
|
||||
else
|
||||
new_mesh = cgal_off_meshing(CGAL::Three::Three::mainWindow(),
|
||||
sMesh,
|
||||
polylines_item,
|
||||
offset_value,
|
||||
angle,
|
||||
sizing,
|
||||
approx,
|
||||
edge_size,
|
||||
tag_index);
|
||||
CGAL::Three::Three::getMutex()->lock();
|
||||
CGAL::Three::Three::getWaitCondition()->wakeAll();
|
||||
CGAL::Three::Three::getMutex()->unlock();
|
||||
Q_EMIT resultReady(new_mesh);
|
||||
}
|
||||
Q_SIGNALS:
|
||||
void resultReady(SMesh *new_mesh);
|
||||
};
|
||||
|
||||
using namespace CGAL::Three;
|
||||
class Polyhedron_demo_offset_meshing_plugin :
|
||||
public QObject,
|
||||
protected Polyhedron_demo_plugin_interface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface)
|
||||
Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0")
|
||||
|
||||
public:
|
||||
void init(QMainWindow* mainWindow, CGAL::Three::Scene_interface* scene_interface, Messages_interface*) {
|
||||
this->scene = scene_interface;
|
||||
this->mw = mainWindow;
|
||||
actionOffsetMeshing = new QAction(tr("Offset Meshing"), mw);
|
||||
actionOffsetMeshing->setProperty("subMenuName", "3D Surface Mesh Generation");
|
||||
if(actionOffsetMeshing) {
|
||||
connect(actionOffsetMeshing, SIGNAL(triggered()),
|
||||
this, SLOT(offset_meshing()));
|
||||
}
|
||||
|
||||
actionInflateMesh= new QAction(tr("Inflate Mesh"), mw);
|
||||
actionInflateMesh->setProperty("subMenuName", "Operations on Polyhedra");
|
||||
if(actionInflateMesh) {
|
||||
connect(actionInflateMesh, SIGNAL(triggered()),
|
||||
this, SLOT(inflate_mesh()));
|
||||
}
|
||||
}
|
||||
|
||||
bool applicable(QAction*) const {
|
||||
if ( scene->selectionIndices().size() != 1 &&
|
||||
scene->selectionIndices().size() != 2 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_FOREACH(CGAL::Three::Scene_interface::Item_id index, scene->selectionIndices())
|
||||
{
|
||||
if ( qobject_cast<Scene_surface_mesh_item*>(scene->item(index)) ||
|
||||
qobject_cast<Scene_polygon_soup_item*>(scene->item(index)) )
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<QAction*> actions() const {
|
||||
return QList<QAction*>() << actionOffsetMeshing
|
||||
<< actionInflateMesh;
|
||||
}
|
||||
public Q_SLOTS:
|
||||
void offset_meshing();
|
||||
void inflate_mesh();
|
||||
|
||||
private:
|
||||
QAction* actionOffsetMeshing;
|
||||
QAction* actionInflateMesh;
|
||||
Scene_interface *scene;
|
||||
QMainWindow *mw;
|
||||
}; // end class Polyhedron_demo_offset_meshing_plugin
|
||||
|
||||
void Polyhedron_demo_offset_meshing_plugin::inflate_mesh()
|
||||
{
|
||||
const CGAL::Three::Scene_interface::Item_id index = scene->mainSelectionIndex();
|
||||
Scene_item* item = scene->item(index);
|
||||
if(item == nullptr){
|
||||
return;
|
||||
}
|
||||
|
||||
Scene_surface_mesh_item* sm_item =
|
||||
qobject_cast<Scene_surface_mesh_item*>(item);
|
||||
|
||||
if(sm_item == nullptr){
|
||||
return;
|
||||
}
|
||||
|
||||
SMesh* sMesh = sm_item->face_graph();
|
||||
|
||||
if(sMesh == nullptr){
|
||||
return;
|
||||
}
|
||||
|
||||
double diag = sm_item->diagonalBbox();
|
||||
double offset_value = QInputDialog::getDouble(mw,
|
||||
QString("Choose Inflate Distance"),
|
||||
QString("Inflate Distance (use negative number for deflate)"),
|
||||
0.1*diag,
|
||||
-(std::numeric_limits<double>::max)(),
|
||||
(std::numeric_limits<double>::max)(), 10);
|
||||
|
||||
auto vpm = get(CGAL::vertex_point,*sMesh);
|
||||
auto vnm =
|
||||
sMesh->property_map<vertex_descriptor, EPICK::Vector_3 >("v:normal").first;
|
||||
|
||||
for(const auto& v : vertices(*sMesh))
|
||||
{
|
||||
Point_3 p = get(vpm, v);
|
||||
EPICK::Vector_3 n = get(vnm, v);
|
||||
n/=(CGAL::sqrt(n.squared_length()));
|
||||
put(vpm, v, p + offset_value*n);
|
||||
}
|
||||
sm_item->invalidateOpenGLBuffers();
|
||||
}
|
||||
|
||||
void Polyhedron_demo_offset_meshing_plugin::offset_meshing()
|
||||
{
|
||||
Scene_surface_mesh_item* sm_item = nullptr;
|
||||
Scene_polygon_soup_item* soup_item = nullptr;
|
||||
Scene_polylines_item* polylines_item = nullptr;
|
||||
Scene_item* item = nullptr;
|
||||
|
||||
bool mesh_or_soup_item_found = false;
|
||||
|
||||
Q_FOREACH(CGAL::Three::Scene_interface::Item_id index, scene->selectionIndices())
|
||||
{
|
||||
if (!mesh_or_soup_item_found)
|
||||
{
|
||||
sm_item = qobject_cast<Scene_surface_mesh_item*>(scene->item(index));
|
||||
if (sm_item == nullptr)
|
||||
{
|
||||
soup_item = qobject_cast<Scene_polygon_soup_item*>(item);
|
||||
if (soup_item != nullptr)
|
||||
{
|
||||
item=scene->item(index);
|
||||
mesh_or_soup_item_found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item=scene->item(index);
|
||||
mesh_or_soup_item_found = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
polylines_item = qobject_cast<Scene_polylines_item*>(scene->item(index));
|
||||
}
|
||||
|
||||
SMesh* sMesh = nullptr;
|
||||
double diag = 0;
|
||||
Scene_item::Bbox box;
|
||||
if(sm_item)
|
||||
{
|
||||
sMesh = sm_item->face_graph();
|
||||
if(!sMesh)
|
||||
return;
|
||||
box = bbox(sMesh);
|
||||
}
|
||||
else if(soup_item != nullptr)
|
||||
{
|
||||
box = bbox(soup_item);
|
||||
}
|
||||
else if(soup_item == nullptr)
|
||||
return;
|
||||
double X=(box.max)(0)-(box.min)(0),
|
||||
Y = (box.max)(1)-(box.min)(1),
|
||||
Z = (box.max)(2)-(box.min)(2);
|
||||
diag = CGAL::sqrt(X*X+Y*Y+Z*Z);
|
||||
double offset_value = QInputDialog::getDouble(mw,
|
||||
QString("Choose Offset Value"),
|
||||
QString("Offset Value (use negative number for inset)"),
|
||||
0.1*diag,
|
||||
-(std::numeric_limits<double>::max)(),
|
||||
(std::numeric_limits<double>::max)(), 10);
|
||||
|
||||
QDialog dialog(mw);
|
||||
Ui::Remeshing_dialog ui;
|
||||
ui.setupUi(&dialog);
|
||||
ui.angle->setRange(1.0, 30.0);
|
||||
connect(ui.buttonBox, SIGNAL(accepted()),
|
||||
&dialog, SLOT(accept()));
|
||||
connect(ui.buttonBox, SIGNAL(rejected()),
|
||||
&dialog, SLOT(reject()));
|
||||
|
||||
ui.sizing->setRange(diag * 10e-6, // min
|
||||
diag); // max
|
||||
ui.sizing->setValue(diag * 0.05); // default value
|
||||
|
||||
ui.approx->setRange(diag * 10e-7, // min
|
||||
diag); // max
|
||||
ui.approx->setValue(diag * 0.005);
|
||||
|
||||
if (polylines_item!=nullptr)
|
||||
{
|
||||
ui.edge_sizing->setRange(diag * 10e-6, // min
|
||||
diag); // max
|
||||
ui.edge_sizing->setValue(diag * 0.05); // default value
|
||||
}
|
||||
else
|
||||
ui.edge_sizing->setEnabled(false);
|
||||
|
||||
int i = dialog.exec();
|
||||
if(i == QDialog::Rejected)
|
||||
return;
|
||||
|
||||
const double angle = ui.angle->value();
|
||||
const double approx = ui.approx->value();
|
||||
const double sizing = ui.sizing->value();
|
||||
const double edge_size=polylines_item!=nullptr?ui.edge_sizing->value():0;
|
||||
const int tag_index = ui.tags->currentIndex();
|
||||
|
||||
if(tag_index < 0) return;
|
||||
|
||||
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||
|
||||
std::cerr << "mesh with:"
|
||||
<< "\n angle=" << angle
|
||||
<< "\n sizing=" << sizing
|
||||
<< "\n approx=" << approx
|
||||
<< "\n tag=" << tag_index
|
||||
<< std::boolalpha
|
||||
<< std::endl;
|
||||
Mesher_thread* worker = nullptr;
|
||||
if(soup_item)
|
||||
worker = new Mesher_thread(nullptr,
|
||||
soup_item,
|
||||
polylines_item,
|
||||
offset_value,
|
||||
angle,
|
||||
sizing,
|
||||
approx,
|
||||
edge_size,
|
||||
tag_index);
|
||||
else
|
||||
worker = new Mesher_thread(sMesh,
|
||||
nullptr,
|
||||
polylines_item,
|
||||
offset_value,
|
||||
angle,
|
||||
sizing,
|
||||
approx,
|
||||
edge_size,
|
||||
tag_index);
|
||||
connect(worker, &QThread::finished, worker, &QObject::deleteLater);
|
||||
connect(worker, &Mesher_thread::resultReady, this,
|
||||
[item, angle, sizing, approx, offset_value/* , index */]
|
||||
(SMesh *new_mesh){
|
||||
QApplication::restoreOverrideCursor();
|
||||
if(!new_mesh){
|
||||
CGAL::Three::Three::getMutex()->lock();
|
||||
CGAL::Three::Three::isLocked() = false;
|
||||
CGAL::Three::Three::getMutex()->unlock();
|
||||
return;
|
||||
}
|
||||
Scene_surface_mesh_item* new_item = new Scene_surface_mesh_item(new_mesh);
|
||||
new_item->setName(tr("%1 offset %5 (%2 %3 %4)")
|
||||
.arg(item->name())
|
||||
.arg(angle)
|
||||
.arg(sizing)
|
||||
.arg(approx)
|
||||
.arg(offset_value));
|
||||
new_item->setColor(Qt::magenta);
|
||||
new_item->setWireframeMode();
|
||||
CGAL::Three::Three::scene()->addItem(new_item);
|
||||
// CGAL::Three::Three::scene()->itemChanged(index);
|
||||
QApplication::restoreOverrideCursor();
|
||||
CGAL::Three::Three::getMutex()->lock();
|
||||
CGAL::Three::Three::isLocked() = false;
|
||||
CGAL::Three::Three::getMutex()->unlock();
|
||||
});
|
||||
QMessageBox* message_box = new QMessageBox(QMessageBox::NoIcon,
|
||||
"Meshing",
|
||||
"Offset meshing in progress...",
|
||||
QMessageBox::Cancel,
|
||||
mw);
|
||||
message_box->setDefaultButton(QMessageBox::Cancel);
|
||||
QAbstractButton* cancelButton = message_box->button(QMessageBox::Cancel);
|
||||
cancelButton->setText(tr("Stop"));
|
||||
|
||||
connect(cancelButton, &QAbstractButton::clicked,
|
||||
this, [worker](){
|
||||
worker->terminate();
|
||||
QApplication::restoreOverrideCursor();//waitcursor
|
||||
QApplication::restoreOverrideCursor();//busycursor
|
||||
});
|
||||
connect(worker, &Mesher_thread::finished,
|
||||
message_box, &QMessageBox::close);
|
||||
message_box->open();
|
||||
|
||||
QApplication::setOverrideCursor(Qt::BusyCursor);
|
||||
CGAL::Three::Three::getMutex()->lock();
|
||||
CGAL::Three::Three::isLocked() = true;
|
||||
CGAL::Three::Three::getMutex()->unlock();
|
||||
worker->start();
|
||||
}
|
||||
|
||||
#include "Offset_meshing_plugin.moc"
|
||||
|
||||
#endif // CGAL_POLYHEDRON_DEMO_USE_SURFACE_MESHER
|
||||
Loading…
Reference in New Issue