ansys-mechanical-core 0.11.12__py3-none-any.whl → 0.11.14__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.
- ansys/mechanical/core/__init__.py +0 -1
- ansys/mechanical/core/_version.py +48 -48
- ansys/mechanical/core/embedding/app.py +681 -610
- ansys/mechanical/core/embedding/background.py +11 -2
- ansys/mechanical/core/embedding/enum_importer.py +0 -1
- ansys/mechanical/core/embedding/global_importer.py +50 -0
- ansys/mechanical/core/embedding/imports.py +30 -58
- ansys/mechanical/core/embedding/initializer.py +6 -4
- ansys/mechanical/core/embedding/logger/__init__.py +219 -219
- ansys/mechanical/core/embedding/messages.py +190 -0
- ansys/mechanical/core/embedding/resolver.py +48 -41
- ansys/mechanical/core/embedding/rpc/__init__.py +36 -0
- ansys/mechanical/core/embedding/rpc/client.py +259 -0
- ansys/mechanical/core/embedding/rpc/server.py +403 -0
- ansys/mechanical/core/embedding/rpc/utils.py +118 -0
- ansys/mechanical/core/embedding/runtime.py +22 -0
- ansys/mechanical/core/embedding/transaction.py +51 -0
- ansys/mechanical/core/feature_flags.py +1 -0
- ansys/mechanical/core/ide_config.py +226 -212
- ansys/mechanical/core/mechanical.py +2399 -2324
- ansys/mechanical/core/misc.py +176 -176
- ansys/mechanical/core/pool.py +712 -712
- ansys/mechanical/core/run.py +321 -321
- {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/METADATA +40 -27
- ansys_mechanical_core-0.11.14.dist-info/RECORD +52 -0
- {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/WHEEL +1 -1
- ansys_mechanical_core-0.11.12.dist-info/RECORD +0 -45
- {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info}/entry_points.txt +0 -0
- {ansys_mechanical_core-0.11.12.dist-info → ansys_mechanical_core-0.11.14.dist-info/licenses}/LICENSE +0 -0
@@ -76,12 +76,21 @@ class BackgroundApp:
|
|
76
76
|
"""
|
77
77
|
return BackgroundApp.__app
|
78
78
|
|
79
|
-
def
|
80
|
-
"""Post callable method to the background app thread."""
|
79
|
+
def _post(self, callable: typing.Callable, try_post: bool = False):
|
81
80
|
if BackgroundApp.__stopped:
|
82
81
|
raise RuntimeError("Cannot use BackgroundApp after stopping it.")
|
82
|
+
if try_post:
|
83
|
+
return BackgroundApp.__poster.try_post(callable)
|
83
84
|
return BackgroundApp.__poster.post(callable)
|
84
85
|
|
86
|
+
def post(self, callable: typing.Callable):
|
87
|
+
"""Post callable method to the background app thread."""
|
88
|
+
return self._post(callable)
|
89
|
+
|
90
|
+
def try_post(self, callable: typing.Callable):
|
91
|
+
"""Try post callable method to the background app thread."""
|
92
|
+
return self._post(callable, try_post=True)
|
93
|
+
|
85
94
|
def stop(self) -> None:
|
86
95
|
"""Stop the background app thread."""
|
87
96
|
if BackgroundApp.__stopped:
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (C) 2022 - 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
|
+
"""Import Mechanical globals."""
|
23
|
+
|
24
|
+
from ansys.mechanical.core.embedding.app import is_initialized
|
25
|
+
|
26
|
+
if not is_initialized():
|
27
|
+
raise Exception("Globals cannot be imported until the embedded app is initialized.")
|
28
|
+
|
29
|
+
import clr
|
30
|
+
|
31
|
+
clr.AddReference("Ansys.Mechanical.DataModel")
|
32
|
+
clr.AddReference("Ansys.ACT.Interfaces")
|
33
|
+
|
34
|
+
|
35
|
+
clr.AddReference("System.Collections")
|
36
|
+
clr.AddReference("Ansys.ACT.WB1")
|
37
|
+
clr.AddReference("Ansys.Mechanical.DataModel")
|
38
|
+
|
39
|
+
# from Ansys.ACT.Mechanical import Transaction
|
40
|
+
# When ansys-pythonnet issue #14 is fixed, uncomment above
|
41
|
+
from Ansys.ACT.Core.Math import Point2D, Point3D # noqa isort: skip
|
42
|
+
from Ansys.ACT.Math import Vector3D # noqa isort: skip
|
43
|
+
from Ansys.Core.Units import Quantity # noqa isort: skip
|
44
|
+
from Ansys.Mechanical.DataModel import MechanicalEnums # noqa isort: skip
|
45
|
+
from Ansys.Mechanical.Graphics import Point, SectionPlane # noqa isort: skip
|
46
|
+
|
47
|
+
from ansys.mechanical.core.embedding.transaction import Transaction # noqa isort: skip
|
48
|
+
|
49
|
+
import System # noqa isort: skip
|
50
|
+
import Ansys # noqa isort: skip
|
@@ -46,36 +46,37 @@ def global_variables(app: "ansys.mechanical.core.App", enums: bool = False) -> t
|
|
46
46
|
To also import all the enums, set the parameter enums to true.
|
47
47
|
"""
|
48
48
|
vars = global_entry_points(app)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
clr.AddReference("Ansys.Mechanical.DataModel")
|
54
|
-
# from Ansys.ACT.Mechanical import Transaction
|
55
|
-
# When ansys-pythonnet issue #14 is fixed, uncomment above
|
56
|
-
from Ansys.ACT.Core.Math import Point2D, Point3D
|
57
|
-
from Ansys.ACT.Math import Vector3D
|
58
|
-
from Ansys.ACT.Mechanical.Fields import VariableDefinitionType
|
59
|
-
from Ansys.Core.Units import Quantity
|
60
|
-
from Ansys.Mechanical.DataModel import MechanicalEnums
|
61
|
-
from Ansys.Mechanical.Graphics import Point, SectionPlane
|
62
|
-
|
63
|
-
import System # isort: skip
|
64
|
-
import Ansys # isort: skip
|
65
|
-
|
66
|
-
vars["Quantity"] = Quantity
|
67
|
-
vars["System"] = System
|
68
|
-
vars["Ansys"] = Ansys
|
49
|
+
|
50
|
+
from ansys.mechanical.core.embedding.app import is_initialized
|
51
|
+
from ansys.mechanical.core.embedding.transaction import Transaction
|
52
|
+
|
69
53
|
vars["Transaction"] = Transaction
|
70
|
-
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
54
|
+
|
55
|
+
# Import modules if the app is initialized
|
56
|
+
if is_initialized():
|
57
|
+
from ansys.mechanical.core.embedding.global_importer import (
|
58
|
+
Ansys,
|
59
|
+
MechanicalEnums,
|
60
|
+
Point,
|
61
|
+
Point2D,
|
62
|
+
Point3D,
|
63
|
+
Quantity,
|
64
|
+
SectionPlane,
|
65
|
+
System,
|
66
|
+
Vector3D,
|
67
|
+
)
|
68
|
+
|
69
|
+
vars["Quantity"] = Quantity
|
70
|
+
vars["System"] = System
|
71
|
+
vars["Ansys"] = Ansys
|
72
|
+
vars["MechanicalEnums"] = MechanicalEnums
|
73
|
+
# Graphics
|
74
|
+
vars["Point"] = Point
|
75
|
+
vars["SectionPlane"] = SectionPlane
|
76
|
+
# Math
|
77
|
+
vars["Point2D"] = Point2D
|
78
|
+
vars["Point3D"] = Point3D
|
79
|
+
vars["Vector3D"] = Vector3D
|
79
80
|
|
80
81
|
if enums:
|
81
82
|
vars.update(get_all_enums())
|
@@ -95,32 +96,3 @@ def get_all_enums() -> typing.Dict[str, typing.Any]:
|
|
95
96
|
if type(the_enum).__name__ == "CLRMetatype":
|
96
97
|
enums[attr] = the_enum
|
97
98
|
return enums
|
98
|
-
|
99
|
-
|
100
|
-
class Transaction: # When ansys-pythonnet issue #14 is fixed, this class will be removed
|
101
|
-
"""
|
102
|
-
A class to speed up bulk user interactions using Ansys ACT Mechanical Transaction.
|
103
|
-
|
104
|
-
Example
|
105
|
-
-------
|
106
|
-
>>> with Transaction() as transaction:
|
107
|
-
... pass # Perform bulk user interactions here
|
108
|
-
...
|
109
|
-
"""
|
110
|
-
|
111
|
-
def __init__(self):
|
112
|
-
"""Initialize the Transaction class."""
|
113
|
-
import clr
|
114
|
-
|
115
|
-
clr.AddReference("Ansys.ACT.WB1")
|
116
|
-
import Ansys
|
117
|
-
|
118
|
-
self._transaction = Ansys.ACT.Mechanical.Transaction()
|
119
|
-
|
120
|
-
def __enter__(self):
|
121
|
-
"""Enter the context of the transaction."""
|
122
|
-
return self
|
123
|
-
|
124
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
125
|
-
"""Exit the context of the transaction and disposes of resources."""
|
126
|
-
self._transaction.Dispose()
|
@@ -30,6 +30,7 @@ import warnings
|
|
30
30
|
|
31
31
|
from ansys.mechanical.core.embedding.loader import load_clr
|
32
32
|
from ansys.mechanical.core.embedding.resolver import resolve
|
33
|
+
from ansys.mechanical.core.mechanical import LOG
|
33
34
|
|
34
35
|
INITIALIZED_VERSION = None
|
35
36
|
"""Constant for the initialized version."""
|
@@ -85,7 +86,6 @@ def _get_latest_default_version() -> int:
|
|
85
86
|
If multiple versions are detected, select the latest one, as no specific version is provided.
|
86
87
|
"""
|
87
88
|
awp_roots = [value for key, value in os.environ.items() if key.startswith("AWP_ROOT")]
|
88
|
-
|
89
89
|
if not awp_roots:
|
90
90
|
raise Exception("No Mechanical installations found.")
|
91
91
|
|
@@ -94,13 +94,15 @@ def _get_latest_default_version() -> int:
|
|
94
94
|
folder = os.path.basename(os.path.normpath(path))
|
95
95
|
version = folder.split("v")[-1]
|
96
96
|
versions_found.append(int(version))
|
97
|
+
|
98
|
+
LOG.info(f"Available versions of Mechanical: {versions_found}")
|
99
|
+
|
97
100
|
latest_version = max(versions_found)
|
98
101
|
|
99
102
|
if len(awp_roots) > 1:
|
100
|
-
|
103
|
+
LOG.warning(
|
101
104
|
f"Multiple versions of Mechanical found! Using latest version {latest_version} ..."
|
102
105
|
)
|
103
|
-
|
104
106
|
return latest_version
|
105
107
|
|
106
108
|
|
@@ -159,7 +161,7 @@ def __check_loaded_libs(version: int = None): # pragma: no cover
|
|
159
161
|
# For 2025 R1, PyMechanical will crash on shutdown if libX11.so is already loaded
|
160
162
|
# before starting Mechanical
|
161
163
|
if __is_lib_loaded("libX11.so"):
|
162
|
-
|
164
|
+
LOG.warning(
|
163
165
|
"libX11.so is loaded prior to initializing the Embedded Instance of Mechanical.\
|
164
166
|
Python will crash on shutdown..."
|
165
167
|
)
|
@@ -1,219 +1,219 @@
|
|
1
|
-
# Copyright (C) 2022 - 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
|
-
"""Embedding logger.
|
24
|
-
|
25
|
-
Module to interact with the built-in logging system of Mechanical.
|
26
|
-
|
27
|
-
Usage
|
28
|
-
-----
|
29
|
-
|
30
|
-
Configuring logger
|
31
|
-
~~~~~~~~~~~~~~~~~~
|
32
|
-
|
33
|
-
Configuring the logger can be done using the :class:`Configuration <ansys.mechanical.core.embedding.logger.Configuration>` class:
|
34
|
-
|
35
|
-
.. code:: python
|
36
|
-
import ansys.mechanical.core as mech
|
37
|
-
from ansys.mechanical.core.embedding.logger import Configuration, Logger
|
38
|
-
|
39
|
-
Configuration.configure(level=logging.INFO, to_stdout=True, base_directory=None)
|
40
|
-
app = mech.App(version=251)
|
41
|
-
|
42
|
-
Then, the :class:`Logger <ansys.mechanical.core.embedding.logger.Logger>` class can be used to write messages to the log:
|
43
|
-
|
44
|
-
.. code:: python
|
45
|
-
|
46
|
-
Logger.error("message")
|
47
|
-
|
48
|
-
|
49
|
-
"""
|
50
|
-
|
51
|
-
import logging
|
52
|
-
import os
|
53
|
-
import typing
|
54
|
-
|
55
|
-
from ansys.mechanical.core.embedding import initializer
|
56
|
-
from ansys.mechanical.core.embedding.logger import environ, linux_api, sinks, windows_api
|
57
|
-
|
58
|
-
LOGGING_SINKS: typing.Set[int] = set()
|
59
|
-
"""Constant for logging sinks."""
|
60
|
-
|
61
|
-
LOGGING_CONTEXT: str = "PYMECHANICAL"
|
62
|
-
"""Constant for logging context."""
|
63
|
-
|
64
|
-
|
65
|
-
def _get_backend() -> (
|
66
|
-
typing.Union[windows_api.APIBackend, linux_api.APIBackend, environ.EnvironBackend]
|
67
|
-
):
|
68
|
-
"""Get the appropriate logger backend.
|
69
|
-
|
70
|
-
Before embedding is initialized, logging is configured via environment variables.
|
71
|
-
After embedding is initialized, logging is configured by making API calls into the
|
72
|
-
Mechanical logging system.
|
73
|
-
|
74
|
-
However, the API is mostly the same in both cases, though some methods only work
|
75
|
-
in one of the two backends.
|
76
|
-
|
77
|
-
Setting the base directory only works before initializing.
|
78
|
-
Actually logging a message or flushing the log only works after initializing.
|
79
|
-
"""
|
80
|
-
# TODO - use abc instead of a union type?
|
81
|
-
embedding_initialized = initializer.INITIALIZED_VERSION is not None
|
82
|
-
if not embedding_initialized:
|
83
|
-
return environ.EnvironBackend()
|
84
|
-
if os.name == "nt":
|
85
|
-
return windows_api.APIBackend()
|
86
|
-
return linux_api.APIBackend()
|
87
|
-
|
88
|
-
|
89
|
-
class Configuration:
|
90
|
-
"""Configures logger for Mechanical embedding."""
|
91
|
-
|
92
|
-
@classmethod
|
93
|
-
def configure(cls, level=logging.WARNING, directory=None, base_directory=None, to_stdout=True):
|
94
|
-
"""Configure the logger for PyMechanical embedding.
|
95
|
-
|
96
|
-
Parameters
|
97
|
-
----------
|
98
|
-
level : int, optional
|
99
|
-
Level of logging that is defined in the ``logging`` package. The default is 'DEBUG'.
|
100
|
-
Options are ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, and ``"ERROR"``.
|
101
|
-
directory : str, optional
|
102
|
-
Directory to write log file to. The default is ``None``, but by default the log
|
103
|
-
will appear somewhere in the system temp folder.
|
104
|
-
base_directory: str, optional
|
105
|
-
Base directory to write log files to. Each instance of Mechanical will write its
|
106
|
-
log to a time-stamped subfolder within this directory. This is only possible to set
|
107
|
-
before Mechanical is initialized.
|
108
|
-
to_stdout : bool, optional
|
109
|
-
Whether to write log messages to the standard output, which is the
|
110
|
-
command line. The default is ``True``.
|
111
|
-
"""
|
112
|
-
# Set up the global log configuration.
|
113
|
-
cls.set_log_directory(directory)
|
114
|
-
cls.set_log_base_directory(base_directory)
|
115
|
-
|
116
|
-
# Set up the sink-specific log configuration and store to global state.
|
117
|
-
cls._store_stdout_sink_enabled(to_stdout)
|
118
|
-
file_sink_enabled = directory is not None or base_directory is not None
|
119
|
-
cls._store_file_sink_enabled(file_sink_enabled)
|
120
|
-
|
121
|
-
# Commit the sink-specific log configuration global state to the backend.
|
122
|
-
cls._commit_enabled_configuration()
|
123
|
-
cls.set_log_level(level)
|
124
|
-
|
125
|
-
@classmethod
|
126
|
-
def set_log_to_stdout(cls, value: bool) -> None:
|
127
|
-
"""Configure logging to write to the standard output."""
|
128
|
-
cls._store_stdout_sink_enabled(value)
|
129
|
-
cls._commit_enabled_configuration()
|
130
|
-
|
131
|
-
@classmethod
|
132
|
-
def set_log_to_file(cls, value: bool) -> None:
|
133
|
-
"""Configure logging to write to a file."""
|
134
|
-
cls._store_file_sink_enabled(value)
|
135
|
-
cls._commit_enabled_configuration()
|
136
|
-
|
137
|
-
@classmethod
|
138
|
-
def set_log_level(cls, level: int) -> None:
|
139
|
-
"""Set the log level for all configured sinks."""
|
140
|
-
if len(LOGGING_SINKS) == 0:
|
141
|
-
raise Exception("No logging backend configured!")
|
142
|
-
cls._commit_level_configuration(level)
|
143
|
-
|
144
|
-
@classmethod
|
145
|
-
def set_log_directory(cls, value: str) -> None:
|
146
|
-
"""Configure logging to write to a directory."""
|
147
|
-
if value is None:
|
148
|
-
return
|
149
|
-
_get_backend().set_directory(value)
|
150
|
-
|
151
|
-
@classmethod
|
152
|
-
def set_log_base_directory(cls, directory: str) -> None:
|
153
|
-
"""Configure logging to write in a time-stamped subfolder in this directory."""
|
154
|
-
if directory is None:
|
155
|
-
return
|
156
|
-
_get_backend().set_base_directory(directory)
|
157
|
-
|
158
|
-
@classmethod
|
159
|
-
def _commit_level_configuration(cls, level: int) -> None:
|
160
|
-
for sink in LOGGING_SINKS:
|
161
|
-
_get_backend().set_log_level(level, sink)
|
162
|
-
|
163
|
-
@classmethod
|
164
|
-
def _commit_enabled_configuration(cls) -> None:
|
165
|
-
for sink in LOGGING_SINKS:
|
166
|
-
_get_backend().enable(sink)
|
167
|
-
|
168
|
-
@classmethod
|
169
|
-
def _store_stdout_sink_enabled(cls, value: bool) -> None:
|
170
|
-
if value:
|
171
|
-
LOGGING_SINKS.add(sinks.StandardSinks.CONSOLE)
|
172
|
-
else:
|
173
|
-
LOGGING_SINKS.discard(sinks.StandardSinks.CONSOLE)
|
174
|
-
|
175
|
-
@classmethod
|
176
|
-
def _store_file_sink_enabled(cls, value: bool) -> None:
|
177
|
-
if value:
|
178
|
-
LOGGING_SINKS.add(sinks.StandardSinks.STANDARD_LOG_FILE)
|
179
|
-
else:
|
180
|
-
LOGGING_SINKS.discard(sinks.StandardSinks.STANDARD_LOG_FILE)
|
181
|
-
|
182
|
-
|
183
|
-
class Logger:
|
184
|
-
"""Provides the ``Logger`` class for embedding."""
|
185
|
-
|
186
|
-
@classmethod
|
187
|
-
def flush(cls):
|
188
|
-
"""Flush the log."""
|
189
|
-
_get_backend().flush()
|
190
|
-
|
191
|
-
@classmethod
|
192
|
-
def can_log_message(cls, level: int) -> bool:
|
193
|
-
"""Get whether a message at this level is logged."""
|
194
|
-
return _get_backend().can_log_message(level)
|
195
|
-
|
196
|
-
@classmethod
|
197
|
-
def debug(cls, msg: str):
|
198
|
-
"""Write a debug message to the log."""
|
199
|
-
_get_backend().log_message(logging.DEBUG, LOGGING_CONTEXT, msg)
|
200
|
-
|
201
|
-
@classmethod
|
202
|
-
def error(cls, msg: str):
|
203
|
-
"""Write a error message to the log."""
|
204
|
-
_get_backend().log_message(logging.ERROR, LOGGING_CONTEXT, msg)
|
205
|
-
|
206
|
-
@classmethod
|
207
|
-
def info(cls, msg: str):
|
208
|
-
"""Write an info message to the log."""
|
209
|
-
_get_backend().log_message(logging.INFO, LOGGING_CONTEXT, msg)
|
210
|
-
|
211
|
-
@classmethod
|
212
|
-
def warning(cls, msg: str):
|
213
|
-
"""Write a warning message to the log."""
|
214
|
-
_get_backend().log_message(logging.WARNING, LOGGING_CONTEXT, msg)
|
215
|
-
|
216
|
-
@classmethod
|
217
|
-
def fatal(cls, msg: str):
|
218
|
-
"""Write a fatal message to the log."""
|
219
|
-
_get_backend().log_message(logging.FATAL, LOGGING_CONTEXT, msg)
|
1
|
+
# Copyright (C) 2022 - 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
|
+
"""Embedding logger.
|
24
|
+
|
25
|
+
Module to interact with the built-in logging system of Mechanical.
|
26
|
+
|
27
|
+
Usage
|
28
|
+
-----
|
29
|
+
|
30
|
+
Configuring logger
|
31
|
+
~~~~~~~~~~~~~~~~~~
|
32
|
+
|
33
|
+
Configuring the logger can be done using the :class:`Configuration <ansys.mechanical.core.embedding.logger.Configuration>` class:
|
34
|
+
|
35
|
+
.. code:: python
|
36
|
+
import ansys.mechanical.core as mech
|
37
|
+
from ansys.mechanical.core.embedding.logger import Configuration, Logger
|
38
|
+
|
39
|
+
Configuration.configure(level=logging.INFO, to_stdout=True, base_directory=None)
|
40
|
+
app = mech.App(version=251)
|
41
|
+
|
42
|
+
Then, the :class:`Logger <ansys.mechanical.core.embedding.logger.Logger>` class can be used to write messages to the log:
|
43
|
+
|
44
|
+
.. code:: python
|
45
|
+
|
46
|
+
Logger.error("message")
|
47
|
+
|
48
|
+
|
49
|
+
"""
|
50
|
+
|
51
|
+
import logging
|
52
|
+
import os
|
53
|
+
import typing
|
54
|
+
|
55
|
+
from ansys.mechanical.core.embedding import initializer
|
56
|
+
from ansys.mechanical.core.embedding.logger import environ, linux_api, sinks, windows_api
|
57
|
+
|
58
|
+
LOGGING_SINKS: typing.Set[int] = set()
|
59
|
+
"""Constant for logging sinks."""
|
60
|
+
|
61
|
+
LOGGING_CONTEXT: str = "PYMECHANICAL"
|
62
|
+
"""Constant for logging context."""
|
63
|
+
|
64
|
+
|
65
|
+
def _get_backend() -> (
|
66
|
+
typing.Union[windows_api.APIBackend, linux_api.APIBackend, environ.EnvironBackend]
|
67
|
+
):
|
68
|
+
"""Get the appropriate logger backend.
|
69
|
+
|
70
|
+
Before embedding is initialized, logging is configured via environment variables.
|
71
|
+
After embedding is initialized, logging is configured by making API calls into the
|
72
|
+
Mechanical logging system.
|
73
|
+
|
74
|
+
However, the API is mostly the same in both cases, though some methods only work
|
75
|
+
in one of the two backends.
|
76
|
+
|
77
|
+
Setting the base directory only works before initializing.
|
78
|
+
Actually logging a message or flushing the log only works after initializing.
|
79
|
+
"""
|
80
|
+
# TODO - use abc instead of a union type?
|
81
|
+
embedding_initialized = initializer.INITIALIZED_VERSION is not None
|
82
|
+
if not embedding_initialized:
|
83
|
+
return environ.EnvironBackend()
|
84
|
+
if os.name == "nt":
|
85
|
+
return windows_api.APIBackend()
|
86
|
+
return linux_api.APIBackend()
|
87
|
+
|
88
|
+
|
89
|
+
class Configuration:
|
90
|
+
"""Configures logger for Mechanical embedding."""
|
91
|
+
|
92
|
+
@classmethod
|
93
|
+
def configure(cls, level=logging.WARNING, directory=None, base_directory=None, to_stdout=True):
|
94
|
+
"""Configure the logger for PyMechanical embedding.
|
95
|
+
|
96
|
+
Parameters
|
97
|
+
----------
|
98
|
+
level : int, optional
|
99
|
+
Level of logging that is defined in the ``logging`` package. The default is 'DEBUG'.
|
100
|
+
Options are ``"DEBUG"``, ``"INFO"``, ``"WARNING"``, and ``"ERROR"``.
|
101
|
+
directory : str, optional
|
102
|
+
Directory to write log file to. The default is ``None``, but by default the log
|
103
|
+
will appear somewhere in the system temp folder.
|
104
|
+
base_directory: str, optional
|
105
|
+
Base directory to write log files to. Each instance of Mechanical will write its
|
106
|
+
log to a time-stamped subfolder within this directory. This is only possible to set
|
107
|
+
before Mechanical is initialized.
|
108
|
+
to_stdout : bool, optional
|
109
|
+
Whether to write log messages to the standard output, which is the
|
110
|
+
command line. The default is ``True``.
|
111
|
+
"""
|
112
|
+
# Set up the global log configuration.
|
113
|
+
cls.set_log_directory(directory)
|
114
|
+
cls.set_log_base_directory(base_directory)
|
115
|
+
|
116
|
+
# Set up the sink-specific log configuration and store to global state.
|
117
|
+
cls._store_stdout_sink_enabled(to_stdout)
|
118
|
+
file_sink_enabled = directory is not None or base_directory is not None
|
119
|
+
cls._store_file_sink_enabled(file_sink_enabled)
|
120
|
+
|
121
|
+
# Commit the sink-specific log configuration global state to the backend.
|
122
|
+
cls._commit_enabled_configuration()
|
123
|
+
cls.set_log_level(level)
|
124
|
+
|
125
|
+
@classmethod
|
126
|
+
def set_log_to_stdout(cls, value: bool) -> None:
|
127
|
+
"""Configure logging to write to the standard output."""
|
128
|
+
cls._store_stdout_sink_enabled(value)
|
129
|
+
cls._commit_enabled_configuration()
|
130
|
+
|
131
|
+
@classmethod
|
132
|
+
def set_log_to_file(cls, value: bool) -> None:
|
133
|
+
"""Configure logging to write to a file."""
|
134
|
+
cls._store_file_sink_enabled(value)
|
135
|
+
cls._commit_enabled_configuration()
|
136
|
+
|
137
|
+
@classmethod
|
138
|
+
def set_log_level(cls, level: int) -> None:
|
139
|
+
"""Set the log level for all configured sinks."""
|
140
|
+
if len(LOGGING_SINKS) == 0:
|
141
|
+
raise Exception("No logging backend configured!")
|
142
|
+
cls._commit_level_configuration(level)
|
143
|
+
|
144
|
+
@classmethod
|
145
|
+
def set_log_directory(cls, value: str) -> None:
|
146
|
+
"""Configure logging to write to a directory."""
|
147
|
+
if value is None:
|
148
|
+
return
|
149
|
+
_get_backend().set_directory(value)
|
150
|
+
|
151
|
+
@classmethod
|
152
|
+
def set_log_base_directory(cls, directory: str) -> None:
|
153
|
+
"""Configure logging to write in a time-stamped subfolder in this directory."""
|
154
|
+
if directory is None:
|
155
|
+
return
|
156
|
+
_get_backend().set_base_directory(directory)
|
157
|
+
|
158
|
+
@classmethod
|
159
|
+
def _commit_level_configuration(cls, level: int) -> None:
|
160
|
+
for sink in LOGGING_SINKS:
|
161
|
+
_get_backend().set_log_level(level, sink)
|
162
|
+
|
163
|
+
@classmethod
|
164
|
+
def _commit_enabled_configuration(cls) -> None:
|
165
|
+
for sink in LOGGING_SINKS:
|
166
|
+
_get_backend().enable(sink)
|
167
|
+
|
168
|
+
@classmethod
|
169
|
+
def _store_stdout_sink_enabled(cls, value: bool) -> None:
|
170
|
+
if value:
|
171
|
+
LOGGING_SINKS.add(sinks.StandardSinks.CONSOLE)
|
172
|
+
else:
|
173
|
+
LOGGING_SINKS.discard(sinks.StandardSinks.CONSOLE)
|
174
|
+
|
175
|
+
@classmethod
|
176
|
+
def _store_file_sink_enabled(cls, value: bool) -> None:
|
177
|
+
if value:
|
178
|
+
LOGGING_SINKS.add(sinks.StandardSinks.STANDARD_LOG_FILE)
|
179
|
+
else:
|
180
|
+
LOGGING_SINKS.discard(sinks.StandardSinks.STANDARD_LOG_FILE)
|
181
|
+
|
182
|
+
|
183
|
+
class Logger:
|
184
|
+
"""Provides the ``Logger`` class for embedding."""
|
185
|
+
|
186
|
+
@classmethod
|
187
|
+
def flush(cls):
|
188
|
+
"""Flush the log."""
|
189
|
+
_get_backend().flush()
|
190
|
+
|
191
|
+
@classmethod
|
192
|
+
def can_log_message(cls, level: int) -> bool:
|
193
|
+
"""Get whether a message at this level is logged."""
|
194
|
+
return _get_backend().can_log_message(level)
|
195
|
+
|
196
|
+
@classmethod
|
197
|
+
def debug(cls, msg: str):
|
198
|
+
"""Write a debug message to the log."""
|
199
|
+
_get_backend().log_message(logging.DEBUG, LOGGING_CONTEXT, msg)
|
200
|
+
|
201
|
+
@classmethod
|
202
|
+
def error(cls, msg: str):
|
203
|
+
"""Write a error message to the log."""
|
204
|
+
_get_backend().log_message(logging.ERROR, LOGGING_CONTEXT, msg)
|
205
|
+
|
206
|
+
@classmethod
|
207
|
+
def info(cls, msg: str):
|
208
|
+
"""Write an info message to the log."""
|
209
|
+
_get_backend().log_message(logging.INFO, LOGGING_CONTEXT, msg)
|
210
|
+
|
211
|
+
@classmethod
|
212
|
+
def warning(cls, msg: str):
|
213
|
+
"""Write a warning message to the log."""
|
214
|
+
_get_backend().log_message(logging.WARNING, LOGGING_CONTEXT, msg)
|
215
|
+
|
216
|
+
@classmethod
|
217
|
+
def fatal(cls, msg: str):
|
218
|
+
"""Write a fatal message to the log."""
|
219
|
+
_get_backend().log_message(logging.FATAL, LOGGING_CONTEXT, msg)
|