Skip to content

Commit bbc802c

Browse files
authored
Merge pull request #31 from compas-dev/feature/straight_skeleton_2_offsert
Adding weighted and unweighted polygon offsets using polygon skeletons
2 parents 7a232fe + 56c5ad4 commit bbc802c

12 files changed

+358
-9
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
* Added `compas_cgal.straight_skeleton_2.create_interior_straight_skeleton`.
1313
* Added `compas_cgal.straight_skeleton_2.create_interior_straight_skeleton_with_holes`.
14+
* Added `compas_cgal.straight_skeleton_2.create_offset_polygons_2_inner`.
15+
* Added `compas_cgal.straight_skeleton_2.create_offset_polygons_2_outer`.
16+
* Added `compas_cgal.straight_skeleton_2.create_weighted_offset_polygons_2_inner`.
17+
* Added `compas_cgal.straight_skeleton_2.create_weighted_offset_polygons_2_outer`.
1418

1519
### Changed
1620

docs/PLACEHOLDER

-1
This file was deleted.

docs/_images/PLACEHOLDER

-1
This file was deleted.
Loading
Loading

docs/examples/straight_skeleton_2.rst

+18
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,21 @@
1818

1919
.. literalinclude:: straight_skeleton_2_holes.py
2020
:language: python
21+
22+
23+
.. figure:: /_images/cgal_straight_skeleton_2_offset.png
24+
:figclass: figure
25+
:class: figure-img img-fluid
26+
27+
28+
.. literalinclude:: straight_skeleton_2_offset.py
29+
:language: python
30+
31+
32+
.. figure:: /_images/cgal_straight_skeleton_2_offset_weighted.png
33+
:figclass: figure
34+
:class: figure-img img-fluid
35+
36+
37+
.. literalinclude:: straight_skeleton_2_offset_weighted.py
38+
:language: python
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from compas.geometry import Polygon
2+
from compas_viewer import Viewer
3+
4+
from compas_cgal.straight_skeleton_2 import create_offset_polygons_2
5+
6+
points = [
7+
(-1.91, 3.59, 0.0),
8+
(-5.53, -5.22, 0.0),
9+
(-0.39, -1.98, 0.0),
10+
(2.98, -5.51, 0.0),
11+
(4.83, -2.02, 0.0),
12+
(9.70, -3.63, 0.0),
13+
(12.23, 1.25, 0.0),
14+
(3.42, 0.66, 0.0),
15+
(2.92, 4.03, 0.0),
16+
(-1.91, 3.59, 0.0),
17+
]
18+
polygon = Polygon(points)
19+
offset = 1.5
20+
21+
offset_polygons_inner = create_offset_polygons_2(points, offset)
22+
offset_polygons_outer = create_offset_polygons_2(points, -offset)
23+
24+
# ==============================================================================
25+
# Viz
26+
# ==============================================================================
27+
28+
viewer = Viewer(width=1600, height=900)
29+
viewer.scene.add(polygon)
30+
viewer.config.renderer.show_grid = False
31+
32+
for opolygon in offset_polygons_inner:
33+
viewer.scene.add(opolygon, linecolor=(1.0, 0.0, 0.0), facecolor=(1.0, 1.0, 1.0, 0.0))
34+
for opolygon in offset_polygons_outer:
35+
viewer.scene.add(opolygon, linecolor=(0.0, 0.0, 1.0), facecolor=(1.0, 1.0, 1.0, 0.0))
36+
37+
viewer.show()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from compas.geometry import Polygon
2+
from compas_viewer import Viewer
3+
4+
from compas_cgal.straight_skeleton_2 import create_weighted_offset_polygons_2
5+
6+
points = [
7+
(-1.91, 3.59, 0.0),
8+
(-5.53, -5.22, 0.0),
9+
(-0.39, -1.98, 0.0),
10+
(2.98, -5.51, 0.0),
11+
(4.83, -2.02, 0.0),
12+
(9.70, -3.63, 0.0),
13+
(12.23, 1.25, 0.0),
14+
(3.42, 0.66, 0.0),
15+
(2.92, 4.03, 0.0),
16+
(-1.91, 3.59, 0.0),
17+
]
18+
polygon = Polygon(points)
19+
20+
21+
distances = [0.1, 0.3, 0.6, 0.1, 0.7, 0.5, 0.2, 0.4, 0.8, 0.2]
22+
weights = [1.0 / d for d in distances]
23+
offset = 1.0
24+
offset_polygons_outer = create_weighted_offset_polygons_2(points, -offset, weights)
25+
26+
# ==============================================================================
27+
# Viz
28+
# ==============================================================================
29+
30+
viewer = Viewer(width=1600, height=900)
31+
viewer.scene.add(polygon)
32+
viewer.config.renderer.show_grid = False
33+
34+
for opolygon in offset_polygons_outer:
35+
viewer.scene.add(opolygon, linecolor=(0.0, 0.0, 1.0), facecolor=(1.0, 1.0, 1.0, 0.0))
36+
37+
viewer.show()

src/compas_cgal/straight_skeleton_2.py

+85-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy as np
2+
from compas.geometry import Polygon
23
from compas.geometry import normal_polygon
34
from compas.tolerance import TOL
45

@@ -26,8 +27,9 @@ def create_interior_straight_skeleton(points) -> PolylinesNumpy:
2627
If the normal of the polygon is not [0, 0, 1].
2728
"""
2829
points = list(points)
29-
if not TOL.is_allclose(normal_polygon(points, True), [0, 0, 1]):
30-
raise ValueError("Please pass a polygon with a normal vector of [0, 0, 1].")
30+
normal = normal_polygon(points, True)
31+
if not TOL.is_allclose(normal, [0, 0, 1]):
32+
raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal))
3133
V = np.asarray(points, dtype=np.float64)
3234
return straight_skeleton_2.create_interior_straight_skeleton(V)
3335

@@ -54,15 +56,91 @@ def create_interior_straight_skeleton_with_holes(points, holes) -> PolylinesNump
5456
If the normal of a hole is not [0, 0, -1].
5557
"""
5658
points = list(points)
57-
if not TOL.is_allclose(normal_polygon(points, True), [0, 0, 1]):
58-
raise ValueError("Please pass a polygon with a normal vector of [0, 0, 1].")
59+
normal = normal_polygon(points, True)
60+
if not TOL.is_allclose(normal, [0, 0, 1]):
61+
raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal))
5962
V = np.asarray(points, dtype=np.float64)
6063

6164
H = []
62-
for hole in holes:
65+
for i, hole in enumerate(holes):
6366
points = list(hole)
64-
if not TOL.is_allclose(normal_polygon(points, True), [0, 0, -1]):
65-
raise ValueError("Please pass a hole with a normal vector of [0, 0, -1].")
67+
normal_hole = normal_polygon(points, True)
68+
if not TOL.is_allclose(normal_hole, [0, 0, -1]):
69+
raise ValueError("The normal of the hole should be [0, 0, -1]. The normal of the provided {}-th hole is {}".format(i, normal_hole))
6670
hole = np.asarray(points, dtype=np.float64)
6771
H.append(hole)
6872
return straight_skeleton_2.create_interior_straight_skeleton_with_holes(V, H)
73+
74+
75+
def create_offset_polygons_2(points, offset) -> list[Polygon]:
76+
"""Compute the polygon offset.
77+
78+
Parameters
79+
----------
80+
points : list of point coordinates or :class:`compas.geometry.Polygon`
81+
The points of the polygon.
82+
offset : float
83+
The offset distance. If negative, the offset is outside the polygon, otherwise inside.
84+
85+
Returns
86+
-------
87+
list[:class:`Polygon`]
88+
The offset polygon(s).
89+
90+
Raises
91+
------
92+
ValueError
93+
If the normal of the polygon is not [0, 0, 1].
94+
"""
95+
points = list(points)
96+
normal = normal_polygon(points, True)
97+
if not TOL.is_allclose(normal, [0, 0, 1]):
98+
raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal))
99+
V = np.asarray(points, dtype=np.float64)
100+
offset = float(offset)
101+
if offset < 0: # outside
102+
offset_polygons = straight_skeleton_2.create_offset_polygons_2_outer(V, abs(offset))[1:] # first one is box
103+
else: # inside
104+
offset_polygons = straight_skeleton_2.create_offset_polygons_2_inner(V, offset)
105+
return [Polygon(points.tolist()) for points in offset_polygons]
106+
107+
108+
def create_weighted_offset_polygons_2(points, offset, weights) -> list[Polygon]:
109+
"""Compute the polygon offset with weights.
110+
111+
Parameters
112+
----------
113+
points : list of point coordinates or :class:`compas.geometry.Polygon`
114+
The points of the polygon.
115+
offset : float
116+
The offset distance. If negative, the offset is outside the polygon, otherwise inside.
117+
weights : list of float
118+
The weights for each edge, starting with the edge between the last and the first point.
119+
120+
Returns
121+
-------
122+
list[:class:`Polygon`]
123+
The offset polygon(s).
124+
125+
Raises
126+
------
127+
ValueError
128+
If the normal of the polygon is not [0, 0, 1].
129+
ValueError
130+
If the number of weights does not match the number of points.
131+
"""
132+
points = list(points)
133+
normal = normal_polygon(points, True)
134+
if not TOL.is_allclose(normal, [0, 0, 1]):
135+
raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal))
136+
137+
V = np.asarray(points, dtype=np.float64)
138+
offset = float(offset)
139+
W = np.asarray(weights, dtype=np.float64)
140+
if W.shape[0] != V.shape[0]:
141+
raise ValueError("The number of weights should be equal to the number of points %d != %d." % (W.shape[0], V.shape[0]))
142+
if offset < 0:
143+
offset_polygons = straight_skeleton_2.create_weighted_offset_polygons_2_outer(V, abs(offset), W)[1:]
144+
else:
145+
offset_polygons = straight_skeleton_2.create_weighted_offset_polygons_2_inner(V, offset, W)
146+
return [Polygon(points.tolist()) for points in offset_polygons]

src/straight_skeleton_2.cpp

+134
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#include <CGAL/Polygon_2.h>
44
#include <CGAL/create_straight_skeleton_2.h>
55
#include <CGAL/create_straight_skeleton_from_polygon_with_holes_2.h>
6+
#include <CGAL/create_offset_polygons_2.h>
7+
#include <CGAL/create_weighted_offset_polygons_from_polygon_with_holes_2.h>
8+
#include <CGAL/create_weighted_straight_skeleton_2.h>
69

710
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
811
typedef K::Point_2 Point;
@@ -12,6 +15,8 @@ typedef CGAL::Straight_skeleton_2<K> Ss;
1215
typedef boost::shared_ptr<Ss> SsPtr;
1316
typedef CGAL::Straight_skeleton_2<K>::Halfedge_const_handle Halfedge_const_handle;
1417
typedef CGAL::Straight_skeleton_2<K>::Vertex_const_handle Vertex_const_handle;
18+
typedef boost::shared_ptr<Polygon_2> PolygonPtr;
19+
typedef std::vector<PolygonPtr> PolygonPtrVector;
1520

1621
compas::Edges pmp_create_interior_straight_skeleton(
1722
Eigen::Ref<const compas::RowMatrixXd> &V)
@@ -86,6 +91,108 @@ compas::Edges pmp_create_interior_straight_skeleton_with_holes(
8691

8792
}
8893

94+
std::vector<compas::RowMatrixXd> pmp_create_offset_polygons_2_inner(Eigen::Ref<const compas::RowMatrixXd> &V, double &offset){
95+
Polygon_2 poly;
96+
for (int i = 0; i < V.rows(); i++){
97+
poly.push_back(Point(V(i, 0), V(i, 1)));
98+
}
99+
PolygonPtrVector offset_polygons = CGAL::create_interior_skeleton_and_offset_polygons_2(offset, poly);
100+
101+
std::vector<compas::RowMatrixXd> result;
102+
for(auto pi = offset_polygons.begin(); pi != offset_polygons.end(); ++pi){
103+
std::size_t n = (*pi)->size();
104+
compas::RowMatrixXd points(n, 3);
105+
int j = 0;
106+
for (auto vi = (*pi)->vertices_begin(); vi != (*pi)->vertices_end(); ++vi){
107+
points(j, 0) = (double)(*vi).x();
108+
points(j, 1) = (double)(*vi).y();
109+
points(j, 2) = 0;
110+
j++;
111+
}
112+
result.push_back(points);
113+
}
114+
return result;
115+
}
116+
117+
std::vector<compas::RowMatrixXd> pmp_create_offset_polygons_2_outer(Eigen::Ref<const compas::RowMatrixXd> &V, double &offset){
118+
Polygon_2 poly;
119+
for (int i = 0; i < V.rows(); i++){
120+
poly.push_back(Point(V(i, 0), V(i, 1)));
121+
}
122+
PolygonPtrVector offset_polygons = CGAL::create_exterior_skeleton_and_offset_polygons_2(offset, poly);
123+
124+
std::vector<compas::RowMatrixXd> result;
125+
for(auto pi = offset_polygons.begin(); pi != offset_polygons.end(); ++pi){
126+
std::size_t n = (*pi)->size();
127+
compas::RowMatrixXd points(n, 3);
128+
int j = 0;
129+
for (auto vi = (*pi)->vertices_begin(); vi != (*pi)->vertices_end(); ++vi){
130+
points(j, 0) = (double)(*vi).x();
131+
points(j, 1) = (double)(*vi).y();
132+
points(j, 2) = 0;
133+
j++;
134+
}
135+
result.push_back(points);
136+
}
137+
return result;
138+
}
139+
140+
std::vector<compas::RowMatrixXd> pmp_create_weighted_offset_polygons_2_inner(Eigen::Ref<const compas::RowMatrixXd> &V, double &offset, Eigen::Ref<const compas::RowMatrixXd> &weights){
141+
Polygon_2 poly;
142+
for (int i = 0; i < V.rows(); i++){
143+
poly.push_back(Point(V(i, 0), V(i, 1)));
144+
}
145+
std::vector<double> weights_vec;
146+
for (int i = 0; i < weights.rows(); i++){
147+
weights_vec.push_back(weights(i, 0));
148+
}
149+
SsPtr iss = CGAL::create_interior_weighted_straight_skeleton_2(poly, weights_vec);
150+
PolygonPtrVector offset_polygons = CGAL::create_offset_polygons_2<Polygon_2>(offset, *iss);
151+
152+
std::vector<compas::RowMatrixXd> result;
153+
for(auto pi = offset_polygons.begin(); pi != offset_polygons.end(); ++pi){
154+
std::size_t n = (*pi)->size();
155+
compas::RowMatrixXd points(n, 3);
156+
int j = 0;
157+
for (auto vi = (*pi)->vertices_begin(); vi != (*pi)->vertices_end(); ++vi){
158+
points(j, 0) = (double)(*vi).x();
159+
points(j, 1) = (double)(*vi).y();
160+
points(j, 2) = 0;
161+
j++;
162+
}
163+
result.push_back(points);
164+
}
165+
return result;
166+
}
167+
168+
std::vector<compas::RowMatrixXd> pmp_create_weighted_offset_polygons_2_outer(Eigen::Ref<const compas::RowMatrixXd> &V, double &offset, Eigen::Ref<const compas::RowMatrixXd> &weights){
169+
Polygon_2 poly;
170+
for (int i = 0; i < V.rows(); i++){
171+
poly.push_back(Point(V(i, 0), V(i, 1)));
172+
}
173+
std::vector<double> weights_vec;
174+
for (int i = 0; i < weights.rows(); i++){
175+
weights_vec.push_back(weights(i, 0));
176+
}
177+
SsPtr iss = CGAL::create_exterior_weighted_straight_skeleton_2(offset, weights_vec, poly);
178+
PolygonPtrVector offset_polygons = CGAL::create_offset_polygons_2<Polygon_2>(offset, *iss);
179+
180+
std::vector<compas::RowMatrixXd> result;
181+
for(auto pi = offset_polygons.begin(); pi != offset_polygons.end(); ++pi){
182+
std::size_t n = (*pi)->size();
183+
compas::RowMatrixXd points(n, 3);
184+
int j = 0;
185+
for (auto vi = (*pi)->vertices_begin(); vi != (*pi)->vertices_end(); ++vi){
186+
points(j, 0) = (double)(*vi).x();
187+
points(j, 1) = (double)(*vi).y();
188+
points(j, 2) = 0;
189+
j++;
190+
}
191+
result.push_back(points);
192+
}
193+
return result;
194+
}
195+
89196
// ===========================================================================
90197
// PyBind11
91198
// ===========================================================================
@@ -104,4 +211,31 @@ void init_straight_skeleton_2(pybind11::module &m)
104211
&pmp_create_interior_straight_skeleton_with_holes,
105212
pybind11::arg("V").noconvert(),
106213
pybind11::arg("holes").noconvert());
214+
215+
submodule.def(
216+
"create_offset_polygons_2_inner",
217+
&pmp_create_offset_polygons_2_inner,
218+
pybind11::arg("V").noconvert(),
219+
pybind11::arg("offset").noconvert());
220+
221+
submodule.def(
222+
"create_offset_polygons_2_outer",
223+
&pmp_create_offset_polygons_2_outer,
224+
pybind11::arg("V").noconvert(),
225+
pybind11::arg("offset").noconvert());
226+
227+
submodule.def(
228+
"create_weighted_offset_polygons_2_inner",
229+
&pmp_create_weighted_offset_polygons_2_inner,
230+
pybind11::arg("V").noconvert(),
231+
pybind11::arg("offset").noconvert(),
232+
pybind11::arg("weights").noconvert());
233+
234+
submodule.def(
235+
"create_weighted_offset_polygons_2_outer",
236+
&pmp_create_weighted_offset_polygons_2_outer,
237+
pybind11::arg("V").noconvert(),
238+
pybind11::arg("offset").noconvert(),
239+
pybind11::arg("weights").noconvert());
240+
107241
};

0 commit comments

Comments
 (0)