IO: write_PLY() for Epeck (#7874)

## Summary of Changes

As reported in #7868 the function `CGAL::IO::write_PLY()` used in binary
mode does not correctly write the coordinates if the points are from a
kernel with exact constructions.
~~This PR applies `to_double()" to the coordinates.~~

~~After a discussion with @MaelRL we decided that the user is in charge
to pass a `vertex_point_map` as named parameter that does the
conversion. This is straightforward as we offer the
[`Cartesian_converter_property_map`](https://doc.cgal.org/latest/Property_map/structCGAL_1_1Cartesian__converter__property__map.html).~~

Moving back to the previous proposal: hardcode some to_double and
to_float casts such that we meet the requirements of the file format,
whatever the input.

As the problem is the same for the vertex normals we add a named
parameter `vertex_normal_map`.

### Todo
- [x] Fix the generic function `write_polygon_mesh()`. Currently it is
fixed for `Surface_mesh`

## Release Management

* Affected package(s): Stream_support
* Issue(s) solved (if any): fix #7868 and fix
https://github.com/CGAL/cgal/issues/7327
* License and copyright ownership:  unchanged
* upcoming integration, update #9072 and test it
This commit is contained in:
Sebastien Loriot 2025-11-13 09:38:55 +01:00 committed by GitHub
commit dfc5fb5065
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 582 additions and 51 deletions

View File

@ -18,6 +18,7 @@
#include <CGAL/boost/graph/IO/Generic_facegraph_builder.h>
#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <fstream>
#include <string>
@ -44,7 +45,7 @@ class PLY_builder
typedef typename Base::Face_container Face_container;
public:
PLY_builder(std::istream& is) : Base(is) { }
PLY_builder(std::istream& is, std::string& comments) : Base(is), comments(comments) { }
template <typename NamedParameters>
bool read(std::istream& is,
@ -52,19 +53,22 @@ public:
Face_container& faces,
const NamedParameters& np)
{
return read_PLY(is, points, faces, np);
return read_PLY(is, points, faces, comments, np);
}
std::string& comments;
};
template <typename Graph, typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY_BGL(std::istream& is,
Graph& g,
std::string& comments,
const CGAL_NP_CLASS& np = parameters::default_values())
{
typedef typename CGAL::GetVertexPointMap<Graph, CGAL_NP_CLASS>::type VPM;
typedef typename boost::property_traits<VPM>::value_type Point;
internal::PLY_builder<Graph, Point> builder(is);
internal::PLY_builder<Graph, Point> builder(is, comments);
return builder(g, np);
}
@ -84,6 +88,7 @@ bool read_PLY_BGL(std::istream& is,
\param is the input stream
\param g the graph to be built from the input data
\param comments a string included line by line in the header of the PLY stream (each line will be precedeed by "comment ")
\param np optional \ref bgl_namedparameters "Named Parameters" described below
\cgalNamedParamsBegin
@ -132,15 +137,31 @@ template <typename Graph,
typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY(std::istream& is,
Graph& g,
std::string& comments,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<!internal::is_Point_set_or_Range_or_Iterator<Graph>::value>* = nullptr
#endif
)
{
return internal::read_PLY_BGL(is, g, np);
return internal::read_PLY_BGL(is, g, comments, np);
}
template <typename Graph,
typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY(std::istream& is,
Graph& g,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<!internal::is_Point_set_or_Range_or_Iterator<Graph>::value>* = nullptr
#endif
)
{
std::string unused_comments;
return internal::read_PLY_BGL(is, g, unused_comments, np);
}
/*!
\ingroup PkgBGLIoFuncsPLY
@ -153,6 +174,7 @@ bool read_PLY(std::istream& is,
\param fname the name of the input file
\param g the graph to be built from the input data
\param comments a string included line by line in the header of the PLY stream (each line will be precedeed by "comment" )
\param np optional \ref bgl_namedparameters "Named Parameters" described below
\cgalNamedParamsBegin
@ -207,6 +229,7 @@ template <typename Graph,
typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY(const std::string& fname,
Graph& g,
std::string& comments,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<!internal::is_Point_set_or_Range_or_Iterator<Graph>::value>* = nullptr
@ -218,16 +241,29 @@ bool read_PLY(const std::string& fname,
{
std::ifstream is(fname, std::ios::binary);
CGAL::IO::set_mode(is, CGAL::IO::BINARY);
return internal::read_PLY_BGL(is, g, np);
return read_PLY(is, g, comments, np);
}
else
{
std::ifstream is(fname);
CGAL::IO::set_mode(is, CGAL::IO::ASCII);
return internal::read_PLY_BGL(is, g, np);
return read_PLY(is, g, comments, np);
}
}
template <typename Graph,
typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY(const std::string& fname,
Graph& g,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<!internal::is_Point_set_or_Range_or_Iterator<Graph>::value>* = nullptr
#endif
)
{
std::string unused_comment;
return read_PLY(fname, g, unused_comment, np);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
// Write
@ -259,6 +295,15 @@ bool read_PLY(const std::string& fname,
must be available in `Graph`.}
\cgalParamNEnd
\cgalParamNBegin{vertex_normal_map}
\cgalParamDescription{a property map associating normals to the vertices of `g`}
\cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<Graph>::%vertex_descriptor`
as key type and `%Vector_3` as value type}
\cgalParamDefault{`boost::get(CGAL::vertex_point, g)`}
\cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t`
must be available in `Graph`.}
\cgalParamNEnd
\cgalParamNBegin{vertex_index_map}
\cgalParamDescription{a property map associating to each vertex of `graph` a unique index}
\cgalParamType{a class model of `WritablePropertyMap` with `boost::graph_traits<Graph>::%vertex_descriptor`
@ -326,6 +371,8 @@ bool write_PLY(std::ostream& os,
bool has_vcolor = !is_default_parameter<CGAL_NP_CLASS, internal_np::vertex_color_map_t>::value;
bool has_fcolor = !is_default_parameter<CGAL_NP_CLASS, internal_np::face_color_map_t>::value;
constexpr bool has_vnormal = !is_default_parameter<CGAL_NP_CLASS, internal_np::vertex_normal_map_t>::value;
VIMap vim = CGAL::get_initialized_vertex_index_map(g, np);
Vpm vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_const_property_map(boost::vertex_point, g));
@ -351,8 +398,20 @@ bool write_PLY(std::ostream& os,
}
}
os << "element vertex " << vertices(g).size() << std::endl;
if constexpr (std::is_same<typename Kernel_traits<Point_3>::Kernel::FT, float>::value)
{
internal::output_property_header(os, make_ply_point_writer (CGAL::Identity_property_map<Point_3>()));
}
else
{
typedef typename Kernel_traits<Point_3>::Kernel K;
typedef decltype(std::declval<CGAL::Cartesian_converter<K, Epick> >().operator()(std::declval<Point_3>())) Target_point;
auto fvpm = CGAL::make_cartesian_converter_property_map<Target_point>(vpm);
internal::output_property_header(os, make_ply_point_writer (fvpm));
}
//if vcm is not default add v:color property
if(has_vcolor)
{
@ -362,10 +421,30 @@ bool write_PLY(std::ostream& os,
<< "property uchar alpha" << std::endl;
}
if constexpr (has_vnormal)
{
auto vnm = get_parameter(np, internal_np::vertex_normal_map);
typedef decltype(vnm) Normal_map;
typedef typename Normal_map::value_type Vector_3;
typedef typename Kernel_traits<Vector_3>::Kernel K;
typedef typename K::FT FT;
if constexpr (std::is_same<FT, float>::value)
{
internal::output_property_header(os, make_ply_normal_writer (CGAL::Identity_property_map<Vector_3>()));
}
else
{
typedef decltype(std::declval<CGAL::Cartesian_converter<K, Epick> >().operator()(std::declval<Vector_3>())) Target_vector;
auto fvnm = CGAL::make_cartesian_converter_property_map<Target_vector>(vnm);
internal::output_property_header(os, make_ply_normal_writer (fvnm));
}
}
os << "element face " << faces(g).size() << std::endl;
internal::output_property_header(
os, std::make_pair(CGAL::Identity_property_map<std::vector<std::size_t> >(),
PLY_property<std::vector<int> >("vertex_indices")));
//if fcm is not default add f:color property
if(has_fcolor)
{
@ -378,8 +457,42 @@ bool write_PLY(std::ostream& os,
for(vertex_descriptor vd : vertices(g))
{
const Point_3& p = get(vpm, vd);
if constexpr (std::is_same<typename Kernel_traits<Point_3>::Kernel::FT, float>::value)
{
decltype(auto) p = get(vpm, vd);
internal::output_properties(os, &p, make_ply_point_writer (CGAL::Identity_property_map<Point_3>()));
}
else
{
typedef typename Kernel_traits<Point_3>::Kernel K;
typedef CGAL::cpp20::remove_cvref_t<decltype(std::declval<CGAL::Cartesian_converter<K, Epick> >().operator()(std::declval<Point_3>()))> Target_point;
CGAL::Cartesian_converter_property_map<Target_point, Vpm> fvpm = CGAL::make_cartesian_converter_property_map<Target_point>(vpm);
decltype(auto) fp = get(fvpm, vd);
internal::output_properties(os, &fp, make_ply_point_writer (CGAL::Identity_property_map<Target_point>()));
}
std::cout << "using generic writer" << std::endl;
if constexpr (!parameters::is_default_parameter<CGAL_NP_CLASS, internal_np::vertex_normal_map_t>::value)
{
auto vnm = get_parameter(np, internal_np::vertex_normal_map);
typedef decltype(vnm) Normal_map;
typedef typename Normal_map::value_type Vector_3;
if constexpr (std::is_same<typename Kernel_traits<Vector_3>::Kernel::FT, float>::value)
{
decltype(auto) vec = get(vnm,vd);
internal::output_properties(os, &vec, make_ply_normal_writer (CGAL::Identity_property_map<Vector_3>()));
}
else
{
typedef typename Kernel_traits<Vector_3>::Kernel K;
typedef CGAL::cpp20::remove_cvref_t<decltype(std::declval<CGAL::Cartesian_converter<K, Epick> >().operator()(std::declval<Vector_3>()))> Target_vector;
auto fvnm = CGAL::make_cartesian_converter_property_map<Target_vector>(vnm);
decltype(auto) fvec = get(fvnm, vd);
internal::output_properties(os, &fvec, make_ply_normal_writer (CGAL::Identity_property_map<Target_vector>()));
}
}
if(has_vcolor)
{
const CGAL::IO::Color& c = get(vcm, vd);
@ -455,6 +568,15 @@ bool write_PLY(std::ostream& os, const Graph& g, const CGAL_NP_CLASS& np = param
must be available in `Graph`.}
\cgalParamNEnd
\cgalParamNBegin{vertex_normal_map}
\cgalParamDescription{a property map associating normals to the vertices of `g`}
\cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<Graph>::%vertex_descriptor`
as key type and `%Vector_3` as value type}
\cgalParamDefault{`boost::get(CGAL::vertex_point, g)`}
\cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t`
must be available in `Graph`.}
\cgalParamNEnd
\cgalParamNBegin{vertex_index_map}
\cgalParamDescription{a property map associating to each vertex of `graph` a unique index between `0` and `num_vertices(graph) - 1`}
\cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits<Graph>::%vertex_descriptor`

View File

@ -1,13 +1,19 @@
Algebraic_foundations
Arithmetic_kernel
BGL
Cartesian_kernel
Circulator
Distance_2
Distance_3
Filtered_kernel
Homogeneous_kernel
Hash_map
Installation
Intersections_2
Intersections_3
Interval_support
Kernel_23
Kernel_d
Modular_arithmetic
Number_types
Profiling_tools
@ -15,3 +21,4 @@ Property_map
Random_numbers
STL_Extension
Stream_support
CGAL_Core

View File

@ -7,6 +7,7 @@
#include <CGAL/Three/CGAL_Lab_io_plugin_interface.h>
#include <CGAL/Surface_mesh/IO/PLY.h>
#include <CGAL/Three/Three.h>
#include <CGAL/use.h>
#include <QInputDialog>
#include <QApplication>
@ -132,8 +133,9 @@ load(QFileInfo fileinfo, bool& ok, bool add_to_scene) {
std::vector<CGAL::IO::Color> fcolors;
std::vector<CGAL::IO::Color> vcolors;
if (!(CGAL::IO::read_PLY (in, points, polygons, fcolors, vcolors)))
if (!(CGAL::IO::read_PLY (in, points, polygons, comments, fcolors, vcolors)))
{
CGAL_USE(comments);
QApplication::restoreOverrideCursor();
ok = false;
return QList<Scene_item*>();

View File

@ -708,7 +708,6 @@ struct Cartesian_converter_property_map
{
return CGAL::Cartesian_converter<K1, K2>()(get(pm.vpm, k));
}
friend void put(Cartesian_converter_property_map<GeomObject, Vpm>& pm, const key_type& k, const value_type& v)
{
put(pm.vpm, k, CGAL::Cartesian_converter<K2, K1>()(v));

View File

@ -94,25 +94,25 @@ bool is_pretty(std::ios& s);
\subsection IOstreamInput Input Operator
\cgal defines input operators for classes that are derived
from the class `istream`. This allows to read from istreams
from the class `std::istream`. This allows to read from istreams
as `std::cin`, as well as from `std::istringstream` and `std::ifstream`.
The input operator is defined for all classes in the \cgal `Kernel`.
Let `is` be an input stream.
\code{.cpp}
// Extracts object `c` from the stream `is`. Returns `is`.
istream& operator>>(istream& is, Class c);
istream& operator>>(istream& is, Class& c);
\endcode
\code{.cpp}
#include <iostream>
#include <fstream>
#include <CGAL/Cartesian.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Segment_2.h>
typedef CGAL::Point_2< CGAL::Cartesian<double> > Point;
typedef CGAL::Segment_2< CGAL::Cartesian<double> > Segment;
typedef CGAL::Point_2< CGAL::Simple_cartesian<double> > Point;
typedef CGAL::Segment_2< CGAL::Simple_cartesian<double> > Segment;
int
main()
@ -123,7 +123,7 @@ main()
CGAL::IO::set_ascii_mode(std::cin);
std::cin >> p >> q;
std::ifstream f("data.txt");
std::ifstream f("data.txt", std::ios::binary);
CGAL::IO::set_binary_mode(f);
f >> s >> p;
@ -150,11 +150,11 @@ ostream& operator<<(ostream& os, Class c);
#include <iostream>
#include <fstream>
#include <CGAL/Cartesian.h>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Segment_2.h>
typedef CGAL::Point_2< CGAL::Cartesian<double> > Point;
typedef CGAL::Segment_2< CGAL::Cartesian<double> > Segment;
typedef CGAL::Point_2< CGAL::Simple_cartesian<double> > Point;
typedef CGAL::Segment_2< CGAL::Simple_cartesian<double> > Segment;
int main()
{

View File

@ -47,6 +47,7 @@ template <class PointRange, class PolygonRange, class ColorOutputIterator, class
bool read_PLY(std::istream& is,
PointRange& points,
PolygonRange& polygons,
std::string& comments,
HEdgesOutputIterator hedges_out,
ColorOutputIterator fc_out,
ColorOutputIterator vc_out,
@ -72,6 +73,8 @@ bool read_PLY(std::istream& is,
return false;
}
comments = reader.comments();
for(std::size_t i=0; i<reader.number_of_elements(); ++i)
{
internal::PLY_element& element = reader.element(i);
@ -228,6 +231,7 @@ template <class PointRange, class PolygonRange, class ColorRange, class HEdgesRa
bool read_PLY(std::istream& is,
PointRange& points,
PolygonRange& polygons,
std::string& comments,
HEdgesRange& hedges,
ColorRange& fcolors,
ColorRange& vcolors,
@ -235,7 +239,7 @@ bool read_PLY(std::istream& is,
const bool verbose = false,
std::enable_if_t<internal::is_Range<PolygonRange>::value>* = nullptr)
{
return internal::read_PLY(is, points, polygons,
return internal::read_PLY(is, points, polygons, comments,
std::back_inserter(hedges),
std::back_inserter(fcolors),
std::back_inserter(vcolors),
@ -247,6 +251,7 @@ template <class PointRange, class PolygonRange, class ColorRange>
bool read_PLY(std::istream& is,
PointRange& points,
PolygonRange& polygons,
std::string& comments,
ColorRange& fcolors,
ColorRange& vcolors,
const bool verbose = false)
@ -254,7 +259,7 @@ bool read_PLY(std::istream& is,
std::vector<std::pair<unsigned int, unsigned int> > dummy_pui;
std::vector<std::pair<float, float> > dummy_pf;
return internal::read_PLY(is, points, polygons,
return internal::read_PLY(is, points, polygons, comments,
std::back_inserter(dummy_pui),
std::back_inserter(fcolors),
std::back_inserter(vcolors),
@ -284,7 +289,8 @@ bool read_PLY(std::istream& is,
* \param is the input stream
* \param points points of the soup of polygons
* \param polygons a range of polygons. Each element in it describes a polygon
* using the indices of the points in `points`.
* using the indices of the points in `points`
* \param comments a string that will contain all the comments found in the PLY file
* \param np optional \ref bgl_namedparameters "Named Parameters" described below
*
* \cgalNamedParamsBegin
@ -307,6 +313,7 @@ template <class PointRange, class PolygonRange, typename CGAL_NP_TEMPLATE_PARAME
bool read_PLY(std::istream& is,
PointRange& points,
PolygonRange& polygons,
std::string& comments,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<internal::is_Range<PolygonRange>::value>* = nullptr
@ -319,7 +326,7 @@ bool read_PLY(std::istream& is,
std::vector<std::pair<unsigned int, unsigned int> > dummy_pui;
std::vector<std::pair<float, float> > dummy_pf;
return internal::read_PLY(is, points, polygons, std::back_inserter(dummy_pui),
return internal::read_PLY(is, points, polygons, comments, std::back_inserter(dummy_pui),
choose_parameter(get_parameter(np, internal_np::face_color_output_iterator),
CGAL::Emptyset_iterator()),
choose_parameter(get_parameter(np, internal_np::vertex_color_output_iterator),
@ -328,6 +335,20 @@ bool read_PLY(std::istream& is,
choose_parameter(get_parameter(np, internal_np::verbose), true));
}
template <class PointRange, class PolygonRange, typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY(std::istream& is,
PointRange& points,
PolygonRange& polygons,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<internal::is_Range<PolygonRange>::value>* = nullptr
#endif
)
{
std::string unused_comments;
return read_PLY(is, points, polygons, unused_comments, np);
}
/*!
* \ingroup PkgStreamSupportIoFuncsPLY
*
@ -344,7 +365,8 @@ bool read_PLY(std::istream& is,
* \param fname the path to the input file
* \param points points of the soup of polygons
* \param polygons a range of polygons. Each element in it describes a polygon
* using the indices of the points in `points`.
* using the indices of the points in `points`
* \param comments a string that will contain all the comments found in the PLY file
* \param np optional \ref bgl_namedparameters "Named Parameters" described below
*
* \cgalNamedParamsBegin
@ -367,6 +389,7 @@ template <typename PointRange, typename PolygonRange, typename CGAL_NP_TEMPLATE_
bool read_PLY(const std::string& fname,
PointRange& points,
PolygonRange& polygons,
std::string& comments,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<internal::is_Range<PolygonRange>::value>* = nullptr
@ -378,16 +401,30 @@ bool read_PLY(const std::string& fname,
{
std::ifstream is(fname, std::ios::binary);
CGAL::IO::set_mode(is, CGAL::IO::BINARY);
return read_PLY(is, points, polygons, np);
return read_PLY(is, points, polygons, comments, np);
}
else
{
std::ifstream is(fname);
CGAL::IO::set_mode(is, CGAL::IO::ASCII);
return read_PLY(is, points, polygons, np);
return read_PLY(is, points, polygons, comments, np);
}
}
template <typename PointRange, typename PolygonRange, typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY(const std::string& fname,
PointRange& points,
PolygonRange& polygons,
const CGAL_NP_CLASS& np = parameters::default_values()
#ifndef DOXYGEN_RUNNING
, std::enable_if_t<internal::is_Range<PolygonRange>::value>* = nullptr
#endif
)
{
std::string unused_comments;
return read_PLY(fname, points, polygons, unused_comments, np);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
// Write

View File

@ -12,6 +12,7 @@
#define CGAL_IO_PLY_PLY_WRITER_H
#include <CGAL/IO/io.h>
#include <CGAL/number_utils.h>
namespace CGAL {
namespace IO {

View File

@ -317,7 +317,7 @@ bool write_STL(std::ostream& os,
const Point& q = get(point_map, points[face[1]]);
const Point& r = get(point_map, points[face[2]]);
const Vector_3 n = collinear(p,q,r) ? Vector_3(1,0,0) : unit_normal(p,q,r);
const Vector_3 n = internal::construct_normal_of_STL_face(p, q, r, k);
const float coords[12] = { static_cast<float>(n.x()), static_cast<float>(n.y()), static_cast<float>(n.z()),
static_cast<float>(p.x()), static_cast<float>(p.y()), static_cast<float>(p.z()),

View File

@ -91,7 +91,7 @@ enum Mode {ASCII = 0, PRETTY, BINARY};
\brief Inserts object `c` in the stream `os`. Returns `os`.
\cgal defines output operators for classes that are derived
from the class `ostream`. This allows to write to ostreams
as `cout` or `cerr`, as well as to `std::ostringstream`
as `std::cout` or `std::cerr`, as well as to `std::ostringstream`
and `std::ofstream`.
The output operator is defined for all classes in the \cgal `Kernel` and for the class `Color` as well.
@ -233,8 +233,8 @@ public:
\relates Output_rep
\brief stream output of the \c Output_rep calls its \c operator().
\cgal defines output operators for classes that are derived from the class `ostream`.
This enables to write to ostreams as `cout` or `cerr`, as well as to `std::ostringstream`
\cgal defines output operators for classes that are derived from the class `std::ostream`.
This enables to write to output streams as `std::cout` or `std::cerr`, as well as to `std::ostringstream`
and `std::ofstream`.
The output operator is defined for all classes in the \cgal `Kernel` and for the class `Color` as well.
*/
@ -455,7 +455,7 @@ public:
\brief stream input to the \c Input_rep calls its \c operator().
\brief \cgal defines input operators for classes that are derived
from the class `istream`. This allows to read from istreams
from the class `std::istream`. This allows to read from input streams
as `std::cin`, as well as from `std::istringstream` and `std::ifstream`.
The input operator is defined for all classes in the \cgal `Kernel`.
*/

View File

@ -0,0 +1,169 @@
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Exact_rational.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/boost/graph/helpers.h>
#include <CGAL/boost/graph/IO/PLY.h>
#include <CGAL/boost/graph/IO/polygon_mesh_io.h>
#include <CGAL/Surface_mesh/IO/PLY.h>
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
#include <CGAL/boost/graph/generators.h>
// typedef CGAL::Exact_predicates_exact_constructions_kernel Epeck;
typedef CGAL::Simple_cartesian<CGAL::Exact_rational> Epeck;
typedef CGAL::Surface_mesh<Epeck::Point_3> Surface_mesh;
typedef boost::graph_traits<Surface_mesh>::vertex_descriptor vertex_descriptor;
typedef CGAL::Polyhedron_3<Epeck> Polyhedron_3;
typedef boost::graph_traits<Polyhedron_3>::vertex_descriptor pvertex_descriptor;
typedef Epeck::Point_3 Point_3;
typedef Epeck::Vector_3 Vector_3;
typedef CGAL::Simple_cartesian<double> SC;
typedef Surface_mesh::template Property_map<vertex_descriptor, Vector_3> VNMap;
typedef CGAL::Cartesian_converter_property_map< SC::Vector_3, VNMap> ScSmVertexNormalMap;
typedef std::map<pvertex_descriptor,Vector_3> PolyhedronVN;
typedef boost::associative_property_map<PolyhedronVN> PolyhedronVNMap;
typedef CGAL::Cartesian_converter_property_map< SC::Vector_3, PolyhedronVNMap> ScPolyhedronVertexNormalMap;
typedef boost::property_map<Surface_mesh, CGAL::vertex_point_t >::type SmVertexPointMap;
typedef CGAL::Cartesian_converter_property_map< SC::Point_3, SmVertexPointMap> ScSmVertexPointMap;
typedef boost::property_map<Polyhedron_3, CGAL::vertex_point_t >::type PolyhedronVertexPointMap;
typedef CGAL::Cartesian_converter_property_map< SC::Point_3, PolyhedronVertexPointMap> ScPolyhedronVertexPointMap;
int main()
{
Point_3 p(0.3, 0.3454, 0), q(0.6694, 0,0), r(0,0,0);
Surface_mesh sm;
Polyhedron_3 po;
CGAL::make_triangle(p,q,r,sm);
CGAL::make_triangle(p,q,r,po);
VNMap smvnpm = sm.template add_property_map<vertex_descriptor, Vector_3>("v:normal", CGAL::NULL_VECTOR).first;
CGAL::Polygon_mesh_processing::compute_vertex_normals(sm, smvnpm);
ScSmVertexNormalMap scsmvnpm(smvnpm);
SmVertexPointMap smvpm = get(CGAL::vertex_point_t(), sm);
ScSmVertexPointMap scsmvpm(smvpm);
PolyhedronVertexPointMap povpm = get(CGAL::vertex_point_t(), po);
ScPolyhedronVertexPointMap scpovpm(povpm);
// from <CGAL/boost/graph/IO/polygon_mesh_io.h>
// https://doc.cgal.org/latest/BGL/group__PkgBGLIoFuncsPLY.html#ga959dcd88ca979d3b6b0806d883a0247f
CGAL::IO::write_polygon_mesh("write_polygon_mesh_sm.ply", sm,
CGAL::parameters::stream_precision(17).use_binary_mode(true).vertex_point_map(scsmvpm).vertex_normal_map(scsmvnpm) );
{
Surface_mesh osm;
if(!CGAL::IO::read_polygon_mesh("write_polygon_mesh_sm.ply", osm))
{
std::cerr << "Error: failed to read 'write_polygon_mesh_sm.ply'" << std::endl;
return EXIT_FAILURE;
}else{
for(const std::string& s : osm.properties<vertex_descriptor>()){
std::cout << s << std::endl;
}
}
}
{
PolyhedronVN vn;
PolyhedronVNMap povnpm(vn);
CGAL::Polygon_mesh_processing::compute_vertex_normals(po, povnpm);
CGAL::IO::write_polygon_mesh("write_polygon_mesh_po.ply", po,
CGAL::parameters::stream_precision(17).use_binary_mode(true).vertex_point_map(povpm).vertex_normal_map(povnpm)) ;
if (!CGAL::IO::read_polygon_mesh("write_polygon_mesh_po.ply", po))
{
std::cerr << "Error: failed to read 'write_polygon_mesh_po.ply'" << std::endl;
return EXIT_FAILURE;
}
ScPolyhedronVertexNormalMap scpovnpm(povnpm);
CGAL::IO::write_polygon_mesh("write_polygon_mesh_po_sc.ply", po,
CGAL::parameters::stream_precision(17).use_binary_mode(true).vertex_point_map(scpovpm).vertex_normal_map(scpovnpm)) ;
if (!CGAL::IO::read_polygon_mesh("write_polygon_mesh_po_sc.ply", po))
{
std::cerr << "Error: failed to read 'write_polygon_mesh_po_sc.ply'" << std::endl;
return EXIT_FAILURE;
}
}
{
Surface_mesh osm;
if(!CGAL::IO::read_polygon_mesh("write_polygon_mesh_po.ply", osm))
{
std::cerr << "Error: failed to read 'write_polygon_mesh_po.ply'" << std::endl;
return EXIT_FAILURE;
}else{
for(const std::string& s : osm.properties<vertex_descriptor>()){
std::cout << s << std::endl;
}
}
}
// OK
// from #include <CGAL/boost/graph/IO/PLY.h>
// https://doc.cgal.org/latest/BGL/group__PkgBGLIoFuncsPLY.html#ga959dcd88ca979d3b6b0806d883a0247f
CGAL::IO::write_PLY("generic_write_PLY_sm.ply", sm, "generic write_PLY(Surface_mesh)",
CGAL::parameters::stream_precision(17).use_binary_mode(true).vertex_point_map(scsmvpm).vertex_normal_map(scsmvnpm));
{
Surface_mesh osm;
if(!CGAL::IO::read_polygon_mesh("generic_write_PLY_sm.ply", osm))
{
std::cerr << "Error: failed to read 'generic_write_PLY_sm.ply'" << std::endl;
return EXIT_FAILURE;
}else{
for(const std::string& s : osm.properties<vertex_descriptor>()){
std::cout << s << std::endl;
}
}
}
// ERROR this produces an invalid plyfile
CGAL::IO::write_PLY("generic_write_PLY_po.ply", po, "generic write_PLY(Polyhedron)",
CGAL::parameters::stream_precision(17).use_binary_mode(true).vertex_point_map(povpm));
{
Surface_mesh osm;
if(!CGAL::IO::read_polygon_mesh("generic_write_PLY_po.ply", osm))
{
std::cerr << "Error: failed to read 'generic_write_PLY_po.ply'" << std::endl;
return EXIT_FAILURE;
}else{
for(const std::string& s : osm.properties<vertex_descriptor>()){
std::cout << s << std::endl;
}
}
}
{
// OK
// from #include <CGAL/Surface_mesh/IO/PLY.h>
// https://doc.cgal.org/latest/Surface_mesh/group__PkgSurfaceMeshIOFuncPLY.html#ga50f0e9f2b293855d2c7f1a62939cbe8d
std::ofstream out("overloaded_write_PLY_sm.ply", std::ios::binary);
CGAL::IO::set_binary_mode(out);
CGAL::IO::write_PLY(out, sm, "overloaded_write_PLY(Surface_mesh)",CGAL::parameters::stream_precision(17).use_binary_mode(true)
.vertex_point_map(scsmvpm).vertex_normal_map(scsmvnpm) );
}
{
Surface_mesh osm;
if(!CGAL::IO::read_polygon_mesh("overloaded_write_PLY_sm.ply", osm))
{
std::cerr << "Error: failed to read 'overloaded_write_PLY_sm.ply'" << std::endl;
return EXIT_FAILURE;
}else{
for(const std::string& s : osm.properties<vertex_descriptor>()){
std::cout << s << std::endl;
}
}
}
return 0;
}

View File

@ -20,6 +20,7 @@
#include <CGAL/boost/graph/iterator.h>
#include <CGAL/Named_function_parameters.h>
#include <CGAL/boost/graph/named_params_helper.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/IO/PLY.h>
@ -420,44 +421,77 @@ public:
}
};
template <typename Point>
template <typename Point, typename NamedParameter>
bool fill_simplex_specific_header(std::ostream& os,
const Surface_mesh<Point>& sm,
std::vector<Abstract_property_printer<
typename Surface_mesh<Point>::Vertex_index>*>& printers,
const std::string& prop)
const std::string& prop,
const NamedParameter& np)
{
using CGAL::parameters::choose_parameter;
using CGAL::parameters::get_parameter;
typedef Surface_mesh<Point> SMesh;
typedef typename SMesh::Vertex_index VIndex;
typedef typename Kernel_traits<Point>::Kernel Kernel;
typedef typename Kernel::FT FT;
typedef typename Kernel::Vector_3 Vector;
typedef typename SMesh::template Property_map<VIndex, Point> Point_map;
typedef typename GetVertexPointMap<SMesh, NamedParameter>::const_type Point_map;
typedef typename SMesh::template Property_map<VIndex, Vector> Vector_map;
typedef typename SMesh::template Property_map<VIndex, Color> Vcolor_map;
Point_map vpm = choose_parameter(get_parameter(np, internal_np::vertex_point),
get_const_property_map(CGAL::vertex_point, sm));
if(prop == "v:connectivity" || prop == "v:removed")
return true;
if(prop == "v:point")
{
if(std::is_same<FT, float>::value)
if constexpr(std::is_same<FT, float>::value)
{
os << "property float x" << std::endl
<< "property float y" << std::endl
<< "property float z" << std::endl;
printers.push_back(new Property_printer<VIndex, Point_map>(vpm));
}
else
{
os << "property double x" << std::endl
<< "property double y" << std::endl
<< "property double z" << std::endl;
auto fvpm = CGAL::make_cartesian_converter_property_map<Epick::Point_3>(vpm);
printers.push_back(new Property_printer<VIndex, decltype(fvpm)>(fvpm));
}
printers.push_back(new Property_printer<VIndex, Point_map>(sm.points()));
return true;
}
if(prop == "v:normal")
if constexpr (!parameters::is_default_parameter<NamedParameter, internal_np::vertex_normal_map_t>::value)
{
auto vnm = get_parameter(np, internal_np::vertex_normal_map);
typedef decltype(vnm) Normal_map;
typedef typename Kernel_traits<typename Normal_map::value_type>::Kernel::FT FloatDouble;
if constexpr (std::is_same<FloatDouble, float>::value)
{
os << "property float nx" << std::endl
<< "property float ny" << std::endl
<< "property float nz" << std::endl;
printers.push_back(new Property_printer<VIndex, Normal_map>(vnm));
}
else
{
os << "property double nx" << std::endl
<< "property double ny" << std::endl
<< "property double nz" << std::endl;
auto fvnm = CGAL::make_cartesian_converter_property_map<Epick::Vector_3>(vnm);
printers.push_back(new Property_printer<VIndex, decltype(fvnm)>(fvnm));
}
return true;
}
else if(prop == "v:normal")
{
auto pmap = sm.template property_map<VIndex, Vector>(prop);
if(pmap.has_value())
@ -467,14 +501,16 @@ bool fill_simplex_specific_header(std::ostream& os,
os << "property float nx" << std::endl
<< "property float ny" << std::endl
<< "property float nz" << std::endl;
printers.push_back(new Property_printer<VIndex, Vector_map>(*pmap));
}
else
{
os << "property double nx" << std::endl
<< "property double ny" << std::endl
<< "property double nz" << std::endl;
auto fvnm = CGAL::make_cartesian_converter_property_map<Epick::Vector_3>(*pmap);
printers.push_back(new Property_printer<VIndex, decltype(fvnm)>(fvnm));
}
printers.push_back(new Property_printer<VIndex, Vector_map>(*pmap));
return true;
}
}
@ -497,12 +533,13 @@ bool fill_simplex_specific_header(std::ostream& os,
return false;
}
template <typename Point>
template <typename Point, typename NamedParameter>
bool fill_simplex_specific_header(std::ostream& os,
const Surface_mesh<Point>& sm,
std::vector<Abstract_property_printer<
typename Surface_mesh<Point>::Face_index>*>& printers,
const std::string& prop)
const std::string& prop,
const NamedParameter&)
{
typedef typename Surface_mesh<Point>::Face_index FIndex;
typedef CGAL::IO::Color Color;
@ -528,12 +565,13 @@ bool fill_simplex_specific_header(std::ostream& os,
return false;
}
template <typename Point>
template <typename Point, typename NamedParameter>
bool fill_simplex_specific_header(std::ostream&,
const Surface_mesh<Point>& ,
std::vector<Abstract_property_printer<
typename Surface_mesh<Point>::Edge_index>*>& ,
const std::string& prop)
const std::string& prop,
const NamedParameter&)
{
if(prop == "e:removed")
return true;
@ -541,12 +579,13 @@ bool fill_simplex_specific_header(std::ostream&,
return false;
}
template <typename Point>
template <typename Point, typename NamedParameter>
bool fill_simplex_specific_header(std::ostream&,
const Surface_mesh<Point>& ,
std::vector<Abstract_property_printer<
typename Surface_mesh<Point>::Halfedge_index>*>& ,
const std::string& prop)
const std::string& prop,
const NamedParameter&)
{
if(prop == "h:connectivity")
return true;
@ -733,7 +772,7 @@ void fill_header(std::ostream& os, const Surface_mesh<Point>& sm,
if (has_fcolor && prop[i] == "f:color")
continue;
if(fill_simplex_specific_header(os, sm, printers, prop[i]))
if(fill_simplex_specific_header(os, sm, printers, prop[i], np))
continue;
fill_header_impl<std::tuple_size<Type_tuple>::value>(Type_tuple(), type_strings, sm, prop[i], os, printers);
@ -773,21 +812,37 @@ void fill_header(std::ostream& os, const Surface_mesh<Point>& sm,
/// \param comments a string used to store the potential comments found in the PLY header.
/// Each line starting by "comment " in the header is appended to the `comments` string
/// (without the "comment " word).
/// \param verbose whether extra information is printed when an incident occurs during reading
/// \param np optional \ref bgl_namedparameters "Named Parameters" described below
///
/// \cgalNamedParamsBegin
/// \cgalParamNBegin{verbose}
/// \cgalParamDescription{whether extra information is printed when an incident occurs during reading}
/// \cgalParamType{Boolean}
/// \cgalParamDefault{`false`}
/// \cgalParamNEnd
/// \cgalNamedParamsEnd
///
/// \pre The data in the stream must represent a two-manifold. If this is not the case
/// the `failbit` of `is` is set and the mesh cleared.
///
/// \returns `true` if reading was successful, `false` otherwise.
///
template <typename P>
template <typename P, typename CGAL_NP_TEMPLATE_PARAMETERS>
bool read_PLY(std::istream& is,
Surface_mesh<P>& sm,
std::string& comments,
bool verbose = true)
const CGAL_NP_CLASS& np = parameters::default_values())
{
typedef typename Surface_mesh<P>::size_type size_type;
// not yet supported: this function only uses the Surface_mesh's internal pmaps
// to make it work, it'd be sufficient to modify Surface_mesh_filler's maps
// static_assert(CGAL::parameters::is_default_parameter<CGAL_NP_CLASS, internal_np::vertex_normal_map_t>::value);
// static_assert(CGAL::parameters::is_default_parameter<CGAL_NP_CLASS, internal_np::vertex_color_map_t>::value);
// static_assert(CGAL::parameters::is_default_parameter<CGAL_NP_CLASS, internal_np::face_color_map_t>::value);
const bool verbose = CGAL::parameters::choose_parameter(CGAL::parameters::get_parameter(np, internal_np::verbose), true);
if(!is.good())
{
if(verbose)
@ -874,6 +929,18 @@ bool read_PLY(std::istream& is,
/// \cond SKIP_IN_MANUAL
#ifndef CGAL_NO_DEPRECATED_CODE
// for backward compatibility
template <typename P>
bool read_PLY(std::istream& is,
Surface_mesh<P>& sm,
std::string& comments,
bool verbose)
{
return read_PLY(is, sm, comments, CGAL::parameters::verbose(verbose));
}
#endif
template <typename P>
bool read_PLY(std::istream& is, Surface_mesh<P>& sm)
{
@ -927,6 +994,7 @@ namespace IO {
/// \cgalNamedParamsEnd
///
/// \returns `true` if writing was successful, `false` otherwise.
template <typename P,
typename CGAL_NP_TEMPLATE_PARAMETERS>
bool write_PLY(std::ostream& os,

View File

@ -0,0 +1,126 @@
#include <CGAL/Surface_mesh/Surface_mesh.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <cstring>
#include <iostream>
#include <fstream>
typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef Kernel::Point_3 Point;
typedef CGAL::Surface_mesh<Point> SMesh;
typedef boost::graph_traits<SMesh>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<SMesh>::face_descriptor face_descriptor;
int main()
{
std::ifstream in(CGAL::data_file_path("meshes/colored_tetra.ply"));
SMesh mesh;
CGAL::IO::read_PLY(in, mesh);
std::cerr << "Read mesh with " << mesh.number_of_vertices() << " vertices and "
<< mesh.number_of_faces() << " faces" << std::endl;
std::cerr << "Properties associated with vertices:" << std::endl;
std::vector<std::string> properties = mesh.properties<SMesh::Vertex_index>();
for(std::size_t i = 0; i < properties.size(); ++ i)
std::cerr << " * " << properties[i] << std::endl;
std::cerr << "Properties associated with faces:" << std::endl;
properties = mesh.properties<SMesh::Face_index>();
for(std::size_t i = 0; i < properties.size(); ++ i)
std::cerr << " * " << properties[i] << std::endl;
mesh.add_property_map<SMesh::Edge_index, short>("id", 42);
mesh.add_property_map<SMesh::Halfedge_index, float>("u", 13.f);
mesh.add_property_map<SMesh::Halfedge_index, float>("v", 37.f);
// Append second mesh
std::ifstream in2("epeck_tetra.ply");
CGAL::IO::read_PLY(in2, mesh);
std::ofstream out("epeck_out.ply");
// CGAL::IO::set_binary_mode(out);
CGAL::IO::write_PLY(out, mesh);
// extra test for read/write of properties
mesh = SMesh();
in.close();
in.open(CGAL::data_file_path("meshes/colored_tetra.ply"));
CGAL::IO::read_PLY(in, mesh);
float fvalue=1001;
auto v_uvmap = mesh.add_property_map<SMesh::Vertex_index, std::vector<float>>("v:uv").first;
auto v_umap = mesh.add_property_map<SMesh::Vertex_index, float>("v:u").first;
auto v_vmap = mesh.add_property_map<SMesh::Vertex_index, float>("v:v").first;
for (SMesh::Vertex_index v : vertices(mesh))
{
v_uvmap[v]={fvalue, -fvalue};
v_umap[v]=fvalue;
v_vmap[v]=-fvalue;
++fvalue;
}
double dvalue=2001;
auto f_uvmap = mesh.add_property_map<SMesh::Face_index, std::vector<double>>("f:uv").first;
auto f_umap = mesh.add_property_map<SMesh::Face_index, double>("f:u").first;
auto f_vmap = mesh.add_property_map<SMesh::Face_index, double>("f:v").first;
for (SMesh::Face_index f : faces(mesh))
{
f_uvmap[f]={dvalue, -dvalue};
f_umap[f]=dvalue;
f_vmap[f]=-dvalue;
++dvalue;
}
out.close();
out.open("epeck_out_ascii.ply");
CGAL::IO::write_PLY(out, mesh);
out.close();
out.open("epeck_out_binary.ply", std::ios::binary);
CGAL::IO::set_binary_mode(out);
CGAL::IO::write_PLY(out, mesh);
out.close();
mesh.clear();
const std::array<std::string,2> fnames = {"epeck_out_ascii.ply", "epeck_out_binary.ply"};
for (std::string fn : fnames)
{
std::cout << "Reading " << fn << "\n";
in.close();
in.open(fn, std::ios::binary);
SMesh mesh_bis;
CGAL::IO::read_PLY(in, mesh_bis);
v_uvmap = mesh_bis.property_map<SMesh::Vertex_index, std::vector<float>>("v:uv").value();
v_umap = mesh_bis.property_map<SMesh::Vertex_index, float>("v:u").value();
v_vmap = mesh_bis.property_map<SMesh::Vertex_index, float>("v:v").value();
fvalue=1001;
for (SMesh::Vertex_index v : vertices(mesh_bis))
{
assert(v_uvmap[v].size()==2);
assert(v_uvmap[v][0]==fvalue);
assert(v_uvmap[v][1]==-fvalue);
assert(v_umap[v]==fvalue);
assert(v_vmap[v]==-fvalue);
++fvalue;
}
f_uvmap = mesh_bis.property_map<SMesh::Face_index, std::vector<double>>("f:uv").value();
f_umap = mesh_bis.property_map<SMesh::Face_index, double>("f:u").value();
f_vmap = mesh_bis.property_map<SMesh::Face_index, double>("f:v").value();
dvalue=2001;
for (SMesh::Face_index f : faces(mesh_bis))
{
assert(f_uvmap[f].size()==2);
assert(f_uvmap[f][0]==dvalue);
assert(f_uvmap[f][1]==-dvalue);
assert(f_umap[f]==dvalue);
assert(f_vmap[f]==-dvalue);
++dvalue;
}
}
return 0;
}

View File

@ -76,7 +76,7 @@ int main()
out.open("out_ascii.ply");
CGAL::IO::write_PLY(out, mesh);
out.close();
out.open("out_binary.ply");
out.open("out_binary.ply", std::ios::binary);
CGAL::IO::set_binary_mode(out);
CGAL::IO::write_PLY(out, mesh);
out.close();
@ -87,7 +87,7 @@ int main()
{
std::cout << "Reading " << fn << "\n";
in.close();
in.open(fn);
in.open(fn, std::ios::binary);
SMesh mesh_bis;
CGAL::IO::read_PLY(in, mesh_bis);