patme 0.4.4__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 patme might be problematic. Click here for more details.
- patme/__init__.py +52 -0
- patme/buildtools/__init__.py +7 -0
- patme/buildtools/rce_releasecreator.py +336 -0
- patme/buildtools/release.py +26 -0
- patme/femtools/__init__.py +5 -0
- patme/femtools/abqmsgfilechecker.py +137 -0
- patme/femtools/fecall.py +1092 -0
- patme/geometry/__init__.py +0 -0
- patme/geometry/area.py +124 -0
- patme/geometry/coordinatesystem.py +635 -0
- patme/geometry/intersect.py +284 -0
- patme/geometry/line.py +183 -0
- patme/geometry/misc.py +420 -0
- patme/geometry/plane.py +464 -0
- patme/geometry/rotate.py +244 -0
- patme/geometry/scale.py +152 -0
- patme/geometry/shape2d.py +50 -0
- patme/geometry/transformations.py +1831 -0
- patme/geometry/translate.py +139 -0
- patme/mechanics/__init__.py +4 -0
- patme/mechanics/loads.py +435 -0
- patme/mechanics/material.py +1260 -0
- patme/service/__init__.py +7 -0
- patme/service/decorators.py +85 -0
- patme/service/duration.py +96 -0
- patme/service/exceptionhook.py +104 -0
- patme/service/exceptions.py +36 -0
- patme/service/io/__init__.py +3 -0
- patme/service/io/basewriter.py +122 -0
- patme/service/logger.py +375 -0
- patme/service/mathutils.py +108 -0
- patme/service/misc.py +71 -0
- patme/service/moveimports.py +217 -0
- patme/service/stringutils.py +419 -0
- patme/service/systemutils.py +290 -0
- patme/sshtools/__init__.py +3 -0
- patme/sshtools/cara.py +435 -0
- patme/sshtools/clustercaller.py +420 -0
- patme/sshtools/facluster.py +350 -0
- patme/sshtools/sshcall.py +168 -0
- patme-0.4.4.dist-info/LICENSE +21 -0
- patme-0.4.4.dist-info/LICENSES/MIT.txt +9 -0
- patme-0.4.4.dist-info/METADATA +168 -0
- patme-0.4.4.dist-info/RECORD +46 -0
- patme-0.4.4.dist-info/WHEEL +4 -0
- patme-0.4.4.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Copyright (C) 2020 Deutsches Zentrum fuer Luft- und Raumfahrt(DLR, German Aerospace Center) <www.dlr.de>
|
|
2
|
+
# SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR)
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
"""
|
|
6
|
+
documentation
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from patme.service.exceptions import ImproperParameterError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Translation(np.ndarray):
|
|
15
|
+
"""
|
|
16
|
+
This class describes a point in 3D space which is represented by a vector of length 3.
|
|
17
|
+
|
|
18
|
+
With this class points in 3D space can be created and operations may be performed.
|
|
19
|
+
For that purpose the class inherits from np.ndarray which is a class for storing
|
|
20
|
+
matrix values and performing operations on them. For further details on np.ndarray
|
|
21
|
+
please refer to the numpy/scipy documentation.
|
|
22
|
+
|
|
23
|
+
Usage::
|
|
24
|
+
|
|
25
|
+
>>> from patme.geometry.translate import Translation
|
|
26
|
+
>>> t = Translation([1,2,0])
|
|
27
|
+
>>> t
|
|
28
|
+
Translation([1, 2, 0])
|
|
29
|
+
>>> # getting only one coordinate
|
|
30
|
+
>>> t.x
|
|
31
|
+
1
|
|
32
|
+
>>> # setting only one coordinate value
|
|
33
|
+
>>> t.x = 5
|
|
34
|
+
>>> t
|
|
35
|
+
Translation([5, 2, 0])
|
|
36
|
+
>>> #adding translations
|
|
37
|
+
>>> t2 = t + t
|
|
38
|
+
>>> t2
|
|
39
|
+
Translation([10, 4, 0])
|
|
40
|
+
>>> # getting the distance between two translations
|
|
41
|
+
>>> t.distance(t2)
|
|
42
|
+
5.385164807134504
|
|
43
|
+
>>> # checking coordinate equality
|
|
44
|
+
>>> t3=Translation([5,2,0])
|
|
45
|
+
>>> t==t;t==t2;t==t3
|
|
46
|
+
True
|
|
47
|
+
False
|
|
48
|
+
True
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
__hash__ = object.__hash__
|
|
53
|
+
"""hash reimplementation due to definition of __eq__. See __hash__ doc for more details.
|
|
54
|
+
https://docs.python.org/3.4/reference/datamodel.html#object.__hash__"""
|
|
55
|
+
|
|
56
|
+
def __new__(cls, input_array=None, id=None):
|
|
57
|
+
"""constructing np.ndarray instance. For more information see
|
|
58
|
+
http://docs.scipy.org/doc/numpy/user/basics.subclassing.html#basics-subclassing"""
|
|
59
|
+
# Input array is an already formed ndarray instance
|
|
60
|
+
# We first cast to be our class type
|
|
61
|
+
if input_array is None:
|
|
62
|
+
input_array = np.zeros(3)
|
|
63
|
+
if not hasattr(input_array, "shape"):
|
|
64
|
+
input_array = np.asarray(input_array)
|
|
65
|
+
if not input_array.shape == (3,):
|
|
66
|
+
raise ImproperParameterError(
|
|
67
|
+
"The given translation must be a vector with 3 elements. Got this instead: %s" % input_array
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
obj = input_array.view(cls)
|
|
71
|
+
# cope with numerical inaccuracy
|
|
72
|
+
setZero = abs(obj) < 1e-15
|
|
73
|
+
if np.any(setZero):
|
|
74
|
+
obj[setZero] = 0.0
|
|
75
|
+
# Finally, we must return the newly created object:
|
|
76
|
+
return obj
|
|
77
|
+
|
|
78
|
+
def distance(self, toPoint):
|
|
79
|
+
"""doc"""
|
|
80
|
+
return np.linalg.norm(self - toPoint)
|
|
81
|
+
|
|
82
|
+
def __eq__(self, other):
|
|
83
|
+
"""doc"""
|
|
84
|
+
try:
|
|
85
|
+
return np.allclose(self, other)
|
|
86
|
+
except TypeError:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
def _getX(self):
|
|
90
|
+
"""doc"""
|
|
91
|
+
return self[0]
|
|
92
|
+
|
|
93
|
+
def _setX(self, x):
|
|
94
|
+
"""doc"""
|
|
95
|
+
self[0] = x
|
|
96
|
+
|
|
97
|
+
def _getY(self):
|
|
98
|
+
"""doc"""
|
|
99
|
+
return self[1]
|
|
100
|
+
|
|
101
|
+
def _setY(self, y):
|
|
102
|
+
"""doc"""
|
|
103
|
+
self[1] = y
|
|
104
|
+
|
|
105
|
+
def _getZ(self):
|
|
106
|
+
"""doc"""
|
|
107
|
+
return self[2]
|
|
108
|
+
|
|
109
|
+
def _setZ(self, z):
|
|
110
|
+
"""doc"""
|
|
111
|
+
self[2] = z
|
|
112
|
+
|
|
113
|
+
x = property(fget=_getX, fset=_setX)
|
|
114
|
+
y = property(fget=_getY, fset=_setY)
|
|
115
|
+
z = property(fget=_getZ, fset=_setZ)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def getNearestPointOrPointsIndex(fromPointOrPoints, toPoints):
|
|
119
|
+
"""calculates the index of the toPoint that is closest to fromPointOrPoints
|
|
120
|
+
|
|
121
|
+
:param fromPointOrPoints: point or 2d array with points (fromPointNum, (x,y,z))
|
|
122
|
+
:param toPoints: 2d array with points (toPointNum, (x,y,z))
|
|
123
|
+
:return: index of nearest point or list of indices of nearest points (len = fromPointNum)
|
|
124
|
+
"""
|
|
125
|
+
points = np.array(fromPointOrPoints)
|
|
126
|
+
if len(points.shape) == 1:
|
|
127
|
+
return np.argmin(np.linalg.norm(points - toPoints, axis=1))
|
|
128
|
+
|
|
129
|
+
# create 3d arrays
|
|
130
|
+
loadPointsExtended = np.array([toPoints])
|
|
131
|
+
pointsExtended = np.array([[point] for point in points])
|
|
132
|
+
distances = np.linalg.norm(pointsExtended - loadPointsExtended, axis=2)
|
|
133
|
+
return np.argmin(distances, axis=1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
import doctest
|
|
138
|
+
|
|
139
|
+
doctest.testmod()
|
patme/mechanics/loads.py
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# Copyright (C) 2020 Deutsches Zentrum fuer Luft- und Raumfahrt(DLR, German Aerospace Center) <www.dlr.de>
|
|
2
|
+
# SPDX-FileCopyrightText: 2022 German Aerospace Center (DLR)
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from scipy.linalg import block_diag
|
|
11
|
+
|
|
12
|
+
from patme import epsilon
|
|
13
|
+
from patme.geometry.rotate import Rotation
|
|
14
|
+
from patme.geometry.translate import Translation
|
|
15
|
+
from patme.service.exceptions import ImproperParameterError, InternalError
|
|
16
|
+
from patme.service.stringutils import indent
|
|
17
|
+
|
|
18
|
+
loadComponentNames = ["fx", "fy", "fz", "mx", "my", "mz"]
|
|
19
|
+
loadComponentUpperNames = ["Fx", "Fy", "Fz", "Mx", "My", "Mz"]
|
|
20
|
+
loadcomponentToIndex = dict(
|
|
21
|
+
[
|
|
22
|
+
(name, compIndex)
|
|
23
|
+
for compIndex, name in list(enumerate(loadComponentNames)) + list(enumerate(loadComponentUpperNames))
|
|
24
|
+
]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PointTypeOfLoad(IntEnum):
|
|
29
|
+
|
|
30
|
+
keypoint = 1
|
|
31
|
+
meshNode = 2
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Loads(list):
|
|
35
|
+
"""This class serves as collection of several single loads."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, *args, **kwargs):
|
|
38
|
+
"""doc"""
|
|
39
|
+
list.__init__(self, *args, **kwargs)
|
|
40
|
+
|
|
41
|
+
def getLoad(self, point=None, rotation=None):
|
|
42
|
+
"""Sums up all loads referencing to a point and rotation.
|
|
43
|
+
|
|
44
|
+
:param point: object of type Translation (defaultes to aircraft coordinate system [0,0,0])
|
|
45
|
+
:param rotation: object of type Rotation (defaultes to aircraft coordinate system )"""
|
|
46
|
+
if point is None:
|
|
47
|
+
point = Translation((0.0, 0.0, 0.0))
|
|
48
|
+
|
|
49
|
+
loadSum = Load(point=point)
|
|
50
|
+
for load in self:
|
|
51
|
+
loadSum += load.getLoad(point)
|
|
52
|
+
|
|
53
|
+
return loadSum
|
|
54
|
+
|
|
55
|
+
def getCutLoads(self):
|
|
56
|
+
"""Retruns an instance of Loads with cutloads of self
|
|
57
|
+
|
|
58
|
+
>>> from patme.geometry.translate import Translation
|
|
59
|
+
>>> loads = Loads([Load([1*loadFactor,2*loadFactor,0,0,0,0], point = Translation([loadFactor,0,0])) for loadFactor in [1,2,3]])
|
|
60
|
+
>>> cutLoads = loads.getCutLoads()
|
|
61
|
+
>>> print(cutLoads)
|
|
62
|
+
[Load([ 6., 12., 0., 0., 0., 16.]), Load([ 5., 10., 0., 0., 0., 6.]), Load([3., 6., 0., 0., 0., 0.])]
|
|
63
|
+
"""
|
|
64
|
+
cutLoads = Loads()
|
|
65
|
+
if not self:
|
|
66
|
+
return cutLoads
|
|
67
|
+
|
|
68
|
+
cutLoads.append(self[-1])
|
|
69
|
+
for load in self[-2::-1]: # reversed without last original element
|
|
70
|
+
cutLoads.append(load + cutLoads[-1].getLoad(load.point))
|
|
71
|
+
|
|
72
|
+
return cutLoads[::-1]
|
|
73
|
+
|
|
74
|
+
def removeZeroLoads(self):
|
|
75
|
+
"""Removes loads from self that are zero"""
|
|
76
|
+
loadIndexesToRemove = [ix for ix, load in enumerate(self) if np.allclose(load, np.zeros(6))]
|
|
77
|
+
for loadIndex in loadIndexesToRemove[::-1]:
|
|
78
|
+
self.pop(loadIndex)
|
|
79
|
+
|
|
80
|
+
def getLoadsMovedToTranslations(self, translations):
|
|
81
|
+
"""This method copies the given loads to the respectively nearest
|
|
82
|
+
translation object of the component(wing or fuselage) and
|
|
83
|
+
returns a list of these loads with damPoints as load.point"""
|
|
84
|
+
translation2NewLoadDict = OrderedDict()
|
|
85
|
+
|
|
86
|
+
# create initial load at each damPoint
|
|
87
|
+
cls = self[0].__class__ if self else Load
|
|
88
|
+
for point in translations:
|
|
89
|
+
translation2NewLoadDict[point] = cls(point=point)
|
|
90
|
+
|
|
91
|
+
for load in self:
|
|
92
|
+
nearestIndex = np.argmin(np.linalg.norm(np.array(translations) - [load.point], axis=1))
|
|
93
|
+
newLoad = load.copy()
|
|
94
|
+
# move load
|
|
95
|
+
newLoad.point = translations[nearestIndex]
|
|
96
|
+
# add load to existing loads at dam-axis
|
|
97
|
+
translation2NewLoadDict[translations[nearestIndex]] += newLoad
|
|
98
|
+
|
|
99
|
+
return Loads(list(translation2NewLoadDict.values()))
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def massesToLoads(masses, acceleration=Translation([0.0, 0.0, 1.0])):
|
|
103
|
+
"""converts masses to loads with the given acceleration"""
|
|
104
|
+
loads = Loads()
|
|
105
|
+
for mass in masses:
|
|
106
|
+
load = list(acceleration * mass.mass) + [0.0, 0.0, 0.0]
|
|
107
|
+
loads.append(Load(load, point=mass.coG))
|
|
108
|
+
return loads
|
|
109
|
+
|
|
110
|
+
def getInfoString(self):
|
|
111
|
+
"""returns a string with the loads and ref points"""
|
|
112
|
+
body = [[load.tolist() + load.point.tolist()] for load in self]
|
|
113
|
+
header = loadComponentNames
|
|
114
|
+
return indent([header] + body, hasHeader=True)
|
|
115
|
+
|
|
116
|
+
def __iadd__(self, otherLoads):
|
|
117
|
+
"""Adds the other Loads to the loads in self.
|
|
118
|
+
|
|
119
|
+
Condition: the length and points of both load lists must be equal or one of
|
|
120
|
+
the load lists must be empty."""
|
|
121
|
+
if len(otherLoads) == 0:
|
|
122
|
+
return self
|
|
123
|
+
elif len(self) == 0:
|
|
124
|
+
self.extend(otherLoads)
|
|
125
|
+
return self
|
|
126
|
+
elif len(self) != len(otherLoads):
|
|
127
|
+
raise ImproperParameterError("The given Loads lists are not of equal length!")
|
|
128
|
+
|
|
129
|
+
for load, otherLoad in zip(self, otherLoads):
|
|
130
|
+
if not np.allclose(load.point, otherLoad.point):
|
|
131
|
+
raise ImproperParameterError("The given Loads do not share the same locations!")
|
|
132
|
+
load += otherLoad
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Load(np.ndarray):
|
|
137
|
+
"""This class represents a Load that is defined by a vector of length 6 (fx,fy,fz,mx,my,mz).
|
|
138
|
+
The load vector is included in a numpy array and can be accessed and altered like this::
|
|
139
|
+
|
|
140
|
+
>>> from patme.mechanics.loads import Load
|
|
141
|
+
>>> load=Load([1,0,0,0,0,0])
|
|
142
|
+
>>> load
|
|
143
|
+
Load([1., 0., 0., 0., 0., 0.])
|
|
144
|
+
>>> load.point
|
|
145
|
+
Translation([0., 0., 0.])
|
|
146
|
+
>>> load.rotation
|
|
147
|
+
Rotation([[1., 0., 0.],
|
|
148
|
+
[0., 1., 0.],
|
|
149
|
+
[0., 0., 1.]])
|
|
150
|
+
|
|
151
|
+
The load can be moved and rotated by the ``point`` and ``rotation`` properties::
|
|
152
|
+
|
|
153
|
+
>>> # translate the load
|
|
154
|
+
>>> # getting a copy of the point object, move it 2[m] in y-direction and
|
|
155
|
+
>>> # move the load - the result is a newly added moment
|
|
156
|
+
>>> p=load.point
|
|
157
|
+
>>> p
|
|
158
|
+
Translation([0., 0., 0.])
|
|
159
|
+
>>> p2=p+[0,2,0]
|
|
160
|
+
>>> p2
|
|
161
|
+
Translation([0., 2., 0.])
|
|
162
|
+
>>> load.point = p2
|
|
163
|
+
>>> load
|
|
164
|
+
Load([1., 0., 0., 0., 0., 2.])
|
|
165
|
+
|
|
166
|
+
>>> # rotate the load
|
|
167
|
+
>>> # getting a copy of the rotation, rotate it by 90 degress around the z-axis
|
|
168
|
+
>>> # and rotate the load
|
|
169
|
+
>>> load=Load([1,0,0,0,0,0])
|
|
170
|
+
>>> r=load.rotation
|
|
171
|
+
>>> r
|
|
172
|
+
Rotation([[1., 0., 0.],
|
|
173
|
+
[0., 1., 0.],
|
|
174
|
+
[0., 0., 1.]])
|
|
175
|
+
>>> r2 = Rotation()
|
|
176
|
+
>>> r2.angles = [0.,0.,np.pi/2]
|
|
177
|
+
>>> r2
|
|
178
|
+
Rotation([[ 6.123234e-17, -1.000000e+00, 0.000000e+00],
|
|
179
|
+
[ 1.000000e+00, 6.123234e-17, -0.000000e+00],
|
|
180
|
+
[ 0.000000e+00, 0.000000e+00, 1.000000e+00]])
|
|
181
|
+
>>> load.rotation = r2
|
|
182
|
+
>>> load
|
|
183
|
+
Load([ 6.123234e-17, -1.000000e+00, 0.000000e+00, 0.000000e+00,
|
|
184
|
+
0.000000e+00, 0.000000e+00])
|
|
185
|
+
|
|
186
|
+
The point and rotation properties always reference to the aircraft coordinate system.
|
|
187
|
+
Thus one can not operate with them directly - a new point or rotation has
|
|
188
|
+
to be calculated outside of the loads class and applied to the load as
|
|
189
|
+
shown in the description above.
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __new__(cls, input_array=None, point=Translation(), rotation=Rotation(), pointTypeOfLoad=None):
|
|
194
|
+
"""constructing np.ndarray instance. For more information see
|
|
195
|
+
http://docs.scipy.org/doc/numpy/user/basics.subclassing.html#basics-subclassing"""
|
|
196
|
+
# Input array is an already formed ndarray instance
|
|
197
|
+
# We first cast to be our class type
|
|
198
|
+
if input_array is None:
|
|
199
|
+
input_array = np.zeros(6)
|
|
200
|
+
|
|
201
|
+
if not hasattr(input_array, "shape"):
|
|
202
|
+
input_array = np.asarray(input_array, dtype=np.float64)
|
|
203
|
+
|
|
204
|
+
if not input_array.shape == (6,):
|
|
205
|
+
raise ImproperParameterError(
|
|
206
|
+
"The given load must be a vector with 6 elements. " + "Got this instead: " + str(input_array)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
obj = input_array.view(cls)
|
|
210
|
+
# add the new attribute to the created instance
|
|
211
|
+
if not isinstance(point, Translation):
|
|
212
|
+
point = Translation(point)
|
|
213
|
+
obj._point = point
|
|
214
|
+
|
|
215
|
+
if not isinstance(rotation, Rotation):
|
|
216
|
+
rotation = Rotation(rotation)
|
|
217
|
+
obj._rotation = rotation
|
|
218
|
+
|
|
219
|
+
obj._pointTypeOfLoad = pointTypeOfLoad
|
|
220
|
+
if pointTypeOfLoad is None:
|
|
221
|
+
obj._pointTypeOfLoad = PointTypeOfLoad.keypoint
|
|
222
|
+
|
|
223
|
+
# Finally, we must return the newly created object:
|
|
224
|
+
return obj
|
|
225
|
+
|
|
226
|
+
def getPickleLoadProperties(self):
|
|
227
|
+
"""returns the properties that are not pickled automatically."""
|
|
228
|
+
return (
|
|
229
|
+
self._point,
|
|
230
|
+
self._point.id,
|
|
231
|
+
self._point.cutout,
|
|
232
|
+
self._point.refType,
|
|
233
|
+
self._rotation,
|
|
234
|
+
self._pointTypeOfLoad,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def setPickleLoadProperties(self, loadProperties):
|
|
238
|
+
"""sets the properties that are not pickled automatically."""
|
|
239
|
+
(
|
|
240
|
+
self._point,
|
|
241
|
+
self._point.id,
|
|
242
|
+
self._point.cutout,
|
|
243
|
+
self._point.refType,
|
|
244
|
+
self._rotation,
|
|
245
|
+
self._pointTypeOfLoad,
|
|
246
|
+
) = loadProperties
|
|
247
|
+
|
|
248
|
+
def __array_finalize__(self, obj):
|
|
249
|
+
"""constructing np.ndarray instance. For more information see
|
|
250
|
+
http://docs.scipy.org/doc/numpy/user/basics.subclassing.html#basics-subclassing"""
|
|
251
|
+
# see InfoArray.__array_finalize__ for comments
|
|
252
|
+
if obj is None:
|
|
253
|
+
return
|
|
254
|
+
self._point = getattr(obj, "_point", Translation())
|
|
255
|
+
"""Reference position of type model.geometry.translation.Translation in reference to the aircraft coordinate system"""
|
|
256
|
+
|
|
257
|
+
self._rotation = getattr(obj, "_rotation", Rotation())
|
|
258
|
+
"""Reference rotation of type model.geometry.rotation.Rotation in reference to the aircraft coordinate system"""
|
|
259
|
+
|
|
260
|
+
self._pointTypeOfLoad = getattr(obj, "_pointTypeOfLoad", PointTypeOfLoad.keypoint)
|
|
261
|
+
"""Type of point where load is applied, either geometric keypoint or mesh node"""
|
|
262
|
+
|
|
263
|
+
def copy(self):
|
|
264
|
+
"""returns a copy of self"""
|
|
265
|
+
return self.getLoad()
|
|
266
|
+
|
|
267
|
+
def getLoad(self, point=None, rotation=None):
|
|
268
|
+
"""returns a copy of the load in reference to optionally differing point and rotation"""
|
|
269
|
+
ret = np.ndarray.copy(self)
|
|
270
|
+
if point is None:
|
|
271
|
+
newPoint = self.point.copy()
|
|
272
|
+
else:
|
|
273
|
+
newPoint = Translation(point).copy()
|
|
274
|
+
|
|
275
|
+
if rotation is None:
|
|
276
|
+
newRotation = self.rotation.copy()
|
|
277
|
+
else:
|
|
278
|
+
newRotation = Rotation(rotation).copy()
|
|
279
|
+
|
|
280
|
+
ret.point = newPoint
|
|
281
|
+
ret.rotation = newRotation
|
|
282
|
+
return ret
|
|
283
|
+
|
|
284
|
+
def getLoadMirrored(self, plane="xz"):
|
|
285
|
+
"""returns a copy of this load mirrored at the given global plane
|
|
286
|
+
|
|
287
|
+
:param plane: one of 'xz','xy','yz'
|
|
288
|
+
"""
|
|
289
|
+
if not self.rotation.isIdentity:
|
|
290
|
+
NotImplementedError("Mirroring a rotated load is actuallay unsupported. Please contact the developer.")
|
|
291
|
+
|
|
292
|
+
mirrorMask = np.ones(6)
|
|
293
|
+
if plane == "xy":
|
|
294
|
+
mirrorMask[2:5] = -1
|
|
295
|
+
elif plane == "xz":
|
|
296
|
+
mirrorMask[[1, 3, 5]] = -1
|
|
297
|
+
elif plane == "yz":
|
|
298
|
+
mirrorMask[[0, 4, 5]] = -1
|
|
299
|
+
else:
|
|
300
|
+
raise InternalError("got wrong parameter for plane: %s" % plane)
|
|
301
|
+
|
|
302
|
+
newLoad = self.copy()
|
|
303
|
+
# mirror load
|
|
304
|
+
newLoad *= mirrorMask
|
|
305
|
+
# mirror the reference point without changing the load itself
|
|
306
|
+
newLoad._point = self.point * mirrorMask[:3]
|
|
307
|
+
|
|
308
|
+
return newLoad
|
|
309
|
+
|
|
310
|
+
def _getMomentsDueToNewPoint(self, point):
|
|
311
|
+
"""this method calculates the moments resulting from moving a force vector.
|
|
312
|
+
It is only used with identity rotation!"""
|
|
313
|
+
# moments due to translation
|
|
314
|
+
return np.cross(self[:3], np.array(point) - self.point)
|
|
315
|
+
|
|
316
|
+
def _setPoint(self, newPoint):
|
|
317
|
+
"""resets the reference point of the load. Thus the load is rotated back to
|
|
318
|
+
the aircraft coordinate system rotation to calculate the correct moments due to
|
|
319
|
+
the newPoint. Then it is rotated back to the original rotation."""
|
|
320
|
+
if not self.rotation.isIdentity:
|
|
321
|
+
originalRotation = self.rotation
|
|
322
|
+
self.rotation = Rotation()
|
|
323
|
+
|
|
324
|
+
if np.linalg.norm(self._point - newPoint) > epsilon:
|
|
325
|
+
self[3:6] += self._getMomentsDueToNewPoint(newPoint)
|
|
326
|
+
self._point = newPoint
|
|
327
|
+
|
|
328
|
+
if not self.rotation.isIdentity:
|
|
329
|
+
self.rotation = originalRotation
|
|
330
|
+
|
|
331
|
+
def _getPoint(self):
|
|
332
|
+
"""returns the reference point
|
|
333
|
+
|
|
334
|
+
Attention: altering this point object will not affect the loads like"""
|
|
335
|
+
return self._point
|
|
336
|
+
|
|
337
|
+
def _getPositionX(self):
|
|
338
|
+
"""returns the x-coordinate of the loads point. (Used for sorting loads)"""
|
|
339
|
+
return self._point.x
|
|
340
|
+
|
|
341
|
+
def _setRotation(self, newRotation):
|
|
342
|
+
"""rotates the load back to and identity rotation and then to newRotation"""
|
|
343
|
+
|
|
344
|
+
# rotate to identity: inverse(oldRotation)*forces .....
|
|
345
|
+
if not self._rotation.isIdentity:
|
|
346
|
+
rotInv = np.linalg.inv(self._rotation)
|
|
347
|
+
self[:] = self[:] @ block_diag(rotInv, rotInv)
|
|
348
|
+
# @ operator is equivalent to np.dot and represents matrix-vector multiplication
|
|
349
|
+
|
|
350
|
+
# rotate to new rotation: newRotation*forces ....
|
|
351
|
+
if not newRotation.isIdentity:
|
|
352
|
+
self[:] = self[:] @ block_diag(newRotation, newRotation)
|
|
353
|
+
|
|
354
|
+
self._rotation = newRotation
|
|
355
|
+
|
|
356
|
+
def _getRotation(self):
|
|
357
|
+
"""returns a copy of the object's rotation attribute"""
|
|
358
|
+
return self._rotation
|
|
359
|
+
|
|
360
|
+
def __str__(self):
|
|
361
|
+
"""doc"""
|
|
362
|
+
return ", ".join(self.astype(str))
|
|
363
|
+
|
|
364
|
+
def _getFX(self):
|
|
365
|
+
return self[0]
|
|
366
|
+
|
|
367
|
+
def _getFY(self):
|
|
368
|
+
return self[1]
|
|
369
|
+
|
|
370
|
+
def _getFZ(self):
|
|
371
|
+
return self[2]
|
|
372
|
+
|
|
373
|
+
def _getMX(self):
|
|
374
|
+
return self[3]
|
|
375
|
+
|
|
376
|
+
def _getMY(self):
|
|
377
|
+
return self[4]
|
|
378
|
+
|
|
379
|
+
def _getMZ(self):
|
|
380
|
+
return self[5]
|
|
381
|
+
|
|
382
|
+
def _setFX(self, fx):
|
|
383
|
+
self[0] = fx
|
|
384
|
+
|
|
385
|
+
def _setFY(self, fy):
|
|
386
|
+
self[1] = fy
|
|
387
|
+
|
|
388
|
+
def _setFZ(self, fz):
|
|
389
|
+
self[2] = fz
|
|
390
|
+
|
|
391
|
+
def _setMX(self, mx):
|
|
392
|
+
self[3] = mx
|
|
393
|
+
|
|
394
|
+
def _setMY(self, my):
|
|
395
|
+
self[4] = my
|
|
396
|
+
|
|
397
|
+
def _setMZ(self, mz):
|
|
398
|
+
self[5] = mz
|
|
399
|
+
|
|
400
|
+
fx = property(fget=_getFX, fset=_setFX)
|
|
401
|
+
fy = property(fget=_getFY, fset=_setFY)
|
|
402
|
+
fz = property(fget=_getFZ, fset=_setFZ)
|
|
403
|
+
mx = property(fget=_getMX, fset=_setMX)
|
|
404
|
+
my = property(fget=_getMY, fset=_setMY)
|
|
405
|
+
mz = property(fget=_getMZ, fset=_setMZ)
|
|
406
|
+
|
|
407
|
+
point = property(fset=_setPoint, fget=_getPoint)
|
|
408
|
+
rotation = property(fset=_setRotation, fget=_getRotation)
|
|
409
|
+
positionX = property(fget=_getPositionX)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
if __name__ == "__main__":
|
|
413
|
+
if 0:
|
|
414
|
+
f = Flow()
|
|
415
|
+
f.machNumber = 0.0
|
|
416
|
+
heightsFt = np.array([5000, 6000, 7000, 8000, 9000, 10000, 39000])
|
|
417
|
+
heights = heightsFt * 0.3048
|
|
418
|
+
pressures = []
|
|
419
|
+
|
|
420
|
+
for height in heights:
|
|
421
|
+
f.h = height
|
|
422
|
+
f._p = None
|
|
423
|
+
pressures.append(f.staticPressure)
|
|
424
|
+
pressures = np.array(pressures)
|
|
425
|
+
dPs = pressures - pressures[-1]
|
|
426
|
+
print(
|
|
427
|
+
indent(
|
|
428
|
+
[["height[ft]", "height[m]", "pressure[Pa]", "dP[Pa]"]] + list(zip(heightsFt, heights, pressures, dPs))
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
elif 1:
|
|
433
|
+
import doctest
|
|
434
|
+
|
|
435
|
+
doctest.testmod() # verbose=True
|