pyflightstream 0.2.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.
- pyflightstream/__init__.py +27 -0
- pyflightstream/cases/__init__.py +332 -0
- pyflightstream/cases/matrix_legacy.py +337 -0
- pyflightstream/commands/__init__.py +544 -0
- pyflightstream/commands/_meta.yaml +18 -0
- pyflightstream/commands/actuators.yaml +167 -0
- pyflightstream/commands/advanced_settings.yaml +136 -0
- pyflightstream/commands/aeroelastic_coupling.yaml +172 -0
- pyflightstream/commands/boundary_conditions.yaml +153 -0
- pyflightstream/commands/ccs_wing_mesh.yaml +74 -0
- pyflightstream/commands/coordinate_systems.yaml +123 -0
- pyflightstream/commands/file_io.yaml +82 -0
- pyflightstream/commands/mesh_import_export.yaml +72 -0
- pyflightstream/commands/motion_definitions.yaml +185 -0
- pyflightstream/commands/probe_points.yaml +116 -0
- pyflightstream/commands/runtime_settings.yaml +160 -0
- pyflightstream/commands/scenes.yaml +14 -0
- pyflightstream/commands/script_controls.yaml +39 -0
- pyflightstream/commands/simulation_controls.yaml +30 -0
- pyflightstream/commands/solver_analysis.yaml +118 -0
- pyflightstream/commands/solver_export.yaml +138 -0
- pyflightstream/commands/solver_initialization.yaml +95 -0
- pyflightstream/commands/solver_settings.yaml +120 -0
- pyflightstream/commands/streamlines.yaml +63 -0
- pyflightstream/commands/surface_sections.yaml +145 -0
- pyflightstream/commands/sweeper.yaml +115 -0
- pyflightstream/commands/unsteady_solver.yaml +68 -0
- pyflightstream/commands/volume_sections.yaml +148 -0
- pyflightstream/farfield/__init__.py +613 -0
- pyflightstream/files/__init__.py +375 -0
- pyflightstream/fsi/__init__.py +57 -0
- pyflightstream/fsi/beam.py +417 -0
- pyflightstream/fsi/centrifugal.py +418 -0
- pyflightstream/fsi/cli.py +206 -0
- pyflightstream/fsi/config.py +316 -0
- pyflightstream/fsi/driver.py +501 -0
- pyflightstream/fsi/kinematics.py +153 -0
- pyflightstream/fsi/loads.py +740 -0
- pyflightstream/fsi/nodes.py +463 -0
- pyflightstream/fsi/state.py +181 -0
- pyflightstream/post/__init__.py +18 -0
- pyflightstream/post/writers.py +195 -0
- pyflightstream/probes/__init__.py +453 -0
- pyflightstream/probes/geometry.py +281 -0
- pyflightstream/probes/planar.py +477 -0
- pyflightstream/qa/__init__.py +59 -0
- pyflightstream/qa/cli.py +364 -0
- pyflightstream/qa/compat.py +285 -0
- pyflightstream/qa/drift.py +406 -0
- pyflightstream/qa/geometry.py +421 -0
- pyflightstream/qa/physics.py +1744 -0
- pyflightstream/qa/probes.py +1023 -0
- pyflightstream/qa/references/PHY-01.yaml +38 -0
- pyflightstream/qa/references/PHY-02.yaml +28 -0
- pyflightstream/qa/references/PHY-05.yaml +29 -0
- pyflightstream/qa/references/PHY-06.yaml +90 -0
- pyflightstream/qa/references/SMI-01.yaml +28 -0
- pyflightstream/qa/references/SMI-02.yaml +28 -0
- pyflightstream/qa/specs.py +1405 -0
- pyflightstream/reference.py +465 -0
- pyflightstream/results/__init__.py +547 -0
- pyflightstream/run/__init__.py +677 -0
- pyflightstream/script/__init__.py +474 -0
- pyflightstream/script/helpers.py +844 -0
- pyflightstream/versions.py +191 -0
- pyflightstream-0.2.0.dist-info/METADATA +88 -0
- pyflightstream-0.2.0.dist-info/RECORD +71 -0
- pyflightstream-0.2.0.dist-info/WHEEL +5 -0
- pyflightstream-0.2.0.dist-info/entry_points.txt +3 -0
- pyflightstream-0.2.0.dist-info/licenses/LICENSE +21 -0
- pyflightstream-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
"""Curated helpers for the common FlightStream workflows (SAD Section 4.3).
|
|
2
|
+
|
|
3
|
+
Pipeline role: a small, curated set of thin typed functions sitting on
|
|
4
|
+
top of :class:`~pyflightstream.script.Script`. Each helper only
|
|
5
|
+
translates its typed arguments into ``emit()`` calls, so every line
|
|
6
|
+
still passes the database validation, phase ordering, and
|
|
7
|
+
cross-reference checks of the builder. Helpers own the conditional
|
|
8
|
+
argument combinations the manual documents in prose (which extras each
|
|
9
|
+
SET_FREESTREAM type takes, when INITIALIZE_SOLVER takes per-surface
|
|
10
|
+
lines or a PERIODIC copy count), because the database records grammar,
|
|
11
|
+
not conditionality.
|
|
12
|
+
|
|
13
|
+
One generated function per command was rejected in the SAD: it would
|
|
14
|
+
reproduce the shape of the AGPL package, create a huge surface, and
|
|
15
|
+
teach nothing. The curated workflows are: free stream and atmosphere,
|
|
16
|
+
actuator disc (SRC-003 pp.323-324), rotary motion (pp.332-333), solver
|
|
17
|
+
settings (pp.339-343), solver initialization (p.337), sweeps (p.406),
|
|
18
|
+
analysis and export selection (pp.350-354), and probe management
|
|
19
|
+
(pp.362-363).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import warnings
|
|
25
|
+
from collections.abc import Sequence
|
|
26
|
+
from typing import Literal
|
|
27
|
+
|
|
28
|
+
from pyflightstream.script import CommandArgumentError, Script
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _toggle(value: bool) -> str:
|
|
32
|
+
return "ENABLE" if value else "DISABLE"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def free_stream(
|
|
36
|
+
script: Script,
|
|
37
|
+
kind: str = "CONSTANT",
|
|
38
|
+
*,
|
|
39
|
+
frame: int | None = None,
|
|
40
|
+
axis: str | None = None,
|
|
41
|
+
rpm: float | None = None,
|
|
42
|
+
profile: str | None = None,
|
|
43
|
+
filetype: str | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Set the free-stream velocity definition (SRC-003 p.322).
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
script : Script
|
|
50
|
+
Script under construction.
|
|
51
|
+
kind : str
|
|
52
|
+
``CONSTANT`` (uniform free stream, the magnitude comes later
|
|
53
|
+
from the solver settings), ``ROTATION`` (rotating frame free
|
|
54
|
+
stream for hover and propeller analyses), or ``CUSTOM``
|
|
55
|
+
(velocity profile imported from a file).
|
|
56
|
+
frame : int, optional
|
|
57
|
+
ROTATION only: local coordinate system carrying the rotation
|
|
58
|
+
axis; it must exist earlier in the script.
|
|
59
|
+
axis : str, optional
|
|
60
|
+
ROTATION only: rotation axis of ``frame``, ``X``, ``Y``, or
|
|
61
|
+
``Z``.
|
|
62
|
+
rpm : float, optional
|
|
63
|
+
ROTATION only: angular velocity in rev/min.
|
|
64
|
+
profile : str, optional
|
|
65
|
+
CUSTOM only: path of the velocity profile file.
|
|
66
|
+
filetype : str, optional
|
|
67
|
+
CUSTOM only: ``STRUCTURED`` or ``UNSTRUCTURED`` profile file.
|
|
68
|
+
"""
|
|
69
|
+
upper = kind.upper()
|
|
70
|
+
rotation_given = [frame is not None, axis is not None, rpm is not None]
|
|
71
|
+
custom_given = [profile is not None, filetype is not None]
|
|
72
|
+
if upper == "ROTATION":
|
|
73
|
+
if not all(rotation_given) or any(custom_given):
|
|
74
|
+
raise CommandArgumentError(
|
|
75
|
+
"SET_FREESTREAM ROTATION takes exactly frame, axis, and rpm: the rotating "
|
|
76
|
+
"free stream needs the axis frame, the axis, and the angular velocity "
|
|
77
|
+
"(SRC-003 p.322)"
|
|
78
|
+
)
|
|
79
|
+
script.emit("SET_FREESTREAM", upper, frame=frame, axis=axis, angular_velocity=rpm)
|
|
80
|
+
elif upper == "CUSTOM":
|
|
81
|
+
if not all(custom_given) or any(rotation_given):
|
|
82
|
+
raise CommandArgumentError(
|
|
83
|
+
"SET_FREESTREAM CUSTOM takes exactly filetype and profile: the imported "
|
|
84
|
+
"velocity profile needs its file structure and path (SRC-003 p.322)"
|
|
85
|
+
)
|
|
86
|
+
script.emit("SET_FREESTREAM", upper, filetype=filetype, filename=profile)
|
|
87
|
+
else:
|
|
88
|
+
if any(rotation_given) or any(custom_given):
|
|
89
|
+
raise CommandArgumentError(
|
|
90
|
+
"SET_FREESTREAM CONSTANT takes no further input; the free-stream magnitude "
|
|
91
|
+
"is a solver setting (SOLVER_SET_VELOCITY, SRC-003 p.339)"
|
|
92
|
+
)
|
|
93
|
+
script.emit("SET_FREESTREAM", upper)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def atmosphere(
|
|
97
|
+
script: Script,
|
|
98
|
+
*,
|
|
99
|
+
altitude: float | None = None,
|
|
100
|
+
altitude_units: str = "METERS",
|
|
101
|
+
density: float | None = None,
|
|
102
|
+
pressure: float | None = None,
|
|
103
|
+
temperature: float | None = None,
|
|
104
|
+
viscosity: float | None = None,
|
|
105
|
+
specific_heat_ratio: float | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Set the working fluid state (SRC-003 p.328).
|
|
108
|
+
|
|
109
|
+
Either from a standard-atmosphere altitude (AIR_ALTITUDE) or from
|
|
110
|
+
the five explicit fluid properties (FLUID_PROPERTIES); the two
|
|
111
|
+
paths are mutually exclusive. Sonic velocity is derived from
|
|
112
|
+
temperature and specific heat ratio and is no longer an input.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
script : Script
|
|
117
|
+
Script under construction.
|
|
118
|
+
altitude : float, optional
|
|
119
|
+
Standard-atmosphere altitude, in ``altitude_units``.
|
|
120
|
+
altitude_units : str
|
|
121
|
+
``METERS`` or ``FEET``.
|
|
122
|
+
density : float, optional
|
|
123
|
+
Fluid density in kg/m^3.
|
|
124
|
+
pressure : float, optional
|
|
125
|
+
Static pressure in Pa.
|
|
126
|
+
temperature : float, optional
|
|
127
|
+
Static temperature in K.
|
|
128
|
+
viscosity : float, optional
|
|
129
|
+
Dynamic viscosity in Pa s.
|
|
130
|
+
specific_heat_ratio : float, optional
|
|
131
|
+
Ratio of specific heats (1.4 for air).
|
|
132
|
+
"""
|
|
133
|
+
properties = (density, pressure, temperature, viscosity, specific_heat_ratio)
|
|
134
|
+
if altitude is not None:
|
|
135
|
+
if any(value is not None for value in properties):
|
|
136
|
+
raise CommandArgumentError(
|
|
137
|
+
"atmosphere takes either an altitude or the five explicit fluid "
|
|
138
|
+
"properties, not both: AIR_ALTITUDE already sets the whole standard "
|
|
139
|
+
"atmosphere state (SRC-003 p.328)"
|
|
140
|
+
)
|
|
141
|
+
script.emit("AIR_ALTITUDE", altitude, altitude_units)
|
|
142
|
+
return
|
|
143
|
+
if any(value is None for value in properties):
|
|
144
|
+
raise CommandArgumentError(
|
|
145
|
+
"atmosphere without an altitude needs all five fluid properties (density, "
|
|
146
|
+
"pressure, temperature, viscosity, specific_heat_ratio), because "
|
|
147
|
+
"FLUID_PROPERTIES sets the complete fluid state (SRC-003 p.328)"
|
|
148
|
+
)
|
|
149
|
+
script.emit(
|
|
150
|
+
"FLUID_PROPERTIES",
|
|
151
|
+
density=density,
|
|
152
|
+
pressure=pressure,
|
|
153
|
+
temperature=temperature,
|
|
154
|
+
viscosity=viscosity,
|
|
155
|
+
specific_heat_ratio=specific_heat_ratio,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def actuator_disc(
|
|
160
|
+
script: Script,
|
|
161
|
+
name: str,
|
|
162
|
+
*,
|
|
163
|
+
frame: int,
|
|
164
|
+
axis: str,
|
|
165
|
+
offset: float,
|
|
166
|
+
r_tip: float,
|
|
167
|
+
r_hub: float,
|
|
168
|
+
rpm: float,
|
|
169
|
+
thrust: float | None = None,
|
|
170
|
+
thrust_type: str = "NEWTONS",
|
|
171
|
+
profile: str | None = None,
|
|
172
|
+
profile_force_unit: str = "NEWTONS",
|
|
173
|
+
n_blades: int | None = None,
|
|
174
|
+
swirl: float | None = None,
|
|
175
|
+
enable: bool = True,
|
|
176
|
+
) -> int:
|
|
177
|
+
"""Create and configure one propeller actuator disc (SRC-003 pp.323-324).
|
|
178
|
+
|
|
179
|
+
The disc is the linearized propeller slipstream surrogate
|
|
180
|
+
(SRC-003 pp.185-187). Exactly one thrust specification is taken:
|
|
181
|
+
a net ``thrust`` (ELLIPTICAL profile) or a radial force
|
|
182
|
+
distribution file ``profile`` (CUSTOM profile, which also needs
|
|
183
|
+
``n_blades``).
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
script : Script
|
|
188
|
+
Script under construction.
|
|
189
|
+
name : str
|
|
190
|
+
Actuator name shown in the interface.
|
|
191
|
+
frame : int
|
|
192
|
+
Local coordinate system carrying the disc axis (index greater
|
|
193
|
+
than 1); it must exist earlier in the script.
|
|
194
|
+
axis : str
|
|
195
|
+
Disc axis within ``frame``: ``X``, ``Y``, or ``Z``.
|
|
196
|
+
offset : float
|
|
197
|
+
Disc position along the axis, in simulation length units.
|
|
198
|
+
r_tip, r_hub : float
|
|
199
|
+
Tip and hub radii, in simulation length units.
|
|
200
|
+
rpm : float
|
|
201
|
+
Rotational speed in rev/min; the sign selects the rotation
|
|
202
|
+
direction about the axis.
|
|
203
|
+
thrust : float, optional
|
|
204
|
+
Net thrust for the ELLIPTICAL model, in ``thrust_type`` units.
|
|
205
|
+
thrust_type : str
|
|
206
|
+
``COEFFICIENT``, ``NEWTONS``, or ``POUNDS``. The manual
|
|
207
|
+
recommends dimensional thrust because the coefficient
|
|
208
|
+
convention must match the solver formulation (SRC-003 p.187).
|
|
209
|
+
profile : str, optional
|
|
210
|
+
Path of the radial thrust profile file for the CUSTOM model.
|
|
211
|
+
profile_force_unit : str
|
|
212
|
+
Force unit used inside the profile file: ``NEWTONS``,
|
|
213
|
+
``KILO-NEWTONS``, ``POUND-FORCE``, or ``KILOGRAM-FORCE``.
|
|
214
|
+
n_blades : int, optional
|
|
215
|
+
Blade count; required with ``profile``.
|
|
216
|
+
swirl : float, optional
|
|
217
|
+
Fraction between 0 and 1 of the swirl velocity kept
|
|
218
|
+
downstream; below 1 mimics a de-swirling stator
|
|
219
|
+
(SRC-003 p.186).
|
|
220
|
+
enable : bool
|
|
221
|
+
Emit ENABLE_ACTUATOR at the end.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
int
|
|
226
|
+
Index of the created actuator, for later citations.
|
|
227
|
+
"""
|
|
228
|
+
if (thrust is None) == (profile is None):
|
|
229
|
+
raise CommandArgumentError(
|
|
230
|
+
"actuator_disc takes exactly one thrust specification: a net thrust "
|
|
231
|
+
"(ELLIPTICAL model) or a radial profile file (CUSTOM model) "
|
|
232
|
+
"(SRC-003 pp.185-187)"
|
|
233
|
+
)
|
|
234
|
+
if profile is not None and n_blades is None:
|
|
235
|
+
raise CommandArgumentError(
|
|
236
|
+
"actuator_disc with a profile file needs n_blades, because the imported "
|
|
237
|
+
"radial distribution is per blade (SRC-003 pp.323-324)"
|
|
238
|
+
)
|
|
239
|
+
if swirl is not None and not 0.0 <= swirl <= 1.0:
|
|
240
|
+
raise CommandArgumentError(
|
|
241
|
+
f"actuator_disc swirl must lie between 0 and 1, got {swirl}: it is the "
|
|
242
|
+
"fraction of the swirl velocity kept downstream (SRC-003 p.186)"
|
|
243
|
+
)
|
|
244
|
+
subtype = "ELLIPTICAL" if thrust is not None else "CUSTOM"
|
|
245
|
+
script.emit("CREATE_NEW_ACTUATOR", "PROPELLER", subtype=subtype, name=name)
|
|
246
|
+
index = script.num_actuators
|
|
247
|
+
script.emit("SET_ACTUATOR_AXIS", index, frame, axis, offset)
|
|
248
|
+
script.emit("SET_ACTUATOR_RADIUS", index, r_tip, r_hub)
|
|
249
|
+
script.emit("SET_PROP_ACTUATOR_RPM", index, rpm)
|
|
250
|
+
if thrust is not None:
|
|
251
|
+
script.emit("SET_PROP_ACTUATOR_THRUST", index, thrust, thrust_type)
|
|
252
|
+
else:
|
|
253
|
+
script.emit("SET_PROP_ACTUATOR_PROFILE", index, profile_force_unit, n_blades, profile)
|
|
254
|
+
if swirl is not None:
|
|
255
|
+
script.emit("SET_PROP_ACTUATOR_SWIRL", index, swirl)
|
|
256
|
+
if enable:
|
|
257
|
+
script.emit("ENABLE_ACTUATOR", index)
|
|
258
|
+
return index
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def rotary_motion(
|
|
262
|
+
script: Script,
|
|
263
|
+
*,
|
|
264
|
+
frame: int,
|
|
265
|
+
axis: str,
|
|
266
|
+
rpm: float,
|
|
267
|
+
boundaries: Sequence[int] | Literal["all"] = "all",
|
|
268
|
+
moving_frames: Sequence[int] | Literal["all"] | None = None,
|
|
269
|
+
start_time: float | None = None,
|
|
270
|
+
wake_stabilization_blades: int | None = None,
|
|
271
|
+
) -> int:
|
|
272
|
+
"""Create and configure one rotary motion (SRC-003 pp.332-333).
|
|
273
|
+
|
|
274
|
+
Rotary motion is the blade-resolved alternative to the actuator
|
|
275
|
+
disc surrogate (SRC-003 p.234); it requires the unsteady solver
|
|
276
|
+
(see :func:`unsteady_solver`).
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
script : Script
|
|
281
|
+
Script under construction.
|
|
282
|
+
frame : int
|
|
283
|
+
Local coordinate system of the rotation (index greater than
|
|
284
|
+
1); it must exist earlier in the script.
|
|
285
|
+
axis : str
|
|
286
|
+
Rotor axis within ``frame``: ``X``, ``Y``, or ``Z``.
|
|
287
|
+
rpm : float
|
|
288
|
+
Rotor speed in rev/min.
|
|
289
|
+
boundaries : sequence of int or ``"all"``
|
|
290
|
+
Geometry boundaries assigned to the motion; ``"all"`` selects
|
|
291
|
+
every boundary (-1 form).
|
|
292
|
+
moving_frames : sequence of int, ``"all"``, or None
|
|
293
|
+
Local frames attached to the motion; None attaches none.
|
|
294
|
+
start_time : float, optional
|
|
295
|
+
Motion start within the solver physical time, in s; a positive
|
|
296
|
+
value converges a steady base flow before the motion begins.
|
|
297
|
+
wake_stabilization_blades : int, optional
|
|
298
|
+
Enables slipstream wake stabilization with this blade count.
|
|
299
|
+
|
|
300
|
+
Returns
|
|
301
|
+
-------
|
|
302
|
+
int
|
|
303
|
+
Identifier of the created motion, for later citations.
|
|
304
|
+
"""
|
|
305
|
+
script.emit("CREATE_NEW_MOTION", "ROTARY")
|
|
306
|
+
motion_id = script.num_motions
|
|
307
|
+
if boundaries == "all":
|
|
308
|
+
script.emit("SET_MOTION_BOUNDARIES", motion_id, -1)
|
|
309
|
+
else:
|
|
310
|
+
script.emit("SET_MOTION_BOUNDARIES", motion_id, len(boundaries), list(boundaries))
|
|
311
|
+
if moving_frames == "all":
|
|
312
|
+
script.emit("SET_MOTION_MOVING_FRAMES", motion_id, -1)
|
|
313
|
+
elif moving_frames is not None:
|
|
314
|
+
script.emit("SET_MOTION_MOVING_FRAMES", motion_id, len(moving_frames), list(moving_frames))
|
|
315
|
+
script.emit("SET_MOTION_COORDINATE_SYSTEM", motion_id, frame)
|
|
316
|
+
script.emit("SET_MOTION_ROTOR_AXIS", motion_id, axis)
|
|
317
|
+
script.emit("SET_MOTION_ROTOR_RPM", motion_id, rpm)
|
|
318
|
+
if start_time is not None:
|
|
319
|
+
script.emit("SET_MOTION_START_TIME", motion_id, start_time)
|
|
320
|
+
if wake_stabilization_blades is not None:
|
|
321
|
+
script.emit(
|
|
322
|
+
"SET_MOTION_SLIPSTREAM_WAKE_STABILIZATION",
|
|
323
|
+
motion_id,
|
|
324
|
+
"ENABLE",
|
|
325
|
+
wake_stabilization_blades,
|
|
326
|
+
)
|
|
327
|
+
return motion_id
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def unsteady_solver(script: Script, *, time_iterations: int, delta_time: float) -> None:
|
|
331
|
+
"""Select unsteady physical time stepping (SRC-003 p.341).
|
|
332
|
+
|
|
333
|
+
For rotary cases the manual recommends 8 to 12 degrees of blade
|
|
334
|
+
rotation per time step and at least two full rotations
|
|
335
|
+
(SRC-003 p.210).
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
script : Script
|
|
340
|
+
Script under construction.
|
|
341
|
+
time_iterations : int
|
|
342
|
+
Number of physical time steps.
|
|
343
|
+
delta_time : float
|
|
344
|
+
Physical time step in s.
|
|
345
|
+
"""
|
|
346
|
+
script.emit("SET_SOLVER_UNSTEADY", time_iterations, delta_time)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def solver_settings(
|
|
350
|
+
script: Script,
|
|
351
|
+
*,
|
|
352
|
+
aoa: float | None = None,
|
|
353
|
+
sideslip: float | None = None,
|
|
354
|
+
velocity: float | None = None,
|
|
355
|
+
mach: float | None = None,
|
|
356
|
+
ref_velocity: float | None = None,
|
|
357
|
+
ref_mach: float | None = None,
|
|
358
|
+
ref_area: float | None = None,
|
|
359
|
+
ref_length: float | None = None,
|
|
360
|
+
iterations: int | None = None,
|
|
361
|
+
convergence: float | None = None,
|
|
362
|
+
forced_iterations: bool | None = None,
|
|
363
|
+
max_threads: int | None = None,
|
|
364
|
+
boundary_layer: str | None = None,
|
|
365
|
+
viscous_coupling: bool | None = None,
|
|
366
|
+
viscous_excluded: Sequence[int] | None = None,
|
|
367
|
+
) -> None:
|
|
368
|
+
"""Set the solver runtime settings that were given (SRC-003 pp.339-343).
|
|
369
|
+
|
|
370
|
+
Only the provided settings are emitted, so the helper serves both
|
|
371
|
+
the initial setup and the re-emission between campaign points.
|
|
372
|
+
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
script : Script
|
|
376
|
+
Script under construction.
|
|
377
|
+
aoa : float, optional
|
|
378
|
+
Angle of attack in deg, magnitude below 90.
|
|
379
|
+
sideslip : float, optional
|
|
380
|
+
Side-slip angle in deg, magnitude below 90.
|
|
381
|
+
velocity : float, optional
|
|
382
|
+
Free-stream velocity magnitude in m/s.
|
|
383
|
+
mach : float, optional
|
|
384
|
+
Free-stream Mach number.
|
|
385
|
+
ref_velocity : float, optional
|
|
386
|
+
Reference velocity in m/s for coefficient normalization; for
|
|
387
|
+
rotary or hover cases use the largest characteristic velocity,
|
|
388
|
+
such as the rotor tip speed (SRC-003 p.201).
|
|
389
|
+
ref_mach : float, optional
|
|
390
|
+
Reference Mach number.
|
|
391
|
+
ref_area : float, optional
|
|
392
|
+
Reference area S_ref in simulation length units squared
|
|
393
|
+
(Q*S_ref force normalization, SRC-003 p.223).
|
|
394
|
+
ref_length : float, optional
|
|
395
|
+
Reference length L_ref in simulation length units
|
|
396
|
+
(Q*S_ref*L_ref moment normalization, SRC-003 p.223).
|
|
397
|
+
iterations : int, optional
|
|
398
|
+
Solver iteration count.
|
|
399
|
+
convergence : float, optional
|
|
400
|
+
Residual threshold declaring convergence (SRC-003 p.200).
|
|
401
|
+
forced_iterations : bool, optional
|
|
402
|
+
Run the full iteration count regardless of convergence.
|
|
403
|
+
max_threads : int, optional
|
|
404
|
+
Parallel core count.
|
|
405
|
+
boundary_layer : str, optional
|
|
406
|
+
``LAMINAR``, ``TRANSITIONAL``, or ``TURBULENT``; the default
|
|
407
|
+
transitional model is stated valid for chord Reynolds numbers
|
|
408
|
+
between 500000 and 1500000 (SRC-003 p.203).
|
|
409
|
+
viscous_coupling : bool, optional
|
|
410
|
+
Couple the semi-empirical boundary layer model to the
|
|
411
|
+
potential flow solution (attached-flow viscosity only,
|
|
412
|
+
SRC-003 pp.207-208).
|
|
413
|
+
viscous_excluded : sequence of int, optional
|
|
414
|
+
Boundaries excluded from viscous coupling.
|
|
415
|
+
"""
|
|
416
|
+
scalar_commands = (
|
|
417
|
+
("SOLVER_SET_AOA", aoa),
|
|
418
|
+
("SOLVER_SET_SIDESLIP", sideslip),
|
|
419
|
+
("SOLVER_SET_VELOCITY", velocity),
|
|
420
|
+
("SOLVER_SET_MACH_NUMBER", mach),
|
|
421
|
+
("SOLVER_SET_REF_VELOCITY", ref_velocity),
|
|
422
|
+
("SOLVER_SET_REF_MACH_NUMBER", ref_mach),
|
|
423
|
+
("SOLVER_SET_REF_AREA", ref_area),
|
|
424
|
+
("SOLVER_SET_REF_LENGTH", ref_length),
|
|
425
|
+
("SOLVER_SET_ITERATIONS", iterations),
|
|
426
|
+
("SOLVER_SET_CONVERGENCE", convergence),
|
|
427
|
+
("SET_MAX_PARALLEL_THREADS", max_threads),
|
|
428
|
+
)
|
|
429
|
+
for command, value in scalar_commands:
|
|
430
|
+
if value is not None:
|
|
431
|
+
script.emit(command, value)
|
|
432
|
+
if forced_iterations is not None:
|
|
433
|
+
script.emit("SOLVER_SET_FORCED_ITERATIONS", _toggle(forced_iterations))
|
|
434
|
+
if boundary_layer is not None:
|
|
435
|
+
script.emit("SET_BOUNDARY_LAYER_TYPE", boundary_layer)
|
|
436
|
+
if viscous_coupling is not None:
|
|
437
|
+
script.emit("SET_SOLVER_VISCOUS_COUPLING", _toggle(viscous_coupling))
|
|
438
|
+
if viscous_excluded is not None:
|
|
439
|
+
script.emit(
|
|
440
|
+
"SET_VISCOUS_EXCLUDED_BOUNDARIES", len(viscous_excluded), list(viscous_excluded)
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def initialize_solver(
|
|
445
|
+
script: Script,
|
|
446
|
+
*,
|
|
447
|
+
solver_model: str = "INCOMPRESSIBLE",
|
|
448
|
+
surfaces: Sequence[tuple[int, bool]] | Literal["all"] = "all",
|
|
449
|
+
wake_termination_x: float | str = "DEFAULT",
|
|
450
|
+
symmetry: str = "NONE",
|
|
451
|
+
periodic_copies: int | None = None,
|
|
452
|
+
wall_collision_avoidance: bool | None = None,
|
|
453
|
+
) -> None:
|
|
454
|
+
"""Initialize the solver, covering the extended forms (SRC-003 p.337).
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
script : Script
|
|
459
|
+
Script under construction.
|
|
460
|
+
solver_model : str
|
|
461
|
+
``INCOMPRESSIBLE``, ``SUBSONIC_PRANDTL_GLAUERT``,
|
|
462
|
+
``TRANSONIC_FIELD_PANEL``, ``TANGENT_CONE``, or
|
|
463
|
+
``MODIFIED_NEWTONIAN``.
|
|
464
|
+
surfaces : sequence of (int, bool) pairs or ``"all"``
|
|
465
|
+
``"all"`` initializes every boundary (-1 form); a sequence of
|
|
466
|
+
``(surface_index, quad_mesher)`` pairs initializes those
|
|
467
|
+
surfaces with the quad mesher toggled per surface.
|
|
468
|
+
wake_termination_x : float or str
|
|
469
|
+
X location of wake termination in the reference frame, or
|
|
470
|
+
``DEFAULT`` for auto-computation.
|
|
471
|
+
symmetry : str
|
|
472
|
+
``NONE``, ``MIRROR``, or ``PERIODIC``. Initializing MIRROR
|
|
473
|
+
with a full (non-half) model diverges instantly
|
|
474
|
+
(SRC-003 p.217).
|
|
475
|
+
periodic_copies : int, optional
|
|
476
|
+
Number of periodic copies; required with PERIODIC symmetry
|
|
477
|
+
and forbidden otherwise.
|
|
478
|
+
wall_collision_avoidance : bool, optional
|
|
479
|
+
Applies to solver models 1 to 3.
|
|
480
|
+
"""
|
|
481
|
+
if (symmetry.upper() == "PERIODIC") != (periodic_copies is not None):
|
|
482
|
+
raise CommandArgumentError(
|
|
483
|
+
"INITIALIZE_SOLVER: PERIODIC symmetry appends the number of copies, so "
|
|
484
|
+
"periodic_copies is required with PERIODIC and forbidden otherwise "
|
|
485
|
+
"(SRC-003 p.337)"
|
|
486
|
+
)
|
|
487
|
+
if periodic_copies is not None and periodic_copies < 1:
|
|
488
|
+
raise CommandArgumentError(
|
|
489
|
+
f"INITIALIZE_SOLVER: periodic_copies must be a positive count, got "
|
|
490
|
+
f"{periodic_copies} (SRC-003 p.337)"
|
|
491
|
+
)
|
|
492
|
+
arguments: dict[str, object] = {
|
|
493
|
+
"solver_model": solver_model,
|
|
494
|
+
"wake_termination_x": str(wake_termination_x),
|
|
495
|
+
"symmetry": symmetry,
|
|
496
|
+
}
|
|
497
|
+
if surfaces == "all":
|
|
498
|
+
arguments["surfaces"] = -1
|
|
499
|
+
else:
|
|
500
|
+
arguments["surfaces"] = len(surfaces)
|
|
501
|
+
arguments["surface_toggles"] = [
|
|
502
|
+
f"{index},{_toggle(quad_mesher)}" for index, quad_mesher in surfaces
|
|
503
|
+
]
|
|
504
|
+
if periodic_copies is not None:
|
|
505
|
+
arguments["symmetry_copies"] = periodic_copies
|
|
506
|
+
if wall_collision_avoidance is not None:
|
|
507
|
+
arguments["wall_collision_avoidance"] = _toggle(wall_collision_avoidance)
|
|
508
|
+
script.emit("INITIALIZE_SOLVER", **arguments)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def sweep(
|
|
512
|
+
script: Script,
|
|
513
|
+
*,
|
|
514
|
+
aoa: Sequence[float] | None = None,
|
|
515
|
+
beta: Sequence[float] | None = None,
|
|
516
|
+
velocity_file: str | None = None,
|
|
517
|
+
clear_solution: bool | None = None,
|
|
518
|
+
ref_velocity_same: bool | None = None,
|
|
519
|
+
post_run_script: str | None = None,
|
|
520
|
+
start: bool = True,
|
|
521
|
+
export_spreadsheet: str | None = None,
|
|
522
|
+
) -> None:
|
|
523
|
+
"""Configure and run a Sweeper Toolbox sweep (SRC-003 p.406).
|
|
524
|
+
|
|
525
|
+
Parameters
|
|
526
|
+
----------
|
|
527
|
+
script : Script
|
|
528
|
+
Script under construction.
|
|
529
|
+
aoa : sequence of float, optional
|
|
530
|
+
Custom angle of attack values in deg.
|
|
531
|
+
beta : sequence of float, optional
|
|
532
|
+
Custom side-slip values in deg.
|
|
533
|
+
velocity_file : str, optional
|
|
534
|
+
Path of the custom velocity list file.
|
|
535
|
+
clear_solution : bool, optional
|
|
536
|
+
Clear the solution between sweep runs instead of reusing it.
|
|
537
|
+
ref_velocity_same : bool, optional
|
|
538
|
+
Keep the reference velocity equal to the free-stream velocity
|
|
539
|
+
at every sweep point.
|
|
540
|
+
post_run_script : str, optional
|
|
541
|
+
Script executed after each sweep point, for example a surface
|
|
542
|
+
section extraction script.
|
|
543
|
+
start : bool
|
|
544
|
+
Emit SWEEPER_START after the configuration.
|
|
545
|
+
export_spreadsheet : str, optional
|
|
546
|
+
Path of the sweep results spreadsheet export.
|
|
547
|
+
"""
|
|
548
|
+
if aoa is None and beta is None and velocity_file is None:
|
|
549
|
+
raise CommandArgumentError(
|
|
550
|
+
"sweep needs at least one axis (aoa, beta, or velocity_file); a sweep "
|
|
551
|
+
"without values has nothing to run (SRC-003 p.406)"
|
|
552
|
+
)
|
|
553
|
+
if aoa is not None:
|
|
554
|
+
script.emit("SWEEPER_SET_AOA_SWEEP", "CUSTOM", list(aoa))
|
|
555
|
+
if beta is not None:
|
|
556
|
+
script.emit("SWEEPER_SET_BETA_SWEEP", "CUSTOM", list(beta))
|
|
557
|
+
if velocity_file is not None:
|
|
558
|
+
script.emit("SWEEPER_SET_VELOCITY_SWEEP", "CUSTOM", velocity_file)
|
|
559
|
+
if clear_solution is not None:
|
|
560
|
+
script.emit("SWEEPER_CLEAR_SOLUTION", _toggle(clear_solution))
|
|
561
|
+
if ref_velocity_same is not None:
|
|
562
|
+
script.emit("SWEEPER_REF_VELOCITY_SAME", _toggle(ref_velocity_same))
|
|
563
|
+
if post_run_script is not None:
|
|
564
|
+
script.emit("SWEEPER_POST_RUN_SCRIPT", "ENABLE", post_run_script)
|
|
565
|
+
if start:
|
|
566
|
+
script.emit("SWEEPER_START")
|
|
567
|
+
if export_spreadsheet is not None:
|
|
568
|
+
script.emit("SWEEPER_EXPORT_SPREADSHEET", export_spreadsheet)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def analysis_setup(
|
|
572
|
+
script: Script,
|
|
573
|
+
*,
|
|
574
|
+
loads_frame: int | None = None,
|
|
575
|
+
moments_model: str | None = None,
|
|
576
|
+
symmetry_loads: bool | None = None,
|
|
577
|
+
load_units: str | None = None,
|
|
578
|
+
boundaries: Sequence[int] | None = None,
|
|
579
|
+
inviscid_only: bool | None = None,
|
|
580
|
+
vorticity_drag_boundaries: Sequence[int] | Literal["all"] | None = None,
|
|
581
|
+
) -> None:
|
|
582
|
+
"""Select how loads and moments are analyzed (SRC-003 pp.350-351).
|
|
583
|
+
|
|
584
|
+
Parameters
|
|
585
|
+
----------
|
|
586
|
+
script : Script
|
|
587
|
+
Script under construction.
|
|
588
|
+
loads_frame : int, optional
|
|
589
|
+
Coordinate system for evaluating loads and moments; index 1 is
|
|
590
|
+
the reference frame.
|
|
591
|
+
moments_model : str, optional
|
|
592
|
+
``PRESSURE`` (solver default) or ``VORTICITY``.
|
|
593
|
+
symmetry_loads : bool, optional
|
|
594
|
+
Include symmetry boundary loads; relevant to half-model runs.
|
|
595
|
+
load_units : str, optional
|
|
596
|
+
``COEFFICIENTS``, ``NEWTONS``, ``KILO-NEWTONS``,
|
|
597
|
+
``POUND-FORCE``, or ``KILOGRAM-FORCE``.
|
|
598
|
+
boundaries : sequence of int, optional
|
|
599
|
+
Boundaries enabled in the analysis; boundaries not listed are
|
|
600
|
+
disabled (SRC-003 p.351).
|
|
601
|
+
inviscid_only : bool, optional
|
|
602
|
+
Restrict the analysis to inviscid loads and moments.
|
|
603
|
+
vorticity_drag_boundaries : sequence of int, ``"all"``, or None
|
|
604
|
+
Boundaries whose induced drag comes from surface vorticity
|
|
605
|
+
integration; boundaries without trailing-edge boundary
|
|
606
|
+
conditions silently report zero induced drag (SRC-003 p.202).
|
|
607
|
+
"""
|
|
608
|
+
# symmetry_loads first: it is an init-phase setting consumed by the
|
|
609
|
+
# in-solve monitors (per-step force plots), so a call mixing it
|
|
610
|
+
# with the analysis-phase selections is only valid before
|
|
611
|
+
# START_SOLVER; pass it alone in that position.
|
|
612
|
+
if symmetry_loads is not None:
|
|
613
|
+
script.emit("SET_ANALYSIS_SYMMETRY_LOADS", _toggle(symmetry_loads))
|
|
614
|
+
if loads_frame is not None:
|
|
615
|
+
script.emit("SET_SOLVER_ANALYSIS_LOADS_FRAME", loads_frame)
|
|
616
|
+
if moments_model is not None:
|
|
617
|
+
script.emit("SET_ANALYSIS_MOMENTS_MODEL", moments_model)
|
|
618
|
+
if load_units is not None:
|
|
619
|
+
script.emit("SET_LOADS_AND_MOMENTS_UNITS", load_units)
|
|
620
|
+
if boundaries is not None:
|
|
621
|
+
script.emit("SET_SOLVER_ANALYSIS_BOUNDARIES", len(boundaries), list(boundaries))
|
|
622
|
+
if inviscid_only is not None:
|
|
623
|
+
script.emit("SET_INVISCID_LOADS", _toggle(inviscid_only))
|
|
624
|
+
if vorticity_drag_boundaries == "all":
|
|
625
|
+
script.emit("SET_VORTICITY_DRAG_BOUNDARIES", -1)
|
|
626
|
+
elif vorticity_drag_boundaries is not None:
|
|
627
|
+
script.emit(
|
|
628
|
+
"SET_VORTICITY_DRAG_BOUNDARIES",
|
|
629
|
+
len(vorticity_drag_boundaries),
|
|
630
|
+
list(vorticity_drag_boundaries),
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def export_results(
|
|
635
|
+
script: Script,
|
|
636
|
+
*,
|
|
637
|
+
spreadsheet: str | None = None,
|
|
638
|
+
tecplot: str | None = None,
|
|
639
|
+
vtk: str | None = None,
|
|
640
|
+
vtk_boundaries: Sequence[int] | Literal["all"] = "all",
|
|
641
|
+
vtk_variables: Sequence[str] | Literal["all"] | None = None,
|
|
642
|
+
vtk_wake: bool = False,
|
|
643
|
+
force_distributions: str | None = None,
|
|
644
|
+
) -> None:
|
|
645
|
+
"""Export the solver results that were requested (SRC-003 pp.352-354).
|
|
646
|
+
|
|
647
|
+
Parameters
|
|
648
|
+
----------
|
|
649
|
+
script : Script
|
|
650
|
+
Script under construction.
|
|
651
|
+
spreadsheet : str, optional
|
|
652
|
+
Path of the loads and moments spreadsheet, the primary
|
|
653
|
+
quantitative output of a steady run.
|
|
654
|
+
tecplot : str, optional
|
|
655
|
+
Path of the Tecplot .dat export.
|
|
656
|
+
vtk : str, optional
|
|
657
|
+
Path of the VTK export.
|
|
658
|
+
vtk_boundaries : sequence of int or ``"all"``
|
|
659
|
+
Boundaries included in the VTK export.
|
|
660
|
+
vtk_variables : sequence of str, ``"all"``, or None
|
|
661
|
+
Variables selected before the VTK export; None keeps the
|
|
662
|
+
current selection. ``CP`` is flagged for depreciation in favor
|
|
663
|
+
of ``CP_REFERENCE`` and ``CP_FREESTREAM`` (SRC-003 p.352); the
|
|
664
|
+
helper warns when it is requested.
|
|
665
|
+
vtk_wake : bool
|
|
666
|
+
Include the wake in the VTK variable selection.
|
|
667
|
+
force_distributions : str, optional
|
|
668
|
+
Path of the force distribution vectors export, all boundaries.
|
|
669
|
+
"""
|
|
670
|
+
if spreadsheet is not None:
|
|
671
|
+
script.emit("EXPORT_SOLVER_ANALYSIS_SPREADSHEET", spreadsheet)
|
|
672
|
+
if tecplot is not None:
|
|
673
|
+
script.emit("EXPORT_SOLVER_ANALYSIS_TECPLOT", tecplot)
|
|
674
|
+
if vtk_variables == "all":
|
|
675
|
+
script.emit("SET_VTK_EXPORT_VARIABLES", -1, _toggle(vtk_wake))
|
|
676
|
+
elif vtk_variables is not None:
|
|
677
|
+
if any(variable.upper() == "CP" for variable in vtk_variables):
|
|
678
|
+
warnings.warn(
|
|
679
|
+
"the CP export variable is flagged for depreciation; prefer "
|
|
680
|
+
"CP_REFERENCE or CP_FREESTREAM (SRC-003 p.352)",
|
|
681
|
+
stacklevel=2,
|
|
682
|
+
)
|
|
683
|
+
script.emit(
|
|
684
|
+
"SET_VTK_EXPORT_VARIABLES", len(vtk_variables), _toggle(vtk_wake), list(vtk_variables)
|
|
685
|
+
)
|
|
686
|
+
if vtk is not None:
|
|
687
|
+
if vtk_boundaries == "all":
|
|
688
|
+
script.emit("EXPORT_SOLVER_ANALYSIS_VTK", vtk, -1)
|
|
689
|
+
else:
|
|
690
|
+
script.emit(
|
|
691
|
+
"EXPORT_SOLVER_ANALYSIS_VTK", vtk, len(vtk_boundaries), list(vtk_boundaries)
|
|
692
|
+
)
|
|
693
|
+
if force_distributions is not None:
|
|
694
|
+
script.emit("EXPORT_SOLVER_ANALYSIS_FORCE_DISTRIBUTIONS", force_distributions, -1)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def probe_points(
|
|
698
|
+
script: Script,
|
|
699
|
+
points: Sequence[tuple[float, float, float]],
|
|
700
|
+
*,
|
|
701
|
+
kind: str = "VOLUME",
|
|
702
|
+
) -> None:
|
|
703
|
+
"""Create individual probe points (SRC-003 p.362).
|
|
704
|
+
|
|
705
|
+
Parameters
|
|
706
|
+
----------
|
|
707
|
+
script : Script
|
|
708
|
+
Script under construction.
|
|
709
|
+
points : sequence of (x, y, z) triples
|
|
710
|
+
Probe positions in the reference frame, simulation length
|
|
711
|
+
units.
|
|
712
|
+
kind : str
|
|
713
|
+
``VOLUME`` or ``SURFACE`` probes.
|
|
714
|
+
"""
|
|
715
|
+
for x, y, z in points:
|
|
716
|
+
script.emit("NEW_PROBE_POINT", kind, x, y, z)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def probe_line(
|
|
720
|
+
script: Script,
|
|
721
|
+
*,
|
|
722
|
+
points: int,
|
|
723
|
+
start: tuple[float, float, float],
|
|
724
|
+
end: tuple[float, float, float],
|
|
725
|
+
) -> None:
|
|
726
|
+
"""Create a survey line of probe points (SRC-003 p.362).
|
|
727
|
+
|
|
728
|
+
Parameters
|
|
729
|
+
----------
|
|
730
|
+
script : Script
|
|
731
|
+
Script under construction.
|
|
732
|
+
points : int
|
|
733
|
+
Number of probe vertices between start and end.
|
|
734
|
+
start, end : (x, y, z) triples
|
|
735
|
+
Line ends in the reference frame, simulation length units.
|
|
736
|
+
"""
|
|
737
|
+
script.emit("NEW_PROBE_LINE", points, *start, *end)
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
def probes_from_file(script: Script, path: str, *, units: str, frame: int = 1) -> None:
|
|
741
|
+
"""Import a probe lattice from a CSV file (SRC-003 pp.362-363).
|
|
742
|
+
|
|
743
|
+
The file rows are X,Y,Z,TYPE with TYPE 0 for surface and 1 for
|
|
744
|
+
volume probes; the first line holds the point count. This is the
|
|
745
|
+
programmatic path for probe lattice generation.
|
|
746
|
+
|
|
747
|
+
Parameters
|
|
748
|
+
----------
|
|
749
|
+
script : Script
|
|
750
|
+
Script under construction.
|
|
751
|
+
path : str
|
|
752
|
+
Probe lattice CSV path.
|
|
753
|
+
units : str
|
|
754
|
+
Length unit of the file coordinates (``METER``, ``INCH``, and
|
|
755
|
+
the other simulation length units).
|
|
756
|
+
frame : int
|
|
757
|
+
Coordinate system of the file coordinates; index 1 is the
|
|
758
|
+
reference frame.
|
|
759
|
+
"""
|
|
760
|
+
script.emit("PROBE_POINTS_IMPORT", units, frame, path)
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
def export_probes(script: Script, path: str, *, update: bool = True) -> None:
|
|
764
|
+
"""Export the probe values, refreshing them first (SRC-003 pp.362-363).
|
|
765
|
+
|
|
766
|
+
Parameters
|
|
767
|
+
----------
|
|
768
|
+
script : Script
|
|
769
|
+
Script under construction.
|
|
770
|
+
path : str
|
|
771
|
+
Export file path.
|
|
772
|
+
update : bool
|
|
773
|
+
Emit UPDATE_PROBE_POINTS first, so the export reflects the
|
|
774
|
+
current solution; the manual instructs refreshing before
|
|
775
|
+
exporting (SRC-003 p.362).
|
|
776
|
+
"""
|
|
777
|
+
if update:
|
|
778
|
+
script.emit("UPDATE_PROBE_POINTS")
|
|
779
|
+
script.emit("EXPORT_PROBE_POINTS", path)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def coordinate_frame(
|
|
783
|
+
script: Script,
|
|
784
|
+
*,
|
|
785
|
+
name: str,
|
|
786
|
+
origin: Sequence[float],
|
|
787
|
+
x_axis: Sequence[float],
|
|
788
|
+
y_axis: Sequence[float],
|
|
789
|
+
z_axis: Sequence[float] | None = None,
|
|
790
|
+
) -> int:
|
|
791
|
+
"""Create and define a local coordinate system, returning its index.
|
|
792
|
+
|
|
793
|
+
Emits CREATE_NEW_COORDINATE_SYSTEM followed by
|
|
794
|
+
EDIT_COORDINATE_SYSTEM with the origin and the three axis vectors
|
|
795
|
+
in the reference frame (coordinate_systems chapter). Use it when
|
|
796
|
+
the solver should carry the same plane a probe grid was
|
|
797
|
+
prescribed on; probe positions themselves are always imported in
|
|
798
|
+
the reference frame (frame 1), so this helper is presentation,
|
|
799
|
+
not placement.
|
|
800
|
+
|
|
801
|
+
Parameters
|
|
802
|
+
----------
|
|
803
|
+
script : Script
|
|
804
|
+
Script under construction.
|
|
805
|
+
name : str
|
|
806
|
+
Name of the new coordinate system.
|
|
807
|
+
origin : sequence of float
|
|
808
|
+
Frame origin in the reference frame (simulation length units).
|
|
809
|
+
x_axis, y_axis : sequence of float
|
|
810
|
+
Axis direction vectors in the reference frame.
|
|
811
|
+
z_axis : sequence of float, optional
|
|
812
|
+
Third axis; computed as the right-handed cross product of
|
|
813
|
+
x_axis and y_axis when omitted.
|
|
814
|
+
|
|
815
|
+
Returns
|
|
816
|
+
-------
|
|
817
|
+
int
|
|
818
|
+
Index of the created frame (the reference frame is 1; created
|
|
819
|
+
local frames follow).
|
|
820
|
+
"""
|
|
821
|
+
if z_axis is None:
|
|
822
|
+
ax, ay, az = x_axis
|
|
823
|
+
bx, by, bz = y_axis
|
|
824
|
+
z_axis = (ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx)
|
|
825
|
+
script.emit("CREATE_NEW_COORDINATE_SYSTEM")
|
|
826
|
+
frame_index = script.num_local_frames + 1
|
|
827
|
+
script.emit(
|
|
828
|
+
"EDIT_COORDINATE_SYSTEM",
|
|
829
|
+
frame=frame_index,
|
|
830
|
+
name=name,
|
|
831
|
+
origin_x=origin[0],
|
|
832
|
+
origin_y=origin[1],
|
|
833
|
+
origin_z=origin[2],
|
|
834
|
+
vector_x_x=x_axis[0],
|
|
835
|
+
vector_x_y=x_axis[1],
|
|
836
|
+
vector_x_z=x_axis[2],
|
|
837
|
+
vector_y_x=y_axis[0],
|
|
838
|
+
vector_y_y=y_axis[1],
|
|
839
|
+
vector_y_z=y_axis[2],
|
|
840
|
+
vector_z_x=z_axis[0],
|
|
841
|
+
vector_z_y=z_axis[1],
|
|
842
|
+
vector_z_z=z_axis[2],
|
|
843
|
+
)
|
|
844
|
+
return frame_index
|