/
misc.py
1308 lines (1035 loc) · 40.4 KB
/
misc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Module for miscellaneous functions and methods"""
from enum import Enum
from functools import wraps
import importlib
import inspect
import os
from pathlib import Path
import platform
import random
import re
import socket
import string
import tempfile
from threading import Thread
from typing import Union
from warnings import warn
import weakref
try:
from ansys.tools.path import get_available_ansys_installations
_HAS_ATP = True
except ModuleNotFoundError:
_HAS_ATP = False
import numpy as np
from ansys.mapdl import core as pymapdl
from ansys.mapdl.core import _HAS_PYVISTA, LOG
from ansys.mapdl.core.errors import MapdlExitedError, MapdlRuntimeError
try:
import ansys.tools.report as pyansys_report
_HAS_PYANSYS_REPORT = True
except ModuleNotFoundError: # pragma: no cover
LOG.debug("The package 'pyansys-tools-report' is not installed.")
_HAS_PYANSYS_REPORT = False
# path of this module
MODULE_PATH = os.path.dirname(inspect.getfile(inspect.currentframe()))
ANSYS_ENV_VARS = [
"PYMAPDL_START_INSTANCE",
"PYMAPDL_PORT",
"PYMAPDL_IP",
"PYMAPDL_MAPDL_EXEC",
"PYMAPDL_MAPDL_VERSION",
"PYMAPDL_MAX_MESSAGE_LENGTH",
"ON_CI",
"ON_LOCAL",
"P_SCHEMA",
]
class ROUTINES(Enum):
"""MAPDL routines."""
BEGIN_LEVEL = 0
PREP7 = 17
SOLUTION = 21
POST1 = 31
POST26 = 36
AUX2 = 52
AUX3 = 53
AUX12 = 62
AUX15 = 65
def check_valid_routine(routine):
"""Check if a routine is valid.
Acceptable aliases for "Begin level" include "begin".
Parameters
----------
routine : str
Routine. For example "PREP7".
Returns
-------
bool
``True`` when routine is valid.
Raises
------
ValueError
Raised when a routine is invalid.
"""
if routine.lower().startswith("begin"):
return True
if not hasattr(ROUTINES, routine.upper()):
valid_routines = []
for item in dir(ROUTINES):
if not item.startswith("_") and not item.startswith("BEGIN"):
valid_routines.append(item)
valid_routines.append("Begin level")
valid_routines_str = "\n".join([f'\t- "{item}"' for item in valid_routines])
raise ValueError(
f"Invalid routine {routine}. Should be one of:\n{valid_routines_str}"
)
return True
class Plain_Report:
def __init__(self, core, optional=None, additional=None, **kwargs):
"""
Base class for a plain report.
Based on `scooby <https://github.com/banesullivan/scooby>`_ package.
Parameters
----------
additional : iter[str]
List of packages or package names to add to output information.
core : iter[str]
The core packages to list first.
optional : iter[str]
A list of packages to list if they are available. If not available,
no warnings or error will be thrown.
"""
self.additional = additional
self.core = core
self.optional = optional
self.kwargs = kwargs
if os.name == "posix":
self.core.extend(["pexpect"])
# Information about the GPU - bare except in case there is a rendering
# bug that the user is trying to report.
if self.kwargs.get("gpu", False) and _HAS_PYVISTA:
from pyvista import PyVistaDeprecationWarning
try:
from pyvista.utilities.errors import (
GPUInfo, # deprecated in pyvista 0.40.0
)
except (PyVistaDeprecationWarning, ImportError):
from pyvista.report import GPUInfo
try:
self.kwargs["extra_meta"] = [(t[1], t[0]) for t in GPUInfo().get_info()]
except RuntimeError as e: # pragma: no cover
self.kwargs["extra_meta"] = ("GPU Details", f"Error: {str(e)}")
else:
self.kwargs["extra_meta"] = ("GPU Details", "None")
def get_version(self, package):
try:
import importlib.metadata as importlib_metadata
except ModuleNotFoundError: # pragma: no cover
import importlib_metadata
try:
return importlib_metadata.version(package.replace(".", "-"))
except importlib_metadata.PackageNotFoundError:
return "Package not found"
def __repr__(self):
header = [
"-" * 79,
"\n",
"PyMAPDL Software and Environment Report",
"\n",
"Packages Requirements",
"*********************",
]
core = ["\nCore packages", "-------------"]
core.extend(
[
f"{each.ljust(20)}: {self.get_version(each)}"
for each in self.core
if self.get_version(each)
]
)
if self.optional:
optional = ["\nOptional packages", "-----------------"]
optional.extend(
[
f"{each.ljust(20)}: {self.get_version(each)}"
for each in self.optional
if self.get_version(each)
]
)
else:
optional = [""]
if self.additional:
additional = ["\nAdditional packages", "-----------------"]
additional.extend(
[
f"{each.ljust(20)}: {self.get_version(each)}"
for each in self.additional
if self.get_version(each)
]
)
else:
additional = [""]
return "\n".join(header + core + optional + additional) + self.mapdl_info()
def mapdl_info(self):
"""Return information regarding the ansys environment and installation."""
# this is here to avoid circular imports
# List installed Ansys
lines = ["", "Ansys Environment Report", "-" * 79]
lines = ["\n", "Ansys Installation", "******************"]
if _HAS_ATP:
mapdl_install = get_available_ansys_installations()
if not mapdl_install:
lines.append("Unable to locate any Ansys installations")
else:
lines.append("Version Location")
lines.append("------------------")
for key in sorted(mapdl_install.keys()):
lines.append(f"{abs(key)} {mapdl_install[key]}")
else:
mapdl_install = None
lines.append(
"Unable to locate any Ansys installations because 'ansys-tools-path is not installed."
)
install_info = "\n".join(lines)
env_info_lines = [
"\n\n\nAnsys Environment Variables",
"***************************",
]
n_var = 0
for key, value in os.environ.items():
if "AWP" in key or "CADOE" in key or "ANSYS" in key:
env_info_lines.append(f"{key:<30} {value}")
n_var += 1
if not n_var:
env_info_lines.append("None")
env_info = "\n".join(env_info_lines)
return install_info + env_info
# Determine which type of report will be used (depending on the
# available packages)
if _HAS_PYANSYS_REPORT:
base_report_class = pyansys_report.Report
else: # pragma: no cover
base_report_class = Plain_Report
class Report(base_report_class):
"""A class for custom scooby.Report."""
def __init__(
self,
additional=None,
ncol=3,
text_width=80,
sort=False,
gpu=True,
ansys_vars=ANSYS_ENV_VARS,
ansys_libs=None,
):
"""Generate a :class:`scooby.Report` instance.
Parameters
----------
additional : list(ModuleType), list(str)
List of packages or package names to add to output information.
ncol : int, optional
Number of package-columns in html table; only has effect if
``mode='HTML'`` or ``mode='html'``. Defaults to 3.
text_width : int, optional
The text width for non-HTML display modes
sort : bool, optional
Alphabetically sort the packages
gpu : bool
Gather information about the GPU. Defaults to ``True`` but if
experiencing rendering issues, pass ``False`` to safely generate
a report.
ansys_vars : list of str, optional
List containing the Ansys environment variables to be reported.
(e.g. ["MYVAR_1", "MYVAR_2" ...]). Defaults to ``None``. Only used for
the `pyansys-tools-report` package.
ansys_libs : dict {str : str}, optional
Dictionary containing the Ansys libraries and versions to be reported.
(e.g. {"MyLib" : "v1.2", ...}). Defaults to ``None``. Only used for
the `pyansys-tools-report` package.
"""
# Mandatory packages
core = [
"ansys.mapdl.core",
"numpy",
"platformdirs",
"scipy",
"grpc", # grpcio
"ansys.api.mapdl.v0", # ansys-api-mapdl-v0
"ansys.mapdl.reader", # ansys-mapdl-reader
"google.protobuf", # protobuf library
]
# Optional packages
optional = ["matplotlib", "pyvista", "pyiges", "tqdm"]
if _HAS_PYANSYS_REPORT:
# Combine all packages into one
all_mapdl_packages = core + optional
if additional is not None:
all_mapdl_packages += additional
# Call the pyansys_report.Report constructor
super().__init__(
additional=all_mapdl_packages,
ncol=ncol,
text_width=text_width,
sort=sort,
gpu=gpu,
ansys_vars=ansys_vars,
ansys_libs=ansys_libs,
)
else:
# Call the PlainReport constructor
super().__init__(
additional=additional,
core=core,
optional=optional,
ncol=ncol,
text_width=text_width,
sort=sort,
gpu=gpu,
)
def is_float(input_string):
"""Returns true when a string can be converted to a float"""
try:
float(input_string)
return True
except ValueError:
return False
def random_string(stringLength=10, letters=string.ascii_lowercase):
"""Generate a random string of fixed length"""
return "".join(random.choice(letters) for i in range(stringLength))
def _check_has_ansys():
"""Safely wraps check_valid_ansys
Returns
-------
has_ansys : bool
True when this local installation has ANSYS installed in a
standard location.
"""
from ansys.mapdl.core.launcher import check_valid_ansys
try:
return check_valid_ansys()
except:
return False
def supress_logging(func):
"""Decorator to suppress logging for a MAPDL instance"""
@wraps(func)
def wrapper(*args, **kwargs):
mapdl = args[0]
prior_log_level = mapdl._log.level
if prior_log_level != "CRITICAL":
mapdl._set_log_level("CRITICAL")
out = func(*args, **kwargs)
if prior_log_level != "CRITICAL":
mapdl._set_log_level(prior_log_level)
return out
return wrapper
def run_as_prep7(func):
"""Run a MAPDL method at PREP7 and always revert to the prior processor"""
@wraps(func)
def wrapper(*args, **kwargs):
mapdl = args[0]
if hasattr(mapdl, "_mapdl"):
mapdl = mapdl._mapdl
prior_processor = mapdl.parameters.routine
if prior_processor != "PREP7":
mapdl.prep7()
out = func(*args, **kwargs)
if prior_processor == "Begin level":
mapdl.finish()
elif prior_processor != "PREP7":
mapdl.run("/%s" % prior_processor)
return out
return wrapper
def threaded(func):
"""Decorator to call a function using a thread"""
@wraps(func)
def wrapper(*args, **kwargs):
name = kwargs.get("name", f"Threaded `{func.__name__}` function")
thread = Thread(target=func, name=name, args=args, kwargs=kwargs)
thread.start()
return thread
return wrapper
def threaded_daemon(func):
"""Decorator to call a function using a daemon thread."""
@wraps(func)
def wrapper(*args, **kwargs):
name = kwargs.pop(
"thread_name", f"Threaded (with Daemon) `{func.__name__}` function"
)
thread = Thread(target=func, name=name, args=args, kwargs=kwargs)
thread.daemon = True
thread.start()
return thread
return wrapper
def unique_rows(a):
"""Returns unique rows of a and indices of those rows"""
if not a.flags.c_contiguous:
a = np.ascontiguousarray(a)
b = a.view(np.dtype((np.void, a.dtype.itemsize * a.shape[1])))
_, idx, idx2 = np.unique(b, True, True)
return a[idx], idx, idx2
def creation_time(path_to_file):
"""The file creation time.
Try to get the date that a file was created, falling back to when
it was last modified if that isn't possible.
See http://stackoverflow.com/a/39501288/1709587 for explanation.
"""
if platform.system() == "Windows":
return os.path.getctime(path_to_file)
else:
stat = os.stat(path_to_file)
try:
return stat.st_birthtime
except AttributeError:
# We're probably on Linux. No easy way to get creation dates here,
# so we'll settle for when its content was last modified.
return stat.st_mtime
def last_created(filenames):
"""Return the last created file given a list of filenames
If all filenames have the same creation time, then return the last
filename.
"""
ctimes = [creation_time(filename) for filename in filenames]
idx = np.argmax(ctimes)
if len(set(ctimes)):
return filenames[-1]
return filenames[idx]
def create_temp_dir(tmpdir=None, name=None):
"""Create a new unique directory at a given temporary directory"""
if tmpdir is None:
tmpdir = tempfile.gettempdir()
elif not os.path.isdir(tmpdir):
os.makedirs(tmpdir)
if not name:
random_name = True
letters_ = string.ascii_lowercase.replace("n", "")
name = random_string(10, letters_)
else:
random_name = False
# running into a rare issue with MAPDL on Windows with "\n" being
# treated literally.
path = os.path.join(tmpdir, name)
if random_name:
# in the *rare* case of a duplicate path
while os.path.isdir(path):
path = os.path.join(tempfile.gettempdir(), name)
if not os.path.exists(path):
try:
os.mkdir(path)
except:
raise MapdlRuntimeError(
"Unable to create temporary working "
f"directory {path}\nPlease specify 'run_location' argument"
)
return path
def no_return(func):
"""Decorator to return nothing from the wrapped function"""
@wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
def get_bounding_box(nodes_xyz):
min_ = np.min(nodes_xyz, axis=0)
max_ = np.max(nodes_xyz, axis=0)
return max_ - min_
def load_file(mapdl, fname, priority_mapdl_file=None):
"""
Provide a file to the MAPDL instance.
Parameters
----------
mapdl
Mapdl instance
fname : str, path
Path to the file.
priority_mapdl_file : bool
In case of the file existing in the MAPDL environment and
in the local Python environment, this parameter specifies
which one has priority. Defaults to ``True``, meaning the MAPDL file
has priority.
Notes
-----
**When running MAPDL locally:**
Checks if the file is reachable or is in the MAPDL directory,
if not, it raises a ``FileNotFound`` exception.
If:
- The file is only the MAPDL directory, this function does nothing
since the file is already accessible to the MAPDL instance.
- If the file exists in both, the Python working directory and the MAPDL
directory, this function does nothing, as the file in the MAPDL working
directory has priority.
**When in remote (not-local)**
Check if the file exists locally or in the working directory, if not, it will raise a ``FileNotFound`` exception.
If the file is local, it will be uploaded.
"""
base_fname = os.path.basename(fname)
if not os.path.exists(fname) and base_fname not in mapdl.list_files():
raise FileNotFoundError(
f"The file {fname} could not be found in the Python working directory ('{os.getcwd()}') "
f"nor in the MAPDL working directory ('{mapdl.directory}')."
)
elif os.path.exists(fname) and base_fname in mapdl.list_files():
if priority_mapdl_file is None:
warn(
f"The file '{base_fname}' is present in both, the python working directory ('{os.getcwd()}') "
f"and in the MAPDL working directory ('{mapdl.directory}'). "
"Using the one already in the MAPDL directory.\n"
"If you prefer to use the file in the Python directory, you shall remove the file in the MAPDL directory."
)
priority_mapdl_file = True
if not priority_mapdl_file:
mapdl.upload(fname)
elif os.path.exists(fname) and base_fname not in mapdl.list_files():
mapdl._log.debug("File is in the Python working directory, uploading.")
mapdl.upload(fname)
elif not os.path.exists(fname) and base_fname in mapdl.list_files():
mapdl._log.debug("File is already in the MAPDL working directory")
# Simplifying name for MAPDL reads it.
return os.path.basename(fname)
def check_valid_ip(ip):
"""Check for valid IP address"""
if ip.lower() != "localhost":
ip = ip.replace('"', "").replace("'", "")
socket.inet_aton(ip)
def check_valid_port(port, lower_bound=1000, high_bound=60000):
if not isinstance(port, int):
raise ValueError("The 'port' parameter should be an integer.")
if lower_bound < port < high_bound:
return
else:
raise ValueError(
f"'port' values should be between {lower_bound} and {high_bound}."
)
def update_information_first(update=False):
"""
Decorator to wrap :class:`Information <ansys.mapdl.core.misc.Information>`
methods to force update the fields when accessed.
Parameters
----------
update : bool, optional
If ``True``, the class information is updated by calling ``/STATUS``
before accessing the methods. By default ``False``
"""
def decorator(function):
@wraps(function)
def wrapper(self, *args, **kwargs):
if update or not self._stats:
self._update()
return function(self, *args, **kwargs)
return wrapper
return decorator
class Information:
"""
This class provide some MAPDL information from ``/STATUS`` MAPDL command.
It is also the object that is called when you issue ``print(mapdl)``,
which means ``print`` calls ``mapdl.info.__str__()``.
Notes
-----
You cannot directly modify the values of this class.
Some of the results are cached for later calls.
Examples
--------
>>> mapdl.info
Product: Ansys Mechanical Enterprise
MAPDL Version: 24.1
ansys.mapdl Version: 0.68.0
>>> print(mapdl)
Product: Ansys Mechanical Enterprise
MAPDL Version: 24.1
ansys.mapdl Version: 0.68.0
>>> mapdl.info.product
'Ansys Mechanical Enterprise'
>>> info = mapdl.info
>>> info.mapdl_version
'RELEASE 2021 R2 BUILD 21.2 UPDATE 20210601'
"""
def __init__(self, mapdl):
"""Class Initializer"""
from ansys.mapdl.core.mapdl import MapdlBase # lazy import to avoid circular
if not isinstance(mapdl, MapdlBase): # pragma: no cover
raise TypeError("Must be implemented from MAPDL class")
self._mapdl_weakref = weakref.ref(mapdl)
self._stats = None
self._repr_keys = {
"Product": "product",
"MAPDL Version": "mapdl_version",
"PyMAPDL Version": "pymapdl_version",
}
@property
def _mapdl(self):
"""Return the weakly referenced MAPDL instance."""
return self._mapdl_weakref()
def _update(self):
"""We might need to do more calls if we implement properties
that change over the MAPDL session."""
try:
if self._mapdl._exited: # pragma: no cover
raise MapdlExitedError("Information class: MAPDL exited")
stats = self._mapdl.slashstatus("ALL")
except Exception: # pragma: no cover
self._stats = None
raise MapdlExitedError("Information class: MAPDL exited")
stats = stats.replace("\n ", "\n") # Bit of formatting
self._stats = stats
self._mapdl._log.debug("Information class: Updated")
def __repr__(self):
if not self._stats:
self._update()
return "\n".join(
[
f"{each_name}:".ljust(25) + f"{getattr(self, each_attr)}".ljust(25)
for each_name, each_attr in self._repr_keys.items()
]
)
@property
@update_information_first(False)
def product(self):
"""Retrieve the product from the MAPDL instance."""
return self._get_product()
@property
@update_information_first(False)
def mapdl_version(self):
"""Retrieve the MAPDL version from the MAPDL instance."""
return self._get_mapdl_version()
@property
@update_information_first(False)
def mapdl_version_release(self):
"""Retrieve the MAPDL version release from the MAPDL instance."""
st = self._get_mapdl_version()
return self._get_between("RELEASE", "BUILD", st).strip()
@property
@update_information_first(False)
def mapdl_version_build(self):
"""Retrieve the MAPDL version build from the MAPDL instance."""
st = self._get_mapdl_version()
return self._get_between("BUILD", "UPDATE", st).strip()
@property
@update_information_first(False)
def mapdl_version_update(self):
"""Retrieve the MAPDL version update from the MAPDL instance."""
st = self._get_mapdl_version()
return self._get_between("UPDATE", "", st).strip()
@property
@update_information_first(False)
def pymapdl_version(self):
"""Retrieve the PyMAPDL version from the MAPDL instance."""
return self._get_pymapdl_version()
@property
@update_information_first(False)
def products(self):
"""Retrieve the products from the MAPDL instance."""
return self._get_products()
@property
@update_information_first(False)
def preprocessing_capabilities(self):
"""Retrieve the preprocessing capabilities from the MAPDL instance."""
return self._get_preprocessing_capabilities()
@property
@update_information_first(False)
def aux_capabilities(self):
"""Retrieve the aux capabilities from the MAPDL instance."""
return self._get_aux_capabilities()
@property
@update_information_first(True)
def solution_options(self):
"""Retrieve the solution options from the MAPDL instance."""
return self._get_solution_options()
@property
@update_information_first(False)
def post_capabilities(self):
"""Retrieve the post capabilities from the MAPDL instance."""
return self._get_post_capabilities()
@property
@update_information_first(True)
def titles(self):
"""Retrieve the titles from the MAPDL instance."""
return self._get_titles()
@property
@update_information_first(True)
def title(self):
"""Retrieve and set the title from the MAPDL instance."""
return self._mapdl.inquire("", "title")
@title.setter
def title(self, title):
return self._mapdl.run(f"/TITLE, {title}")
@property
@update_information_first(True)
def stitles(self, i=None):
"""Retrieve or set the value for the MAPDL stitle (subtitles).
If 'stitle' includes newline characters (`\\n`), then each line
is assigned to one STITLE.
If 'stitle' is equals ``None``, the stitles are reset.
If ``i`` is supplied, only set the stitle number i.
Starting from 0 up to 3 (Python indexing).
"""
if not i:
return self._get_stitles()
else:
return self._get_stitles()[i]
@stitles.setter
def stitles(self, stitle, i=None):
if stitle is None:
# Case to empty
stitle = ["", "", "", ""]
if not isinstance(stitle, (str, list)):
raise ValueError("Only str or list are allowed for stitle")
if isinstance(stitle, str):
if "\n" in stitle:
stitle = stitle.splitlines()
else:
stitle = "\n".join(
[stitle[ii : ii + 70] for ii in range(0, len(stitle), 70)]
)
if any([len(each) > 70 for each in stitle]):
raise ValueError("The number of characters per subtitle is limited to 70.")
if not i:
for each_index, each_stitle in zip(range(1, 5), stitle):
self._mapdl.stitle(each_index, each_stitle)
else:
self._mapdl.stitle(i, stitle)
@property
@update_information_first(True)
def units(self):
"""Retrieve the units from the MAPDL instance."""
return self._get_units()
@property
@update_information_first(True)
def scratch_memory_status(self):
"""Retrieve the scratch memory status from the MAPDL instance."""
return self._get_scratch_memory_status()
@property
@update_information_first(True)
def database_status(self):
"""Retrieve the database status from the MAPDL instance."""
return self._get_database_status()
@property
@update_information_first(True)
def config_values(self):
"""Retrieve the config values from the MAPDL instance."""
return self._get_config_values()
@property
@update_information_first(True)
def global_status(self):
"""Retrieve the global status from the MAPDL instance."""
return self._get_global_status()
@property
@update_information_first(True)
def job_information(self):
"""Retrieve the job information from the MAPDL instance."""
return self._get_job_information()
@property
@update_information_first(True)
def model_information(self):
"""Retrieve the model information from the MAPDL instance."""
return self._get_model_information()
@property
@update_information_first(True)
def boundary_condition_information(self):
"""Retrieve the boundary condition information from the MAPDL instance."""
return self._get_boundary_condition_information()
@property
@update_information_first(True)
def routine_information(self):
"""Retrieve the routine information from the MAPDL instance."""
return self._get_routine_information()
@property
@update_information_first(True)
def solution_options_configuration(self):
"""Retrieve the solution options configuration from the MAPDL instance."""
return self._get_solution_options_configuration()
@property
@update_information_first(True)
def load_step_options(self):
"""Retrieve the load step options from the MAPDL instance."""
return self._get_load_step_options()
def _get_between(self, init_string, end_string=None, string=None):
if not string:
self._update()
string = self._stats
st = string.find(init_string) + len(init_string)
if not end_string:
en = None
else:
en = string.find(end_string)
return "\n".join(string[st:en].splitlines()).strip()
def _get_product(self):
return self._get_products().splitlines()[0]
def _get_mapdl_version(self):
titles_ = self._get_titles()
st = titles_.find("RELEASE")
en = titles_.find("INITIAL", st)
return titles_[st:en].split("CUSTOMER")[0].strip()
def _get_pymapdl_version(self):
return pymapdl.__version__
def _get_title(self):
match = re.match(r"TITLE=(.*)$", self._get_titles())
if match:
return match.groups(1)[0].strip()
def _get_stitles(self):
return [
(
re.search(f"SUBTITLE {i}=(.*)", self._get_titles())
.groups(1)[0]
.strip()
if re.search(f"SUBTITLE {i}=(.*)", self._get_titles())
else ""