From c73f2b70983f57040d3ae78510ed062aa139fa73 Mon Sep 17 00:00:00 2001 From: Deepak Rangarajan Date: Tue, 22 Dec 2020 14:39:57 -0500 Subject: [PATCH 1/5] Add limited support for hull --- include/csg_types.hpp | 29 ++++++++++++++++++++- src/csg/cgal_helper_polyhedron.cpp | 34 ++++++++++++++++++++++++ src/csg/levelset_3d.cpp | 6 +++++ src/csg/parser.cpp | 42 +++++++++++++++++++++++++++++- src/csg/tests/CMakeLists.txt | 2 ++ src/csg/tests/levelset/hull.t.cpp | 23 ++++++++++++++++ src/csg/tests/parser/hull.t.cpp | 24 +++++++++++++++++ src/csg_cgal_helper.hpp | 5 ++++ 8 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/csg/tests/levelset/hull.t.cpp create mode 100644 src/csg/tests/parser/hull.t.cpp diff --git a/include/csg_types.hpp b/include/csg_types.hpp index 5d16bda..1d826bd 100644 --- a/include/csg_types.hpp +++ b/include/csg_types.hpp @@ -106,6 +106,7 @@ template struct Intersection; template struct Difference; struct LinearExtrude; struct RotateExtrude; +struct Hull; template struct TypeHelper; @@ -120,7 +121,7 @@ template <> struct TypeHelper { using Type = std::variant, Intersection, Difference, Mulmatrix, - LinearExtrude, RotateExtrude, Polyhedron>; + LinearExtrude, RotateExtrude, Polyhedron, Hull>; }; template struct Union { @@ -193,6 +194,32 @@ struct RotateExtrude { Union group; }; +struct Hull { +private: + std::shared_ptr m_cgal_polyhedron; + std::shared_ptr m_cgal_aabb_tree; + +public: + std::optional name; + std::tuple cube_size; + std::array cube_center; + double sphere_radius; + + void generate_polyhedron() { + m_cgal_polyhedron = cgal_helper::create_polyhedron_from_hull( + cube_size, cube_center, sphere_radius); + m_cgal_aabb_tree = cgal_helper::create_aabb_tree(m_cgal_polyhedron); + } + + const std::shared_ptr &cgal_polyhedron() const { + return m_cgal_polyhedron; + } + + const std::shared_ptr &cgal_aabb_tree() const { + return m_cgal_aabb_tree; + } +}; + struct Tree { Union top; }; diff --git a/src/csg/cgal_helper_polyhedron.cpp b/src/csg/cgal_helper_polyhedron.cpp index f4c79a1..ceb747c 100644 --- a/src/csg/cgal_helper_polyhedron.cpp +++ b/src/csg/cgal_helper_polyhedron.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace { @@ -96,4 +98,36 @@ bool inside(const std::shared_ptr &tree, double xx, double yy, return inside_tester(pt) == CGAL::ON_BOUNDED_SIDE; } +std::shared_ptr +create_polyhedron_from_hull(const std::tuple &cube_size, + const std::array &cube_center, + double sphere_radius) { + + auto p = std::make_shared(); + + std::list points; + CGAL::Random_points_in_sphere_3 s(sphere_radius); + std::copy_n(s, 250, std::back_inserter(points)); + + CGAL::Random_points_in_cube_3 c(1.0); + std::list points_c; + std::copy_n(c, 250, std::back_inserter(points_c)); + + auto [sa, sb, sc] = cube_size; + + const cgal_helper::CK::Aff_transformation_3 transf(sa/2,0.0,0.0,cube_center[0], + 0.0,sb/2,0.0,cube_center[1], + 0.0,0.0,sc/2,cube_center[2]); + std::transform(points_c.begin(),points_c.end(),points_c.begin(),transf); + + points.insert(points.end(), points_c.begin(), points_c.end()); + + // compute convex hull of non-collinear points + CGAL::convex_hull_3(points.begin(), points.end(), *p); + + CGAL_assertion(p->is_valid()); + + return p; +} + } // namespace cgal_helper diff --git a/src/csg/levelset_3d.cpp b/src/csg/levelset_3d.cpp index c2b5eee..75343dd 100644 --- a/src/csg/levelset_3d.cpp +++ b/src/csg/levelset_3d.cpp @@ -30,6 +30,7 @@ double signed_distance_3d(const Type3D &, double, double, double); double signed_distance_3d(const LinearExtrude &, double, double, double); double signed_distance_3d(const RotateExtrude &, double, double, double); double signed_distance_3d(const Polyhedron &, double, double, double); +double signed_distance_3d(const Hull &, double, double, double); double signed_distance_2d(const Union2D &, double, double); @@ -170,6 +171,11 @@ double signed_distance_3d(const Polyhedron &polyhedron, double xx, double yy, : -1.0; } +double signed_distance_3d(const Hull &hull, double xx, double yy, double zz) { + // TODO: support signed distance instead of -1.0/1.0 + return cgal_helper::inside(hull.cgal_aabb_tree(), xx, yy, zz) ? 1.0 : -1.0; +} + double signed_distance_3d(const Type3D &obj, double xx, double yy, double zz) { return std::visit( diff --git a/src/csg/parser.cpp b/src/csg/parser.cpp index fca91f2..3712d14 100644 --- a/src/csg/parser.cpp +++ b/src/csg/parser.cpp @@ -171,6 +171,9 @@ struct extrude_lin : seq, obj_list, R_BLK> {}; +struct hull : seq, L_FUN, R_FUN, + L_BLK, obj_list, R_BLK> {}; + struct render : seq, L_FUN, attr_list, R_FUN, opt, obj_list, @@ -184,7 +187,7 @@ struct colorgroup : seq, L_FUN, vector, R_FUN, template struct csg_obj : sor, mulmat, bool_exp, group, - extrude_lin, extrude_rot, render, colorgroup> {}; + extrude_lin, extrude_rot, hull, render, colorgroup> {}; template struct obj_list : plus, space>> {}; @@ -485,6 +488,43 @@ template <> struct action { } }; +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + csg::Hull hull; + + if (not st.current_3d_group.size() == 2) { + throw csg::Exception("action", "can be done only on two 3D objects"); + } + + auto &obj0 = st.current_3d_group[0]; + if(not std::holds_alternative(obj0)) { + throw csg::Exception("action", "first obj must be a 3D mulmat"); + } + auto &mm = std::get(obj0); + if(not mm.group.objs.size() == 1) { + throw csg::Exception("action", "first obj must be 3D mulmat of cube"); + } + if(not std::holds_alternative(mm.group.objs[0])) { + throw csg::Exception("action", "first obj must be 3D mulmat of cube"); + } + auto &cub = std::get(mm.group.objs[0]); + hull.cube_size = cub.size; + hull.cube_center = mm.translation; + + auto &obj1 = st.current_3d_group[1]; + if(not std::holds_alternative(obj1)) { + throw csg::Exception("action", "second obj must be a sphere"); + } + hull.sphere_radius = std::get(obj1).radius; + + hull.generate_polyhedron(); + + st.current_3d_objs.back().push_back(hull); + } +}; + std::optional get_name(AttrMap curr_attr) { if (curr_attr.count("$name")) { auto quoted_name = std::get(curr_attr["$name"]); diff --git a/src/csg/tests/CMakeLists.txt b/src/csg/tests/CMakeLists.txt index 94b3fb7..7450fc8 100644 --- a/src/csg/tests/CMakeLists.txt +++ b/src/csg/tests/CMakeLists.txt @@ -5,11 +5,13 @@ add_executable(unit_tests_csg EXCLUDE_FROM_ALL levelset/boolean.t.cpp levelset/extrude.t.cpp + levelset/hull.t.cpp levelset/internal_flow.t.cpp levelset/primitives.t.cpp levelset/transform.t.cpp parser/boolean.t.cpp parser/extrude.t.cpp + parser/hull.t.cpp parser/nest.cpp parser/other.t.cpp parser/primitives.t.cpp diff --git a/src/csg/tests/levelset/hull.t.cpp b/src/csg/tests/levelset/hull.t.cpp new file mode 100644 index 0000000..d10dbaf --- /dev/null +++ b/src/csg/tests/levelset/hull.t.cpp @@ -0,0 +1,23 @@ +#include "catch2/catch.hpp" + +#include +#include + +namespace { + +TEST_CASE("hull levelset", "[Levelset Extrude]") { + csg::Hull my_hull; + my_hull.cube_size = {1, 2, 3}; + my_hull.cube_center = {5, 5, 6}; + my_hull.sphere_radius = 1; + my_hull.generate_polyhedron(); + + auto my_tree = std::make_shared(); + my_tree->top.objs.push_back(my_hull); + csg::CsgIF my_levelset(my_tree); + + SECTION("Outside") { CHECK_FALSE(0 < my_levelset(5.6, 5.1, 5.1)); } + SECTION("Inside") { CHECK(0 < my_levelset(5.1, 5.1, 5.1)); } +} + +} // namespace diff --git a/src/csg/tests/parser/hull.t.cpp b/src/csg/tests/parser/hull.t.cpp new file mode 100644 index 0000000..bb286fb --- /dev/null +++ b/src/csg/tests/parser/hull.t.cpp @@ -0,0 +1,24 @@ +#include "catch2/catch.hpp" + +#include +#include +#include + +namespace { +TEST_CASE("hull", "[csg]") { + auto st = *csg::parse_csg(R"( +hull() { + multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 5], [0, 0, 0, 1]]) { + cube(size = [2, 2, 2], center = false); + } + sphere($fn = 0, $fa = 12, $fs = 2, r = 1); +} +)"); + auto hull = std::get(st.top.objs.back()); + auto [XX, YY, ZZ] = hull.cube_size; + CHECK(XX == 2); + CHECK(YY == 2); + CHECK(ZZ == 2); +} + +} diff --git a/src/csg_cgal_helper.hpp b/src/csg_cgal_helper.hpp index d437d51..7dbded3 100644 --- a/src/csg_cgal_helper.hpp +++ b/src/csg_cgal_helper.hpp @@ -41,6 +41,11 @@ bool inside(const Polygon &polygon, double xx, double yy); double abs_max_distance(const Polygon &ploygon, double xx, double yy, bool inside); +std::shared_ptr +create_polyhedron_from_hull(const std::tuple &cube_size, + const std::array &cube_center, + double sphere_radius); + } // namespace cgal_helper #endif -- GitLab From 8f1bd11e4b6a07baed8229ac946a38032c6ce871 Mon Sep 17 00:00:00 2001 From: Deepak Rangarajan Date: Wed, 30 Dec 2020 11:12:34 -0500 Subject: [PATCH 2/5] choose points on sphere, polish parser, more tests --- src/csg/cgal_helper_polyhedron.cpp | 19 ++++++---- src/csg/parser.cpp | 56 +++++++++++++++--------------- src/csg/tests/levelset/hull.t.cpp | 21 ++++++++--- src/csg/tests/parser/hull.t.cpp | 55 +++++++++++++++++++++++++---- 4 files changed, 105 insertions(+), 46 deletions(-) diff --git a/src/csg/cgal_helper_polyhedron.cpp b/src/csg/cgal_helper_polyhedron.cpp index ceb747c..f0a538e 100644 --- a/src/csg/cgal_helper_polyhedron.cpp +++ b/src/csg/cgal_helper_polyhedron.cpp @@ -103,22 +103,27 @@ create_polyhedron_from_hull(const std::tuple &cube_size, const std::array &cube_center, double sphere_radius) { + // This is currently chosen somewhat arbitrarily + // based on manual testing + // TODO: Is this sufficient? + const int NUM_SAMPLING_PTS = 250; + auto p = std::make_shared(); std::list points; - CGAL::Random_points_in_sphere_3 s(sphere_radius); - std::copy_n(s, 250, std::back_inserter(points)); + CGAL::Random_points_on_sphere_3 s(sphere_radius); + std::copy_n(s, NUM_SAMPLING_PTS, std::back_inserter(points)); CGAL::Random_points_in_cube_3 c(1.0); std::list points_c; - std::copy_n(c, 250, std::back_inserter(points_c)); + std::copy_n(c, NUM_SAMPLING_PTS, std::back_inserter(points_c)); auto [sa, sb, sc] = cube_size; - const cgal_helper::CK::Aff_transformation_3 transf(sa/2,0.0,0.0,cube_center[0], - 0.0,sb/2,0.0,cube_center[1], - 0.0,0.0,sc/2,cube_center[2]); - std::transform(points_c.begin(),points_c.end(),points_c.begin(),transf); + const cgal_helper::CK::Aff_transformation_3 transf( + sa / 2, 0.0, 0.0, cube_center[0], 0.0, sb / 2, 0.0, cube_center[1], 0.0, + 0.0, sc / 2, cube_center[2]); + std::transform(points_c.begin(), points_c.end(), points_c.begin(), transf); points.insert(points.end(), points_c.begin(), points_c.end()); diff --git a/src/csg/parser.cpp b/src/csg/parser.cpp index 3712d14..cbeb790 100644 --- a/src/csg/parser.cpp +++ b/src/csg/parser.cpp @@ -171,8 +171,9 @@ struct extrude_lin : seq, obj_list, R_BLK> {}; -struct hull : seq, L_FUN, R_FUN, - L_BLK, obj_list, R_BLK> {}; +struct hull + : seq, L_FUN, R_FUN, L_BLK, + obj_list, R_BLK> {}; struct render : seq, L_FUN, attr_list, R_FUN, @@ -494,34 +495,33 @@ template <> struct action { std::stringstream ss(in.string()); csg::Hull hull; - if (not st.current_3d_group.size() == 2) { - throw csg::Exception("action", "can be done only on two 3D objects"); - } - - auto &obj0 = st.current_3d_group[0]; - if(not std::holds_alternative(obj0)) { - throw csg::Exception("action", "first obj must be a 3D mulmat"); - } - auto &mm = std::get(obj0); - if(not mm.group.objs.size() == 1) { - throw csg::Exception("action", "first obj must be 3D mulmat of cube"); - } - if(not std::holds_alternative(mm.group.objs[0])) { - throw csg::Exception("action", "first obj must be 3D mulmat of cube"); - } - auto &cub = std::get(mm.group.objs[0]); - hull.cube_size = cub.size; - hull.cube_center = mm.translation; - - auto &obj1 = st.current_3d_group[1]; - if(not std::holds_alternative(obj1)) { - throw csg::Exception("action", "second obj must be a sphere"); + // Only a very limited hull is currently supported + if (st.current_3d_group.size() == 2) { + auto &obj0 = st.current_3d_group[0]; + auto &obj1 = st.current_3d_group[1]; + if (std::holds_alternative(obj0) and + std::holds_alternative(obj1)) { + auto &mm = std::get(obj0); + if (mm.group.objs.size() == 1 and + std::holds_alternative(mm.group.objs[0])) { + auto &cub = std::get(mm.group.objs[0]); + if (cub.center) { + hull.cube_size = cub.size; + hull.cube_center = mm.translation; + hull.sphere_radius = std::get(obj1).radius; + hull.generate_polyhedron(); + st.current_3d_objs.back().push_back(hull); + return; + } + } + } } - hull.sphere_radius = std::get(obj1).radius; - - hull.generate_polyhedron(); - st.current_3d_objs.back().push_back(hull); + // If the conditions were not met and throw exception + std::string except_src = "action"; + std::string except_msg = + "hull supported only on a multmatrix of a centered cube followed by a sphere"; + throw csg::Exception(except_src, except_msg); } }; diff --git a/src/csg/tests/levelset/hull.t.cpp b/src/csg/tests/levelset/hull.t.cpp index d10dbaf..8d08295 100644 --- a/src/csg/tests/levelset/hull.t.cpp +++ b/src/csg/tests/levelset/hull.t.cpp @@ -7,17 +7,28 @@ namespace { TEST_CASE("hull levelset", "[Levelset Extrude]") { csg::Hull my_hull; - my_hull.cube_size = {1, 2, 3}; - my_hull.cube_center = {5, 5, 6}; - my_hull.sphere_radius = 1; + + double XX = 1.0, YY = 2.0, ZZ = 3.0, Cx = 5.0, Cy = 5.0, Cz = 6.0, R = 1.0; + + my_hull.cube_size = {XX, YY, ZZ}; + my_hull.cube_center = {Cx, Cy, Cz}; + my_hull.sphere_radius = R; my_hull.generate_polyhedron(); auto my_tree = std::make_shared(); my_tree->top.objs.push_back(my_hull); csg::CsgIF my_levelset(my_tree); - SECTION("Outside") { CHECK_FALSE(0 < my_levelset(5.6, 5.1, 5.1)); } - SECTION("Inside") { CHECK(0 < my_levelset(5.1, 5.1, 5.1)); } + SECTION("Outside") { + CHECK_FALSE(0 < my_levelset(0, 0, Cz)); + CHECK_FALSE(0 < my_levelset(1.3 * R, 0, 0)); + CHECK_FALSE(0 < my_levelset(Cx + 1.1 * XX / 2, Cy, Cz)); + } + SECTION("Inside") { + CHECK(0 < my_levelset(0, 0, 0)); + CHECK(0 < my_levelset(Cx, Cy, Cz)); + CHECK(0 < my_levelset(1.1 * R, 0, 0)); + } } } // namespace diff --git a/src/csg/tests/parser/hull.t.cpp b/src/csg/tests/parser/hull.t.cpp index bb286fb..7e4100e 100644 --- a/src/csg/tests/parser/hull.t.cpp +++ b/src/csg/tests/parser/hull.t.cpp @@ -5,20 +5,63 @@ #include namespace { -TEST_CASE("hull", "[csg]") { +TEST_CASE("hull of translated, centered cube and a sphere", "[csg]") { auto st = *csg::parse_csg(R"( hull() { - multmatrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 5], [0, 0, 0, 1]]) { - cube(size = [2, 2, 2], center = false); + multmatrix([[1, 0, 0, 5], [0, 1, 0, 6], [0, 0, 1, 7], [0, 0, 0, 1]]) { + cube(size = [1, 2, 3], center = true); } - sphere($fn = 0, $fa = 12, $fs = 2, r = 1); + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); } )"); auto hull = std::get(st.top.objs.back()); auto [XX, YY, ZZ] = hull.cube_size; - CHECK(XX == 2); + auto [Cx, Cy, Cz] = hull.cube_center; + + CHECK(XX == 1); CHECK(YY == 2); - CHECK(ZZ == 2); + CHECK(ZZ == 3); + + CHECK(Cx == 5); + CHECK(Cy == 6); + CHECK(Cz == 7); + + CHECK(hull.sphere_radius == 4); +} + +TEST_CASE("hull with non-centered cube not supported", "[csg]") { + CHECK_THROWS_WITH(csg::parse_csg(R"( +hull() { + multmatrix([[1, 0, 0, 5], [0, 1, 0, 6], [0, 0, 1, 7], [0, 0, 0, 1]]) { + cube(size = [1, 2, 3], center = false); + } + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); +} +)"), + Catch::Contains("action")); } +TEST_CASE("hull with three elements not supported", "[csg]") { + CHECK_THROWS_WITH(csg::parse_csg(R"( +hull() { + multmatrix([[1, 0, 0, 5], [0, 1, 0, 6], [0, 0, 1, 7], [0, 0, 0, 1]]) { + cube(size = [1, 2, 3], center = true); + } + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); +} +)"), + Catch::Contains("action")); +} + +TEST_CASE("hull with other shape combos not supported", "[csg]") { + CHECK_THROWS_WITH(csg::parse_csg(R"( +hull() { + cube(size = [1, 2, 3], center = true); + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); } +)"), + Catch::Contains("action")); +} + +} // namespace -- GitLab From c6e0cb3d6a2c1333c45dee32b7a962858548d7a6 Mon Sep 17 00:00:00 2001 From: Deepak Rangarajan Date: Wed, 30 Dec 2020 11:19:25 -0500 Subject: [PATCH 3/5] polish --- include/csg_types.hpp | 2 ++ src/csg/cgal_helper_polyhedron.cpp | 2 +- src/csg/tests/levelset/hull.t.cpp | 2 +- src/csg/tests/parser/hull.t.cpp | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/csg_types.hpp b/include/csg_types.hpp index 1d826bd..305e29c 100644 --- a/include/csg_types.hpp +++ b/include/csg_types.hpp @@ -201,6 +201,8 @@ private: public: std::optional name; + + //TODO: Extend support beyond just sphere & cube std::tuple cube_size; std::array cube_center; double sphere_radius; diff --git a/src/csg/cgal_helper_polyhedron.cpp b/src/csg/cgal_helper_polyhedron.cpp index f0a538e..1fa87b3 100644 --- a/src/csg/cgal_helper_polyhedron.cpp +++ b/src/csg/cgal_helper_polyhedron.cpp @@ -104,7 +104,7 @@ create_polyhedron_from_hull(const std::tuple &cube_size, double sphere_radius) { // This is currently chosen somewhat arbitrarily - // based on manual testing + // based on manual testing and CGAL examples // TODO: Is this sufficient? const int NUM_SAMPLING_PTS = 250; diff --git a/src/csg/tests/levelset/hull.t.cpp b/src/csg/tests/levelset/hull.t.cpp index 8d08295..5467bd2 100644 --- a/src/csg/tests/levelset/hull.t.cpp +++ b/src/csg/tests/levelset/hull.t.cpp @@ -5,7 +5,7 @@ namespace { -TEST_CASE("hull levelset", "[Levelset Extrude]") { +TEST_CASE("hull of a sphere and tranlsated cube", "[Levelset Hull]") { csg::Hull my_hull; double XX = 1.0, YY = 2.0, ZZ = 3.0, Cx = 5.0, Cy = 5.0, Cz = 6.0, R = 1.0; diff --git a/src/csg/tests/parser/hull.t.cpp b/src/csg/tests/parser/hull.t.cpp index 7e4100e..615f01b 100644 --- a/src/csg/tests/parser/hull.t.cpp +++ b/src/csg/tests/parser/hull.t.cpp @@ -38,7 +38,7 @@ hull() { sphere($fn = 0, $fa = 12, $fs = 2, r = 4); } )"), - Catch::Contains("action")); + Catch::Contains("action")); } TEST_CASE("hull with three elements not supported", "[csg]") { @@ -51,7 +51,7 @@ hull() { sphere($fn = 0, $fa = 12, $fs = 2, r = 4); } )"), - Catch::Contains("action")); + Catch::Contains("action")); } TEST_CASE("hull with other shape combos not supported", "[csg]") { @@ -61,7 +61,7 @@ hull() { sphere($fn = 0, $fa = 12, $fs = 2, r = 4); } )"), - Catch::Contains("action")); + Catch::Contains("action")); } } // namespace -- GitLab From 399f748e769edc3f476b5976211d47dcbf46938b Mon Sep 17 00:00:00 2001 From: Mark Meredith Date: Wed, 30 Dec 2020 17:29:08 +0000 Subject: [PATCH 4/5] typo --- src/csg/tests/levelset/hull.t.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/csg/tests/levelset/hull.t.cpp b/src/csg/tests/levelset/hull.t.cpp index 5467bd2..7909907 100644 --- a/src/csg/tests/levelset/hull.t.cpp +++ b/src/csg/tests/levelset/hull.t.cpp @@ -5,7 +5,7 @@ namespace { -TEST_CASE("hull of a sphere and tranlsated cube", "[Levelset Hull]") { +TEST_CASE("hull of a sphere and translated cube", "[Levelset Hull]") { csg::Hull my_hull; double XX = 1.0, YY = 2.0, ZZ = 3.0, Cx = 5.0, Cy = 5.0, Cz = 6.0, R = 1.0; -- GitLab From 7e85c4d11cb9c36f8f46d7e41ab6a344a6ee3c77 Mon Sep 17 00:00:00 2001 From: Deepak Rangarajan Date: Wed, 30 Dec 2020 13:12:22 -0500 Subject: [PATCH 5/5] fix error message --- src/csg/parser.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/csg/parser.cpp b/src/csg/parser.cpp index cbeb790..e05e05f 100644 --- a/src/csg/parser.cpp +++ b/src/csg/parser.cpp @@ -520,7 +520,8 @@ template <> struct action { // If the conditions were not met and throw exception std::string except_src = "action"; std::string except_msg = - "hull supported only on a multmatrix of a centered cube followed by a sphere"; + "hull() support is limited to a multmatrix of a centered cube followed " + "by a sphere; added for the CLR support"; throw csg::Exception(except_src, except_msg); } }; -- GitLab