quadros 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quadros/__init__.py +3 -0
- quadros/__main__.py +6 -0
- quadros/_legacy_extract_paraview_state.py +517 -0
- quadros/_legacy_render_nek_isosurface_views.py +2023 -0
- quadros/cli.py +176 -0
- quadros/encode.py +35 -0
- quadros/fields.py +166 -0
- quadros/manifests.py +40 -0
- quadros/paraview.py +15 -0
- quadros/render.py +84 -0
- quadros-0.1.0.dist-info/METADATA +120 -0
- quadros-0.1.0.dist-info/RECORD +15 -0
- quadros-0.1.0.dist-info/WHEEL +4 -0
- quadros-0.1.0.dist-info/entry_points.txt +2 -0
- quadros-0.1.0.dist-info/licenses/LICENSE +201 -0
quadros/__init__.py
ADDED
quadros/__main__.py
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import xml.etree.ElementTree as ET
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def clean_label(text: str) -> str:
|
|
14
|
+
return re.sub(r"[^A-Za-z0-9_.-]+", "_", str(text)).strip("_") or "item"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def maybe_number(value: str) -> Any:
|
|
18
|
+
text = str(value)
|
|
19
|
+
try:
|
|
20
|
+
number = float(text)
|
|
21
|
+
except ValueError:
|
|
22
|
+
return text
|
|
23
|
+
if number.is_integer() and re.fullmatch(r"[-+]?\d+", text):
|
|
24
|
+
return int(number)
|
|
25
|
+
return number
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def to_float_list(values: list[Any]) -> list[float]:
|
|
29
|
+
return [float(value) for value in values]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def first(values: list[Any], default=None):
|
|
33
|
+
return values[0] if values else default
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def last_nonempty(values: list[Any], default=None):
|
|
37
|
+
for value in reversed(values):
|
|
38
|
+
if str(value) != "":
|
|
39
|
+
return value
|
|
40
|
+
return default
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def rgb_to_hex(values: list[Any]) -> str | None:
|
|
44
|
+
if len(values) < 3:
|
|
45
|
+
return None
|
|
46
|
+
ints = [max(0, min(255, round(float(channel) * 255))) for channel in values[:3]]
|
|
47
|
+
return "#{:02X}{:02X}{:02X}".format(*ints)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def pyvista_camera_position(camera: dict[str, Any]) -> list[list[float]]:
|
|
51
|
+
return [
|
|
52
|
+
list(camera["position"]),
|
|
53
|
+
list(camera["focal_point"]),
|
|
54
|
+
list(camera["view_up"]),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_identity_transform(transform: dict[str, Any]) -> bool:
|
|
59
|
+
position = transform.get("position") or [0.0, 0.0, 0.0]
|
|
60
|
+
scale = transform.get("scale") or [1.0, 1.0, 1.0]
|
|
61
|
+
orientation = transform.get("orientation") or [0.0, 0.0, 0.0]
|
|
62
|
+
origin = transform.get("origin") or [0.0, 0.0, 0.0]
|
|
63
|
+
matrix = transform.get("user_transform") or [
|
|
64
|
+
1.0, 0.0, 0.0, 0.0,
|
|
65
|
+
0.0, 1.0, 0.0, 0.0,
|
|
66
|
+
0.0, 0.0, 1.0, 0.0,
|
|
67
|
+
0.0, 0.0, 0.0, 1.0,
|
|
68
|
+
]
|
|
69
|
+
identity_matrix = [
|
|
70
|
+
1.0, 0.0, 0.0, 0.0,
|
|
71
|
+
0.0, 1.0, 0.0, 0.0,
|
|
72
|
+
0.0, 0.0, 1.0, 0.0,
|
|
73
|
+
0.0, 0.0, 0.0, 1.0,
|
|
74
|
+
]
|
|
75
|
+
return (
|
|
76
|
+
all(abs(float(value)) < 1e-12 for value in position)
|
|
77
|
+
and all(abs(float(value) - 1.0) < 1e-12 for value in scale)
|
|
78
|
+
and all(abs(float(value)) < 1e-12 for value in orientation)
|
|
79
|
+
and all(abs(float(value)) < 1e-12 for value in origin)
|
|
80
|
+
and all(abs(float(value) - identity_matrix[index]) < 1e-12 for index, value in enumerate(matrix))
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ParaViewState:
|
|
85
|
+
def __init__(self, path: Path):
|
|
86
|
+
self.path = path
|
|
87
|
+
self.tree = ET.parse(path)
|
|
88
|
+
self.root = self.tree.getroot()
|
|
89
|
+
self.state = self.root.find(".//ServerManagerState")
|
|
90
|
+
self.version = None if self.state is None else self.state.attrib.get("version")
|
|
91
|
+
self.proxies = {
|
|
92
|
+
proxy.attrib["id"]: proxy
|
|
93
|
+
for proxy in self.root.findall(".//Proxy")
|
|
94
|
+
if "id" in proxy.attrib
|
|
95
|
+
}
|
|
96
|
+
self.collections: dict[str, list[str]] = {}
|
|
97
|
+
self.item_names: dict[str, list[dict[str, str | None]]] = {}
|
|
98
|
+
self._load_proxy_collection_names()
|
|
99
|
+
|
|
100
|
+
def _load_proxy_collection_names(self) -> None:
|
|
101
|
+
for collection in self.root.findall(".//ProxyCollection"):
|
|
102
|
+
collection_name = collection.attrib.get("name", "")
|
|
103
|
+
for item in collection.findall("Item"):
|
|
104
|
+
proxy_id = item.attrib.get("id")
|
|
105
|
+
if not proxy_id:
|
|
106
|
+
continue
|
|
107
|
+
self.collections.setdefault(collection_name, []).append(proxy_id)
|
|
108
|
+
self.item_names.setdefault(proxy_id, []).append(
|
|
109
|
+
{
|
|
110
|
+
"collection": collection_name,
|
|
111
|
+
"name": item.attrib.get("name"),
|
|
112
|
+
"logname": item.attrib.get("logname"),
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def proxy_name(self, proxy_id: str) -> str:
|
|
117
|
+
preferred_collections = (
|
|
118
|
+
"sources",
|
|
119
|
+
"views",
|
|
120
|
+
"representations",
|
|
121
|
+
"lookup_tables",
|
|
122
|
+
"scalar_bars",
|
|
123
|
+
"layouts",
|
|
124
|
+
)
|
|
125
|
+
candidates = self.item_names.get(proxy_id, [])
|
|
126
|
+
for collection_name in preferred_collections:
|
|
127
|
+
for item in candidates:
|
|
128
|
+
if item["collection"] == collection_name and item["name"]:
|
|
129
|
+
return str(item["name"])
|
|
130
|
+
for item in candidates:
|
|
131
|
+
if item["name"]:
|
|
132
|
+
return str(item["name"])
|
|
133
|
+
proxy = self.proxies.get(proxy_id)
|
|
134
|
+
if proxy is not None:
|
|
135
|
+
return f"{proxy.attrib.get('type', 'Proxy')}_{proxy_id}"
|
|
136
|
+
return proxy_id
|
|
137
|
+
|
|
138
|
+
def proxy_logname(self, proxy_id: str) -> str | None:
|
|
139
|
+
for item in self.item_names.get(proxy_id, []):
|
|
140
|
+
if item["logname"]:
|
|
141
|
+
return str(item["logname"])
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
def prop(self, proxy_id: str, name: str):
|
|
145
|
+
proxy = self.proxies[proxy_id]
|
|
146
|
+
for prop in proxy.findall("Property"):
|
|
147
|
+
if prop.attrib.get("name") == name:
|
|
148
|
+
return prop
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
def values(self, proxy_id: str, name: str) -> list[Any]:
|
|
152
|
+
prop = self.prop(proxy_id, name)
|
|
153
|
+
if prop is None:
|
|
154
|
+
return []
|
|
155
|
+
|
|
156
|
+
elements = []
|
|
157
|
+
for element in prop.findall("Element"):
|
|
158
|
+
if "value" not in element.attrib:
|
|
159
|
+
continue
|
|
160
|
+
index = int(element.attrib.get("index", len(elements)))
|
|
161
|
+
elements.append((index, maybe_number(element.attrib["value"])))
|
|
162
|
+
if elements:
|
|
163
|
+
return [value for _, value in sorted(elements)]
|
|
164
|
+
if "value" in prop.attrib:
|
|
165
|
+
return [maybe_number(prop.attrib["value"])]
|
|
166
|
+
return []
|
|
167
|
+
|
|
168
|
+
def proxy_refs(self, proxy_id: str, name: str) -> list[str]:
|
|
169
|
+
prop = self.prop(proxy_id, name)
|
|
170
|
+
if prop is None:
|
|
171
|
+
return []
|
|
172
|
+
refs = []
|
|
173
|
+
for proxy in prop.findall("Proxy"):
|
|
174
|
+
if "value" in proxy.attrib:
|
|
175
|
+
refs.append(proxy.attrib["value"])
|
|
176
|
+
return refs
|
|
177
|
+
|
|
178
|
+
def proxies_by(self, *, group: str | None = None, proxy_type: str | None = None):
|
|
179
|
+
for proxy_id, proxy in self.proxies.items():
|
|
180
|
+
if group is not None and proxy.attrib.get("group") != group:
|
|
181
|
+
continue
|
|
182
|
+
if proxy_type is not None and proxy.attrib.get("type") != proxy_type:
|
|
183
|
+
continue
|
|
184
|
+
yield proxy_id, proxy
|
|
185
|
+
|
|
186
|
+
def scalar_name(self, proxy_id: str) -> str | None:
|
|
187
|
+
return last_nonempty(
|
|
188
|
+
self.values(proxy_id, "SelectInputScalars")
|
|
189
|
+
or self.values(proxy_id, "InputScalars")
|
|
190
|
+
or self.values(proxy_id, "Scalars")
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def source_summary(self) -> list[dict[str, Any]]:
|
|
194
|
+
sources = []
|
|
195
|
+
for proxy_id, proxy in self.proxies_by(group="sources"):
|
|
196
|
+
file_names = (
|
|
197
|
+
self.values(proxy_id, "FileName")
|
|
198
|
+
or self.values(proxy_id, "FileNames")
|
|
199
|
+
or self.values(proxy_id, "CaseFileName")
|
|
200
|
+
)
|
|
201
|
+
timestep_values = [
|
|
202
|
+
float(value)
|
|
203
|
+
for value in self.values(proxy_id, "TimestepValues")
|
|
204
|
+
if isinstance(value, (int, float)) and abs(float(value)) > 1e-15
|
|
205
|
+
]
|
|
206
|
+
if not file_names and not timestep_values:
|
|
207
|
+
continue
|
|
208
|
+
sources.append(
|
|
209
|
+
{
|
|
210
|
+
"id": proxy_id,
|
|
211
|
+
"name": self.proxy_name(proxy_id),
|
|
212
|
+
"logname": self.proxy_logname(proxy_id),
|
|
213
|
+
"type": proxy.attrib.get("type"),
|
|
214
|
+
"file_names": file_names,
|
|
215
|
+
"timestep_values": timestep_values,
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
return sources
|
|
219
|
+
|
|
220
|
+
def contour_summary(self) -> list[dict[str, Any]]:
|
|
221
|
+
contours = []
|
|
222
|
+
for proxy_id, proxy in self.proxies_by(group="filters", proxy_type="Contour"):
|
|
223
|
+
input_id = first(self.proxy_refs(proxy_id, "Input"))
|
|
224
|
+
contours.append(
|
|
225
|
+
{
|
|
226
|
+
"id": proxy_id,
|
|
227
|
+
"name": self.proxy_name(proxy_id),
|
|
228
|
+
"logname": self.proxy_logname(proxy_id),
|
|
229
|
+
"type": proxy.attrib.get("type"),
|
|
230
|
+
"input_id": input_id,
|
|
231
|
+
"input_name": self.proxy_name(input_id) if input_id else None,
|
|
232
|
+
"field": self.scalar_name(proxy_id),
|
|
233
|
+
"values": to_float_list(self.values(proxy_id, "ContourValues")),
|
|
234
|
+
"compute_normals": bool(first(self.values(proxy_id, "ComputeNormals"), 0)),
|
|
235
|
+
"compute_gradients": bool(first(self.values(proxy_id, "ComputeGradients"), 0)),
|
|
236
|
+
"compute_scalars": bool(first(self.values(proxy_id, "ComputeScalars"), 0)),
|
|
237
|
+
"generate_triangles": bool(first(self.values(proxy_id, "GenerateTriangles"), 0)),
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
return contours
|
|
241
|
+
|
|
242
|
+
def representation_summary(self) -> list[dict[str, Any]]:
|
|
243
|
+
representations = []
|
|
244
|
+
for proxy_id, proxy in self.proxies_by(group="representations", proxy_type="GeometryRepresentation"):
|
|
245
|
+
input_id = first(self.proxy_refs(proxy_id, "Input"))
|
|
246
|
+
diffuse_color = to_float_list(self.values(proxy_id, "DiffuseColor"))
|
|
247
|
+
ambient_color = to_float_list(self.values(proxy_id, "AmbientColor"))
|
|
248
|
+
color_array_values = self.values(proxy_id, "ColorArrayName")
|
|
249
|
+
opacity_array_values = self.values(proxy_id, "OpacityArray")
|
|
250
|
+
transform = {
|
|
251
|
+
"position": to_float_list(self.values(proxy_id, "Position") or [0.0, 0.0, 0.0]),
|
|
252
|
+
"scale": to_float_list(self.values(proxy_id, "Scale") or [1.0, 1.0, 1.0]),
|
|
253
|
+
"orientation": to_float_list(self.values(proxy_id, "Orientation") or [0.0, 0.0, 0.0]),
|
|
254
|
+
"origin": to_float_list(self.values(proxy_id, "Origin") or [0.0, 0.0, 0.0]),
|
|
255
|
+
"user_transform": to_float_list(
|
|
256
|
+
self.values(proxy_id, "UserTransform")
|
|
257
|
+
or [
|
|
258
|
+
1.0, 0.0, 0.0, 0.0,
|
|
259
|
+
0.0, 1.0, 0.0, 0.0,
|
|
260
|
+
0.0, 0.0, 1.0, 0.0,
|
|
261
|
+
0.0, 0.0, 0.0, 1.0,
|
|
262
|
+
]
|
|
263
|
+
),
|
|
264
|
+
}
|
|
265
|
+
representations.append(
|
|
266
|
+
{
|
|
267
|
+
"id": proxy_id,
|
|
268
|
+
"name": self.proxy_name(proxy_id),
|
|
269
|
+
"logname": self.proxy_logname(proxy_id),
|
|
270
|
+
"input_id": input_id,
|
|
271
|
+
"input_name": self.proxy_name(input_id) if input_id else None,
|
|
272
|
+
"visible": bool(first(self.values(proxy_id, "Visibility"), 0)),
|
|
273
|
+
"representation": first(self.values(proxy_id, "Representation")),
|
|
274
|
+
"opacity": float(first(self.values(proxy_id, "Opacity"), 1.0)),
|
|
275
|
+
"diffuse_color": diffuse_color,
|
|
276
|
+
"diffuse_hex": rgb_to_hex(diffuse_color),
|
|
277
|
+
"ambient_color": ambient_color,
|
|
278
|
+
"ambient_hex": rgb_to_hex(ambient_color),
|
|
279
|
+
"color_array": last_nonempty(color_array_values),
|
|
280
|
+
"opacity_array": last_nonempty(opacity_array_values),
|
|
281
|
+
"line_width": first(self.values(proxy_id, "LineWidth")),
|
|
282
|
+
"mesh_visible": bool(first(self.values(proxy_id, "MeshVisibility"), 0)),
|
|
283
|
+
"transform": transform,
|
|
284
|
+
"transform_is_identity": is_identity_transform(transform),
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
return representations
|
|
288
|
+
|
|
289
|
+
def camera_summary(self) -> list[dict[str, Any]]:
|
|
290
|
+
views = []
|
|
291
|
+
for proxy_id, proxy in self.proxies_by(group="views", proxy_type="RenderView"):
|
|
292
|
+
camera = {
|
|
293
|
+
"position": to_float_list(self.values(proxy_id, "CameraPosition")),
|
|
294
|
+
"focal_point": to_float_list(self.values(proxy_id, "CameraFocalPoint")),
|
|
295
|
+
"view_up": to_float_list(self.values(proxy_id, "CameraViewUp")),
|
|
296
|
+
"parallel_projection": bool(first(self.values(proxy_id, "CameraParallelProjection"), 0)),
|
|
297
|
+
"parallel_scale": float(first(self.values(proxy_id, "CameraParallelScale"), 0.0)),
|
|
298
|
+
"view_angle": float(first(self.values(proxy_id, "CameraViewAngle"), 30.0)),
|
|
299
|
+
}
|
|
300
|
+
views.append(
|
|
301
|
+
{
|
|
302
|
+
"id": proxy_id,
|
|
303
|
+
"name": self.proxy_name(proxy_id),
|
|
304
|
+
"logname": self.proxy_logname(proxy_id),
|
|
305
|
+
"type": proxy.attrib.get("type"),
|
|
306
|
+
"camera": camera,
|
|
307
|
+
"pyvista_camera_position": pyvista_camera_position(camera),
|
|
308
|
+
"view_size": [int(value) for value in self.values(proxy_id, "ViewSize")],
|
|
309
|
+
"center_of_rotation": to_float_list(self.values(proxy_id, "CenterOfRotation")),
|
|
310
|
+
"representation_ids": self.proxy_refs(proxy_id, "Representations"),
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
return views
|
|
314
|
+
|
|
315
|
+
def lookup_table_summary(self) -> list[dict[str, Any]]:
|
|
316
|
+
tables = []
|
|
317
|
+
for proxy_id, proxy in self.proxies_by(group="lookup_tables"):
|
|
318
|
+
rgb_points = self.values(proxy_id, "RGBPoints")
|
|
319
|
+
tables.append(
|
|
320
|
+
{
|
|
321
|
+
"id": proxy_id,
|
|
322
|
+
"name": self.proxy_name(proxy_id),
|
|
323
|
+
"logname": self.proxy_logname(proxy_id),
|
|
324
|
+
"type": proxy.attrib.get("type"),
|
|
325
|
+
"color_space": first(self.values(proxy_id, "ColorSpace")),
|
|
326
|
+
"rgb_points": rgb_points,
|
|
327
|
+
"opacity_mapping": bool(first(self.values(proxy_id, "EnableOpacityMapping"), 0)),
|
|
328
|
+
"nan_opacity": first(self.values(proxy_id, "NanOpacity")),
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
return tables
|
|
332
|
+
|
|
333
|
+
def animation_summary(self) -> dict[str, Any]:
|
|
334
|
+
scenes = list(self.proxies_by(group="animation", proxy_type="AnimationScene"))
|
|
335
|
+
if not scenes:
|
|
336
|
+
return {}
|
|
337
|
+
proxy_id, _ = scenes[0]
|
|
338
|
+
return {
|
|
339
|
+
"id": proxy_id,
|
|
340
|
+
"animation_time": first(self.values(proxy_id, "AnimationTime")),
|
|
341
|
+
"start_time": first(self.values(proxy_id, "StartTime")),
|
|
342
|
+
"end_time": first(self.values(proxy_id, "EndTime")),
|
|
343
|
+
"number_of_frames": first(self.values(proxy_id, "NumberOfFrames")),
|
|
344
|
+
"frames_per_timestep": first(self.values(proxy_id, "FramesPerTimestep")),
|
|
345
|
+
"play_mode": first(self.values(proxy_id, "PlayMode")),
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
def summary(self) -> dict[str, Any]:
|
|
349
|
+
contours = self.contour_summary()
|
|
350
|
+
representations = self.representation_summary()
|
|
351
|
+
contour_by_id = {contour["id"]: contour for contour in contours}
|
|
352
|
+
for representation in representations:
|
|
353
|
+
representation["filter"] = contour_by_id.get(representation["input_id"])
|
|
354
|
+
return {
|
|
355
|
+
"source_file": str(self.path),
|
|
356
|
+
"generated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
357
|
+
"paraview_version": self.version,
|
|
358
|
+
"animation": self.animation_summary(),
|
|
359
|
+
"sources": self.source_summary(),
|
|
360
|
+
"contours": contours,
|
|
361
|
+
"representations": representations,
|
|
362
|
+
"visible_representations": [rep for rep in representations if rep["visible"]],
|
|
363
|
+
"views": self.camera_summary(),
|
|
364
|
+
"lookup_tables": self.lookup_table_summary(),
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def camera_json(summary: dict[str, Any]) -> dict[str, Any]:
|
|
369
|
+
camera_views = {}
|
|
370
|
+
for view in summary["views"]:
|
|
371
|
+
name = clean_label(view["name"])
|
|
372
|
+
camera = dict(view["camera"])
|
|
373
|
+
camera["pyvista_camera_position"] = view["pyvista_camera_position"]
|
|
374
|
+
camera_views[name] = camera
|
|
375
|
+
return {"camera_views": camera_views}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def render_config(summary: dict[str, Any]) -> dict[str, Any]:
|
|
379
|
+
visible_contours = []
|
|
380
|
+
iso_colors: dict[str, str] = {}
|
|
381
|
+
iso_opacities: dict[str, float] = {}
|
|
382
|
+
|
|
383
|
+
for representation in summary["visible_representations"]:
|
|
384
|
+
contour = representation.get("filter")
|
|
385
|
+
if not contour or contour.get("type") != "Contour":
|
|
386
|
+
continue
|
|
387
|
+
field = contour.get("field")
|
|
388
|
+
if not field:
|
|
389
|
+
continue
|
|
390
|
+
for value in contour.get("values", []):
|
|
391
|
+
visible_contours.append((field, float(value)))
|
|
392
|
+
if representation.get("diffuse_hex"):
|
|
393
|
+
iso_colors.setdefault(field, representation["diffuse_hex"])
|
|
394
|
+
iso_opacities.setdefault(field, float(representation.get("opacity", 1.0)))
|
|
395
|
+
|
|
396
|
+
unique_specs = []
|
|
397
|
+
seen = set()
|
|
398
|
+
for field, value in visible_contours:
|
|
399
|
+
key = (field, value)
|
|
400
|
+
if key in seen:
|
|
401
|
+
continue
|
|
402
|
+
seen.add(key)
|
|
403
|
+
unique_specs.append(f"{field}={value:g}")
|
|
404
|
+
|
|
405
|
+
view_names = [clean_label(view["name"]) for view in summary["views"]]
|
|
406
|
+
scene = {
|
|
407
|
+
"name": f"{clean_label(Path(summary['source_file']).stem)}_visible_contours",
|
|
408
|
+
"iso_specs": [],
|
|
409
|
+
"iso_colors": iso_colors,
|
|
410
|
+
"iso_opacities": iso_opacities,
|
|
411
|
+
"compositions": [
|
|
412
|
+
{
|
|
413
|
+
"name": "paraview_visible",
|
|
414
|
+
"iso_specs": unique_specs,
|
|
415
|
+
"views": view_names,
|
|
416
|
+
"resolutions": ["fullhd", "2k", "4k"],
|
|
417
|
+
}
|
|
418
|
+
],
|
|
419
|
+
"roi": "auto",
|
|
420
|
+
"views": view_names,
|
|
421
|
+
"resolutions": ["fullhd", "2k", "4k"],
|
|
422
|
+
"camera_zoom": 1.0,
|
|
423
|
+
"show_bounds": False,
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
"_generated_from": summary["source_file"],
|
|
427
|
+
"_note": (
|
|
428
|
+
"This maps visible ParaView contour settings into the auto_snp renderer. "
|
|
429
|
+
"If the ParaView state contains multiple data sources, run the renderer once per Nek snapshot."
|
|
430
|
+
),
|
|
431
|
+
**camera_json(summary),
|
|
432
|
+
"scenes": [scene],
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def write_json(path: Path, payload: dict[str, Any], indent: int) -> None:
|
|
437
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
438
|
+
with path.open("w", encoding="utf-8") as handle:
|
|
439
|
+
json.dump(payload, handle, indent=indent)
|
|
440
|
+
handle.write("\n")
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def print_report(summary: dict[str, Any], written: list[Path]) -> None:
|
|
444
|
+
print(f"ParaView state: {summary['source_file']}")
|
|
445
|
+
print(f"ParaView version: {summary.get('paraview_version')}")
|
|
446
|
+
print(f"Sources: {len(summary['sources'])}")
|
|
447
|
+
for source in summary["sources"]:
|
|
448
|
+
files = ", ".join(str(item) for item in source.get("file_names", []))
|
|
449
|
+
print(f" - {source['name']} ({source['type']}): {files}")
|
|
450
|
+
print(f"Contours: {len(summary['contours'])}")
|
|
451
|
+
for contour in summary["contours"]:
|
|
452
|
+
values = ", ".join(f"{value:g}" for value in contour["values"])
|
|
453
|
+
print(f" - {contour['name']}: {contour['field']} = {values} from {contour['input_name']}")
|
|
454
|
+
print(f"Visible geometry representations: {len(summary['visible_representations'])}")
|
|
455
|
+
for rep in summary["visible_representations"]:
|
|
456
|
+
contour = rep.get("filter") or {}
|
|
457
|
+
target = contour.get("name", rep.get("input_name"))
|
|
458
|
+
transform_note = ""
|
|
459
|
+
if not rep["transform_is_identity"]:
|
|
460
|
+
transform = rep["transform"]
|
|
461
|
+
transform_note = (
|
|
462
|
+
f", transform position={transform['position']}, "
|
|
463
|
+
f"orientation={transform['orientation']}, scale={transform['scale']}"
|
|
464
|
+
)
|
|
465
|
+
print(
|
|
466
|
+
f" - {rep['name']} -> {target}: "
|
|
467
|
+
f"opacity={rep['opacity']}, color={rep.get('diffuse_hex')}, "
|
|
468
|
+
f"mesh_visible={rep['mesh_visible']}{transform_note}"
|
|
469
|
+
)
|
|
470
|
+
print(f"Views: {len(summary['views'])}")
|
|
471
|
+
for view in summary["views"]:
|
|
472
|
+
camera = view["camera"]
|
|
473
|
+
print(
|
|
474
|
+
f" - {view['name']}: position={camera['position']}, "
|
|
475
|
+
f"focal_point={camera['focal_point']}, parallel_scale={camera['parallel_scale']}"
|
|
476
|
+
)
|
|
477
|
+
print(f" PyVista camera_position={view['pyvista_camera_position']}")
|
|
478
|
+
for path in written:
|
|
479
|
+
print(f"Wrote {path}")
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def parse_args() -> argparse.Namespace:
|
|
483
|
+
parser = argparse.ArgumentParser(
|
|
484
|
+
description="Extract cameras, contours, sources, and visible representation settings from a ParaView .pvsm state."
|
|
485
|
+
)
|
|
486
|
+
parser.add_argument("pvsm", type=Path, help="ParaView .pvsm state file")
|
|
487
|
+
parser.add_argument("--summary-json", type=Path, help="Detailed extracted summary JSON")
|
|
488
|
+
parser.add_argument("--camera-json", type=Path, help="Camera JSON usable as PYVISTA_CAMERA_FILE")
|
|
489
|
+
parser.add_argument("--render-config", type=Path, help="auto_snp render config generated from visible contours")
|
|
490
|
+
parser.add_argument("--indent", type=int, default=2, help="JSON indentation")
|
|
491
|
+
parser.add_argument("--no-write", action="store_true", help="Only print the report")
|
|
492
|
+
return parser.parse_args()
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def main() -> int:
|
|
496
|
+
args = parse_args()
|
|
497
|
+
state = ParaViewState(args.pvsm)
|
|
498
|
+
summary = state.summary()
|
|
499
|
+
|
|
500
|
+
stem = args.pvsm.with_suffix("")
|
|
501
|
+
summary_path = args.summary_json or stem.with_name(f"{stem.name}_paraview_summary.json")
|
|
502
|
+
camera_path = args.camera_json or stem.with_name(f"{stem.name}_camera_views.json")
|
|
503
|
+
config_path = args.render_config or stem.with_name(f"{stem.name}_render_config.json")
|
|
504
|
+
|
|
505
|
+
written = []
|
|
506
|
+
if not args.no_write:
|
|
507
|
+
write_json(summary_path, summary, args.indent)
|
|
508
|
+
write_json(camera_path, camera_json(summary), args.indent)
|
|
509
|
+
write_json(config_path, render_config(summary), args.indent)
|
|
510
|
+
written.extend([summary_path, camera_path, config_path])
|
|
511
|
+
|
|
512
|
+
print_report(summary, written)
|
|
513
|
+
return 0
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
if __name__ == "__main__":
|
|
517
|
+
raise SystemExit(main())
|