ansys-fluent-core 0.35.dev1__py3-none-any.whl → 0.36.dev0__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.
Potentially problematic release.
This version of ansys-fluent-core might be problematic. Click here for more details.
- ansys/fluent/core/__init__.py +2 -2
- ansys/fluent/core/codegen/__init__.py +1 -0
- ansys/fluent/core/codegen/datamodelgen.py +13 -2
- ansys/fluent/core/codegen/settingsgen.py +4 -0
- ansys/fluent/core/docker/docker_compose.py +30 -1
- ansys/fluent/core/examples/downloads.py +3 -4
- ansys/fluent/core/fluent_connection.py +2 -3
- ansys/fluent/core/generated/api_tree/api_objects.json +1 -1
- ansys/fluent/core/generated/datamodel_231/flicing.py +25 -25
- ansys/fluent/core/generated/datamodel_231/meshing.py +202 -202
- ansys/fluent/core/generated/datamodel_232/flicing.py +25 -25
- ansys/fluent/core/generated/datamodel_232/meshing.py +161 -161
- ansys/fluent/core/generated/datamodel_241/flicing.py +30 -30
- ansys/fluent/core/generated/datamodel_241/meshing.py +241 -241
- ansys/fluent/core/generated/datamodel_242/flicing.py +45 -45
- ansys/fluent/core/generated/datamodel_242/meshing.py +318 -318
- ansys/fluent/core/generated/datamodel_242/part_management.py +9 -9
- ansys/fluent/core/generated/datamodel_251/flicing.py +25 -25
- ansys/fluent/core/generated/datamodel_251/meshing.py +395 -395
- ansys/fluent/core/generated/datamodel_251/part_management.py +6 -6
- ansys/fluent/core/generated/datamodel_252/flicing.py +45 -45
- ansys/fluent/core/generated/datamodel_252/meshing.py +394 -394
- ansys/fluent/core/generated/datamodel_252/part_management.py +5 -5
- ansys/fluent/core/generated/datamodel_261/flicing.py +20 -20
- ansys/fluent/core/generated/datamodel_261/meshing.py +500 -493
- ansys/fluent/core/generated/datamodel_261/meshing_workflow.py +61694 -0
- ansys/fluent/core/generated/datamodel_261/part_management.py +5 -5
- ansys/fluent/core/generated/datamodel_261/preferences.py +28 -0
- ansys/fluent/core/generated/datamodel_261/solver_workflow.py +14 -0
- ansys/fluent/core/generated/fluent_version_261.py +3 -3
- ansys/fluent/core/generated/meshing/tui_261.py +388 -10
- ansys/fluent/core/generated/solver/settings_261.py +8273 -3465
- ansys/fluent/core/generated/solver/settings_261.pyi +5805 -2181
- ansys/fluent/core/generated/solver/tui_261.py +1049 -335
- ansys/fluent/core/launcher/container_launcher.py +12 -3
- ansys/fluent/core/launcher/fluent_container.py +5 -3
- ansys/fluent/core/launcher/launch_options.py +2 -2
- ansys/fluent/core/launcher/launcher.py +2 -6
- ansys/fluent/core/launcher/pim_launcher.py +76 -3
- ansys/fluent/core/launcher/process_launch_string.py +1 -2
- ansys/fluent/core/launcher/slurm_launcher.py +4 -3
- ansys/fluent/core/launcher/standalone_launcher.py +3 -2
- ansys/fluent/core/module_config.py +5 -10
- ansys/fluent/core/report.py +1 -1
- ansys/fluent/core/services/__init__.py +2 -0
- ansys/fluent/core/services/datamodel_se.py +4 -1
- ansys/fluent/core/services/field_data.py +24 -0
- ansys/fluent/core/services/reduction.py +2 -0
- ansys/fluent/core/services/settings.py +1 -1
- ansys/fluent/core/services/solution_variables.py +92 -0
- ansys/fluent/core/session.py +1 -2
- ansys/fluent/core/session_base_meshing.py +8 -0
- ansys/fluent/core/session_meshing.py +5 -0
- ansys/fluent/core/session_pure_meshing.py +6 -0
- ansys/fluent/core/session_pure_meshing.pyi +5 -0
- ansys/fluent/core/session_utilities.py +8 -5
- ansys/fluent/core/solver/flobject.py +19 -0
- ansys/fluent/core/solver/function/reduction.py +2 -0
- ansys/fluent/core/ui/__init__.py +64 -0
- ansys/fluent/core/ui/jupyter_ui.py +203 -0
- ansys/fluent/core/ui/standalone_web_ui.py +296 -0
- ansys/fluent/core/ui/utils.py +173 -0
- ansys/fluent/core/utils/deprecate.py +1 -0
- {ansys_fluent_core-0.35.dev1.dist-info → ansys_fluent_core-0.36.dev0.dist-info}/METADATA +25 -19
- {ansys_fluent_core-0.35.dev1.dist-info → ansys_fluent_core-0.36.dev0.dist-info}/RECORD +67 -62
- {ansys_fluent_core-0.35.dev1.dist-info → ansys_fluent_core-0.36.dev0.dist-info}/WHEEL +0 -0
- {ansys_fluent_core-0.35.dev1.dist-info → ansys_fluent_core-0.36.dev0.dist-info}/licenses/LICENSE +0 -0
|
@@ -34,6 +34,9 @@ from ansys.fluent.core.generated.datamodel_252.preferences import (
|
|
|
34
34
|
Root as preferences_root,
|
|
35
35
|
)
|
|
36
36
|
from ansys.fluent.core.generated.datamodel_252.workflow import Root as workflow_root
|
|
37
|
+
from ansys.fluent.core.generated.datamodel_261.meshing_workflow import (
|
|
38
|
+
Root as meshing_workflow_root,
|
|
39
|
+
)
|
|
37
40
|
from ansys.fluent.core.generated.meshing.tui_252 import main_menu
|
|
38
41
|
|
|
39
42
|
class PureMeshing:
|
|
@@ -45,6 +48,8 @@ class PureMeshing:
|
|
|
45
48
|
def meshing_utilities(self) -> meshing_utilities_root: ...
|
|
46
49
|
@property
|
|
47
50
|
def workflow(self) -> workflow_root: ...
|
|
51
|
+
@property
|
|
52
|
+
def meshing_workflow(self) -> meshing_workflow_root: ...
|
|
48
53
|
def watertight(self): ...
|
|
49
54
|
def fault_tolerant(self): ...
|
|
50
55
|
def two_dimensional_meshing(self): ...
|
|
@@ -90,8 +90,9 @@ class SessionBase:
|
|
|
90
90
|
|
|
91
91
|
Parameters
|
|
92
92
|
----------
|
|
93
|
-
ui_mode : UIMode
|
|
94
|
-
Defines the user interface mode for Fluent.
|
|
93
|
+
ui_mode : UIMode or str, optional
|
|
94
|
+
Defines the user interface mode for Fluent. Accepts either a ``UIMode`` value
|
|
95
|
+
or a corresponding string such as ``"no_gui"``, ``"hidden_gui"``, or ``"gui"``.
|
|
95
96
|
graphics_driver : FluentWindowsGraphicsDriver or FluentLinuxGraphicsDriver
|
|
96
97
|
Specifies the graphics driver for Fluent. Options are from the ``FluentWindowsGraphicsDriver`` enum
|
|
97
98
|
(for Windows) or the ``FluentLinuxGraphicsDriver`` enum (for Linux).
|
|
@@ -194,8 +195,9 @@ class SessionBase:
|
|
|
194
195
|
|
|
195
196
|
Parameters
|
|
196
197
|
----------
|
|
197
|
-
ui_mode : UIMode
|
|
198
|
-
Defines the user interface mode for Fluent.
|
|
198
|
+
ui_mode : UIMode or str, optional
|
|
199
|
+
Defines the user interface mode for Fluent. Accepts either a ``UIMode`` value
|
|
200
|
+
or a corresponding string such as ``"no_gui"``, ``"hidden_gui"``, or ``"gui"``.
|
|
199
201
|
graphics_driver : FluentWindowsGraphicsDriver or FluentLinuxGraphicsDriver
|
|
200
202
|
Specifies the graphics driver for Fluent. Options are from the ``FluentWindowsGraphicsDriver`` enum
|
|
201
203
|
(for Windows) or the ``FluentLinuxGraphicsDriver`` enum (for Linux).
|
|
@@ -287,7 +289,8 @@ class SessionBase:
|
|
|
287
289
|
Parameters
|
|
288
290
|
----------
|
|
289
291
|
ui_mode : UIMode or str, optional
|
|
290
|
-
Defines the user interface mode for Fluent.
|
|
292
|
+
Defines the user interface mode for Fluent. Accepts either a ``UIMode`` value
|
|
293
|
+
or a corresponding string such as ``"no_gui"``, ``"hidden_gui"``, or ``"gui"``.
|
|
291
294
|
graphics_driver : FluentWindowsGraphicsDriver or FluentLinuxGraphicsDriver
|
|
292
295
|
Specifies the graphics driver for Fluent. Options are from the ``FluentWindowsGraphicsDriver`` enum
|
|
293
296
|
(for Windows) or the ``FluentLinuxGraphicsDriver`` enum (for Linux).
|
|
@@ -80,6 +80,7 @@ from ansys.fluent.core.variable_strategies import (
|
|
|
80
80
|
FluentFieldDataNamingStrategy as naming_strategy,
|
|
81
81
|
)
|
|
82
82
|
import ansys.units
|
|
83
|
+
from ansys.units import VariableDescriptor
|
|
83
84
|
|
|
84
85
|
from . import _docstrings
|
|
85
86
|
from ..pyfluent_warnings import warning_for_fluent_dev_version
|
|
@@ -676,7 +677,21 @@ class Textual(Property):
|
|
|
676
677
|
Either str or VariableDescriptor.
|
|
677
678
|
kwargs : Any
|
|
678
679
|
Keyword arguments.
|
|
680
|
+
|
|
681
|
+
Raises
|
|
682
|
+
------
|
|
683
|
+
TypeError
|
|
684
|
+
If state is not a string.
|
|
679
685
|
"""
|
|
686
|
+
allowed_types = (str, VariableDescriptor)
|
|
687
|
+
|
|
688
|
+
if not isinstance(state, allowed_types):
|
|
689
|
+
if self._has_migration_adapter:
|
|
690
|
+
return self.base_set_state(state=state, **kwargs)
|
|
691
|
+
raise TypeError(
|
|
692
|
+
f"Expected state to be {' or '.join(t.__name__ for t in allowed_types)}, "
|
|
693
|
+
f"got {type(state).__name__}."
|
|
694
|
+
)
|
|
680
695
|
return self.base_set_state(state=_to_field_name_str(state), **kwargs)
|
|
681
696
|
|
|
682
697
|
|
|
@@ -2282,6 +2297,10 @@ def get_cls(name, info, parent=None, version=None, parent_taboo=None):
|
|
|
2282
2297
|
)
|
|
2283
2298
|
cls._allowed_values = allowed_values
|
|
2284
2299
|
|
|
2300
|
+
has_migration_adapter = info.get("has-migration-adapter?", False)
|
|
2301
|
+
if has_migration_adapter:
|
|
2302
|
+
cls._has_migration_adapter = True
|
|
2303
|
+
|
|
2285
2304
|
except Exception:
|
|
2286
2305
|
print(
|
|
2287
2306
|
f"Unable to construct class for '{name}' of "
|
|
@@ -211,6 +211,8 @@ def _eval_reduction(
|
|
|
211
211
|
weight = "Weight=" + str(weight)
|
|
212
212
|
locations = str(locations) + ", " + weight
|
|
213
213
|
|
|
214
|
+
if hasattr(expr, "definition"):
|
|
215
|
+
expr = expr.definition()
|
|
214
216
|
expr_str = _expr_to_expr_str(naming_strategy().to_string(expr))
|
|
215
217
|
if condition:
|
|
216
218
|
expr_str = expr_str + ", " + condition
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""Public exposure of ui UI for PyFluent."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def in_jupyter():
|
|
27
|
+
"""Checks if the library is being used in a Jupyter environment."""
|
|
28
|
+
try:
|
|
29
|
+
from IPython import get_ipython
|
|
30
|
+
|
|
31
|
+
return "IPKernelApp" in get_ipython().config
|
|
32
|
+
except (ImportError, AttributeError):
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if in_jupyter():
|
|
37
|
+
from ansys.fluent.core.ui.jupyter_ui import (
|
|
38
|
+
set_auto_refresh,
|
|
39
|
+
settings_ui,
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
from ansys.fluent.core.ui.standalone_web_ui import ( # noqa: F401
|
|
43
|
+
build_settings_view,
|
|
44
|
+
set_auto_refresh,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ui(settings_obj):
|
|
49
|
+
"""PyFluent ui UI wrapper."""
|
|
50
|
+
if in_jupyter():
|
|
51
|
+
import IPython
|
|
52
|
+
from IPython.display import display
|
|
53
|
+
|
|
54
|
+
if hasattr(IPython, "get_ipython") and "ZMQInteractiveShell" in str(
|
|
55
|
+
type(IPython.get_ipython())
|
|
56
|
+
):
|
|
57
|
+
display(settings_ui(settings_obj))
|
|
58
|
+
else:
|
|
59
|
+
import panel as pn
|
|
60
|
+
|
|
61
|
+
pn.extension()
|
|
62
|
+
view = build_settings_view(settings_obj)
|
|
63
|
+
view.servable()
|
|
64
|
+
pn.serve(view)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""Render ui UI in Jupyter notebook."""
|
|
24
|
+
|
|
25
|
+
from ansys.fluent.core.ui.utils import (
|
|
26
|
+
_parse_path,
|
|
27
|
+
_render_widget_from_props_generic,
|
|
28
|
+
_safe_get_properties,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import ipywidgets as widgets
|
|
33
|
+
except ModuleNotFoundError as exc:
|
|
34
|
+
raise ModuleNotFoundError(
|
|
35
|
+
"Missing dependencies, use 'pip install ansys-fluent-core[ui-jupyter]' to install them."
|
|
36
|
+
) from exc
|
|
37
|
+
|
|
38
|
+
from ansys.fluent.core.solver.flobject import (
|
|
39
|
+
BaseCommand,
|
|
40
|
+
Group,
|
|
41
|
+
NamedObject,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def set_auto_refresh():
|
|
46
|
+
"""Refreshes the UI w.r.t. server state for each command execution or parameter invocation."""
|
|
47
|
+
raise NotImplementedError("This is yet to be implemented in jupyter environment.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _render_widgets_from_props(settings_obj, label, props):
|
|
51
|
+
"""Render widget using pre-fetched props instead of repeated calls."""
|
|
52
|
+
return _render_widget_from_props_generic(settings_obj, label, props, widgets)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _param_ui(settings_obj, props):
|
|
56
|
+
label = props["python_name"].replace("_", " ").capitalize()
|
|
57
|
+
|
|
58
|
+
def get_fn():
|
|
59
|
+
try:
|
|
60
|
+
return getattr(props["parent"], props["python_name"])
|
|
61
|
+
except AttributeError:
|
|
62
|
+
return props["parent"][props["obj_name"]]
|
|
63
|
+
|
|
64
|
+
def set_fn(v):
|
|
65
|
+
return setattr(settings_obj.parent, props["python_name"], v)
|
|
66
|
+
|
|
67
|
+
widget = _render_widgets_from_props(get_fn(), label, props)
|
|
68
|
+
output = widgets.Output()
|
|
69
|
+
with output:
|
|
70
|
+
output.clear_output()
|
|
71
|
+
print(_parse_path(settings_obj))
|
|
72
|
+
if hasattr(widget, "_is_list_text"):
|
|
73
|
+
typ, parse_csv = widget._is_list_text
|
|
74
|
+
|
|
75
|
+
def commit_text_list(change):
|
|
76
|
+
if change["name"] == "value":
|
|
77
|
+
raw = change["new"]
|
|
78
|
+
vals = (
|
|
79
|
+
[typ(v.strip()) for v in raw.split(",") if v.strip()]
|
|
80
|
+
if parse_csv
|
|
81
|
+
else list(change["new"])
|
|
82
|
+
)
|
|
83
|
+
with output:
|
|
84
|
+
output.clear_output()
|
|
85
|
+
set_fn(vals)
|
|
86
|
+
print(f"{_parse_path(settings_obj)} = {vals}")
|
|
87
|
+
|
|
88
|
+
widget.observe(commit_text_list)
|
|
89
|
+
else:
|
|
90
|
+
|
|
91
|
+
def on_change(change):
|
|
92
|
+
if change["name"] == "value":
|
|
93
|
+
with output:
|
|
94
|
+
output.clear_output()
|
|
95
|
+
try:
|
|
96
|
+
set_fn(change["new"])
|
|
97
|
+
print(f"{_parse_path(settings_obj)} = {change['new']}")
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error setting {label}: {e}")
|
|
100
|
+
|
|
101
|
+
widget.observe(on_change)
|
|
102
|
+
return widgets.VBox([widget, output])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _command_ui(func, props):
|
|
106
|
+
"""
|
|
107
|
+
Renders input widgets for function arguments based on .argument_names()
|
|
108
|
+
and executes func(**kwargs) on button click.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# Get argument names from the function object
|
|
112
|
+
if not hasattr(func, "argument_names"):
|
|
113
|
+
return widgets.HTML("Command has no 'argument_names()'.")
|
|
114
|
+
|
|
115
|
+
arg_names = func.argument_names
|
|
116
|
+
arg_widgets = {}
|
|
117
|
+
controls = []
|
|
118
|
+
for name in arg_names:
|
|
119
|
+
child_obj = getattr(func, name)
|
|
120
|
+
child_props = _safe_get_properties(child_obj)
|
|
121
|
+
widget = _render_widgets_from_props(child_obj, name, child_props)
|
|
122
|
+
arg_widgets[name] = widget
|
|
123
|
+
controls.append(widget)
|
|
124
|
+
|
|
125
|
+
# Run button
|
|
126
|
+
button = widgets.Button(
|
|
127
|
+
description=f"Run {props['python_name']}", button_style="success"
|
|
128
|
+
)
|
|
129
|
+
output = widgets.Output()
|
|
130
|
+
with output:
|
|
131
|
+
output.clear_output()
|
|
132
|
+
print(_parse_path(func))
|
|
133
|
+
|
|
134
|
+
def on_click(_):
|
|
135
|
+
kwargs = {name: w.value for name, w in arg_widgets.items()}
|
|
136
|
+
with output:
|
|
137
|
+
output.clear_output()
|
|
138
|
+
try:
|
|
139
|
+
func(**kwargs)
|
|
140
|
+
kwargs_str = "("
|
|
141
|
+
for k, v in kwargs.items():
|
|
142
|
+
if type(v) is str:
|
|
143
|
+
if v != "":
|
|
144
|
+
kwargs_str += f"{k}='{v}', "
|
|
145
|
+
else:
|
|
146
|
+
kwargs_str += f"{k}={v}, "
|
|
147
|
+
print(f"{_parse_path(func)}" + kwargs_str.strip()[:-1] + ")")
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print("Error:", e)
|
|
150
|
+
|
|
151
|
+
button.on_click(on_click)
|
|
152
|
+
return widgets.VBox(controls + [button, output])
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def settings_ui(obj, indent=0):
|
|
156
|
+
"""Render settings objects into ui graphics."""
|
|
157
|
+
props = _safe_get_properties(obj)
|
|
158
|
+
if isinstance(obj, (Group, NamedObject)):
|
|
159
|
+
if isinstance(obj, Group):
|
|
160
|
+
command_names = obj.get_active_command_names()
|
|
161
|
+
child_names = obj.get_active_child_names() + command_names
|
|
162
|
+
else:
|
|
163
|
+
command_names = obj.command_names
|
|
164
|
+
child_names = list(obj) + command_names
|
|
165
|
+
accordions = []
|
|
166
|
+
for child_name in child_names:
|
|
167
|
+
|
|
168
|
+
def lazy_loader(name=child_name, parent=obj, lvl=indent + 1):
|
|
169
|
+
try:
|
|
170
|
+
child_obj = getattr(parent, name)
|
|
171
|
+
except AttributeError:
|
|
172
|
+
child_obj = parent[name]
|
|
173
|
+
return settings_ui(child_obj, lvl)
|
|
174
|
+
|
|
175
|
+
acc = widgets.Accordion(children=[widgets.HTML("Loading...")])
|
|
176
|
+
if child_name in command_names:
|
|
177
|
+
acc.set_title(0, f"⚡ {child_name}")
|
|
178
|
+
else:
|
|
179
|
+
acc.set_title(0, child_name)
|
|
180
|
+
|
|
181
|
+
def on_selected(change, loader=lazy_loader, accordion=acc):
|
|
182
|
+
if change["name"] == "selected_index" and change["new"] == 0:
|
|
183
|
+
if isinstance(accordion.children[0], widgets.HTML):
|
|
184
|
+
accordion.children = [loader()]
|
|
185
|
+
|
|
186
|
+
acc.observe(on_selected, names="selected_index")
|
|
187
|
+
accordions.append(acc)
|
|
188
|
+
|
|
189
|
+
return widgets.VBox(accordions)
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
if isinstance(obj, BaseCommand):
|
|
193
|
+
return (
|
|
194
|
+
widgets.VBox([_command_ui(obj, props)])
|
|
195
|
+
if props["is_active"]
|
|
196
|
+
else widgets.HTML("")
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
return (
|
|
200
|
+
widgets.VBox([_param_ui(obj, props)])
|
|
201
|
+
if props["is_active"]
|
|
202
|
+
else widgets.HTML("")
|
|
203
|
+
)
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""Web UI for Fluent settings using Panel with lazy loading and batched property access."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any, Callable, Dict, List
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
import panel as pn
|
|
31
|
+
import param
|
|
32
|
+
except ModuleNotFoundError as exc:
|
|
33
|
+
raise ModuleNotFoundError(
|
|
34
|
+
"Missing dependencies, use 'pip install ansys-fluent-core[ui]' to install them."
|
|
35
|
+
) from exc
|
|
36
|
+
|
|
37
|
+
from ansys.fluent.core.solver.flobject import (
|
|
38
|
+
BaseCommand,
|
|
39
|
+
Group,
|
|
40
|
+
NamedObject,
|
|
41
|
+
)
|
|
42
|
+
from ansys.fluent.core.ui.utils import (
|
|
43
|
+
_parse_path,
|
|
44
|
+
_render_widget_from_props_generic,
|
|
45
|
+
_safe_get_properties,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
_path_backup_dict = {}
|
|
49
|
+
|
|
50
|
+
AUTO_REFRESH = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def set_auto_refresh():
|
|
54
|
+
"""Refreshes the UI w.r.t. server state for each command execution or parameter invocation."""
|
|
55
|
+
global AUTO_REFRESH
|
|
56
|
+
AUTO_REFRESH = True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
pn.extension()
|
|
60
|
+
|
|
61
|
+
# global trigger for refresh (Panel Param depends on it)
|
|
62
|
+
_refresh = pn.state.cache.get("fluent_refresh", None)
|
|
63
|
+
if _refresh is None:
|
|
64
|
+
|
|
65
|
+
class Refresh(param.Parameterized):
|
|
66
|
+
"""Refresh."""
|
|
67
|
+
|
|
68
|
+
bump = param.Event()
|
|
69
|
+
|
|
70
|
+
_refresh = Refresh()
|
|
71
|
+
pn.state.cache["fluent_refresh"] = _refresh
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _render_widget_from_props(
|
|
75
|
+
settings_obj, label: str, props: Dict[str, Any]
|
|
76
|
+
) -> pn.viewable.Viewable:
|
|
77
|
+
"""Produce a Panel widget from type+props. No backend mutation here."""
|
|
78
|
+
return _render_widget_from_props_generic(settings_obj, label, props, pn.widgets)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _param_view(settings_obj, props: Dict[str, Any]) -> pn.viewable.Viewable:
|
|
82
|
+
label = props["python_name"].replace("_", " ").capitalize()
|
|
83
|
+
|
|
84
|
+
def get_fn():
|
|
85
|
+
try:
|
|
86
|
+
return getattr(props["parent"], props["python_name"])
|
|
87
|
+
except AttributeError:
|
|
88
|
+
return props["parent"][props["obj_name"]]
|
|
89
|
+
|
|
90
|
+
def set_fn(v):
|
|
91
|
+
return setattr(settings_obj.parent, props["python_name"], v)
|
|
92
|
+
|
|
93
|
+
w = _render_widget_from_props(get_fn(), label, props)
|
|
94
|
+
obj_apth = _parse_path(settings_obj)
|
|
95
|
+
if obj_apth in _path_backup_dict:
|
|
96
|
+
console = pn.pane.Markdown(
|
|
97
|
+
f"```\n{obj_apth} = {_path_backup_dict[obj_apth]}\n```",
|
|
98
|
+
sizing_mode="stretch_width",
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
console = pn.pane.Markdown(f"```\n{obj_apth}\n```", sizing_mode="stretch_width")
|
|
102
|
+
|
|
103
|
+
# Change handlers
|
|
104
|
+
if hasattr(w, "_is_list_text"):
|
|
105
|
+
typ, parse_csv = w._is_list_text
|
|
106
|
+
|
|
107
|
+
def _commit_list(event):
|
|
108
|
+
if event is None:
|
|
109
|
+
return
|
|
110
|
+
newv = event.new
|
|
111
|
+
if parse_csv:
|
|
112
|
+
raw = newv or ""
|
|
113
|
+
vals = [typ(v.strip()) for v in raw.split(",") if v.strip()]
|
|
114
|
+
else:
|
|
115
|
+
vals = list(newv or [])
|
|
116
|
+
try:
|
|
117
|
+
set_fn(vals)
|
|
118
|
+
console.object = f"```\n{_parse_path(settings_obj)} = {vals}\n```"
|
|
119
|
+
_path_backup_dict[_parse_path(settings_obj)] = vals
|
|
120
|
+
if AUTO_REFRESH:
|
|
121
|
+
_refresh.bump = True
|
|
122
|
+
except Exception as e:
|
|
123
|
+
console.object = f"```\nError setting {label}: {e}\n```"
|
|
124
|
+
|
|
125
|
+
w.param.watch(_commit_list, "value", onlychanged=True)
|
|
126
|
+
else:
|
|
127
|
+
|
|
128
|
+
def _commit(event):
|
|
129
|
+
if event is None:
|
|
130
|
+
return
|
|
131
|
+
try:
|
|
132
|
+
set_fn(event.new)
|
|
133
|
+
console.object = f"```\n{_parse_path(settings_obj)} = {event.new}\n```"
|
|
134
|
+
_path_backup_dict[_parse_path(settings_obj)] = event.new
|
|
135
|
+
if AUTO_REFRESH:
|
|
136
|
+
_refresh.bump = True
|
|
137
|
+
except Exception as e:
|
|
138
|
+
console.object = f"```\nError setting {label}: {e}\n```"
|
|
139
|
+
|
|
140
|
+
w.param.watch(_commit, "value", onlychanged=True)
|
|
141
|
+
|
|
142
|
+
return pn.Column(
|
|
143
|
+
w, console, sizing_mode="stretch_width", margin=(10, 20), align="start"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _command_view(func, props: Dict[str, Any]) -> pn.viewable.Viewable:
|
|
148
|
+
"""Render command arguments (on demand) and execute only on click."""
|
|
149
|
+
# Safely fetch argument names (does NOT execute the command)
|
|
150
|
+
if not hasattr(func, "argument_names"):
|
|
151
|
+
return pn.pane.HTML("<i>Command has no 'argument_names()'.</i>")
|
|
152
|
+
arg_names = func.argument_names
|
|
153
|
+
arg_widgets: Dict[str, Any] = {}
|
|
154
|
+
controls: List[pn.viewable.Viewable] = []
|
|
155
|
+
|
|
156
|
+
# Build argument widgets immediately when this view is created
|
|
157
|
+
for name in arg_names:
|
|
158
|
+
param_obj = getattr(func, name) # safe: this just references the arg handle
|
|
159
|
+
pprops = _safe_get_properties(param_obj)
|
|
160
|
+
widget = _render_widget_from_props(param_obj, name, pprops)
|
|
161
|
+
arg_widgets[name] = widget
|
|
162
|
+
controls.append(widget)
|
|
163
|
+
|
|
164
|
+
btn = pn.widgets.Button(name=f"Run {props['python_name']}", button_type="success")
|
|
165
|
+
obj_path = _parse_path(func)
|
|
166
|
+
if obj_path in _path_backup_dict:
|
|
167
|
+
console = pn.pane.Markdown(
|
|
168
|
+
f"```\n{_path_backup_dict[obj_path]}\n```", sizing_mode="stretch_width"
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
console = pn.pane.Markdown(f"```\n{obj_path}\n```", sizing_mode="stretch_width")
|
|
172
|
+
|
|
173
|
+
def _run(_):
|
|
174
|
+
kwargs = {n: w.value for n, w in arg_widgets.items()}
|
|
175
|
+
try:
|
|
176
|
+
func(**kwargs) # Executes ONLY here
|
|
177
|
+
# Render kwargs similarly to your ipywidgets formatter
|
|
178
|
+
parts = []
|
|
179
|
+
for k, v in kwargs.items():
|
|
180
|
+
if isinstance(v, str):
|
|
181
|
+
if v != "":
|
|
182
|
+
parts.append(f"{k}='{v}'")
|
|
183
|
+
else:
|
|
184
|
+
parts.append(f"{k}={v}")
|
|
185
|
+
call = f"{_parse_path(func)}({', '.join(parts)})"
|
|
186
|
+
console.object = f"```\n{call}\n```"
|
|
187
|
+
_path_backup_dict[_parse_path(func)] = call
|
|
188
|
+
if AUTO_REFRESH:
|
|
189
|
+
_refresh.bump = True
|
|
190
|
+
except Exception as e:
|
|
191
|
+
console.object = f"```\nError: {e}\n```"
|
|
192
|
+
|
|
193
|
+
btn.on_click(_run)
|
|
194
|
+
return pn.Column(
|
|
195
|
+
*controls,
|
|
196
|
+
btn,
|
|
197
|
+
console,
|
|
198
|
+
sizing_mode="stretch_width",
|
|
199
|
+
margin=(10, 20),
|
|
200
|
+
align="start",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ---------------------------
|
|
205
|
+
# Lazy accordion (recursive)
|
|
206
|
+
# ---------------------------
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _lazy_section(
|
|
210
|
+
title: str, loader: Callable[[], pn.viewable.Viewable]
|
|
211
|
+
) -> pn.Accordion:
|
|
212
|
+
"""
|
|
213
|
+
A single-node Accordion whose body is constructed ON FIRST EXPAND.
|
|
214
|
+
Subsequent expands reuse the existing content.
|
|
215
|
+
"""
|
|
216
|
+
placeholder = pn.pane.Markdown("*(loading…)*")
|
|
217
|
+
acc = pn.Accordion((title, placeholder), sizing_mode="stretch_width")
|
|
218
|
+
|
|
219
|
+
def _load_content():
|
|
220
|
+
try:
|
|
221
|
+
return loader()
|
|
222
|
+
except Exception as e:
|
|
223
|
+
return pn.pane.Markdown(f"**Error loading section**: {e}")
|
|
224
|
+
|
|
225
|
+
def _maybe_load(event=None):
|
|
226
|
+
if acc.active and 0 in acc.active:
|
|
227
|
+
content = _load_content()
|
|
228
|
+
acc[0] = (title, content)
|
|
229
|
+
|
|
230
|
+
# Trigger when first opened
|
|
231
|
+
acc.param.watch(_maybe_load, "active", onlychanged=True)
|
|
232
|
+
|
|
233
|
+
def _on_refresh(event):
|
|
234
|
+
if acc.active and 0 in acc.active:
|
|
235
|
+
_maybe_load()
|
|
236
|
+
|
|
237
|
+
_refresh.param.watch(_on_refresh, "bump")
|
|
238
|
+
|
|
239
|
+
return acc
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ---------------------------
|
|
243
|
+
# Main entry (recursive renderer)
|
|
244
|
+
# ---------------------------
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _settings_view(obj, indent: int = 0) -> pn.viewable.Viewable:
|
|
248
|
+
"""Recursively build the view for a settings object (lazy children)."""
|
|
249
|
+
props = _safe_get_properties(obj)
|
|
250
|
+
|
|
251
|
+
if isinstance(obj, (Group, NamedObject)):
|
|
252
|
+
if isinstance(obj, Group):
|
|
253
|
+
command_names = obj.get_active_command_names()
|
|
254
|
+
child_names = obj.get_active_child_names() + command_names
|
|
255
|
+
else:
|
|
256
|
+
command_names = obj.command_names
|
|
257
|
+
child_names = list(obj) + command_names
|
|
258
|
+
else:
|
|
259
|
+
if isinstance(obj, BaseCommand):
|
|
260
|
+
return _command_view(obj, props) if props["is_active"] else pn.pane.HTML("")
|
|
261
|
+
else:
|
|
262
|
+
return _param_view(obj, props) if props["is_active"] else pn.pane.HTML("")
|
|
263
|
+
|
|
264
|
+
sections: List[pn.viewable.Viewable] = []
|
|
265
|
+
|
|
266
|
+
for child_name in child_names:
|
|
267
|
+
# Build a lazy loader that only resolves the child on expand
|
|
268
|
+
def _loader(name=child_name, parent=obj, lvl=indent + 1):
|
|
269
|
+
try:
|
|
270
|
+
child_obj = getattr(parent, name)
|
|
271
|
+
except AttributeError:
|
|
272
|
+
child_obj = parent[name]
|
|
273
|
+
return _settings_view(child_obj, lvl)
|
|
274
|
+
|
|
275
|
+
# Each child gets its own one-item accordion (mirrors your ipywidgets UX)
|
|
276
|
+
if child_name in command_names:
|
|
277
|
+
display_name = f"⚡ {child_name}"
|
|
278
|
+
else:
|
|
279
|
+
display_name = child_name
|
|
280
|
+
sections.append(_lazy_section(display_name, _loader))
|
|
281
|
+
|
|
282
|
+
return pn.Column(
|
|
283
|
+
*(pn.Column(sec, margin=(5, 0)) for sec in sections),
|
|
284
|
+
sizing_mode="fixed",
|
|
285
|
+
margin=(10, 20),
|
|
286
|
+
align="start",
|
|
287
|
+
css_classes=["rounded-box"],
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def build_settings_view(settings_obj) -> pn.viewable.Viewable:
|
|
292
|
+
"""
|
|
293
|
+
Public API: pass any Fluent settings object.
|
|
294
|
+
Internally uses _root to render absolute paths and builds a lazy, web UI.
|
|
295
|
+
"""
|
|
296
|
+
return _settings_view(settings_obj)
|