Skip to content

Commit deb8722

Browse files
committed
Added paraview converter
1 parent 84c1f82 commit deb8722

File tree

8 files changed

+209
-65
lines changed

8 files changed

+209
-65
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ pyansys/Interface.py
2424

2525
# Testing
2626
Testing/
27+

doc/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@
5151
# built documents.
5252
#
5353
# The short X.Y version.
54-
version = u'0.17'
54+
version = u'0.18'
5555

5656
# The full version, including alpha/beta/rc tags.
57-
release = u'0.17.1'
57+
release = u'0.18.0'
5858

5959
# The language for content autogenerated by Sphinx. Refer to documentation
6060
# for a list of supported languages.

doc/loading_results.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,5 +216,31 @@ The phase of the result can be changed by modifying the ``phase`` option. See
216216
``help(result.PlotCyclicNodalResult)``` for details on its implementation.
217217
218218
219+
Exporting to Paraview
220+
~~~~~~~~~~~~~~~~~~~~~
219221
222+
Paraview is a visualization application that can be used for rapid generation
223+
of plots and graphs using VTK through a GUI. ``pyansys`` can translate the
224+
ANSYS result files to Paraview compatible files containing the geometry and
225+
nodal results from the analysis:
220226
227+
.. code:: python
228+
229+
import pyansys
230+
from pyansys import examples
231+
232+
# load example beam result file
233+
result = pyansys.ResultReader(examples.rstfile)
234+
235+
# save as a binary vtk xml file
236+
result.SaveAsVTK('beam.vtu')
237+
238+
The vtk xml file can now be loaded using paraview. This screenshot shows the
239+
nodal displacement of the first result from the result file plotted within
240+
`Paraview <https://www.paraview.org/>`_. Within the vtk file are two point
241+
arrays (``NodalResult`` and ``NodalStress``) for each result in the result
242+
file. The nodal result values will depend on the analysis type, while
243+
nodal stress will always be the node average stress in the Sx, Sy Sz, Sxy, Syz,
244+
and Sxz directions.
245+
246+
.. image:: paraview.jpg

doc/paraview.jpg

242 KB
Loading

pyansys/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# major, minor, patch
2-
version_info = 0, 17, 2
2+
version_info = 0, 18, 0
33

44
# Nice string for the version
55
__version__ = '.'.join(map(str, version_info))

pyansys/archive_reader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def Plot(self):
250250
grid = self.uGrid
251251

252252
else:
253-
raise Exception('Unstructred grid not generated. Run ParseVTK or ParseFEM first.')
253+
raise Exception('Unstructred grid not generated. Run ParseFEM first.')
254254

255255
if not grid.GetNumberOfCells():
256256
raise Exception('Unstructured grid contains no cells')
@@ -275,7 +275,7 @@ def SaveAsVTK(self, filename, binary=True):
275275
The file extension will select the type of writer to use. *.vtk will
276276
use the legacy writer, while *.vtu will select the VTK XML writer.
277277
278-
Run ParseFEM or ParseVTK before running this to generate the vtk object
278+
Run ParseFEM before running this to generate the vtk object
279279
280280
Parameters
281281
----------
@@ -300,7 +300,7 @@ def SaveAsVTK(self, filename, binary=True):
300300

301301
# Check if the unstructured grid exists
302302
if not hasattr(self, 'uGrid'):
303-
raise Exception('Run ParseFEM or ParseVTK first.')
303+
raise Exception('Run ParseFEM first.')
304304

305305
# Write the grid
306306
self.uGrid.WriteGrid(filename, binary)

pyansys/binary_reader.py

Lines changed: 131 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ def PlotCyclicNodalResult(self, rnum, phase=0, comp='norm', as_abs=False, label=
404404
"""
405405
Plots a nodal result from a cyclic analysis.
406406
407+
407408
Parameters
408409
----------
409410
rnum : interger
@@ -428,11 +429,13 @@ def PlotCyclicNodalResult(self, rnum, phase=0, comp='norm', as_abs=False, label=
428429
default. When disabled, plots the maximum response of a single
429430
sector of the cyclic solution in the component of interest.
430431
432+
431433
Returns
432434
-------
433435
cpos : list
434436
Camera position from vtk render window.
435-
437+
438+
436439
Notes
437440
-----
438441
None
@@ -642,17 +645,20 @@ def PlotNodalResult(self, rnum, comp='norm', as_abs=False, label=''):
642645

643646
def GetTimeValues(self):
644647
"""
645-
SUMMARY
646648
Returns table of time values for results. For a modal analysis, this
647649
corresponds to the frequencies of each mode.
648650
649651
650-
INPUTS
652+
Parameters
653+
----------
651654
None
652655
653656
654-
OUTPUTS
655-
tvalues (np.float64 array)
657+
Returns
658+
-------
659+
tvalues : np.float64 array
660+
Table of time values for results. For a modal analysis, this
661+
corresponds to the frequencies of each mode.
656662
657663
"""
658664

@@ -677,6 +683,7 @@ def GetNodalResult(self, rnum, sort=True):
677683
"""
678684
Returns the nodal result for a result number
679685
686+
680687
Parameters
681688
----------
682689
rnum : interger
@@ -687,11 +694,13 @@ def GetNodalResult(self, rnum, sort=True):
687694
node numbering (self.nnum) (default). If left unsorted, results
688695
correspond to the nodal equivalence array self.resultheader['neqv']
689696
697+
690698
Returns
691699
-------
692700
result : numpy.float array
693701
Result is (nnod x numdof), or number of nodes by degrees of freedom
694-
702+
703+
695704
Notes
696705
-----
697706
None
@@ -832,7 +841,6 @@ def StoreGeometry(self):
832841
NODES - node numbers defining the element
833842
"""
834843

835-
836844
# allocate memory for this (a maximum of 21 points per element)
837845
etype = np.empty(nelm, np.int32)
838846

@@ -905,7 +913,7 @@ def NodalStress(self, rnum):
905913
None
906914
907915
"""
908-
#%% debug cell
916+
909917
# Get the header information from the header dictionary
910918
endian = self.resultheader['endian']
911919
rpointers = self.resultheader['rpointers']
@@ -939,6 +947,7 @@ def NodalStress(self, rnum):
939947
ele_ind_table += element_rst_ptr
940948

941949
# Each element header contains 25 records for the individual results
950+
# get the location of the nodal stress
942951
table_index = e_table.index('ptrENS')
943952

944953
# check number of records to read (differs with each version)
@@ -948,57 +957,43 @@ def NodalStress(self, rnum):
948957
nnode_elem = nodstr[etype[0]]
949958
f.seek((ele_ind_table[0] + ptrENS - 2)*4)
950959
nitem = np.fromfile(f, endian + 'i', 1)[0]/nnode_elem
951-
952-
953-
nstresses = self.edge_idx.size
954-
stresses = np.empty((nstresses, 6), np.float32)
960+
f.close()
955961

956-
#%% debug cell 2
957-
c = 0
958-
for i in range(len(ele_ind_table)):
959-
# Element nodal stresses, ptrENS, is the third item in the table
960-
f.seek((ele_ind_table[i] + table_index)*4)
961-
ptrENS = np.fromfile(f, endian + 'i', 1)[0]
962-
963-
# read the stresses evaluated at the intergration points or nodes
964-
nnode_elem = nodstr[etype[i]]
965-
966-
f.seek((ele_ind_table[i] + ptrENS)*4)
967-
stress = np.fromfile(f, endian + 'f', nnode_elem*nitem).reshape((-1, nitem))#[:, sidx]
968-
969-
# store stresses
970-
stresses[c:c + nnode_elem] = stress[:, :6]
971-
c += nnode_elem
972-
973-
# close file
974-
f.close()
975-
976-
977-
# Average the stresses for each element at each node
978-
# enode = self.edge_node_num_idx
979-
# s_node = np.empty((enode.size, 6), np.float32)
980-
# for i in range(6):
981-
# s_node[:, i] = np.bincount(self.edge_idx, weights=stresses[:, i])[enode]
982-
# ntimes = np.bincount(self.edge_idx)[enode]
983-
# s_node /= ntimes.reshape((-1, 1))
962+
# number of nodes
963+
nnod = self.resultheader['nnod']
984964

965+
# different behavior depending on version of ANSYS
966+
# v15 seems to use floating point while < v14.5 uses double and stores
967+
# principle values
968+
if nitem == 6: # single precision >= v14.5
969+
ele_data_arr = np.zeros((nnod, 6), np.float32)
970+
_rstHelper.LoadStress(self.filename, table_index, ele_ind_table,
971+
nodstr, etype, nitem, ele_data_arr,
972+
self.edge_idx)
973+
elif nitem == 22: # double precision < v14.5
974+
nitem = 11
975+
ele_data_arr = np.zeros((nnod, 6), np.float64)
976+
_rstHelper.LoadStressDouble(self.filename, table_index,
977+
ele_ind_table, nodstr, etype, nitem,
978+
ele_data_arr, self.edge_idx)
979+
980+
elif nitem == 11: # single precision < v14.5
981+
ele_data_arr = np.zeros((nnod, 6), np.float32)
982+
_rstHelper.LoadStress(self.filename, table_index, ele_ind_table,
983+
nodstr, etype, nitem, ele_data_arr,
984+
self.edge_idx)
985985

986+
else:
987+
raise Exception('Invalid nitem. Unable to load nodal stresses')
986988

987-
# grabe element results from binary
989+
# Average based on the edges of elements
988990
enode = self.edge_node_num_idx
989991
ntimes = np.bincount(self.edge_idx)[enode]
990-
991-
nnod = self.resultheader['nnod']
992-
ele_data_arr = np.zeros((nnod, 6), np.float32)
993-
_rstHelper.LoadStress(self.filename, table_index,
994-
ele_ind_table, nodstr, etype,
995-
nitem, ele_data_arr, self.edge_idx)
996-
997992
s_node = ele_data_arr[enode]
998993
s_node /= ntimes.reshape((-1, 1))
999994

1000995
return s_node
1001-
996+
1002997

1003998
def PlotNodalStress(self, rnum, stype):
1004999
"""
@@ -1010,26 +1005,25 @@ def PlotNodalStress(self, rnum, stype):
10101005
across elements, stresses will vary based on the element they are
10111006
evaluated from.
10121007
1008+
10131009
Parameters
10141010
----------
10151011
rnum : interger
10161012
Result set using zero based indexing.
10171013
stype : string
10181014
Stress type from the following list: [Sx Sy Sz Sxy Syz Sxz]
10191015
1016+
10201017
Returns
10211018
-------
10221019
None
10231020
1024-
Notes
1025-
-----
1026-
None
1027-
10281021
"""
10291022

10301023
stress_types = ['Sx', 'Sy', 'Sz', 'Sxy', 'Syz', 'Sxz', 'Seqv']
10311024
if stype not in stress_types:
1032-
raise Exception("Stress type not in \n ['Sx', 'Sy', 'Sz', 'Sxy', 'Syz', 'Sxz']")
1025+
raise Exception('Stress type not in \n' +\
1026+
"['Sx', 'Sy', 'Sz', 'Sxy', 'Syz', 'Sxz']")
10331027

10341028
sidx = ['Sx', 'Sy', 'Sz', 'Sxy', 'Syz', 'Sxz'].index(stype)
10351029

@@ -1052,6 +1046,53 @@ def PlotNodalStress(self, rnum, stype):
10521046
del plobj
10531047

10541048
return cpos
1049+
1050+
1051+
def SaveAsVTK(self, filename, binary=True):
1052+
"""
1053+
Writes all appends all results to an unstructured grid and writes it to
1054+
disk.
1055+
1056+
The file extension will select the type of writer to use. *.vtk will
1057+
use the legacy writer, while *.vtu will select the VTK XML writer.
1058+
1059+
1060+
Parameters
1061+
----------
1062+
filename : str
1063+
Filename of grid to be written. The file extension will select the
1064+
type of writer to use. *.vtk will use the legacy writer, while
1065+
*.vtu will select the VTK XML writer.
1066+
binary : bool, optional
1067+
Writes as a binary file by default. Set to False to write ASCII
1068+
1069+
1070+
Notes
1071+
-----
1072+
Binary files write much faster than ASCII, but binary files written on
1073+
one system may not be readable on other systems. Binary can only be
1074+
selected for the legacy writer.
1075+
1076+
1077+
"""
1078+
1079+
# Copy grid as to not write results to original object
1080+
grid = self.uGrid.Copy()
1081+
1082+
for i in range(self.nsets):
1083+
# Nodal Results
1084+
val = self.GetNodalResult(i)
1085+
grid.AddPointScalars(val, 'NodalResult{:03d}'.format(i))
1086+
1087+
# Nodal Stress values are only valid
1088+
stress = self.NodalStress(i)
1089+
val = np.zeros((grid.GetNumberOfPoints(), stress.shape[1]))
1090+
val[self.edge_node_num_idx] = stress
1091+
grid.AddPointScalars(val, 'NodalStress{:03d}'.format(i))
1092+
1093+
# Write to file and clean up
1094+
grid.WriteGrid(filename)
1095+
del grid
10551096

10561097

10571098
def GetResultInfo(filename):
@@ -1205,4 +1246,39 @@ def delete_row_csc(mat, i):
12051246
mat.indptr[i:-1] = mat.indptr[i+1:]
12061247
mat.indptr[i:] -= n
12071248
mat.indptr = mat.indptr[:-1]
1208-
mat._shape = (mat._shape[0]-1, mat._shape[1])
1249+
mat._shape = (mat._shape[0]-1, mat._shape[1])
1250+
1251+
1252+
# =============================================================================
1253+
# load stress debug using numpy
1254+
# =============================================================================
1255+
# #%% numpy debug
1256+
# f = open(self.filename)
1257+
# nstresses = self.edge_idx.size
1258+
# stresses = np.empty((nstresses, 6), np.float32)
1259+
# c = 0
1260+
# for i in range(len(ele_ind_table)):
1261+
# # Element nodal stresses, ptrENS, is the third item in the table
1262+
# f.seek((ele_ind_table[i] + table_index)*4)
1263+
# ptrENS = np.fromfile(f, endian + 'i', 1)[0]
1264+
#
1265+
# # read the stresses evaluated at the intergration points or nodes
1266+
# nnode_elem = nodstr[etype[i]]
1267+
#
1268+
# f.seek((ele_ind_table[i] + ptrENS)*4)
1269+
# stress = np.fromfile(f, endian + 'f', nnode_elem*nitem).reshape((-1, nitem))#[:, sidx]
1270+
#
1271+
# # store stresses
1272+
# stresses[c:c + nnode_elem] = stress[:, :6]
1273+
# c += nnode_elem
1274+
#
1275+
# # close file
1276+
# f.close()
1277+
#
1278+
# # Average the stresses for each element at each node
1279+
# enode = self.edge_node_num_idx
1280+
# s_node = np.empty((enode.size, 6), np.float32)
1281+
# for i in range(6):
1282+
# s_node[:, i] = np.bincount(self.edge_idx, weights=stresses[:, i])[enode]
1283+
# ntimes = np.bincount(self.edge_idx)[enode]
1284+
# s_node /= ntimes.reshape((-1, 1))

0 commit comments

Comments
 (0)