-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathslf2msh.py
More file actions
executable file
·185 lines (152 loc) · 6.22 KB
/
slf2msh.py
File metadata and controls
executable file
·185 lines (152 loc) · 6.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python3
"""
Convert TELEMAC SLF to Gmsh .msh with interpolation.
Usage: python slf2msh_interp.py target_mesh.slf --interpolate source_results.slf
"""
import xarray as xr
import numpy as np
import os
import sys
import argparse
from scipy.interpolate import griddata
# ---------------------------------------------------------------------
# 1. Helper: Write Gmsh NodeData
# ---------------------------------------------------------------------
def write_node_data(f, name, values, timestep=0):
values = np.asarray(values)
n = len(values)
# Handle scalar vs vector shapes
if values.ndim == 1:
ncomp = 1
else:
ncomp = values.shape[1]
f.write("$NodeData\n")
f.write("1\n") # Num string tags
f.write(f'"{name}"\n') # Name
f.write("1\n") # Num real tags
f.write(f"{float(timestep)}\n")
f.write("3\n") # Num int tags
f.write(f"{timestep}\n") # Time step
f.write(f"{ncomp}\n") # Num components
f.write(f"{n}\n") # Num nodes
if ncomp == 1:
for i, v in enumerate(values, start=1):
f.write(f"{i} {float(v)}\n")
else:
for i, row in enumerate(values, start=1):
row_floats = " ".join(str(float(x)) for x in row)
f.write(f"{i} {row_floats}\n")
f.write("$EndNodeData\n")
# ---------------------------------------------------------------------
# 2. Helper: Write Full MSH
# ---------------------------------------------------------------------
def write_gmsh_with_fields(x, y, tri, fields, outfile, timestep=0):
with open(outfile, "w") as f:
# Header
f.write("$MeshFormat\n2.2 0 8\n$EndMeshFormat\n")
# Nodes
f.write(f"$Nodes\n{len(x)}\n")
for i, (xx, yy) in enumerate(zip(x, y), start=1):
# Gmsh expects 3D coords (x, y, z). We set z=0.0
f.write(f"{i} {xx} {yy} 0.0\n")
f.write("$EndNodes\n")
# Elements (Triangles)
f.write(f"$Elements\n{len(tri)}\n")
for i, (a, b, c) in enumerate(tri, start=1):
# 2 = Triangle 3-node, 0 tags, node indices (1-based)
f.write(f"{i} 2 0 {a + 1} {b + 1} {c + 1}\n")
f.write("$EndElements\n")
# Fields
for name, arr in fields.items():
print(f" -> Writing field: {name}")
write_node_data(f, name, arr, timestep)
print(f"\n[Success] Output written to: {outfile}")
# ---------------------------------------------------------------------
# 3. Main Logic
# ---------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Map fields from a source SLF to a target SLF and export to Gmsh."
)
parser.add_argument("infile", help="Target SLF (Geometry/Fine Mesh)")
parser.add_argument(
"-i", "--interpolate", required=True, help="Source SLF (Coarse Results)"
)
parser.add_argument(
"-t",
"--timestep",
type=int,
default=-1,
help="Timestep to extract (-1 for last step, default)",
)
args = parser.parse_args()
# --- Load Target (Geometry) ---
print(f"[1/3] Loading Target Mesh: {args.infile}")
try:
ds_tgt = xr.open_dataset(args.infile, engine="selafin")
except Exception as e:
print(
f"Error: Could not open {args.infile}. Ensure 'selafin' engine is installed."
)
sys.exit(1)
x_tgt = ds_tgt["x"].values
y_tgt = ds_tgt["y"].values
# Handle connectivity (ikle2)
if "ikle2" in ds_tgt.attrs:
tri = np.asarray(ds_tgt.attrs["ikle2"], dtype=int) - 1
else:
print("Error: Target file missing 'ikle2' connectivity attribute.")
sys.exit(1)
# Initialize fields with whatever exists in Target (e.g., Bottom)
fields = {}
# Filter out coords/time
tgt_vars = [v for v in ds_tgt.variables if v not in ("x", "y", "time")]
# Load target data (usually only Geometry/Bottom)
for name in tgt_vars:
val = ds_tgt[name].values
# If variable has time dimension, take the requested step (or 0)
if val.ndim > 1:
idx = args.timestep if args.timestep >= 0 else -1
fields[name] = val[idx]
else:
fields[name] = val
# --- Load Source (Results) & Interpolate ---
if args.interpolate:
print(f"[2/3] Interpolating from: {args.interpolate}")
ds_src = xr.open_dataset(args.interpolate, engine="selafin")
x_src = ds_src["x"].values
y_src = ds_src["y"].values
# Prepare coordinates for Griddata (N, 2)
points_src = np.column_stack((x_src, y_src))
points_tgt = np.column_stack((x_tgt, y_tgt))
src_vars = [v for v in ds_src.variables if v not in ("x", "y", "time")]
# Determine source timestep
t_idx = args.timestep if args.timestep >= 0 else -1
real_time = ds_src["time"].values[t_idx]
print(f" Using Source Time: {real_time} s (Index: {t_idx})")
for name in src_vars:
# ONLY interpolate if target doesn't already have it
# This protects the high-res 'BOTTOM' from being overwritten by low-res 'BOTTOM'
if name not in fields:
print(f" ... Interpolating {name}")
val_src = ds_src[name].values
# Extract specific timestep data
data_src = val_src[t_idx] if val_src.ndim > 1 else val_src
# 1. Linear Interpolation (Accurate inside hull)
interp_val = griddata(points_src, data_src, points_tgt, method="linear")
# 2. Nearest Neighbor (Fill NaNs at boundaries)
if np.isnan(interp_val).any():
mask_nan = np.isnan(interp_val)
fill_val = griddata(
points_src, data_src, points_tgt[mask_nan], method="nearest"
)
interp_val[mask_nan] = fill_val
fields[name] = interp_val
else:
print(f" ... Skipping {name} (exists in target)")
# --- Write Output ---
outfile = os.path.splitext(args.infile)[0] + ".msh"
print(f"[3/3] Exporting to Gmsh...")
write_gmsh_with_fields(x_tgt, y_tgt, tri, fields, outfile, timestep=0)
if __name__ == "__main__":
main()