Fixes for 3MF and OFF

This commit is contained in:
Maxime Gimeno 2020-05-13 10:49:25 +02:00
parent 6122f27aac
commit d6812d9561
14 changed files with 134 additions and 94 deletions

View File

@ -136,20 +136,29 @@ bool write_OFF_BGL(std::ostream& os,
template <typename FaceGraph, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS> template <typename FaceGraph, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
bool write_OFF(std::ostream& os, const FaceGraph& g, const CGAL_BGL_NP_CLASS& np) bool write_OFF(std::ostream& os, const FaceGraph& g, const CGAL_BGL_NP_CLASS& np,
typename boost::disable_if<
typename boost::has_range_const_iterator<FaceGraph>::type
>::type* =0)
{ {
return IO::internal::write_OFF_BGL(os, g, np); return IO::internal::write_OFF_BGL(os, g, np);
} }
template <typename FaceGraph, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS> template <typename FaceGraph, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
bool write_OFF(const char* fname, const FaceGraph& g, const CGAL_BGL_NP_CLASS& np) bool write_OFF(const char* fname, const FaceGraph& g, const CGAL_BGL_NP_CLASS& np
,typename boost::disable_if<
typename boost::has_range_const_iterator<FaceGraph>::type
>::type* =0)
{ {
std::ofstream out(fname); std::ofstream out(fname);
return write_OFF(out, g, np); return write_OFF(out, g, np);
} }
template <typename FaceGraph, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS> template <typename FaceGraph, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
bool write_OFF(const std::string& fname, const FaceGraph& g, const CGAL_BGL_NP_CLASS& np) bool write_OFF(const std::string& fname, const FaceGraph& g, const CGAL_BGL_NP_CLASS& np
,typename boost::disable_if<
typename boost::has_range_const_iterator<FaceGraph>::type
>::type* =0)
{ {
return write_OFF(fname.c_str(), g, np); return write_OFF(fname.c_str(), g, np);
} }

View File

@ -125,4 +125,17 @@ if (VTK_FOUND)
message(STATUS "Tests that use VTK will not be compiled.") message(STATUS "Tests that use VTK will not be compiled.")
endif() endif()
endif() endif()
find_path(3MF_INCLUDE_DIR
NAMES Model/COM/NMR_DLLInterfaces.h
DOC "Path to lib3MF headers"
)
find_library(3MF_LIBRARIES NAMES 3MF DOC "Path to the lib3MF library")
if(3MF_LIBRARIES AND 3MF_INCLUDE_DIR AND EXISTS "${3MF_INCLUDE_DIR}/Model/COM/NMR_DLLInterfaces.h")
include_directories(${3MF_INCLUDE_DIR})
create_single_source_cgal_program( test_3mf_to_sm.cpp )
target_link_libraries(test_3mf_to_sm PRIVATE ${3MF_LIBRARIES})
add_definitions(-DCGAL_LINKED_WITH_3MF)
else()
message(STATUS "NOTICE : This program requires the lib3MF library, and will not be compiled.")
endif()
endif() #VTK_FOUND endif() #VTK_FOUND

View File

@ -99,7 +99,7 @@ int main(int argc, char** argv)
colors.clear(); colors.clear();
vertex_id_map.clear(); vertex_id_map.clear();
i = 0; i = 0;
vpm = get(boost::vertex_point, tube); vpm = get(boost::vertex_point, tube);
for(auto v : tube.vertices()) for(auto v : tube.vertices())
{ {
@ -127,8 +127,8 @@ int main(int argc, char** argv)
meshes.resize(2); meshes.resize(2);
meshes[0] = sphere; meshes[0] = sphere;
meshes[1] = tube; meshes[1] = tube;
// @fixme this should be in BGL
CGAL::write_triangle_meshes_to_3mf("meshes.3mf", meshes, names); //CGAL::write_triangle_meshes_to_3mf("meshes.3mf", meshes, names);
std::cout<<"OK."<<std::endl; std::cout<<"OK."<<std::endl;
#endif //CGAL_LINKED_WITH_3MF #endif //CGAL_LINKED_WITH_3MF

View File

@ -307,8 +307,11 @@ bool read_OFF(const std::string& fname,
template <typename OutputIterator> template <typename OutputIterator>
bool bool
read_OFF( read_OFF(
std::istream& stream, ///< input stream. std::istream& stream,
OutputIterator output) OutputIterator output,
typename std::enable_if<
CGAL::is_iterator<OutputIterator>::value
>::type* =0)
{ {
return read_OFF<typename value_type_traits<OutputIterator>::type> return read_OFF<typename value_type_traits<OutputIterator>::type>
(stream, output, CGAL::parameters::all_default()); (stream, output, CGAL::parameters::all_default());

View File

@ -294,8 +294,11 @@ bool read_XYZ(const std::string& fname,
template <typename OutputIterator> template <typename OutputIterator>
bool bool
read_XYZ( read_XYZ(
std::istream& stream, ///< input stream. std::istream& stream,
OutputIterator output) OutputIterator output,
typename std::enable_if<
CGAL::is_iterator<OutputIterator>::value
>::type* =0)
{ {
return read_XYZ<typename value_type_traits<OutputIterator>::type> return read_XYZ<typename value_type_traits<OutputIterator>::type>
(stream, output, CGAL::parameters::all_default()); (stream, output, CGAL::parameters::all_default());

View File

@ -19,6 +19,7 @@
#include <CGAL/point_set_processing_assertions.h> #include <CGAL/point_set_processing_assertions.h>
#include <CGAL/boost/graph/named_params_helper.h> #include <CGAL/boost/graph/named_params_helper.h>
#include <CGAL/Iterator_range.h> #include <CGAL/Iterator_range.h>
#include <boost/range/value_type.hpp>
#include <boost/utility/enable_if.hpp> #include <boost/utility/enable_if.hpp>
@ -115,7 +116,10 @@ bool
write_OFF( write_OFF(
const char* fname, const char* fname,
const PointRange& points, const PointRange& points,
const NamedParameters& np) const NamedParameters& np
,typename boost::disable_if<
typename boost::has_range_const_iterator<NamedParameters>::type
>::type* =0)
{ {
std::ofstream os(fname); std::ofstream os(fname);
return write_OFF(os, points, np); return write_OFF(os, points, np);
@ -127,7 +131,10 @@ bool
write_OFF( write_OFF(
const std::string& fname, const std::string& fname,
const PointRange& points, const PointRange& points,
const NamedParameters& np) const NamedParameters& np
,typename boost::disable_if<
typename boost::has_range_const_iterator<NamedParameters>::type
>::type* =0)
{ {
return write_OFF(fname.c_str(), points, np); return write_OFF(fname.c_str(), points, np);
} }
@ -160,7 +167,10 @@ template <typename PointRange>
bool bool
write_OFF( write_OFF(
std::ostream& stream, ///< output stream. std::ostream& stream, ///< output stream.
const PointRange& points) const PointRange& points,
typename boost::enable_if<
typename boost::has_range_const_iterator<PointRange>::type
>::type* =0)
{ {
return write_OFF return write_OFF
(stream, points, CGAL::Point_set_processing_3::parameters::all_default(points)); (stream, points, CGAL::Point_set_processing_3::parameters::all_default(points));
@ -170,7 +180,10 @@ template <typename PointRange>
bool bool
write_OFF( write_OFF(
const char* fname, const char* fname,
const PointRange& points) const PointRange& points,
typename boost::enable_if<
typename boost::has_range_const_iterator<PointRange>::type
>::type* =0)
{ {
std::ofstream os(fname); std::ofstream os(fname);
return write_OFF(os, points, CGAL::Point_set_processing_3::parameters::all_default(points)); return write_OFF(os, points, CGAL::Point_set_processing_3::parameters::all_default(points));
@ -180,7 +193,10 @@ template <typename PointRange>
bool bool
write_OFF( write_OFF(
const std::string& fname, const std::string& fname,
const PointRange& points) const PointRange& points
,typename boost::enable_if<
typename boost::has_range_const_iterator<PointRange>::type
>::type* =0)
{ {
return write_OFF(fname, points, CGAL::Point_set_processing_3::parameters::all_default(points)); return write_OFF(fname, points, CGAL::Point_set_processing_3::parameters::all_default(points));
} }

View File

@ -6,8 +6,7 @@
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h> #include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
#include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h> #include <CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h>
#include <CGAL/IO/read_3mf.h> #include <CGAL/IO/3MF.h>
#include <CGAL/IO/write_3mf.h>
#include <QFileDialog> #include <QFileDialog>
#include "Scene_surface_mesh_item.h" #include "Scene_surface_mesh_item.h"
@ -241,14 +240,14 @@ class Io_3mf_plugin:
color.set_rgb(c.red(), c.green(), c.blue()); color.set_rgb(c.red(), c.green(), c.blue());
} }
CGAL::write_mesh_to_model(points, triangles, colors, CGAL::IO::write_mesh_to_model(points, triangles, colors,
sm_item->name().toStdString(), &pMeshObject, pModel); sm_item->name().toStdString(), &pMeshObject, pModel);
} }
for(Scene_points_with_normal_item* pts_item : pts_items) for(Scene_points_with_normal_item* pts_item : pts_items)
{ {
QColor qc = pts_item->color(); QColor qc = pts_item->color();
CGAL::Color color(qc.red(), qc.green(), qc.blue()); CGAL::Color color(qc.red(), qc.green(), qc.blue());
CGAL::write_point_cloud_to_model(pts_item->point_set()->points(), color, CGAL::IO::write_point_cloud_to_model(pts_item->point_set()->points(), color,
pts_item->name().toStdString(), pts_item->name().toStdString(),
&pMeshObject, pModel); &pMeshObject, pModel);
} }
@ -259,12 +258,12 @@ class Io_3mf_plugin:
{ {
QColor qc = pol_item->color(); QColor qc = pol_item->color();
CGAL::Color color(qc.red(), qc.green(), qc.blue()); CGAL::Color color(qc.red(), qc.green(), qc.blue());
CGAL::write_polyline_to_model(*pol_it,color, CGAL::IO::write_polyline_to_model(*pol_it,color,
pol_item->name().toStdString(), pol_item->name().toStdString(),
&pMeshObject, pModel); &pMeshObject, pModel);
} }
} }
CGAL::export_model_to_file(fi.filePath().toUtf8().toStdString(), pModel); CGAL::IO::export_model_to_file(fi.filePath().toUtf8().toStdString(), pModel);
items = to_return; items = to_return;
return true; return true;
} }

View File

@ -67,11 +67,11 @@ if(has_cxx_rvalues LESS 0 OR has_cxx_variadic LESS 0)
message(STATUS "NOTICE : LAS/PLY IO examples require a C++11 compiler and will not be compiled.") message(STATUS "NOTICE : LAS/PLY IO examples require a C++11 compiler and will not be compiled.")
else() else()
set(needed_cxx_features cxx_rvalue_references cxx_variadic_templates) set(needed_cxx_features cxx_rvalue_references cxx_variadic_templates)
polyhedron_demo_plugin(ply_plugin PLY_io_plugin KEYWORDS IO PointSetProcessing Classification PMP) polyhedron_demo_plugin(ply_plugin PLY_io_plugin KEYWORDS IO PointSetProcessing Classification PMP)
target_link_libraries(ply_plugin PUBLIC scene_points_with_normal_item scene_polygon_soup_item scene_surface_mesh_item scene_textured_item) target_link_libraries(ply_plugin PUBLIC scene_points_with_normal_item scene_polygon_soup_item scene_surface_mesh_item scene_textured_item)
target_compile_features(ply_plugin PRIVATE ${needed_cxx_features}) target_compile_features(ply_plugin PRIVATE ${needed_cxx_features})
if (LASLIB_FOUND) if (LASLIB_FOUND)
polyhedron_demo_plugin(las_plugin LAS_io_plugin KEYWORDS IO PointSetProcessing Classification) polyhedron_demo_plugin(las_plugin LAS_io_plugin KEYWORDS IO PointSetProcessing Classification)
target_link_libraries(las_plugin PUBLIC scene_points_with_normal_item) target_link_libraries(las_plugin PUBLIC scene_points_with_normal_item)
@ -93,6 +93,7 @@ if(3MF_LIBRARIES AND 3MF_INCLUDE_DIR AND EXISTS "${3MF_INCLUDE_DIR}/Model/COM/NM
include_directories(${3MF_INCLUDE_DIR}) include_directories(${3MF_INCLUDE_DIR})
polyhedron_demo_plugin(3mf_io_plugin 3mf_io_plugin KEYWORDS IO PMP) polyhedron_demo_plugin(3mf_io_plugin 3mf_io_plugin KEYWORDS IO PMP)
target_link_libraries(3mf_io_plugin PRIVATE scene_surface_mesh_item scene_points_with_normal_item scene_polylines_item ${3MF_LIBRARIES}) target_link_libraries(3mf_io_plugin PRIVATE scene_surface_mesh_item scene_points_with_normal_item scene_polylines_item ${3MF_LIBRARIES})
add_definitions(-DCGAL_LINKED_WITH_3MF)
else() else()
message(STATUS "NOTICE : The 3mf_io_plugin requires the lib3MF library in a version < 2.0, and will not be compiled.") message(STATUS "NOTICE : The 3mf_io_plugin requires the lib3MF library in a version < 2.0, and will not be compiled.")
endif() endif()

View File

@ -16,6 +16,7 @@
#include <CGAL/Search_traits_adapter.h> #include <CGAL/Search_traits_adapter.h>
#include <CGAL/linear_least_squares_fitting_3.h> #include <CGAL/linear_least_squares_fitting_3.h>
#include <CGAL/algorithm.h> #include <CGAL/algorithm.h>
#include <CGAL/Point_set_3/IO.h>
#include <QObject> #include <QObject>
#include <QApplication> #include <QApplication>

View File

@ -32,51 +32,6 @@ namespace CGAL {
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
/// Read /// Read
/*!
* \ingroup IOstreamFunctions
*
* \brief extracts ranges of points and triangles from a 3mf file.
*
* \tparam PointRanges a model of the concepts `RandomAccessContainer` and
* `BackInsertionSequence` whose `value type` is
* a model of the concepts `RandomAccessContainer` and `BackInsertionSequence`
* whose `value type` is the point type.
* \tparam PolygonRanges a model of the concept `RandomAccessContainer` whose
* `value_type` is a model of the concept `RandomAccessContainer`
* whose `value_type` is a model of the concept `RandomAccessContainer` whose
* `value_type` is std::size_t.
* \tparam ColorRanges a model of the concepts `RandomAccessContainer` and
* `BackInsertionSequence` whose `value type` is
* a model of the concepts `RandomAccessContainer` and `BackInsertionSequence`
* whose `value type` is `CGAL::Color`.
*
* \param file_name the name of the 3mf file to read.
* \param all_points a `PointRanges` that will contain the points of the meshes in `file_name`.
* Each of these meshes will add a range of its points.
* \param all_polygons a `PolygonRanges` that will contain the triangles of the meshes in `file_name`.
* Each of these meshes will add a range of its triangles. A `triangle` of
* `all_polygons[i]` contains the indices of its points in `all_points[i]`.
* \param all_colors will contain the color of each triangle for each soup.
* \param names will contain the name of each mesh in `file_name` if any.
* If the i'th mesh has no name, it will be called "Unknown Mesh" in names.
*
* \return the number of soups read.
*/
template<typename PointRanges, typename PolygonRanges, typename ColorRanges>
int read_triangle_soups_from_3mf(const std::string& file_name,
PointRanges& all_points,
PolygonRanges& all_polygons,
ColorRanges& all_colors,
std::vector<std::string>& names)
{
typedef typename PointRanges::value_type PointRange;
typedef typename PolygonRanges::value_type PolygonRange;
typedef typename ColorRanges::value_type ColorRange;
return read_from_3mf<PointRanges,PolygonRanges,ColorRanges,
PointRange, PolygonRange, ColorRange>
(file_name, all_points, all_polygons, all_colors, names,
extract_soups<PointRange, PolygonRange, ColorRange>);
}
template<typename PointRanges, typename PolygonRanges, typename ColorRanges, template<typename PointRanges, typename PolygonRanges, typename ColorRanges,
typename PointRange, typename PolygonRange, typename ColorRange> typename PointRange, typename PolygonRange, typename ColorRange>
@ -413,6 +368,52 @@ int read_from_3mf(const std::string& file_name,
return all_points.size(); return all_points.size();
} }
/*!
* \ingroup IOstreamFunctions
*
* \brief extracts ranges of points and triangles from a 3mf file.
*
* \tparam PointRanges a model of the concepts `RandomAccessContainer` and
* `BackInsertionSequence` whose `value type` is
* a model of the concepts `RandomAccessContainer` and `BackInsertionSequence`
* whose `value type` is the point type.
* \tparam PolygonRanges a model of the concept `RandomAccessContainer` whose
* `value_type` is a model of the concept `RandomAccessContainer`
* whose `value_type` is a model of the concept `RandomAccessContainer` whose
* `value_type` is std::size_t.
* \tparam ColorRanges a model of the concepts `RandomAccessContainer` and
* `BackInsertionSequence` whose `value type` is
* a model of the concepts `RandomAccessContainer` and `BackInsertionSequence`
* whose `value type` is `CGAL::Color`.
*
* \param file_name the name of the 3mf file to read.
* \param all_points a `PointRanges` that will contain the points of the meshes in `file_name`.
* Each of these meshes will add a range of its points.
* \param all_polygons a `PolygonRanges` that will contain the triangles of the meshes in `file_name`.
* Each of these meshes will add a range of its triangles. A `triangle` of
* `all_polygons[i]` contains the indices of its points in `all_points[i]`.
* \param all_colors will contain the color of each triangle for each soup.
* \param names will contain the name of each mesh in `file_name` if any.
* If the i'th mesh has no name, it will be called "Unknown Mesh" in names.
*
* \return the number of soups read.
*/
template<typename PointRanges, typename PolygonRanges, typename ColorRanges>
int read_triangle_soups_from_3mf(const std::string& file_name,
PointRanges& all_points,
PolygonRanges& all_polygons,
ColorRanges& all_colors,
std::vector<std::string>& names)
{
typedef typename PointRanges::value_type PointRange;
typedef typename PolygonRanges::value_type PolygonRange;
typedef typename ColorRanges::value_type ColorRange;
return read_from_3mf<PointRanges,PolygonRanges,ColorRanges,
PointRange, PolygonRange, ColorRange>
(file_name, all_points, all_polygons, all_colors, names,
extract_soups<PointRange, PolygonRange, ColorRange>);
}
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
/// Write /// Write
@ -468,10 +469,10 @@ bool write_triangle_soups_to_3mf(const std::string& file_name,
} }
std::vector<CGAL::Color> colors(all_polygons[id].size()); std::vector<CGAL::Color> colors(all_polygons[id].size());
write_mesh_to_model(all_points[id], all_polygons[id], colors, name, &pMeshObject, pModel); IO::write_mesh_to_model(all_points[id], all_polygons[id], colors, name, &pMeshObject, pModel);
} }
return export_model_to_file(file_name, pModel); return IO::export_model_to_file(file_name, pModel);
} }
} // namespace CGAL } // namespace CGAL

View File

@ -24,6 +24,9 @@
#include <vector> #include <vector>
#include <string> #include <string>
/*
* \attention Only versions inferior to 2.0 of lib3mf are supported.
* */
namespace CGAL { namespace CGAL {
namespace tmf_internal { namespace tmf_internal {
@ -59,7 +62,7 @@ NMR::MODELMESHCOLOR_SRGB fnCreateColor(unsigned char red, unsigned char green,
} // namespace tmf_internal } // namespace tmf_internal
namespace 3MF { namespace IO {
bool add_build_item(NMR::PLib3MFModel * pModel, bool add_build_item(NMR::PLib3MFModel * pModel,
NMR::PLib3MFModelMeshObject* pMeshObject) NMR::PLib3MFModelMeshObject* pMeshObject)
@ -357,12 +360,8 @@ bool write_polyline_to_model(const PointRange& points,
return write_points(points, color, pc_name, pMeshObject, pModel); return write_points(points, color, pc_name, pMeshObject, pModel);
} }
} // namespace 3MF } // namespace IO
} // namespace CGAL } // namespace CGAL
*
* \attention Only versions inferior to 2.0 of lib3mf are supported.
*
* \attention Only versions inferior to 2.0 of lib3mf are supported.
#endif // CGAL_LINKED_WITH_3MF #endif // CGAL_LINKED_WITH_3MF

View File

@ -251,7 +251,10 @@ bool write_OFF(std::ostream& os,
template <typename PointRange, typename PolygonRange> template <typename PointRange, typename PolygonRange>
bool write_OFF(std::ostream& os, bool write_OFF(std::ostream& os,
const PointRange& points, const PointRange& points,
const PolygonRange& polygons) const PolygonRange& polygons
,typename boost::enable_if<
typename boost::has_range_const_iterator<PolygonRange>::type
>::type* =0)
{ {
return write_OFF(os, points, polygons, parameters::all_default()); return write_OFF(os, points, polygons, parameters::all_default());
} }
@ -277,7 +280,10 @@ bool write_OFF(const char* fname,
template <typename PointRange, typename PolygonRange> template <typename PointRange, typename PolygonRange>
bool write_OFF(const char* fname, bool write_OFF(const char* fname,
const PointRange& points, const PointRange& points,
const PolygonRange& polygons) const PolygonRange& polygons
,typename boost::enable_if<
typename boost::has_range_const_iterator<PolygonRange>::type
>::type* =0)
{ {
return write_OFF(fname, points, polygons, parameters::all_default()); return write_OFF(fname, points, polygons, parameters::all_default());
} }
@ -294,7 +300,10 @@ bool write_OFF(const std::string& fname,
template <typename PointRange, typename PolygonRange> template <typename PointRange, typename PolygonRange>
bool write_OFF(const std::string& fname, bool write_OFF(const std::string& fname,
const PointRange& points, const PointRange& points,
const PolygonRange& polygons) const PolygonRange& polygons
,typename boost::enable_if<
typename boost::has_range_const_iterator<PolygonRange>::type
>::type* =0)
{ {
return write_OFF(fname, points, polygons, parameters::all_default()); return write_OFF(fname, points, polygons, parameters::all_default());
} }

View File

@ -8,26 +8,12 @@ project( Stream_support_Tests )
find_package(CGAL QUIET) find_package(CGAL QUIET)
if ( CGAL_FOUND ) if ( CGAL_FOUND )
find_path(3MF_INCLUDE_DIR
NAMES Model/COM/NMR_DLLInterfaces.h
DOC "Path to lib3MF headers"
)
find_library(3MF_LIBRARIES NAMES 3MF DOC "Path to the lib3MF library")
# create a target per cppfile # create a target per cppfile
file(GLOB cppfiles RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) file(GLOB cppfiles RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
foreach(cppfile ${cppfiles}) foreach(cppfile ${cppfiles})
if ( "${cppfile}" STREQUAL "test_3mf_to_sm.cpp" )
if(3MF_LIBRARIES AND 3MF_INCLUDE_DIR AND EXISTS "${3MF_INCLUDE_DIR}/Model/COM/NMR_DLLInterfaces.h")
include_directories(${3MF_INCLUDE_DIR})
create_single_source_cgal_program( "${cppfile}" )
target_link_libraries(test_3mf_to_sm PRIVATE ${3MF_LIBRARIES})
else()
message(STATUS "NOTICE : This program requires the lib3MF library, and will not be compiled.")
endif()
else()
create_single_source_cgal_program( "${cppfile}" ) create_single_source_cgal_program( "${cppfile}" )
endif()
endforeach() endforeach()
else() else()