diff --git a/CHANGELOG.md b/CHANGELOG.md index e15869f2..01e70c47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Added `compas_cgal.straight_skeleton_2.create_interior_straight_skeleton`. +* Added `compas_cgal.straight_skeleton_2.create_interior_straight_skeleton_with_holes`. ### Changed diff --git a/docs/_images/cgal_straight_skeleton_2_holes.png b/docs/_images/cgal_straight_skeleton_2_holes.png new file mode 100644 index 00000000..b535e924 Binary files /dev/null and b/docs/_images/cgal_straight_skeleton_2_holes.png differ diff --git a/docs/api/compas_cgal.straight_skeleton_2.rst b/docs/api/compas_cgal.straight_skeleton_2.rst index 356ed845..3f376856 100644 --- a/docs/api/compas_cgal.straight_skeleton_2.rst +++ b/docs/api/compas_cgal.straight_skeleton_2.rst @@ -9,3 +9,4 @@ compas_cgal.straight_skeleton_2 :nosignatures: create_interior_straight_skeleton + create_interior_straight_skeleton_with_holes diff --git a/docs/examples/straight_skeleton_2.rst b/docs/examples/straight_skeleton_2.rst index 6da398d6..cc1a9bf7 100644 --- a/docs/examples/straight_skeleton_2.rst +++ b/docs/examples/straight_skeleton_2.rst @@ -9,3 +9,12 @@ .. literalinclude:: straight_skeleton_2.py :language: python + + +.. figure:: /_images/cgal_straight_skeleton_2_holes.png + :figclass: figure + :class: figure-img img-fluid + + +.. literalinclude:: straight_skeleton_2_holes.py + :language: python diff --git a/docs/examples/straight_skeleton_2_holes.py b/docs/examples/straight_skeleton_2_holes.py new file mode 100644 index 00000000..bc2cd655 --- /dev/null +++ b/docs/examples/straight_skeleton_2_holes.py @@ -0,0 +1,42 @@ +from compas.datastructures import Graph +from compas.geometry import Polygon +from compas_viewer import Viewer + +from compas_cgal.straight_skeleton_2 import create_interior_straight_skeleton_with_holes + +points = [ + (-1.91, 3.59, 0.0), + (-5.53, -5.22, 0.0), + (-0.39, -1.98, 0.0), + (2.98, -5.51, 0.0), + (4.83, -2.02, 0.0), + (9.70, -3.63, 0.0), + (12.23, 1.25, 0.0), + (3.42, 0.66, 0.0), + (2.92, 4.03, 0.0), + (-1.91, 3.59, 0.0), +] + +holes = [ + [(0.42, 0.88, 0.0), (1.1, -1.0, 0.0), (-1.97, -0.93, 0.0), (-1.25, 1.82, 0.0)], + [(4.25, -0.64, 0.0), (2.9, -3.03, 0.0), (2.12, -2.16, 0.0), (2.89, -0.36, 0.0)], + [(10.6, 0.29, 0.0), (9.48, -1.54, 0.0), (5.48, -1.26, 0.0), (5.98, -0.04, 0.0)], +] + + +polygon = Polygon(points) +holes = [Polygon(hole) for hole in holes] +lines = create_interior_straight_skeleton_with_holes(polygon, holes) +graph = Graph.from_lines(lines) + +# ============================================================================== +# Viz +# ============================================================================== + +viewer = Viewer(width=1600, height=900) +viewer.renderer_config.show_grid = False +viewer.scene.add(graph, edgecolor=(1.0, 0.0, 0.0)) +viewer.scene.add(polygon) +for hole in holes: + viewer.scene.add(hole) +viewer.show() diff --git a/src/compas_cgal/straight_skeleton_2.py b/src/compas_cgal/straight_skeleton_2.py index 2385647d..38b9eff5 100644 --- a/src/compas_cgal/straight_skeleton_2.py +++ b/src/compas_cgal/straight_skeleton_2.py @@ -30,3 +30,39 @@ def create_interior_straight_skeleton(points) -> PolylinesNumpy: raise ValueError("Please pass a polygon with a normal vector of [0, 0, 1].") V = np.asarray(points, dtype=np.float64) return straight_skeleton_2.create_interior_straight_skeleton(V) + + +def create_interior_straight_skeleton_with_holes(points, holes) -> PolylinesNumpy: + """Compute the skeleton of a polygon with holes. + + Parameters + ---------- + points : list of point coordinates or :class:`compas.geometry.Polygon` + The points of the polygon. + holes : list of list of point coordinates or list of :class:`compas.geometry.Polygon` + The holes of the polygon. + + Returns + ------- + :attr:`compas_cgal.types.PolylinesNumpy` + The skeleton of the polygon. + + Raises + ------ + ValueError + If the normal of the polygon is not [0, 0, 1]. + If the normal of a hole is not [0, 0, -1]. + """ + points = list(points) + if not TOL.is_allclose(normal_polygon(points, True), [0, 0, 1]): + raise ValueError("Please pass a polygon with a normal vector of [0, 0, 1].") + V = np.asarray(points, dtype=np.float64) + + H = [] + for hole in holes: + points = list(hole) + if not TOL.is_allclose(normal_polygon(points, True), [0, 0, -1]): + raise ValueError("Please pass a hole with a normal vector of [0, 0, -1].") + hole = np.asarray(points, dtype=np.float64) + H.append(hole) + return straight_skeleton_2.create_interior_straight_skeleton_with_holes(V, H) diff --git a/src/straight_skeleton_2.cpp b/src/straight_skeleton_2.cpp index 51945462..02f16972 100644 --- a/src/straight_skeleton_2.cpp +++ b/src/straight_skeleton_2.cpp @@ -2,10 +2,12 @@ #include "straight_skeleton_2.h" #include #include +#include typedef CGAL::Exact_predicates_inexact_constructions_kernel K; typedef K::Point_2 Point; typedef CGAL::Polygon_2 Polygon_2; +typedef CGAL::Polygon_with_holes_2 Polygon_with_holes; typedef CGAL::Straight_skeleton_2 Ss; typedef boost::shared_ptr SsPtr; typedef CGAL::Straight_skeleton_2::Halfedge_const_handle Halfedge_const_handle; @@ -39,6 +41,50 @@ compas::Edges pmp_create_interior_straight_skeleton( return edgelist; }; +compas::Edges pmp_create_interior_straight_skeleton_with_holes( + Eigen::Ref &V, + std::vector> &holes) +{ + Polygon_2 outer; + for (int i = 0; i < V.rows(); i++) + { + outer.push_back(Point(V(i, 0), V(i, 1))); + } + Polygon_with_holes poly(outer); + + + for (auto hit : holes) + { + compas::RowMatrixXd H = hit; + Polygon_2 hole; + for (int i = 0; i < H.rows(); i++) + { + hole.push_back(Point(H(i, 0), H(i, 1))); + } + poly.add_hole(hole); + + } + + SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly); + compas::Edges edgelist; + for(auto hit = iss->halfedges_begin(); hit != iss->halfedges_end(); ++hit){ + const Halfedge_const_handle h = hit; + if(!h->is_bisector()){ + continue; + } + const Vertex_const_handle& v1 = h->vertex(); + const Vertex_const_handle& v2 = h->opposite()->vertex(); + if(&*v1 < &*v2){ + std::vector s_vec = {v1->point().x(), v1->point().y(), 0}; + std::vector t_vec = {v2->point().x(), v2->point().y(), 0}; + compas::Edge edge = std::make_tuple(s_vec, t_vec); + edgelist.push_back(edge); + } + + } + return edgelist; + +} // =========================================================================== // PyBind11 @@ -52,4 +98,10 @@ void init_straight_skeleton_2(pybind11::module &m) "create_interior_straight_skeleton", &pmp_create_interior_straight_skeleton, pybind11::arg("V").noconvert()); + + submodule.def( + "create_interior_straight_skeleton_with_holes", + &pmp_create_interior_straight_skeleton_with_holes, + pybind11::arg("V").noconvert(), + pybind11::arg("holes").noconvert()); }; diff --git a/src/straight_skeleton_2.h b/src/straight_skeleton_2.h index d84b3c3a..baad3171 100644 --- a/src/straight_skeleton_2.h +++ b/src/straight_skeleton_2.h @@ -7,4 +7,9 @@ compas::Edges pmp_create_interior_straight_skeleton( Eigen::Ref &V); + +compas::Edges pmp_create_interior_straight_skeleton_with_holes( + Eigen::Ref &V, + std::vector> &holes); + #endif /* COMPAS_STRAIGHT_SKELETON_2_H */ diff --git a/tests/test_straight_skeleton_2.py b/tests/test_straight_skeleton_2.py index 47e4fad3..80d419d4 100644 --- a/tests/test_straight_skeleton_2.py +++ b/tests/test_straight_skeleton_2.py @@ -1,6 +1,8 @@ -from compas_cgal.straight_skeleton_2 import create_interior_straight_skeleton from compas.tolerance import TOL +from compas_cgal.straight_skeleton_2 import create_interior_straight_skeleton +from compas_cgal.straight_skeleton_2 import create_interior_straight_skeleton_with_holes + def test_straight_polygon(): points = [ @@ -17,6 +19,22 @@ def test_straight_polygon(): assert len(lines) == 8 +def test_straight_skeleton_with_holes(): + points = [ + (-1, -1, 0), + (0, -12, 0), + (1, -1, 0), + (12, 0, 0), + (1, 1, 0), + (0, 12, 0), + (-1, 1, 0), + (-12, 0, 0), + ] + hole = [(-1, 0, 0), (0, 1, 0), (1, 0, 0), (0, -1, 0)] + lines = create_interior_straight_skeleton_with_holes(points, [hole]) + assert len(lines) == 20 + + def test_straight_polygon_2_compare(): points = [ (-1.91, 3.59, 0.0),