Skip to content

Commit cdb6ea5

Browse files
authored
Merge pull request #30 from ZenanH/main
🎉 Release new version v0.1.23
2 parents 53c253c + 846d0ed commit cdb6ea5

23 files changed

Lines changed: 3908 additions & 652 deletions

CondaPkg.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ numpy = "==2.2.4"
77
rtree = "==1.4.0"
88
python-gmsh = "==4.13.1"
99
trimesh = "==4.6.6"
10-
shapely = "==2.0.7"
10+
shapely = "==2.1.0"
1111

1212
[pip.deps]
1313
rasterio = "==1.4.1"

Project.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
name = "MaterialPointGenerator"
22
uuid = "18deba1c-5063-41ee-a388-01f264c5a914"
33
authors = ["ZenanH <zenan.huo@outlook.com>"]
4-
version = "0.1.22"
4+
version = "0.1.23"
55

66
[deps]
77
CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
88
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
9-
Gmsh = "705231aa-382f-11e9-3f0c-b7cb4346fdeb"
10-
GMT = "5752ebe1-31b9-557e-87aa-f909b540aa54"
9+
LibGEOS = "a90b1aa1-3769-5649-ba7e-abc5a9d163eb"
1110
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1211
LiveServer = "16fef848-5104-11e9-1b77-fb7a48bbb589"
1312
NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
@@ -19,8 +18,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1918
[compat]
2019
CondaPkg = "0.2"
2120
DelimitedFiles = "1"
22-
Gmsh = "0.3"
23-
GMT = "1.28"
21+
LibGEOS = "0.9"
2422
LiveServer = "1.5"
2523
NearestNeighbors = "0.4"
2624
PrecompileTools = "1.2"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![CI](https://github.com/LandslideSIM/MaterialPointGenerator.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/LandslideSIM/MaterialPointGenerator.jl/actions/workflows/ci.yml)
44
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg?logo=quicklook)](https://LandslideSIM.github.io/MaterialPointGenerator.jl/stable)
55
[![Dev](https://img.shields.io/badge/docs-dev-red.svg?logo=quicklook)](https://LandslideSIM.github.io/MaterialPointGenerator.jl/dev)
6-
[![Version](https://img.shields.io/badge/version-v0.1.22-pink)]()
6+
[![Version](https://img.shields.io/badge/version-v0.1.23-pink)]()
77

88
During the EGU2023 conference, when I presented a high-performance MPM (Material Point Method) solver, I was asked,
99
"How do you discretize the computational model for the MPM?" I didn't have a clear answer (I didn't even consider it a problem) because the models were relatively simple and could be generated directly using some straightforward functions. However, as computational models gradually became more complex and diverse, I began to realize that this was indeed a very good question. The preprocessing for MPM should not be a computationally intensive task; it should be fast enough. Yet, I couldn't find a "plug-and-play" generalized code for this purpose. Some literatures have contributed to this issue, and I built upon their work to create a comprehensive and refined julia package.
257 KB
Loading

docs/src/workflow/DEM.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ The DEM file is a simple three-column array. Each DEM must be rasterized to ensu
1414

1515
```@docs
1616
rasterizeDEM(
17-
dem ::Matrix{T2},
17+
dem ::AbstractMatrix{T2},
1818
h ::T2;
19-
k ::T1 = 10,
20-
p ::T1 = 2,
21-
trimbounds::Matrix{T2}= [0.0 0.0],
22-
dembounds ::Vector{T2}= [0.0, 0.0]
19+
k ::T1 = 10,
20+
p ::T1 = 2,
21+
trimbounds::AbstractMatrix{T2} = [0.0 0.0],
22+
dembounds ::AbstractVector{T2} = [0.0, 0.0]
2323
) where {T1, T2}
2424
```
2525

@@ -31,9 +31,9 @@ Suppose we have a DEM and we want to close it with a base plane, for example, at
3131

3232
```@docs
3333
dem2particle(
34-
dem ::Matrix{T2},
35-
h ::T2,
36-
bottom::T2
34+
dem ::AbstractMatrix{T2},
35+
h ::Real,
36+
bottom::Real
3737
) where T2
3838
```
3939

@@ -43,9 +43,9 @@ If the base used to close DEM-1 is not a flat surface, we can designate another
4343

4444
```@docs
4545
dem2particle(
46-
dem ::Matrix{T2},
47-
h ::T2,
48-
bottom::Matrix{T2}
46+
dem ::AbstractMatrix{T2},
47+
h ::Real,
48+
bottom::AbstractMatrix{T2}
4949
) where T2
5050
```
5151

@@ -63,10 +63,10 @@ To prepare for this, you need to have layered surface files in DEM format, which
6363

6464
```@docs
6565
dem2particle(
66-
dem ::Matrix{T2},
67-
h ::T2,
68-
bottom::T2,
69-
layer ::Vector{Matrix{T2}}
66+
dem ::AbstractMatrix{T2},
67+
h ::Real,
68+
bottom::Real,
69+
layer ::AbstractVector{AbstractMatrix{T2}}
7070
) where T2
7171
```
7272

@@ -76,9 +76,9 @@ This workflow also supports the case where a bottom DEM is provided:
7676

7777
```@docs
7878
dem2particle(
79-
dem ::Matrix{T2},
80-
h ::T2,
81-
bottom::Matrix{T2},
79+
dem ::AbstractMatrix{T2},
80+
h ::Real,
81+
bottom::AbstractMatrix{T2},
8282
layer ::Vector{Matrix{T2}}
8383
) where T2
8484
```

docs/src/workflow/polygon.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ For the second scenario, to support complex polygons in any situation, we need t
1111
We are preparing to discretize a two-dimensional pentagon, noting that the specified distance is between the material points in both the x and y directions.
1212

1313
```@docs
14-
polygon2particle(polygon, lpx, lpy)
14+
polygon2particle(polygon::AbstractMatrix{T}, lpx, lpy) where T<:Real
1515
```
1616

1717
```julia
@@ -39,7 +39,7 @@ julia> pts = polygon2particle(polygon, 0.01, 0.01)
3939
Please use pre-processing tools like Gmsh or MeshLab to ensure that the current mesh is closed in advance.
4040

4141
```@docs
42-
polygon2particle(stl_file::String, output_file::String, h; verbose::Bool=true)
42+
polygon2particle(stl_file::String, output_file::String, h; verbose::Bool=false)
4343
```
4444

4545
## Advanced
@@ -49,7 +49,11 @@ This involves some advanced operations for partitioning the generated material p
4949
For the first case, we can utilize a practical function to check if a point is inside the polygon.
5050

5151
```@docs
52-
particle_in_polygon(px::T, py::T, polygon) where T
52+
particle_in_polygon(
53+
polygon::AbstractMatrix{T},
54+
px ::Real,
55+
py ::Real
56+
) where T<:Real
5357
```
5458

5559
For the second case, in addition to the `.stl` file, we also need to provide a `.msh` file.
@@ -61,6 +65,6 @@ polygon2particle(
6165
output_file::String,
6266
nid_file ::String,
6367
h;
64-
verbose ::Bool=true
68+
verbose ::Bool=false
6569
)
6670
```

src/MaterialPointGenerator.jl

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,20 @@
1111

1212
module MaterialPointGenerator
1313

14-
using CondaPkg, DelimitedFiles, Gmsh, NearestNeighbors, Printf, PythonCall
14+
using CondaPkg, DelimitedFiles, LibGEOS, NearestNeighbors, Printf, PythonCall
1515
using LinearAlgebra: mul!, eigen, Symmetric, normalize
1616
using Statistics: mean
17-
using GMT: concavehull
1817
using LiveServer: serve
1918
using PrecompileTools: @setup_workload, @compile_workload
2019

2120
import LinearAlgebra.LAPACK: syev!
2221

2322
const trimesh = PythonCall.pynew()
2423
const np = PythonCall.pynew()
25-
const o3d = PythonCall.pynew()
24+
const rtree = PythonCall.pynew()
25+
const shapely = PythonCall.pynew()
2626
const MultiPolygon = PythonCall.pynew()
2727
const Polygon = PythonCall.pynew()
28-
const LineString = PythonCall.pynew()
2928
const Point = PythonCall.pynew()
3029
const mapping = PythonCall.pynew()
3130
const unary_union = PythonCall.pynew()
@@ -36,29 +35,26 @@ const pygmsh = PythonCall.pynew()
3635
const pyKDTree = PythonCall.pynew()
3736
const pytime = PythonCall.pynew()
3837
const embreex = PythonCall.pynew()
39-
const ConvexHull = PythonCall.pynew()
40-
const v_contains = PythonCall.pynew()
4138

4239
function __init__()
4340
@info "checking environment..."
4441
# import Python modules
4542
PythonCall.pycopy!(trimesh , pyimport("trimesh" ))
4643
PythonCall.pycopy!(np , pyimport("numpy" ))
44+
PythonCall.pycopy!(rtree , pyimport("rtree" ))
45+
PythonCall.pycopy!(shapely , pyimport("shapely" ))
4746
PythonCall.pycopy!(rasterio, pyimport("rasterio"))
4847
PythonCall.pycopy!(pygmsh , pyimport("gmsh" ))
4948
PythonCall.pycopy!(pytime , pyimport("time" ))
5049
# import their submudules
5150
PythonCall.pycopy!(Polygon , pyimport("shapely.geometry" ).Polygon )
52-
PythonCall.pycopy!(LineString , pyimport("shapely.geometry" ).LineString )
5351
PythonCall.pycopy!(Point , pyimport("shapely.geometry" ).Point )
5452
PythonCall.pycopy!(mapping , pyimport("shapely.geometry" ).mapping )
5553
PythonCall.pycopy!(unary_union , pyimport("shapely.ops" ).unary_union )
5654
PythonCall.pycopy!(rotate , pyimport("shapely.affinity" ).rotate )
5755
PythonCall.pycopy!(rasterize , pyimport("rasterio.features" ).rasterize )
5856
PythonCall.pycopy!(pyKDTree , pyimport("scipy.spatial" ).cKDTree )
5957
PythonCall.pycopy!(MultiPolygon, pyimport("shapely.geometry" ).MultiPolygon)
60-
PythonCall.pycopy!(ConvexHull , pyimport("scipy.spatial" ).ConvexHull )
61-
PythonCall.pycopy!(v_contains , pyimport("shapely.vectorized").contains )
6258
if !Sys.isapple()
6359
try
6460
if !haskey(CondaPkg.current_pip_packages(), "embreex")
@@ -94,6 +90,8 @@ include(joinpath(@__DIR__, "utils.jl" ))
9490
@compile_workload begin
9591
# functions in utils.jl
9692
getnormals(points)
93+
# function in dem.jl
94+
getpolygon(rand(10, 2))
9795
end
9896
end
9997

src/_polyhedron/_utils.jl

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,20 @@ end
3838
areaPBC = cross2D(v2x - px, v2y - py, v3x - px , v3y - py ) / areaABC
3939
areaAPC = cross2D(px - v1x, py - v1y, v3x - v1x, v3y - v1y) / areaABC
4040

41-
alpha = areaPBC
42-
beta = areaAPC
43-
gamma = 1 - alpha - beta
41+
α = areaPBC
42+
β = areaAPC
43+
γ = 1 - α - β
4444

4545
# 3) 用 (alpha,beta,gamma) 插值三顶点的 z 值
46-
z = alpha*v1z + beta*v2z + gamma*v3z
46+
z = α*v1z + β*v2z + γ*v3z
4747

48-
return floor((z - mzmin) / h) * h + mzmin
48+
return z#, floor((z - mzmin) / h) * h + mzmin
4949
end
5050

51-
function check_projection!(meshdata::GmshMesh{T1, T2}, h::T2, p2t::Vector, mxmin::T2,
51+
function check_projection!(meshdata::DataMesh{T1, T2}, h::T2, p2t::Vector, mxmin::T2,
5252
mymin::T2, niy::T1) where {T1, T2}
5353

54+
p2t = copy(p2t)
5455
@inbounds for i in axes(meshdata.data, 1)
5556
v1x, v1y = meshdata.data[i][1], meshdata.data[i][2]
5657
v2x, v2y = meshdata.data[i][4], meshdata.data[i][5]
@@ -77,7 +78,7 @@ function check_projection!(meshdata::GmshMesh{T1, T2}, h::T2, p2t::Vector, mxmin
7778
return nothing
7879
end
7980

80-
function fill_voxel!(pts::Vector{Bool}, p2t::Vector, meshdata::GmshMesh{T1, T2}, niy::T1,
81+
function fill_voxel!(pts::Vector{Bool}, p2t::Vector, meshdata::DataMesh{T1, T2}, niy::T1,
8182
mxmin::T2, mymin::T2, mzmin::T2, mxmax::T2, mymax::T2, mzmax::T2, h::T2) where {T1, T2}
8283

8384
@inbounds for i in axes(p2t, 1)
@@ -93,8 +94,8 @@ function fill_voxel!(pts::Vector{Bool}, p2t::Vector, meshdata::GmshMesh{T1, T2},
9394
push!(tri_order, z)
9495
end
9596
sort!(unique!(tri_order))
96-
tri_order[ 1] = ceil( T1, (tri_order[ 1] - mzmin) / h) * h + mzmin
97-
tri_order[end] = floor(T1, (tri_order[end] - mzmin) / h) * h + mzmin
97+
#tri_order[ 1] = ceil( T1, (tri_order[ 1] - mzmin) / h) * h + mzmin
98+
#tri_order[end] = floor(T1, (tri_order[end] - mzmin) / h) * h + mzmin
9899
fill_layers!(pts, px, py, tri_order, mxmin, mymin, mzmin, mxmax, mymax, mzmax,
99100
h)
100101
end

src/_polyhedron/_workflow.jl

Lines changed: 20 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,26 @@
22
readmesh(mesh_file::String; precision="FP32")
33
Description:
44
---
5-
Read the mesh file (`.stl`) generated by Gmsh. The function will return the vertices and
6-
faces for triangle. It's necessary to check the mesh qulity before using this function.
7-
You can do it in MeshLab or Gmsh GUI. `precision` can be "FP64" or "FP32" in `String`.
5+
Read the mesh file (`.stl`). The function will return the vertices and faces for triangle.
6+
It's necessary to check the mesh qulity before using this function. You can do it in MeshLab
7+
or Gmsh GUI. `precision` can be "FP64" or "FP32" in `String`.
88
"""
9-
function readmesh(mesh_file; precision="FP32")::GmshMesh
9+
function readmesh(mesh_file; precision="FP32")::DataMesh
1010
T1 = precision == "FP32" ? Int32 : Int64
1111
T2 = precision == "FP32" ? Float32 : Float64
1212

13-
# Initialize gmsh
14-
gmsh.initialize()
15-
gmsh.option.setNumber("General.Terminal", 0)
16-
gmsh.option.setNumber("Mesh.Optimize", 1)
17-
gmsh.open(mesh_file)
13+
mesh = trimesh.load(mesh_file)
14+
pyconvert(Bool, mesh.is_watertight) || error(
15+
"The mesh is not watertight. Please check the mesh quality.")
1816

19-
# Optimize mesh to remove duplicate nodes
20-
gmsh.model.mesh.optimize("Netgen")
17+
coords = pyconvert(Array{T2}, mesh.vertices)
18+
triang = pyconvert(Array{T1}, mesh.faces) .+ 1
2119

22-
# Get mesh data
23-
node_tags, node_coords, _ = gmsh.model.mesh.getNodes()
24-
num_nodes = length(node_tags)
25-
vertices = reshape(node_coords, 3, num_nodes)
26-
27-
# check if the mesh is watertight
28-
element_types, element_tags, element_node_tags = gmsh.model.mesh.getElements()
29-
entities = gmsh.model.getEntities(2) # get all 2d entities
30-
boundary = gmsh.model.getBoundary(entities, true, true, true)
31-
isempty(boundary) || error("3D object mesh is not closed")
32-
33-
gmsh.finalize()
34-
@info "Mesh data loaded and checking now"
35-
36-
# Generate vertices and faces in Julia array
37-
triangle_type = 2
38-
triangle_index = findfirst(==(triangle_type), element_types)
39-
isnothing(triangle_index) && error("No triangles found in the mesh")
40-
41-
triangle_node_tags = element_node_tags[triangle_index]
42-
num_triangles = length(triangle_node_tags) ÷ 3
43-
44-
# Map node tags to consecutive indices
45-
node_map = Dict(tag => i for (i, tag) in enumerate(node_tags))
46-
faces = reshape(T1.([node_map[tag] for tag in triangle_node_tags]), 3, num_triangles)
47-
48-
maximum(faces) size(vertices, 2) && error("Invalid mesh data.")
49-
@info "Mesh data checked"
20+
v_tmp = Array{T2}(coords)
21+
zoffset = minimum(v_tmp[:, 3]) > 10 ? 0 : (10-minimum(v_tmp[:, 3]))
22+
v_tmp[:, 3] .+= zoffset
23+
vertices = Array{T2}(v_tmp')
24+
faces = Array{T1}(triang')
5025

5126
data = [Vector{T2}() for _ in 1:size(faces, 2)]
5227
@inbounds for i in axes(faces, 2), j in 1:3
@@ -59,18 +34,17 @@ function readmesh(mesh_file; precision="FP32")::GmshMesh
5934
xmax, ymax, zmax = map(x -> maximum(x), eachrow(vertices))
6035
bounds = [xmin, ymin, zmin, xmax, ymax, zmax]
6136

62-
return GmshMesh{T1, T2}(vertices, faces, data, bounds)
37+
return DataMesh{T1, T2}(vertices, faces, data, bounds, zoffset)
6338
end
6439

65-
function _voxelize(meshdata::GmshMesh{T1, T2}, h::Real) where {T1, T2}
40+
function _voxelize(meshdata::DataMesh{T1, T2}, h::Real) where {T1, T2}
6641
h > 0 || error("h must be positive")
6742
h = T2(h)
6843

6944
# get bbox of the entire mesh
70-
tmp = 4 .+ rand(T2, 6)
71-
mxmin, mxmax = T2(meshdata.bounds[1] - tmp[1]*h), T2(meshdata.bounds[4] + tmp[4]*h)
72-
mymin, mymax = T2(meshdata.bounds[2] - tmp[2]*h), T2(meshdata.bounds[5] + tmp[5]*h)
73-
mzmin, mzmax = T2(meshdata.bounds[3] - tmp[3]*h), T2(meshdata.bounds[6] + tmp[6]*h)
45+
mxmin, mxmax = T2(meshdata.bounds[1])-6, T2(meshdata.bounds[4]+6)
46+
mymin, mymax = T2(meshdata.bounds[2])-6, T2(meshdata.bounds[5]+6)
47+
mzmin, mzmax = T2(meshdata.bounds[3])-6, T2(meshdata.bounds[6]+6)
7448

7549
# get the number of elements in x, y, z directions
7650
nex = ceil(T1, (mxmax - mxmin) / h)
@@ -104,7 +78,7 @@ function _voxelize(meshdata::GmshMesh{T1, T2}, h::Real) where {T1, T2}
10478
end
10579

10680
function _voxelize(
107-
meshdata ::GmshMesh{T1, T2},
81+
meshdata ::DataMesh{T1, T2},
10882
msh_file ::String,
10983
output_xyz::String,
11084
output_nid::String,

0 commit comments

Comments
 (0)