Skip to content

Commit 4d1f48e

Browse files
authored
Merge pull request #33 from compas-dev/feature/ss2_result
Changing straight skeleton return data into `Graph`
2 parents a307f44 + bfa62a5 commit 4d1f48e

8 files changed

+148
-107
lines changed

CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
9+
## Unreleased
10+
11+
### Added
12+
13+
### Changed
14+
15+
* Changed the return values of `compas_cgal.straight_skeleton_2.create_interior_straight_skeleton` and `compas_cgal.straight_skeleton_2.create_interior_straight_skeleton_with_holes`.
16+
* Changed the return values of `compas_cgal.create_interior_straight_skeleton`.
17+
18+
### Removed
19+
20+
821
## [0.7.0] 2024-05-14
922

1023
### Added

docs/examples/straight_skeleton_2.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from compas.datastructures import Graph
2-
from compas.geometry import Polygon
31
from compas_cgal.straight_skeleton_2 import create_interior_straight_skeleton
42
from compas_viewer import Viewer
53

@@ -15,15 +13,22 @@
1513
(2.92, 4.03, 0.0),
1614
(-1.91, 3.59, 0.0),
1715
]
18-
polygon = Polygon(points)
19-
lines = create_interior_straight_skeleton(points)
20-
graph = Graph.from_lines(lines)
16+
17+
18+
graph = create_interior_straight_skeleton(points)
2119

2220
# ==============================================================================
2321
# Viz
2422
# ==============================================================================
2523

2624
viewer = Viewer(width=1600, height=900)
27-
viewer.scene.add(graph, edgecolor=(1.0, 0.0, 0.0))
28-
viewer.scene.add(polygon)
25+
for edge in graph.edges():
26+
line = graph.edge_line(edge)
27+
if graph.edge_attribute(edge, "inner_bisector"):
28+
print(edge, "inner_bisector")
29+
viewer.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2)
30+
elif graph.edge_attribute(edge, "bisector"):
31+
viewer.add(line, linecolor=(0.0, 0.0, 1.0))
32+
else:
33+
viewer.add(line, linecolor=(0.0, 0.0, 0.0))
2934
viewer.show()

docs/examples/straight_skeleton_2_holes.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from compas.datastructures import Graph
21
from compas.geometry import Polygon
32
from compas_viewer import Viewer
43

@@ -26,17 +25,20 @@
2625

2726
polygon = Polygon(points)
2827
holes = [Polygon(hole) for hole in holes]
29-
lines = create_interior_straight_skeleton_with_holes(polygon, holes)
30-
graph = Graph.from_lines(lines)
28+
graph = create_interior_straight_skeleton_with_holes(polygon, holes)
3129

3230
# ==============================================================================
3331
# Viz
3432
# ==============================================================================
3533

3634
viewer = Viewer(width=1600, height=900)
37-
viewer.renderer_config.show_grid = False
38-
viewer.scene.add(graph, edgecolor=(1.0, 0.0, 0.0))
39-
viewer.scene.add(polygon)
40-
for hole in holes:
41-
viewer.scene.add(hole)
35+
36+
for edge in graph.edges():
37+
line = graph.edge_line(edge)
38+
if graph.edge_attribute(edge, "inner_bisector"):
39+
viewer.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2)
40+
elif graph.edge_attribute(edge, "bisector"):
41+
viewer.add(line, linecolor=(0.0, 0.0, 1.0))
42+
else:
43+
viewer.add(line, linecolor=(0.0, 0.0, 0.0))
4244
viewer.show()

src/compas_cgal/straight_skeleton_2.py

+57-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,66 @@
1+
from typing import Tuple
2+
from typing import Union
3+
14
import numpy as np
5+
from compas.datastructures import Graph
26
from compas.geometry import Polygon
37
from compas.geometry import normal_polygon
48
from compas.tolerance import TOL
59

610
from compas_cgal._cgal import straight_skeleton_2
711

8-
from .types import PolylinesNumpy
12+
from .types import IntNx1
13+
from .types import IntNx2
14+
from .types import VerticesNumpy
15+
916

17+
def graph_from_skeleton_data(points: VerticesNumpy, indices: IntNx1, edges: IntNx2, edge_types: IntNx1) -> Graph:
18+
"""Create a graph from the skeleton data.
19+
20+
Parameters
21+
----------
22+
points : :class:`numpy.ndarray`
23+
The vertices of the skeleton, each vertex defined by 3 spatial coordinates.
24+
indices : :class:`numpy.ndarray`
25+
The vertex indices of the skeleton, corresponding to the points.
26+
edges : :class:`numpy.ndarray`
27+
The edges of the skeleton, each edge defined by 2 vertex indices.
28+
edge_types : :class:`numpy.ndarray`
29+
The type per edge, `0` for inner bisector, `1` for bisector, and `2` for boundary.
1030
11-
def create_interior_straight_skeleton(points) -> PolylinesNumpy:
31+
Returns
32+
-------
33+
:class:`compas.datastructures.Graph`
34+
The skeleton as a graph.
35+
"""
36+
graph = Graph()
37+
for pt, i in zip(points, indices):
38+
graph.add_node(key=i, x=pt[0], y=pt[1], z=pt[2])
39+
40+
for edge, etype in zip(edges, edge_types):
41+
edge = graph.add_edge(*edge)
42+
if etype == 0:
43+
graph.edge_attribute(edge, "inner_bisector", True)
44+
elif etype == 1:
45+
graph.edge_attribute(edge, "bisector", True)
46+
else:
47+
graph.edge_attribute(edge, "boundary", True)
48+
return graph
49+
50+
51+
def create_interior_straight_skeleton(points, as_graph=True) -> Union[Graph, Tuple[VerticesNumpy, IntNx1, IntNx2, IntNx1]]:
1252
"""Compute the skeleton of a polygon.
1353
1454
Parameters
1555
----------
1656
points : list of point coordinates or :class:`compas.geometry.Polygon`
1757
The points of the polygon.
58+
as_graph : bool, optional
59+
Whether the skeleton should be returned as a graph, defaults to `True`.
1860
1961
Returns
2062
-------
21-
:attr:`compas_cgal.types.PolylinesNumpy`
63+
:attr:`compas.datastructures.Graph` or tuple of (vertices, indices, edges, edge_types)
2264
The skeleton of the polygon.
2365
2466
Raises
@@ -31,10 +73,13 @@ def create_interior_straight_skeleton(points) -> PolylinesNumpy:
3173
if not TOL.is_allclose(normal, [0, 0, 1]):
3274
raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal))
3375
V = np.asarray(points, dtype=np.float64)
34-
return straight_skeleton_2.create_interior_straight_skeleton(V)
76+
points, indices, edges, edge_types = straight_skeleton_2.create_interior_straight_skeleton(V)
77+
if as_graph:
78+
return graph_from_skeleton_data(points, indices, edges, edge_types)
79+
return points, indices, edges, edge_types
3580

3681

37-
def create_interior_straight_skeleton_with_holes(points, holes) -> PolylinesNumpy:
82+
def create_interior_straight_skeleton_with_holes(points, holes, as_graph=True) -> Union[Graph, Tuple[VerticesNumpy, IntNx1, IntNx2, IntNx1]]:
3883
"""Compute the skeleton of a polygon with holes.
3984
4085
Parameters
@@ -43,10 +88,12 @@ def create_interior_straight_skeleton_with_holes(points, holes) -> PolylinesNump
4388
The points of the polygon.
4489
holes : list of list of point coordinates or list of :class:`compas.geometry.Polygon`
4590
The holes of the polygon.
91+
as_graph : bool, optional
92+
Whether the skeleton should be returned as a graph, defaults to `True`.
4693
4794
Returns
4895
-------
49-
:attr:`compas_cgal.types.PolylinesNumpy`
96+
:attr:`compas.datastructures.Graph` or tuple of (vertices, indices, edges, edge_types)
5097
The skeleton of the polygon.
5198
5299
Raises
@@ -69,7 +116,10 @@ def create_interior_straight_skeleton_with_holes(points, holes) -> PolylinesNump
69116
raise ValueError("The normal of the hole should be [0, 0, -1]. The normal of the provided {}-th hole is {}".format(i, normal_hole))
70117
hole = np.asarray(points, dtype=np.float64)
71118
H.append(hole)
72-
return straight_skeleton_2.create_interior_straight_skeleton_with_holes(V, H)
119+
points, indices, edges, edge_types = straight_skeleton_2.create_interior_straight_skeleton_with_holes(V, H)
120+
if as_graph:
121+
return graph_from_skeleton_data(points, indices, edges, edge_types)
122+
return points, indices, edges, edge_types
73123

74124

75125
def create_offset_polygons_2(points, offset) -> list[Polygon]:

src/compas_cgal/types.py

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
FloatNx3 = Annotated[NDArray[float64], Literal["N", 3]]
1414
IntNx3 = Annotated[NDArray[int64], Literal["N", 3]]
15+
IntNx2 = Annotated[NDArray[int64], Literal["N", 2]]
16+
IntNx1 = Annotated[NDArray[int64], Literal["N", 1]]
1517

1618
VerticesNumpy = FloatNx3
1719
"""An array of vertices, with each vertex defined by 3 spatial coordinates."""

src/straight_skeleton_2.cpp

+48-37
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,64 @@ typedef CGAL::Straight_skeleton_2<K>::Vertex_const_handle Vertex_const_handle;
1818
typedef boost::shared_ptr<Polygon_2> PolygonPtr;
1919
typedef std::vector<PolygonPtr> PolygonPtrVector;
2020

21-
compas::Edges pmp_create_interior_straight_skeleton(
22-
Eigen::Ref<const compas::RowMatrixXd> &V)
23-
{
24-
Polygon_2 poly;
25-
for (int i = 0; i < V.rows(); i++)
26-
{
27-
poly.push_back(Point(V(i, 0), V(i, 1)));
21+
22+
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> mesh_data_from_skeleton(boost::shared_ptr<Ss> &iss){
23+
std::size_t v = iss->size_of_vertices();
24+
std::size_t e = iss->size_of_halfedges() / 2; // halfedges are stored twice
25+
26+
compas::RowMatrixXd Mv(v, 3);
27+
std::vector<int> Mvi; // to save the vertex ids
28+
compas::RowMatrixXi Me(e, 2);
29+
std::vector<int> Mei; // to save the edge type: 0: inner bisector, 1: bisector, 2: boundary
30+
31+
std::size_t i = 0;
32+
for(auto hit = iss->vertices_begin(); hit != iss->vertices_end(); ++hit){
33+
const Vertex_const_handle vh = hit;
34+
Mv(i, 0) = (double)vh->point().x();
35+
Mv(i, 1) = (double)vh->point().y();
36+
Mv(i, 2) = 0;
37+
Mvi.push_back((int)vh->id());
38+
i++;
2839
}
29-
SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly.vertices_begin(), poly.vertices_end());
30-
compas::Edges edgelist;
40+
i = 0;
3141
for(auto hit = iss->halfedges_begin(); hit != iss->halfedges_end(); ++hit){
3242
const Halfedge_const_handle h = hit;
33-
if(!h->is_bisector()){
34-
continue;
35-
}
3643
const Vertex_const_handle& v1 = h->vertex();
3744
const Vertex_const_handle& v2 = h->opposite()->vertex();
45+
3846
if(&*v1 < &*v2){
39-
std::vector<double> s_vec = {v1->point().x(), v1->point().y(), 0};
40-
std::vector<double> t_vec = {v2->point().x(), v2->point().y(), 0};
41-
compas::Edge edge = std::make_tuple(s_vec, t_vec);
42-
edgelist.push_back(edge);
47+
Me(i, 0) = (int)v1->id();
48+
Me(i, 1) = (int)v2->id();
49+
50+
if(h->is_inner_bisector()){
51+
Mei.push_back(0);
52+
}
53+
else if(h->is_bisector()){
54+
Mei.push_back(1);
55+
}else{
56+
Mei.push_back(2);
57+
}
58+
i++;
4359
}
60+
}
61+
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> result = std::make_tuple(Mv, Mvi, Me, Mei);
62+
return result;
63+
}
4464

65+
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> pmp_create_interior_straight_skeleton(
66+
Eigen::Ref<const compas::RowMatrixXd> &V)
67+
{
68+
Polygon_2 poly;
69+
for (int i = 0; i < V.rows(); i++)
70+
{
71+
poly.push_back(Point(V(i, 0), V(i, 1)));
4572
}
46-
return edgelist;
73+
SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly.vertices_begin(), poly.vertices_end());
74+
75+
return mesh_data_from_skeleton(iss);
4776
};
4877

49-
compas::Edges pmp_create_interior_straight_skeleton_with_holes(
78+
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> pmp_create_interior_straight_skeleton_with_holes(
5079
Eigen::Ref<const compas::RowMatrixXd> &V,
5180
std::vector<Eigen::Ref<const compas::RowMatrixXd>> &holes)
5281
{
@@ -57,7 +86,6 @@ compas::Edges pmp_create_interior_straight_skeleton_with_holes(
5786
}
5887
Polygon_with_holes poly(outer);
5988

60-
6189
for (auto hit : holes)
6290
{
6391
compas::RowMatrixXd H = hit;
@@ -71,24 +99,7 @@ compas::Edges pmp_create_interior_straight_skeleton_with_holes(
7199
}
72100

73101
SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly);
74-
compas::Edges edgelist;
75-
for(auto hit = iss->halfedges_begin(); hit != iss->halfedges_end(); ++hit){
76-
const Halfedge_const_handle h = hit;
77-
if(!h->is_bisector()){
78-
continue;
79-
}
80-
const Vertex_const_handle& v1 = h->vertex();
81-
const Vertex_const_handle& v2 = h->opposite()->vertex();
82-
if(&*v1 < &*v2){
83-
std::vector<double> s_vec = {v1->point().x(), v1->point().y(), 0};
84-
std::vector<double> t_vec = {v2->point().x(), v2->point().y(), 0};
85-
compas::Edge edge = std::make_tuple(s_vec, t_vec);
86-
edgelist.push_back(edge);
87-
}
88-
89-
}
90-
return edgelist;
91-
102+
return mesh_data_from_skeleton(iss);
92103
}
93104

94105
std::vector<compas::RowMatrixXd> pmp_create_offset_polygons_2_inner(Eigen::Ref<const compas::RowMatrixXd> &V, double &offset){

src/straight_skeleton_2.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
#include <compas.h>
55

66

7-
compas::Edges pmp_create_interior_straight_skeleton(
7+
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> pmp_create_interior_straight_skeleton(
88
Eigen::Ref<const compas::RowMatrixXd> &V);
99

1010

11-
compas::Edges pmp_create_interior_straight_skeleton_with_holes(
11+
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> pmp_create_interior_straight_skeleton_with_holes(
1212
Eigen::Ref<const compas::RowMatrixXd> &V,
1313
std::vector<Eigen::Ref<const compas::RowMatrixXd>> &holes);
1414

0 commit comments

Comments
 (0)