Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding straight skeleton with holes #30

Merged
merged 3 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Binary file added docs/_images/cgal_straight_skeleton_2_holes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/api/compas_cgal.straight_skeleton_2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ compas_cgal.straight_skeleton_2
:nosignatures:

create_interior_straight_skeleton
create_interior_straight_skeleton_with_holes
9 changes: 9 additions & 0 deletions docs/examples/straight_skeleton_2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
42 changes: 42 additions & 0 deletions docs/examples/straight_skeleton_2_holes.py
Original file line number Diff line number Diff line change
@@ -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)
Comment on lines +29 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the return value is a list polylines (not lines), it will not always be possible to directly convert this into a graph. perhaps it would be helpful to have a helper for making the conversion...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The skeleton function is implemented to always return lines, so it should work to always convert them to a graph.

But I agree that the conversion is not proper yet. Ideally the return value should not be PolylinesNumpy, but a compas.datastructures.mesh, as it could represent CGAL's data type for the StraightSkeleton_2:
https://doc.cgal.org/Manual/4.1/doc_html/cgal_manual/Straight_skeleton_2_ref/Concept_StraightSkeleton_2.html#Cross_link_anchor_1072

I anyway plan to add some other functions like the create_interior_weighted_straight_skeleton_2. So maybe I will tackle this in the next PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so current one can be merged?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes


# ==============================================================================
# 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()
36 changes: 36 additions & 0 deletions src/compas_cgal/straight_skeleton_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
52 changes: 52 additions & 0 deletions src/straight_skeleton_2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
#include "straight_skeleton_2.h"
#include <CGAL/Polygon_2.h>
#include <CGAL/create_straight_skeleton_2.h>
#include <CGAL/create_straight_skeleton_from_polygon_with_holes_2.h>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef K::Point_2 Point;
typedef CGAL::Polygon_2<K> Polygon_2;
typedef CGAL::Polygon_with_holes_2<K> Polygon_with_holes;
typedef CGAL::Straight_skeleton_2<K> Ss;
typedef boost::shared_ptr<Ss> SsPtr;
typedef CGAL::Straight_skeleton_2<K>::Halfedge_const_handle Halfedge_const_handle;
Expand Down Expand Up @@ -39,6 +41,50 @@ compas::Edges pmp_create_interior_straight_skeleton(
return edgelist;
};

compas::Edges pmp_create_interior_straight_skeleton_with_holes(
Eigen::Ref<const compas::RowMatrixXd> &V,
std::vector<Eigen::Ref<const compas::RowMatrixXd>> &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<double> s_vec = {v1->point().x(), v1->point().y(), 0};
std::vector<double> 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
Expand All @@ -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());
};
5 changes: 5 additions & 0 deletions src/straight_skeleton_2.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,9 @@
compas::Edges pmp_create_interior_straight_skeleton(
Eigen::Ref<const compas::RowMatrixXd> &V);


compas::Edges pmp_create_interior_straight_skeleton_with_holes(
Eigen::Ref<const compas::RowMatrixXd> &V,
std::vector<Eigen::Ref<const compas::RowMatrixXd>> &holes);

#endif /* COMPAS_STRAIGHT_SKELETON_2_H */
20 changes: 19 additions & 1 deletion tests/test_straight_skeleton_2.py
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -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),
Expand Down
Loading