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

Changing straight skeleton return data into Graph #33

Merged
merged 11 commits into from
Sep 18, 2024
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## Unreleased

### Added

### Changed

* 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`.
* Changed the return values of `compas_cgal.create_interior_straight_skeleton`.

### Removed


## [0.7.0] 2024-05-14

### Added
Expand Down
20 changes: 13 additions & 7 deletions docs/examples/straight_skeleton_2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from compas.datastructures import Graph
from compas.geometry import Polygon
from compas.geometry import Line
from compas_cgal.straight_skeleton_2 import create_interior_straight_skeleton
from compas_viewer import Viewer

Expand All @@ -15,15 +14,22 @@
(2.92, 4.03, 0.0),
(-1.91, 3.59, 0.0),
]
polygon = Polygon(points)
lines = create_interior_straight_skeleton(points)
graph = Graph.from_lines(lines)


graph = create_interior_straight_skeleton(points)

# ==============================================================================
# Viz
# ==============================================================================

viewer = Viewer(width=1600, height=900)
viewer.scene.add(graph, edgecolor=(1.0, 0.0, 0.0))
viewer.scene.add(polygon)
for edge in graph.edges():
line = Line(*graph.edge_coordinates(edge))
if graph.edge_attribute(edge, "inner_bisector"):
Copy link
Member

Choose a reason for hiding this comment

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

just fyi, there is line = graph.edge_line(edge)

print(edge, "inner_bisector")
viewer.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2)
elif graph.edge_attribute(edge, "bisector"):
viewer.add(line, linecolor=(0.0, 0.0, 1.0))
else:
viewer.add(line, linecolor=(0.0, 0.0, 0.0))
viewer.show()
20 changes: 11 additions & 9 deletions docs/examples/straight_skeleton_2_holes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from compas.datastructures import Graph
from compas.geometry import Polygon
from compas.geometry import Polygon, Line
from compas_viewer import Viewer

from compas_cgal.straight_skeleton_2 import create_interior_straight_skeleton_with_holes
Expand All @@ -26,17 +25,20 @@

polygon = Polygon(points)
holes = [Polygon(hole) for hole in holes]
lines = create_interior_straight_skeleton_with_holes(polygon, holes)
graph = Graph.from_lines(lines)
graph = create_interior_straight_skeleton_with_holes(polygon, holes)

# ==============================================================================
# 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)

for edge in graph.edges():
line = Line(*graph.edge_coordinates(edge))
if graph.edge_attribute(edge, "inner_bisector"):
viewer.add(line, linecolor=(1.0, 0.0, 0.0), linewidth=2)
elif graph.edge_attribute(edge, "bisector"):
viewer.add(line, linecolor=(0.0, 0.0, 1.0))
else:
viewer.add(line, linecolor=(0.0, 0.0, 0.0))
viewer.show()
27 changes: 22 additions & 5 deletions src/compas_cgal/straight_skeleton_2.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import numpy as np
from compas.datastructures import Graph
from compas.geometry import Polygon
from compas.geometry import normal_polygon
from compas.tolerance import TOL

from compas_cgal._cgal import straight_skeleton_2

from .types import PolylinesNumpy

def graph_from_skeleton_data(skeleton_data) -> Graph:
Mv, Mvi, Me, Mei = skeleton_data
Copy link
Member

Choose a reason for hiding this comment

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

why not split these parameters?
now it is quite difficult for a user to know what the input is supposed to be...

Copy link
Member Author

Choose a reason for hiding this comment

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

good point, made the changes and added doc for graph_from_skeleton_data


def create_interior_straight_skeleton(points) -> PolylinesNumpy:
graph = Graph()
for pt, i in zip(Mv, Mvi):
graph.add_node(key=i, x=pt[0], y=pt[1], z=pt[2])

for edge, etype in zip(Me, Mei):
edge = graph.add_edge(*edge)
if etype == 0:
graph.edge_attribute(edge, "inner_bisector", True)
elif etype == 1:
graph.edge_attribute(edge, "bisector", True)
else:
graph.edge_attribute(edge, "boundary", True)
return graph


def create_interior_straight_skeleton(points) -> Graph:
"""Compute the skeleton of a polygon.

Parameters
Expand All @@ -31,10 +48,10 @@ def create_interior_straight_skeleton(points) -> PolylinesNumpy:
if not TOL.is_allclose(normal, [0, 0, 1]):
raise ValueError("The normal of the polygon should be [0, 0, 1]. The normal of the provided polygon is {}".format(normal))
V = np.asarray(points, dtype=np.float64)
return straight_skeleton_2.create_interior_straight_skeleton(V)
return graph_from_skeleton_data(straight_skeleton_2.create_interior_straight_skeleton(V))
Copy link
Member

Choose a reason for hiding this comment

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

if you "star" the output, you could split the params in graph_from_skeleton_data



def create_interior_straight_skeleton_with_holes(points, holes) -> PolylinesNumpy:
def create_interior_straight_skeleton_with_holes(points, holes) -> Graph:
"""Compute the skeleton of a polygon with holes.

Parameters
Expand Down Expand Up @@ -69,7 +86,7 @@ def create_interior_straight_skeleton_with_holes(points, holes) -> PolylinesNump
raise ValueError("The normal of the hole should be [0, 0, -1]. The normal of the provided {}-th hole is {}".format(i, normal_hole))
hole = np.asarray(points, dtype=np.float64)
H.append(hole)
return straight_skeleton_2.create_interior_straight_skeleton_with_holes(V, H)
return graph_from_skeleton_data(straight_skeleton_2.create_interior_straight_skeleton_with_holes(V, H))


def create_offset_polygons_2(points, offset) -> list[Polygon]:
Expand Down
85 changes: 48 additions & 37 deletions src/straight_skeleton_2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,64 @@ typedef CGAL::Straight_skeleton_2<K>::Vertex_const_handle Vertex_const_handle;
typedef boost::shared_ptr<Polygon_2> PolygonPtr;
typedef std::vector<PolygonPtr> PolygonPtrVector;

compas::Edges pmp_create_interior_straight_skeleton(
Eigen::Ref<const compas::RowMatrixXd> &V)
{
Polygon_2 poly;
for (int i = 0; i < V.rows(); i++)
{
poly.push_back(Point(V(i, 0), V(i, 1)));

std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> mesh_data_from_skeleton(boost::shared_ptr<Ss> &iss){
std::size_t v = iss->size_of_vertices();
std::size_t e = iss->size_of_halfedges() / 2; // halfedges are stored twice

compas::RowMatrixXd Mv(v, 3);
std::vector<int> Mvi; // to save the vertex ids
compas::RowMatrixXi Me(e, 2);
std::vector<int> Mei; // to save the edge type: 0: inner bisector, 1: bisector, 2: boundary

std::size_t i = 0;
for(auto hit = iss->vertices_begin(); hit != iss->vertices_end(); ++hit){
const Vertex_const_handle vh = hit;
Mv(i, 0) = (double)vh->point().x();
Mv(i, 1) = (double)vh->point().y();
Mv(i, 2) = 0;
Mvi.push_back((int)vh->id());
i++;
}
SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly.vertices_begin(), poly.vertices_end());
compas::Edges edgelist;
i = 0;
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);
Me(i, 0) = (int)v1->id();
Me(i, 1) = (int)v2->id();

if(h->is_inner_bisector()){
Mei.push_back(0);
}
else if(h->is_bisector()){
Mei.push_back(1);
}else{
Mei.push_back(2);
}
i++;
}
}
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> result = std::make_tuple(Mv, Mvi, Me, Mei);
return result;
}

std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> pmp_create_interior_straight_skeleton(
Eigen::Ref<const compas::RowMatrixXd> &V)
{
Polygon_2 poly;
for (int i = 0; i < V.rows(); i++)
{
poly.push_back(Point(V(i, 0), V(i, 1)));
}
return edgelist;
SsPtr iss = CGAL::create_interior_straight_skeleton_2(poly.vertices_begin(), poly.vertices_end());

return mesh_data_from_skeleton(iss);
};

compas::Edges pmp_create_interior_straight_skeleton_with_holes(
std::tuple<compas::RowMatrixXd, std::vector<int>, compas::RowMatrixXi, std::vector<int>> pmp_create_interior_straight_skeleton_with_holes(
Eigen::Ref<const compas::RowMatrixXd> &V,
std::vector<Eigen::Ref<const compas::RowMatrixXd>> &holes)
{
Expand All @@ -57,7 +86,6 @@ compas::Edges pmp_create_interior_straight_skeleton_with_holes(
}
Polygon_with_holes poly(outer);


for (auto hit : holes)
{
compas::RowMatrixXd H = hit;
Expand All @@ -71,24 +99,7 @@ compas::Edges pmp_create_interior_straight_skeleton_with_holes(
}

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;

return mesh_data_from_skeleton(iss);
}

std::vector<compas::RowMatrixXd> pmp_create_offset_polygons_2_inner(Eigen::Ref<const compas::RowMatrixXd> &V, double &offset){
Expand Down
4 changes: 2 additions & 2 deletions src/straight_skeleton_2.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
#include <compas.h>


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


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

Expand Down
50 changes: 4 additions & 46 deletions tests/test_straight_skeleton_2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
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
from compas_cgal.straight_skeleton_2 import create_offset_polygons_2
Expand All @@ -17,8 +15,8 @@ def test_straight_polygon():
(-1, 1, 0),
(-12, 0, 0),
]
lines = create_interior_straight_skeleton(points)
assert len(lines) == 8
graph = create_interior_straight_skeleton(points)
assert graph.number_of_edges() == 16


def test_straight_skeleton_with_holes():
Expand All @@ -33,48 +31,8 @@ def test_straight_skeleton_with_holes():
(-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),
(-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),
]
lines = create_interior_straight_skeleton(points)

expected = [
[[-1.91, 3.59, 0.0], [-0.139446292, 1.191439787, 0.0]],
[[-5.53, -5.22, 0.0], [-0.139446292, 1.191439787, 0.0]],
[[-0.39, -1.98, 0.0], [0.008499564, 1.241560466, 0.0]],
[[2.98, -5.51, 0.0], [2.44972507, -1.674799065, 0.0]],
[[4.83, -2.02, 0.0], [4.228131167, -0.522007766, 0.0]],
[[8.663865218, -1.084821998, 0.0], [9.7, -3.63, 0.0]],
[[12.23, 1.25, 0.0], [8.663865218, -1.084821998, 0.0]],
[[3.42, 0.66, 0.0], [1.755862468, -1.404991433, 0.0]],
[[2.92, 4.03, 0.0], [0.563706846, 1.033296141, 0.0]],
[[4.228131167, -0.522007766, 0.0], [2.44972507, -1.674799065, 0.0]],
[[4.228131167, -0.522007766, 0.0], [8.663865218, -1.084821998, 0.0]],
[[1.755862468, -1.404991433, 0.0], [2.44972507, -1.674799065, 0.0]],
[[0.563706846, 1.033296141, 0.0], [1.755862468, -1.404991433, 0.0]],
[[-0.139446292, 1.191439787, 0.0], [0.008499564, 1.241560466, 0.0]],
[[0.563706846, 1.033296141, 0.0], [0.008499564, 1.241560466, 0.0]],
]
for act, exp in zip(lines, expected):
sa, ea = act
se, ee = exp
# the line direction sometimes changes ...
assert TOL.is_allclose(sa, se) or TOL.is_allclose(sa, ee)
assert TOL.is_allclose(ea, ee) or TOL.is_allclose(ea, se)
graph = create_interior_straight_skeleton_with_holes(points, [hole])
assert graph.number_of_edges() == 32


def test_offset():
Expand Down
Loading