Skip to content

Commit b79d1f1

Browse files
committed
👨🏿‍🔬 Refactor polygon and STL handling
1 parent 4f5852a commit b79d1f1

6 files changed

Lines changed: 229 additions & 192 deletions

File tree

src/FastPointQuery.jl

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,43 +33,8 @@ function __init__()
3333
end
3434
end
3535

36-
struct STLInfo2D
37-
mesh ::Py
38-
vmin ::Vector
39-
vmax ::Vector
40-
py_vertices ::Py
41-
py_triangles::Py
42-
stl_path ::String
43-
end
44-
45-
struct STLInfo3D
46-
mesh ::Py
47-
vmin ::Vector
48-
vmax ::Vector
49-
py_vertices ::Py
50-
py_triangles::Py
51-
stl_path ::String
52-
end
53-
54-
struct QueryPolygon
55-
polygon::Py
56-
coord ::AbstractVector
57-
end
58-
59-
function QueryPolygon(pypoly::Py)
60-
@pyexec """
61-
def py_tmp(poly, np):
62-
rings = []
63-
exterior = np.array(poly.exterior.coords)
64-
rings.append(exterior)
65-
for interior in poly.interiors:
66-
hole = np.array(interior.coords)
67-
rings.append(hole)
68-
return rings
69-
""" => py_tmp
70-
coord = py2ju(Vector, py_tmp(pypoly, np))
71-
return QueryPolygon(pypoly, coord)
72-
end
36+
include(joinpath(@__DIR__, "fileio/geojson.jl"))
37+
include(joinpath(@__DIR__, "fileio/stl.jl"))
7338

7439
include(joinpath(@__DIR__, "utils.jl"))
7540
include(joinpath(@__DIR__, "polygon.jl"))

src/discretization/_polygon.jl

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#==========================================================================================+
2+
| TABLE OF CONTENTS: |
3+
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4+
| - get_pts |
5+
+==========================================================================================#
6+
17
export get_pts
28

39
"""
@@ -66,15 +72,9 @@ pts = get_pts(stl_data, 0.1; fill=true) # returns points in a filled grid patter
6672
"""
6773
function get_pts(stl_data::STLInfo2D, h::Real; fill::Bool=true)
6874
h > 0 || error("h must be positive")
69-
vertices = stl_data.py_vertices
70-
triangles = stl_data.py_triangles
71-
@pyexec """
72-
def py_tmp(vertices, triangles, shapely):
73-
tris = vertices[triangles]
74-
tris_2d = tris[:, :, :2]
75-
polygons = [shapely.Polygon(pts) for pts in tris_2d]
76-
return shapely.unary_union(polygons)
77-
""" => py_tmp
78-
polygon = QueryPolygon(py_tmp(vertices, triangles, shapely))
75+
triangle_coords_2d = stl_data.py_vertices[stl_data.py_triangles]
76+
tris2d = shapely.polygons(triangle_coords_2d)
77+
region = shapely.unary_union(tris2d)
78+
polygon = QueryPolygon(region)
7979
return get_pts(polygon, h; fill=fill)
8080
end

src/fileio/geojson.jl

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#==========================================================================================+
2+
| TABLE OF CONTENTS: |
3+
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4+
| struct: |
5+
| - QueryPolygon |
6+
+------------------------------------------------------------------------------------------+
7+
| function: |
8+
| - read_polygon |
9+
| - write_polygon |
10+
+==========================================================================================#
11+
12+
export QueryPolygon
13+
export read_polygon, write_polygon
14+
15+
struct QueryPolygon
16+
polygon::Py
17+
coord ::AbstractVector
18+
end
19+
20+
function QueryPolygon(pypoly::Py)
21+
@pyexec """
22+
def py_tmp(poly, np):
23+
rings = []
24+
exterior = np.array(poly.exterior.coords)
25+
rings.append(exterior)
26+
for interior in poly.interiors:
27+
hole = np.array(interior.coords)
28+
rings.append(hole)
29+
return rings
30+
""" => py_tmp
31+
coord = py2ju(Vector, py_tmp(pypoly, np))
32+
return QueryPolygon(pypoly, coord)
33+
end
34+
35+
"""
36+
write_polygon(polygon::QueryPolygon, file_path::String)
37+
38+
Description:
39+
---
40+
Write the polygon to a GeoJSON file. The `polygon` is an instance of `QueryPolygon`, and
41+
`file_path` is the path where the GeoJSON file will be saved.
42+
43+
Example:
44+
---
45+
```julia
46+
polygon_xy = [0 0; 1 0; 1 1; 0 1]' # 2xN array
47+
poly = get_polygon(polygon_xy, ratio=1) # poly.polygon to visualize
48+
write_polygon(poly, "/path/to/polygon.geojson")
49+
# or
50+
write_polygon(poly, "/path/to/polygon") # will automatically add .geojson
51+
"""
52+
function write_polygon(polygon::QueryPolygon, file_path::String)
53+
if !endswith(lowercase(file_path), ".geojson")
54+
file_path *= ".geojson"
55+
end
56+
@pyexec """
57+
def py_tmp(poly, filename, json, shapely):
58+
feature = {
59+
"type": "Feature",
60+
"geometry": shapely.geometry.mapping(poly),
61+
"properties": {}
62+
}
63+
geojson = {
64+
"type": "FeatureCollection",
65+
"features": [feature]
66+
}
67+
with open(filename, "w") as f:
68+
json.dump(geojson, f, indent=2)
69+
""" => py_tmp
70+
py_tmp(polygon.polygon, file_path, pyjson, shapely)
71+
@info """geojson file saved at:
72+
$file_path
73+
"""
74+
end
75+
76+
"""
77+
read_polygon(file_path::String)
78+
79+
Description:
80+
---
81+
Read a polygon from a GeoJSON file. The `file_path` is the path to the GeoJSON file.
82+
83+
Example:
84+
---
85+
```julia
86+
polygon = read_polygon("/path/to/polygon.geojson")
87+
```
88+
"""
89+
function read_polygon(file_path::String)
90+
isfile(file_path) || error("file_path should be a valid file path")
91+
endswith(lowercase(file_path), ".geojson") || error("file_path is not a .geojson file")
92+
@pyexec """
93+
def py_tmp(filename, json, shapely):
94+
with open(filename, "r") as f:
95+
data = json.load(f)
96+
feature = data["features"][0]
97+
geometry = feature["geometry"]
98+
polygon = shapely.geometry.shape(geometry)
99+
return polygon
100+
""" => py_tmp
101+
return QueryPolygon(py_tmp(file_path, pyjson, shapely))
102+
end

src/fileio/stl.jl

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#==========================================================================================+
2+
| TABLE OF CONTENTS: |
3+
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4+
| struct: |
5+
| - STLInfo2D |
6+
| - STLInfo3D |
7+
+------------------------------------------------------------------------------------------+
8+
| function: |
9+
| - readSTL2D |
10+
| - readSTL3D |
11+
+==========================================================================================#
12+
13+
export STLInfo2D, STLInfo3D
14+
export readSTL2D, readSTL3D
15+
16+
struct STLInfo2D
17+
mesh ::Py
18+
vmin ::Vector
19+
vmax ::Vector
20+
py_vertices ::Py
21+
py_triangles::Py
22+
stl_path ::String
23+
end
24+
25+
function Base.show(io::IO, info::STLInfo2D)
26+
println(io, "STLInfo2D:")
27+
println(io, " Path : ", info.stl_path)
28+
println(io, " Vertices : ", info.py_vertices.shape[0])
29+
println(io, " Triangles: ", info.py_triangles.shape[0])
30+
println(io, " X-Y Min : (", info.vmin[1], ", ", info.vmin[2], ")")
31+
println(io, " X-Y Max : (", info.vmax[1], ", ", info.vmax[2], ")")
32+
end
33+
34+
struct STLInfo3D
35+
mesh ::Py
36+
vmin ::Vector
37+
vmax ::Vector
38+
py_vertices ::Py
39+
py_triangles::Py
40+
stl_path ::String
41+
end
42+
43+
function Base.show(io::IO, info::STLInfo3D)
44+
println(io, "STLInfo3D:")
45+
println(io, " Path : ", info.stl_path)
46+
println(io, " Vertices : ", info.py_vertices.shape[0])
47+
println(io, " Triangles: ", info.py_triangles.shape[0])
48+
println(io, " X-Y-Z Min: (", info.vmin[1], ", ", info.vmin[2], ", ", info.vmin[3], ")")
49+
println(io, " X-Y-Z Max: (", info.vmax[1], ", ", info.vmax[2], ", ", info.vmax[3], ")")
50+
end
51+
52+
"""
53+
readSTL2D(stl_file::String)
54+
55+
Description:
56+
---
57+
Read a STL file as a 2D object (z=0) containing the mesh, bounding box, vertices, and
58+
triangles.
59+
60+
Example:
61+
---
62+
```julia
63+
stl_info = readSTL2D("path/to/your/file.stl")
64+
```
65+
"""
66+
function readSTL2D(stl_file)
67+
isfile(stl_file) || error("stl_file should be a valid file path")
68+
mesh = o3d.io.read_triangle_mesh(stl_file)
69+
rows = pyslice(nothing, nothing, nothing)
70+
cols = pyslice(nothing, 2, nothing)
71+
vertices = np.asarray(mesh.vertices)[rows, cols]
72+
triangles = np.asarray(mesh.triangles)
73+
aabb = mesh.get_axis_aligned_bounding_box()
74+
vmin = py2ju(Vector, aabb.get_min_bound())[1:2]
75+
vmax = py2ju(Vector, aabb.get_max_bound())[1:2]
76+
return STLInfo2D(mesh, vmin, vmax, vertices, triangles, stl_file)
77+
end
78+
79+
"""
80+
readSTL3D(stl_file::String)
81+
82+
Description:
83+
---
84+
Read a STL file as a 3D object containing the mesh, bounding box, vertices, and triangles.
85+
86+
Example:
87+
---
88+
```julia
89+
stl_info = readSTL3D("path/to/your/file.stl")
90+
```
91+
"""
92+
function readSTL3D(stl_file)
93+
isfile(stl_file) || error("stl_file should be a valid file path")
94+
mesh = o3d.io.read_triangle_mesh(stl_file)
95+
vertices = np.asarray(mesh.vertices)
96+
triangles = np.asarray(mesh.triangles)
97+
aabb = mesh.get_axis_aligned_bounding_box()
98+
vmin = py2ju(Vector, aabb.get_min_bound())
99+
vmax = py2ju(Vector, aabb.get_max_bound())
100+
return STLInfo3D(mesh, vmin, vmax, vertices, triangles, stl_file)
101+
end

src/polygon.jl

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function pip_query(
3333
edge ::Bool=false
3434
)
3535
func = edge ? shapely.intersects_xy : shapely.contains_xy
36-
return pyconvert(Bool, func(polygon.py_xy, px, py).item())
36+
return py2ju(Bool, func(polygon.py_xy, px, py).item())
3737
end
3838

3939
"""
@@ -65,13 +65,14 @@ function pip_query(
6565
py_points = shapely.points(points')
6666

6767
# check if the particle is inside the polygon
68+
println("\e[1;36m[start]:\e[0m querying points inside the polygon")
6869
if edge
6970
mask = shapely.covers(polygon.polygon, py_points)
7071
else
7172
mask = shapely.within(py_points, polygon.polygon)
7273
end
7374

74-
return pyconvert(Vector{Bool}, mask)
75+
return PyArray(mask)
7576
end
7677

7778
"""
@@ -99,25 +100,17 @@ function pip_query(
99100
)
100101
size(points, 1) == 2 || error("points must be a 2xN array")
101102
py_points = shapely.points(points')
103+
triangle_coords_2d = stl_model.py_vertices[stl_model.py_triangles]
104+
tris2d = shapely.polygons(triangle_coords_2d)
105+
region = shapely.unary_union(tris2d)
106+
107+
# check if the particle is inside the polygon
108+
println("\e[1;36m[start]:\e[0m querying points inside the STL model")
102109
if edge
103-
@pyexec """
104-
def py_pip(vertices, triangles, py_points, shapely):
105-
triangle_coords_2d = vertices[triangles][:, :, :2]
106-
tris2d = shapely.polygons(triangle_coords_2d)
107-
region = shapely.unary_union(tris2d)
108-
mask = shapely.covers(region, py_points)
109-
return mask
110-
""" => py_pip
110+
mask = shapely.covers(region, py_points)
111111
else
112-
@pyexec """
113-
def py_pip(vertices, triangles, py_points, shapely):
114-
triangle_coords_2d = vertices[triangles][:, :, :2]
115-
tris2d = shapely.polygons(triangle_coords_2d)
116-
region = shapely.unary_union(tris2d)
117-
mask = shapely.within(py_points, region)
118-
return mask
119-
""" => py_pip
112+
mask = shapely.within(py_points, region)
120113
end
121-
tmp = py_pip(stl_model.py_vertices, stl_model.py_triangles, py_points, shapely)
122-
return pyconvert(Vector{Bool}, tmp)
114+
115+
return PyArray(mask)
123116
end

0 commit comments

Comments
 (0)