190 lines
6.3 KiB
Python
190 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Export MeshData2x.mat (Fem4) or MeshData2.mat (Fem2) to OpticsFEM Portmesh_*.dat."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
try:
|
|
import scipy.io as sio
|
|
except ImportError:
|
|
sio = None
|
|
|
|
|
|
def _load_mat_mesh(mat_path: Path) -> object:
|
|
"""Load Mesh struct from .mat (v7 or v7.3)."""
|
|
if sio is not None:
|
|
try:
|
|
data = sio.loadmat(str(mat_path), squeeze_me=True, struct_as_record=False)
|
|
return data["Mesh"]
|
|
except NotImplementedError:
|
|
pass
|
|
import h5py
|
|
|
|
class MeshView:
|
|
pass
|
|
|
|
mesh = MeshView()
|
|
with h5py.File(str(mat_path), "r") as f:
|
|
m = f["Mesh"]
|
|
mesh.Nodes = np.asarray(m["Nodes"]).T
|
|
mesh.Elements = np.asarray(m["Elements"]).T
|
|
mesh.Domains = np.asarray(m["Domains"]).reshape(-1)
|
|
mesh.Faces = np.asarray(m["Faces"]).T
|
|
mesh.FacesIndex = np.asarray(m["FacesIndex"]).reshape(-1)
|
|
return mesh
|
|
|
|
|
|
def build_mesh_from_mat(mat_path: Path) -> dict:
|
|
mesh = _load_mat_mesh(mat_path)
|
|
vertex = np.asarray(mesh.Nodes, dtype=float)
|
|
tet_raw = np.asarray(mesh.Elements, dtype=int)
|
|
domain_of_tet = np.asarray(mesh.Domains, dtype=int).reshape(-1, 1)
|
|
tri_raw = np.asarray(mesh.Faces, dtype=int)
|
|
domain_of_tri = np.asarray(mesh.FacesIndex, dtype=int).reshape(-1, 1)
|
|
|
|
nbr_vertex = vertex.shape[0]
|
|
nbr_tet = tet_raw.shape[0]
|
|
# Keep MATLAB Elements vertex order — sorting breaks Nedelec local edge BF mapping.
|
|
tet = np.asarray(tet_raw, dtype=np.int32)
|
|
|
|
# Match MATLAB PhysicMatrixAssembly: unique(...,'rows') sorts edge pairs
|
|
# lexicographically; EdgesOfElements maps to that sorted order (not first-seen).
|
|
el2no = tet.T
|
|
n1 = el2no[[0, 0, 0, 1, 1, 2], :]
|
|
n2 = el2no[[1, 2, 3, 2, 3, 3], :]
|
|
el_ed2no = np.column_stack([n1.reshape(-1, order="F"), n2.reshape(-1, order="F")])
|
|
edge, ic = np.unique(el_ed2no, axis=0, return_inverse=True)
|
|
edge = edge.astype(np.int32)
|
|
edge_of_tet = (ic.reshape(6, -1, order="F").T + 1).astype(np.int32)
|
|
tri = np.asarray(tri_raw, dtype=np.int32)
|
|
|
|
el2no = tet.T
|
|
rows_f1 = [0, 0, 0, 1]
|
|
rows_f2 = [1, 1, 2, 2]
|
|
rows_f3 = [2, 3, 3, 3]
|
|
f1 = el2no[rows_f1, :].reshape(-1, order="F")
|
|
f2 = el2no[rows_f2, :].reshape(-1, order="F")
|
|
f3 = el2no[rows_f3, :].reshape(-1, order="F")
|
|
el_face_array = np.column_stack([f1, f2, f3])
|
|
|
|
conn_of_tri = np.zeros((tri.shape[0], 2), dtype=np.int32)
|
|
for i in range(tri.shape[0]):
|
|
tri_sorted = np.sort(tri[i])
|
|
matches = np.where(np.all(np.sort(el_face_array, axis=1) == tri_sorted, axis=1))[0]
|
|
if matches.size == 0:
|
|
raise RuntimeError(f"boundary tri {i + 1} not found in tet faces")
|
|
index = int(matches[0]) + 1
|
|
conn_of_tri[i, 0] = (index - 1) // 4 + 1
|
|
conn_of_tri[i, 1] = index - (conn_of_tri[i, 0] - 1) * 4
|
|
|
|
norm_of_face = np.zeros((24, 3), dtype=float)
|
|
norm_of_face[0] = [-1, 0, 0]
|
|
norm_of_face[1] = [0, -1, 0]
|
|
norm_of_face[2] = [0, 0, -1]
|
|
norm_of_face[3] = [0, 0, 1]
|
|
norm_of_face[4] = [0, 1, 0]
|
|
norm_of_face[23] = [1, 0, 0]
|
|
|
|
return {
|
|
"NbrVertex": np.int32(nbr_vertex),
|
|
"Vertex": vertex,
|
|
"NbrTet": np.int32(nbr_tet),
|
|
"DomainOfTet": domain_of_tet,
|
|
"Tet": tet.astype(np.int32),
|
|
"NbrEdge": np.int32(edge.shape[0]),
|
|
"Edge": edge,
|
|
"EdgeOfTet": edge_of_tet,
|
|
"NbrTri": np.int32(tri.shape[0]),
|
|
"Tri": tri.astype(np.int32),
|
|
"DomainOfTri": domain_of_tri,
|
|
"ConnOfTri": conn_of_tri,
|
|
"NormOfFace": norm_of_face,
|
|
}
|
|
|
|
|
|
def write_dat(mesh: dict, dat_path: Path) -> None:
|
|
lines: list[str] = []
|
|
lines.append("NbrVertex")
|
|
lines.append(str(int(mesh["NbrVertex"])))
|
|
lines.append("Vertex")
|
|
for row in mesh["Vertex"]:
|
|
lines.append(f"{row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
|
|
|
|
lines.append("NbrTet")
|
|
lines.append(str(int(mesh["NbrTet"])))
|
|
lines.append("Tet")
|
|
for row in mesh["Tet"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])}")
|
|
|
|
lines.append("DomainOfTet")
|
|
for v in mesh["DomainOfTet"].reshape(-1):
|
|
lines.append(str(int(v)))
|
|
|
|
lines.append("NbrEdge")
|
|
lines.append(str(int(mesh["NbrEdge"])))
|
|
lines.append("Edge")
|
|
for row in mesh["Edge"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])}")
|
|
|
|
lines.append("EdgeOfTet")
|
|
for row in mesh["EdgeOfTet"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])} {int(row[4])} {int(row[5])}")
|
|
|
|
lines.append("NbrTri")
|
|
lines.append(str(int(mesh["NbrTri"])))
|
|
lines.append("Tri")
|
|
for row in mesh["Tri"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])}")
|
|
|
|
lines.append("DomainOfTri")
|
|
for v in mesh["DomainOfTri"].reshape(-1):
|
|
lines.append(str(int(v)))
|
|
|
|
lines.append("ConnOfTri")
|
|
for row in mesh["ConnOfTri"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])}")
|
|
|
|
lines.append("NormOfFace")
|
|
active_norms = [
|
|
(idx + 1, row)
|
|
for idx, row in enumerate(mesh["NormOfFace"])
|
|
if np.linalg.norm(row) > 0.0
|
|
]
|
|
lines.append(str(len(active_norms)))
|
|
for domain_id, row in active_norms:
|
|
lines.append(f"{int(domain_id)} {row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
|
|
|
|
dat_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--mat",
|
|
type=Path,
|
|
default=Path(__file__).resolve().parents[2]
|
|
/ "三维matlab代码/2023-2-端口激励问题(四面体网格)/MeshData2x.mat",
|
|
)
|
|
parser.add_argument(
|
|
"--out",
|
|
type=Path,
|
|
default=Path(__file__).resolve().parents[1] / "port/Release/Portmesh_fem4.dat",
|
|
)
|
|
args = parser.parse_args()
|
|
# If out still defaults to fem4 but mat is MeshData2, suggest fem2 name
|
|
if args.out.name == "Portmesh_fem4.dat" and "MeshData2.mat" in args.mat.name and "MeshData2x" not in args.mat.name:
|
|
args.out = args.out.with_name("Portmesh_fem2.dat")
|
|
mesh = build_mesh_from_mat(args.mat)
|
|
args.out.parent.mkdir(parents=True, exist_ok=True)
|
|
write_dat(mesh, args.out)
|
|
print(f"Wrote {args.out} (V={mesh['NbrVertex']}, T={mesh['NbrTet']}, E={mesh['NbrEdge']})")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|