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,1260 @@
|
|
|
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
|
+
"""
|
|
7
|
+
documentation
|
|
8
|
+
"""
|
|
9
|
+
import math
|
|
10
|
+
from collections import OrderedDict
|
|
11
|
+
from functools import reduce
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import numpy.linalg as nplin
|
|
15
|
+
from scipy.linalg import block_diag
|
|
16
|
+
|
|
17
|
+
from patme import epsilon
|
|
18
|
+
from patme.geometry.rotate import Rotation
|
|
19
|
+
from patme.service.exceptions import ImproperParameterError, InternalError
|
|
20
|
+
from patme.service.logger import log
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def abdOffset(abdMatrix, offset=0.0):
|
|
24
|
+
"""Calculates the given ABD matrix offset by offset
|
|
25
|
+
|
|
26
|
+
:param abdMatrix: 6x6 np array
|
|
27
|
+
:param offset: scalar
|
|
28
|
+
"""
|
|
29
|
+
a = abdMatrix[:3, :3]
|
|
30
|
+
b = -offset * a + abdMatrix[:3, 3:6]
|
|
31
|
+
d = offset**2 * a - 2 * offset * abdMatrix[:3, 3:6] + abdMatrix[3:6, 3:6]
|
|
32
|
+
|
|
33
|
+
newAbdMatrix = np.zeros_like(abdMatrix)
|
|
34
|
+
newAbdMatrix[:3, :3] = a
|
|
35
|
+
newAbdMatrix[3:6, :3] = newAbdMatrix[:3, 3:6] = b
|
|
36
|
+
newAbdMatrix[3:6, 3:6] = d
|
|
37
|
+
|
|
38
|
+
return newAbdMatrix
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def abdByPlyShares(q, r0, r90, r_pm45, laminateThickness):
|
|
42
|
+
"""Returns the abd-matrix of a laminate with given thickness and ply shares.
|
|
43
|
+
The laminate is expected to be homogenized by all angles. Thus, the reducedStiffness of each
|
|
44
|
+
angle orientation is reduced by the ply share for calculation of the abd matrix.
|
|
45
|
+
The reference surface is midPlane.
|
|
46
|
+
|
|
47
|
+
:param q: reduced stiffness matrix [3,3] in material coordinate system (0-deg)
|
|
48
|
+
:param r0: relative 0-deg share
|
|
49
|
+
:param r90: relative 90-deg share
|
|
50
|
+
:param r_pm45: relative plus minus 45-deg share
|
|
51
|
+
:param laminateThickness: thickness of the laminate
|
|
52
|
+
"""
|
|
53
|
+
plyShares = [r0, r90, r_pm45 / 2.0, r_pm45 / 2.0]
|
|
54
|
+
if not (np.sum(plyShares) - 1) < epsilon:
|
|
55
|
+
raise ImproperParameterError(f"The ply shares sum has to be 1 but got: {plyShares}")
|
|
56
|
+
|
|
57
|
+
abd = np.zeros((6, 6))
|
|
58
|
+
angles = np.radians(np.array([0, 90, 45, -45]))
|
|
59
|
+
for phi, plyShare in zip(angles, plyShares):
|
|
60
|
+
qTrans = MaterialDefinition.rotateReducedStiffnessMatrix(q, phi)
|
|
61
|
+
abd[:3, :3] += qTrans * plyShare * laminateThickness
|
|
62
|
+
abd[3:, 3:] += qTrans * plyShare * laminateThickness**3 / 12 # no excentricity, e=0
|
|
63
|
+
|
|
64
|
+
return abd
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def abdByLaminationParameters(laminationParameters, stiffnessInvariants, thickness):
|
|
68
|
+
"""calculate 6x6 ABD matrix based on laminaiton parameters and stiffness invariants
|
|
69
|
+
|
|
70
|
+
see [1]C. G. Diaconu und H. Sekine,
|
|
71
|
+
„Layup Optimization for Buckling of Laminated Composite Shells with Restricted Layer Angles“,
|
|
72
|
+
AIAA Journal, Bd. 42, Nr. 10, S. 2153–2163, Okt. 2004.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
abdComponents = []
|
|
76
|
+
lamParm2D = laminationParameters.reshape((3, 4))
|
|
77
|
+
for lamParmComponent, factor, isBMatrix in zip(
|
|
78
|
+
lamParm2D, [thickness, thickness**2 / 4.0, thickness**3 / 12.0], [False, True, False]
|
|
79
|
+
):
|
|
80
|
+
xsiMatrix = _getXsiMatrix(lamParmComponent, isBMatrix)
|
|
81
|
+
abdComponentVec = np.dot(xsiMatrix, stiffnessInvariants)
|
|
82
|
+
abdComponent = np.zeros((3, 3))
|
|
83
|
+
abdComponent[0, 0] = abdComponentVec[0]
|
|
84
|
+
abdComponent[1, 1] = abdComponentVec[1]
|
|
85
|
+
abdComponent[0, 1] = abdComponentVec[2]
|
|
86
|
+
abdComponent[2, 2] = abdComponentVec[3]
|
|
87
|
+
abdComponent[0, 2] = abdComponentVec[4]
|
|
88
|
+
abdComponent[1, 2] = abdComponentVec[5]
|
|
89
|
+
# symm
|
|
90
|
+
abdComponent[1, 0] = abdComponentVec[2]
|
|
91
|
+
abdComponent[2, 0] = abdComponentVec[4]
|
|
92
|
+
abdComponent[2, 1] = abdComponentVec[5]
|
|
93
|
+
|
|
94
|
+
abdComponent *= factor
|
|
95
|
+
abdComponents.append(abdComponent)
|
|
96
|
+
|
|
97
|
+
abdMatrix = np.zeros((6, 6))
|
|
98
|
+
abdMatrix[:3, :3] += abdComponents[0]
|
|
99
|
+
abdMatrix[:3, 3:] += abdComponents[1]
|
|
100
|
+
abdMatrix[3:, :3] += abdComponents[1]
|
|
101
|
+
abdMatrix[3:, 3:] += abdComponents[2]
|
|
102
|
+
|
|
103
|
+
return abdMatrix
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _getXsiMatrix(xsi, isBMatrix):
|
|
107
|
+
"""calculates the xsi matrix based on xsi(lamination parameters either A,B,D)
|
|
108
|
+
|
|
109
|
+
:return: 6x5 matrix"""
|
|
110
|
+
factor = 0.0 if isBMatrix else 1.0
|
|
111
|
+
xsiMatrix = np.array(
|
|
112
|
+
[
|
|
113
|
+
[factor, xsi[0], xsi[1], 0.0, 0.0],
|
|
114
|
+
[factor, -xsi[0], xsi[1], 0.0, 0.0],
|
|
115
|
+
[0.0, 0.0, -xsi[1], factor, 0.0],
|
|
116
|
+
[0.0, 0.0, -xsi[1], 0.0, factor],
|
|
117
|
+
[0.0, xsi[2] / 2.0, xsi[3], 0.0, 0.0],
|
|
118
|
+
[0.0, xsi[2] / 2.0, -xsi[3], 0.0, 0.0],
|
|
119
|
+
]
|
|
120
|
+
)
|
|
121
|
+
return xsiMatrix
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class MaterialDefinition:
|
|
125
|
+
"""classdocs"""
|
|
126
|
+
|
|
127
|
+
def __init__(self, **kwargs):
|
|
128
|
+
"""doc"""
|
|
129
|
+
self.id = None
|
|
130
|
+
self.name = None
|
|
131
|
+
self.description = None
|
|
132
|
+
self.rho = None
|
|
133
|
+
|
|
134
|
+
self.isShell = False
|
|
135
|
+
"""Flag if shell properties are given. Required for cpacs writing"""
|
|
136
|
+
|
|
137
|
+
self.stiffnessMatrix = np.zeros((6, 6))
|
|
138
|
+
"""stiffnesses of material definition"""
|
|
139
|
+
|
|
140
|
+
self.strength = {}
|
|
141
|
+
"""Max strength"""
|
|
142
|
+
self.strengthValues = ["sigma11t", "sigma11c", "sigma22t", "sigma22c", "tau"]
|
|
143
|
+
|
|
144
|
+
self.strain = {}
|
|
145
|
+
"""Max strain"""
|
|
146
|
+
self.strainValues = ["eps11t", "eps11c", "eps22t", "eps22c", "gamma"]
|
|
147
|
+
|
|
148
|
+
self._savedModuli = {}
|
|
149
|
+
|
|
150
|
+
self._kPlaneStressCondition = None
|
|
151
|
+
"""3x3 matrix containing the plane stress stiffnesses.
|
|
152
|
+
calculated according to altenberg page 53. The z-direction is the out of
|
|
153
|
+
plane direction with sigma_z = 0 and so on."""
|
|
154
|
+
|
|
155
|
+
self.number = None
|
|
156
|
+
"""id within fe systems"""
|
|
157
|
+
|
|
158
|
+
self.usedAngles = set()
|
|
159
|
+
"""Angles as integer in degrees which represent the orientations that
|
|
160
|
+
are used with this materialDefinition.
|
|
161
|
+
It is set within Layer.materialDefinition and is needed to create rotated
|
|
162
|
+
materialDefinitions for complex cross sections"""
|
|
163
|
+
|
|
164
|
+
self.thermalConductivity = np.zeros((6,))
|
|
165
|
+
"""Thermal conductivity KXX KXY KXZ KYY KYZ KZZ"""
|
|
166
|
+
|
|
167
|
+
self.thermalExpansionCoeff = np.zeros((6,))
|
|
168
|
+
"""Thermal expansion coefficient in the 3 directions XX XY XZ YY YZ ZZ"""
|
|
169
|
+
|
|
170
|
+
self.thermalExpansionCoeffTemp = 20 + 273.15
|
|
171
|
+
"""Reference temperature for thermalExpansionCoeff"""
|
|
172
|
+
|
|
173
|
+
self.specificHeats = None
|
|
174
|
+
"""Specific heat capacity sample points of the material in [J/(kg*K)].
|
|
175
|
+
Vector with at least len=2"""
|
|
176
|
+
|
|
177
|
+
self.specificHeatTemperatures = None
|
|
178
|
+
"""Temperatures to the specific heat capacities
|
|
179
|
+
Vector with at least len=2"""
|
|
180
|
+
|
|
181
|
+
self.specificHeatDefaultTemp = 20 + 273.15
|
|
182
|
+
"""Default temperature for specificHeat"""
|
|
183
|
+
|
|
184
|
+
self.setStrength(kwargs.pop("strength", {s: 0.0 for s in self.strengthValues}))
|
|
185
|
+
|
|
186
|
+
for key in kwargs:
|
|
187
|
+
if not hasattr(self, key):
|
|
188
|
+
log.warning(f'Setting unknown key "{key}" in class {self.__class__} with name "{str(self)}"')
|
|
189
|
+
setattr(self, key, kwargs[key])
|
|
190
|
+
|
|
191
|
+
def copy(self):
|
|
192
|
+
"""doc"""
|
|
193
|
+
return MaterialDefinition(
|
|
194
|
+
id=self.id,
|
|
195
|
+
name=self.name,
|
|
196
|
+
description=self.description,
|
|
197
|
+
rho=self.rho,
|
|
198
|
+
stiffnessMatrix=self.stiffnessMatrix,
|
|
199
|
+
strength=self.strength,
|
|
200
|
+
number=self.number,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def specificHeatFunction(self, temperature):
|
|
204
|
+
"""returns specific heat to the given temperature
|
|
205
|
+
:param temperature: scalar or vector with temperatures [K]
|
|
206
|
+
:return: scalar or vector with specific heats
|
|
207
|
+
"""
|
|
208
|
+
return np.interp(temperature, self.specificHeatTemperatures, self.specificHeats)
|
|
209
|
+
|
|
210
|
+
def setStiffnessMatrix(
|
|
211
|
+
self,
|
|
212
|
+
# isotrop
|
|
213
|
+
e1,
|
|
214
|
+
g12,
|
|
215
|
+
# transversal isotrop
|
|
216
|
+
e2=None,
|
|
217
|
+
nu12=None,
|
|
218
|
+
nu23=None,
|
|
219
|
+
# orthotrop
|
|
220
|
+
e3=None,
|
|
221
|
+
g23=None,
|
|
222
|
+
g13=None,
|
|
223
|
+
nu31=None,
|
|
224
|
+
):
|
|
225
|
+
"""This method assumes a transverse isotropic material.
|
|
226
|
+
|
|
227
|
+
Altenbach, Holm, Johannes Altenbach, und Rolands Rikards.
|
|
228
|
+
Einführung in die Mechanik der Laminat- und Sandwichtragwerke:
|
|
229
|
+
Modellierung und Berechnung von Balken und Platten aus Verbundwerkstoffen.
|
|
230
|
+
1. Aufl. Stuttgart: Wiley-VCH, 1996.
|
|
231
|
+
|
|
232
|
+
page 45 (Transversale Isotropie)
|
|
233
|
+
|
|
234
|
+
Accepted parameter combinations:
|
|
235
|
+
|
|
236
|
+
- isotrop
|
|
237
|
+
- e1, g12
|
|
238
|
+
- e1, nu12
|
|
239
|
+
- transversal isotrop
|
|
240
|
+
- e1, g12, e2, nu12
|
|
241
|
+
- e1, g12, e2, nu12, nu23
|
|
242
|
+
- e1, g12, e2, nu12, nu23, g23, g13
|
|
243
|
+
- orthotrop
|
|
244
|
+
- e1, g12, e2, nu12, nu23, e3, g23, g13, nu31
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
if g12 is None:
|
|
248
|
+
# isotrop switch if e and nu are given
|
|
249
|
+
g12 = e1 / (2 * (1 + nu12))
|
|
250
|
+
|
|
251
|
+
if not all(np.array([e2, nu12]) != None):
|
|
252
|
+
log.debug(f"Isotrop behavior material assumed for material with id {self.id}")
|
|
253
|
+
e2 = e3 = e1
|
|
254
|
+
nu12 = e1 / 2 / g12 - 1
|
|
255
|
+
nu31 = nu23 = nu12
|
|
256
|
+
g13 = g23 = g12
|
|
257
|
+
elif not all(np.array([e3, g23, g13, nu31]) != None):
|
|
258
|
+
log.debug(f"Transversal isotrop material behavior assumed for material with id {self.id}")
|
|
259
|
+
e3 = e2
|
|
260
|
+
nu31 = nu12
|
|
261
|
+
g13 = g12 if g13 is None else g13
|
|
262
|
+
nu23 = nu12 if nu23 is None else nu23
|
|
263
|
+
g23 = e2 / (2.0 * (1 + nu23)) if g23 is None else g23
|
|
264
|
+
else:
|
|
265
|
+
log.debug(f"Orthotrop material behavior assumed for material with id {self.id}")
|
|
266
|
+
|
|
267
|
+
self._savedModuli = {}
|
|
268
|
+
|
|
269
|
+
matUpperLeft = np.array(
|
|
270
|
+
[
|
|
271
|
+
[1.0 / e1, -nu12 / e1, -nu31 / e1],
|
|
272
|
+
[-nu12 / e1, 1.0 / e2, -nu23 / e2],
|
|
273
|
+
[
|
|
274
|
+
-nu31 / e1,
|
|
275
|
+
-nu23 / e2,
|
|
276
|
+
1.0 / e3,
|
|
277
|
+
],
|
|
278
|
+
]
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
matLowerRight = np.diag([1.0 / g23, 1.0 / g13, 1.0 / g12])
|
|
282
|
+
|
|
283
|
+
compliance = block_diag(matUpperLeft, matLowerRight)
|
|
284
|
+
self.stiffnessMatrix = np.linalg.inv(compliance)
|
|
285
|
+
|
|
286
|
+
def setStrength(self, strength):
|
|
287
|
+
"""This method is intended to set the strength of the material."""
|
|
288
|
+
self.strength.update(strength)
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def getTransformationMatrix(phi):
|
|
292
|
+
"""returns the material Transformation matrix: lamCS → matCS
|
|
293
|
+
see eq 2.76 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
|
|
294
|
+
|
|
295
|
+
:param phi: angle [rad]
|
|
296
|
+
"""
|
|
297
|
+
cos = np.cos(phi)
|
|
298
|
+
sin = np.sin(phi)
|
|
299
|
+
cs = cos * sin
|
|
300
|
+
cc = cos**2
|
|
301
|
+
ss = sin**2
|
|
302
|
+
|
|
303
|
+
T = np.array([[cc, ss, 2 * cs], [ss, cc, -2 * cs], [-cs, cs, cc - ss]])
|
|
304
|
+
return T
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def getTransformationMatrix(phi):
|
|
308
|
+
"""returns the material Transformation matrix: lamCS → matCS
|
|
309
|
+
see eq 2.76 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
|
|
310
|
+
|
|
311
|
+
:param phi: angle [rad]
|
|
312
|
+
"""
|
|
313
|
+
cos = np.cos(phi)
|
|
314
|
+
sin = np.sin(phi)
|
|
315
|
+
cs = cos * sin
|
|
316
|
+
cc = cos**2
|
|
317
|
+
ss = sin**2
|
|
318
|
+
|
|
319
|
+
T = np.array([[cc, ss, 2 * cs], [ss, cc, -2 * cs], [-cs, cs, cc - ss]])
|
|
320
|
+
return T
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def getTransformationInverseMatrix(phi):
|
|
324
|
+
"""returns the material Transformation matrix: matCS → lamCS
|
|
325
|
+
see eq 2.72 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
|
|
326
|
+
|
|
327
|
+
:param phi: angle [rad]
|
|
328
|
+
"""
|
|
329
|
+
cos = np.cos(phi)
|
|
330
|
+
sin = np.sin(phi)
|
|
331
|
+
cs = cos * sin
|
|
332
|
+
cc = cos**2
|
|
333
|
+
ss = sin**2
|
|
334
|
+
|
|
335
|
+
Ti = np.array([[cc, ss, -2 * cs], [ss, cc, 2 * cs], [cs, -cs, cc - ss]])
|
|
336
|
+
return Ti
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
def rotateReducedStiffnessMatrix(redStiff, phi):
|
|
340
|
+
"""transforms the given reduced stiffnessmatrix according to angle phi
|
|
341
|
+
see eq 2.85 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
|
|
342
|
+
|
|
343
|
+
:param redStif: 3x3 matrix
|
|
344
|
+
:param phi: angle [rad]
|
|
345
|
+
"""
|
|
346
|
+
redStiffVec = np.array([[redStiff[0, 0]], [redStiff[0, 1]], [redStiff[1, 1]], [redStiff[2, 2]]])
|
|
347
|
+
|
|
348
|
+
c = np.cos(phi)
|
|
349
|
+
s = np.sin(phi)
|
|
350
|
+
|
|
351
|
+
transM = np.array(
|
|
352
|
+
[
|
|
353
|
+
[c**4, 2 * c**2 * s**2, s**4, 4 * c**2 * s**2],
|
|
354
|
+
[c**2 * s**2, c**4 + s**4, c**2 * s**2, -4 * c**2 * s**2],
|
|
355
|
+
[c**3 * s, c * s * (c**2 - s**2), -c * s**3, -2 * c * s * (c**2 - s**2)],
|
|
356
|
+
[s**4, 2 * c**2 * s**2, c**4, 4 * c**2 * s**2],
|
|
357
|
+
[c * s**3, c * s * (c**2 - s**2), -(c**3) * s, 2 * c * s * (c**2 - s**2)],
|
|
358
|
+
[c**2 * s**2, -2 * c**2 * s**2, c**2 * s**2, (c**2 - s**2) ** 2],
|
|
359
|
+
]
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
redStiffVecGlo = np.mat(transM) * np.mat(redStiffVec)
|
|
363
|
+
redStiffMatGlo = np.array(
|
|
364
|
+
[
|
|
365
|
+
[redStiffVecGlo[0, 0], redStiffVecGlo[1, 0], redStiffVecGlo[2, 0]],
|
|
366
|
+
[redStiffVecGlo[1, 0], redStiffVecGlo[3, 0], redStiffVecGlo[4, 0]],
|
|
367
|
+
[redStiffVecGlo[2, 0], redStiffVecGlo[4, 0], redStiffVecGlo[5, 0]],
|
|
368
|
+
]
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
T = MaterialDefinition.getTransformationMatrix(phi)
|
|
372
|
+
Ti = np.linalg.inv(T)
|
|
373
|
+
|
|
374
|
+
Qbar = np.matmul(np.matmul(Ti, redStiff), T.T)
|
|
375
|
+
# return Qbar
|
|
376
|
+
|
|
377
|
+
return redStiffMatGlo
|
|
378
|
+
|
|
379
|
+
def getSpecificHeat(self, T=None):
|
|
380
|
+
if T is None:
|
|
381
|
+
T = self.specificHeatDefaultTemp
|
|
382
|
+
return self.specificHeatFunction(T)
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def moduli(self):
|
|
386
|
+
"""
|
|
387
|
+
calculates moduli
|
|
388
|
+
|
|
389
|
+
:return: dict with these keys: e11, e22, e33, g12, g23, g13, nu12, nu21, nu31, nu31, nu23
|
|
390
|
+
"""
|
|
391
|
+
if self._savedModuli != {}:
|
|
392
|
+
return self._savedModuli
|
|
393
|
+
stiffnessM = self.stiffnessMatrix
|
|
394
|
+
try:
|
|
395
|
+
complianceM = np.linalg.inv(stiffnessM)
|
|
396
|
+
except np.linalg.LinAlgError:
|
|
397
|
+
raise InternalError(
|
|
398
|
+
"Please check your material definition! "
|
|
399
|
+
+ f"Could not calculate compliance matrix of material with name {self.name} and id {self.id}."
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
e11 = complianceM[0, 0] ** -1
|
|
403
|
+
e22 = complianceM[1, 1] ** -1
|
|
404
|
+
e33 = complianceM[2, 2] ** -1
|
|
405
|
+
|
|
406
|
+
g23 = complianceM[3, 3] ** -1
|
|
407
|
+
g13 = complianceM[4, 4] ** -1
|
|
408
|
+
g12 = complianceM[5, 5] ** -1
|
|
409
|
+
|
|
410
|
+
nu12 = -complianceM[1, 0] * e11
|
|
411
|
+
nu21 = -complianceM[0, 1] * e22
|
|
412
|
+
nu13 = -complianceM[0, 2] * e11
|
|
413
|
+
|
|
414
|
+
nu31 = -complianceM[2, 0] * e33
|
|
415
|
+
nu23 = -complianceM[2, 1] * e22
|
|
416
|
+
nu32 = -complianceM[1, 2] * e33
|
|
417
|
+
|
|
418
|
+
if any(value < 0 for value in [e11, e22, e33, g12, g23, g13]):
|
|
419
|
+
if not hasattr(self, "xPath"):
|
|
420
|
+
self.xPath = ""
|
|
421
|
+
raise InternalError(
|
|
422
|
+
"Please check your material definition! "
|
|
423
|
+
+ "Got negative youngs- or shear modulus at material element at xPath "
|
|
424
|
+
+ self.xPath
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
self._savedModuli = OrderedDict(
|
|
428
|
+
[
|
|
429
|
+
("e11", e11),
|
|
430
|
+
("e22", e22),
|
|
431
|
+
("e33", e33),
|
|
432
|
+
("g12", g12),
|
|
433
|
+
("g23", g23),
|
|
434
|
+
("g13", g13),
|
|
435
|
+
("nu12", nu12),
|
|
436
|
+
("nu21", nu21),
|
|
437
|
+
("nu13", nu13),
|
|
438
|
+
("nu31", nu31),
|
|
439
|
+
("nu23", nu23),
|
|
440
|
+
("nu32", nu32),
|
|
441
|
+
]
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
return self._savedModuli
|
|
445
|
+
|
|
446
|
+
def getRotatedMaterialDefinition(self, orientationAngle):
|
|
447
|
+
r"""The materialDefinitions of this layer with 'orientationAngle' will be calculated.
|
|
448
|
+
|
|
449
|
+
Angle definition:
|
|
450
|
+
|
|
451
|
+
\2 |y /1
|
|
452
|
+
\ | /
|
|
453
|
+
\ | /<--\
|
|
454
|
+
\ | / phi \
|
|
455
|
+
\|/_______\_______x
|
|
456
|
+
'phi' is defined as the positive angle from the global coordinate system (x,y) to the
|
|
457
|
+
local coordinate system (1,2), which is fiberorientated.
|
|
458
|
+
|
|
459
|
+
validated with eLamX from TU Dresden
|
|
460
|
+
|
|
461
|
+
Reference: VDI 2014 Part 3, 2006, 'Development of fibre-reinforced plastics components'
|
|
462
|
+
"""
|
|
463
|
+
newMaterialDefinition = self.copy()
|
|
464
|
+
# reset attributes
|
|
465
|
+
newMaterialDefinition._savedModuli = {}
|
|
466
|
+
newMaterialDefinition._kPlaneStressCondition = None
|
|
467
|
+
|
|
468
|
+
stiff = np.mat(newMaterialDefinition.stiffnessMatrix)
|
|
469
|
+
|
|
470
|
+
c = np.cos(np.radians(orientationAngle))
|
|
471
|
+
s = np.sin(np.radians(orientationAngle))
|
|
472
|
+
trafoMinv = np.mat(
|
|
473
|
+
np.array(
|
|
474
|
+
[
|
|
475
|
+
[c**2.0, s**2.0, 0.0, 0.0, 0.0, -2 * s * c],
|
|
476
|
+
[s**2.0, c**2.0, 0, 0.0, 0.0, 2 * s * c],
|
|
477
|
+
[0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
|
|
478
|
+
[0.0, 0.0, 0.0, c, s, 0.0],
|
|
479
|
+
[0.0, 0.0, 0.0, -s, c, 0.0],
|
|
480
|
+
[s * c, -s * c, 0.0, 0.0, 0.0, c**2 - s**2],
|
|
481
|
+
]
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
rotStiff = trafoMinv * stiff * trafoMinv.T
|
|
486
|
+
|
|
487
|
+
newMaterialDefinition.stiffnessMatrix = rotStiff
|
|
488
|
+
newMaterialDefinition.id = f"{self.id}_{orientationAngle}"
|
|
489
|
+
newMaterialDefinition.description = f"{self.id}rotated {orientationAngle}deg"
|
|
490
|
+
newMaterialDefinition.name = f"{self.name}rotated {orientationAngle}deg"
|
|
491
|
+
|
|
492
|
+
return newMaterialDefinition
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def reducedStiffnessMatrix(self):
|
|
496
|
+
"""doc"""
|
|
497
|
+
return self.getReducedStiffnessMatrixStatic(self.stiffnessMatrix)
|
|
498
|
+
|
|
499
|
+
@staticmethod
|
|
500
|
+
def getReducedStiffnessMatrixStatic(stiffnessMatrix):
|
|
501
|
+
"""calculates the reduced stiffness matrix
|
|
502
|
+
see eq 2.63 [1]R. M. Jones, Mechanics Of Composite Materials, 2 New edition. Philadelphia, PA: Taylor & Francis Inc, 1998.
|
|
503
|
+
|
|
504
|
+
:param stiffnessMatrix:
|
|
505
|
+
"""
|
|
506
|
+
k = stiffnessMatrix
|
|
507
|
+
|
|
508
|
+
redCom = np.zeros((3, 3))
|
|
509
|
+
if k[2, 2] == 0 or k[3, 3] == 0 or k[4, 4] == 0:
|
|
510
|
+
com = nplin.inv(k[:2, :2])
|
|
511
|
+
redCom[:2, :2] = com[:2, :2]
|
|
512
|
+
redCom[2, 2] = k[5, 5] ** -1
|
|
513
|
+
else:
|
|
514
|
+
com = nplin.inv(k)
|
|
515
|
+
redCom[:2, :2] = com[:2, :2]
|
|
516
|
+
redCom[2, 2] = com[5, 5]
|
|
517
|
+
|
|
518
|
+
return nplin.inv(redCom)
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def reducedThermalExpansionCoeff(self):
|
|
522
|
+
"""returns the thermal expansion coefficients in directions XX YY XY"""
|
|
523
|
+
return np.array([self.thermalExpansionCoeff[0], self.thermalExpansionCoeff[3], self.thermalExpansionCoeff[1]])
|
|
524
|
+
|
|
525
|
+
def _getkPlaneStressCondition(self):
|
|
526
|
+
"""doc"""
|
|
527
|
+
if self._kPlaneStressCondition is not None:
|
|
528
|
+
return self._kPlaneStressCondition
|
|
529
|
+
|
|
530
|
+
self._kPlaneStressCondition = np.zeros((3, 3))
|
|
531
|
+
|
|
532
|
+
denom = 1 - self.moduli["nu12"] * self.moduli["nu21"]
|
|
533
|
+
# k11
|
|
534
|
+
self._kPlaneStressCondition[0, 0] = self.moduli["e11"] / denom
|
|
535
|
+
# k22
|
|
536
|
+
self._kPlaneStressCondition[1, 1] = self.moduli["e22"] / denom
|
|
537
|
+
# k66
|
|
538
|
+
self._kPlaneStressCondition[2, 2] = self.moduli["g12"]
|
|
539
|
+
# k12
|
|
540
|
+
self._kPlaneStressCondition[0, 1] = self.moduli["nu12"] * self.moduli["e22"] / denom
|
|
541
|
+
# k21
|
|
542
|
+
self._kPlaneStressCondition[1, 0] = self._kPlaneStressCondition[0, 1]
|
|
543
|
+
|
|
544
|
+
return self._kPlaneStressCondition
|
|
545
|
+
|
|
546
|
+
def _getIsIsotrop(self):
|
|
547
|
+
""":return: True if MaterialDefinition is isotrop. This is calculated by means of the stiffness matrix."""
|
|
548
|
+
return abs((self.stiffnessMatrix[0, 1] / self.stiffnessMatrix[1, 2]) - 1) < epsilon
|
|
549
|
+
|
|
550
|
+
def _getIsOrthotrop(self):
|
|
551
|
+
""":return: True if MaterialDefinition is orthotrop. This is calculated by means of the stiffness matrix."""
|
|
552
|
+
return not np.any(self.stiffnessMatrix[3:, :3])
|
|
553
|
+
|
|
554
|
+
def _getStiffnessInvariants(self):
|
|
555
|
+
"""doc"""
|
|
556
|
+
|
|
557
|
+
reducedStiffness = np.array(
|
|
558
|
+
[
|
|
559
|
+
self.kPlaneStressCondition[0, 0],
|
|
560
|
+
self.kPlaneStressCondition[1, 1],
|
|
561
|
+
self.kPlaneStressCondition[0, 1],
|
|
562
|
+
self.kPlaneStressCondition[2, 2],
|
|
563
|
+
]
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
coefficientMatrix = np.array(
|
|
567
|
+
[
|
|
568
|
+
[3.0 / 8.0, 3.0 / 8.0, 1.0 / 4.0, 1.0 / 2.0],
|
|
569
|
+
[1.0 / 2.0, -1.0 / 2.0, 0.0, 0.0],
|
|
570
|
+
[1.0 / 8.0, 1.0 / 8.0, -1.0 / 4.0, -1.0 / 2.0],
|
|
571
|
+
[1.0 / 8.0, 1.0 / 8.0, 3.0 / 4.0, -1.0 / 2.0],
|
|
572
|
+
[1.0 / 8.0, 1.0 / 8.0, -1.0 / 4.0, 1.0 / 2.0],
|
|
573
|
+
]
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
return np.dot(coefficientMatrix, reducedStiffness)
|
|
577
|
+
|
|
578
|
+
stiffnessInvariants = property(fget=_getStiffnessInvariants)
|
|
579
|
+
kPlaneStressCondition = property(fget=_getkPlaneStressCondition)
|
|
580
|
+
"""see MaterialDefinition._kPlaneStressCondition"""
|
|
581
|
+
|
|
582
|
+
isIsotrop = property(fget=_getIsIsotrop)
|
|
583
|
+
isOrthotrop = property(fget=_getIsOrthotrop)
|
|
584
|
+
specificHeat = property(fget=getSpecificHeat)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
class Composite:
|
|
588
|
+
"""classdocs"""
|
|
589
|
+
|
|
590
|
+
def __init__(self, **kwargs):
|
|
591
|
+
"""doc"""
|
|
592
|
+
self.id = None
|
|
593
|
+
self.name = None
|
|
594
|
+
self.description = None
|
|
595
|
+
self.offset = None
|
|
596
|
+
self.number = None
|
|
597
|
+
self.xPath = None
|
|
598
|
+
self.layers = []
|
|
599
|
+
self._savedModuli = {}
|
|
600
|
+
self._laminationParameters = None
|
|
601
|
+
"""vector of length 12 (eq.7a-c from diaconu 2004)
|
|
602
|
+
defines the lamination parameters for the whole composite"""
|
|
603
|
+
self._stiffnessInvariants = None
|
|
604
|
+
"""vector of length 5 (eq.5 from diaconu 2004)
|
|
605
|
+
stiffness invariants for the material of the whole composite. Only one material can be used for all layers"""
|
|
606
|
+
|
|
607
|
+
for key in kwargs:
|
|
608
|
+
if not hasattr(self, key):
|
|
609
|
+
log.warning('Setting unknown key "%s" in class %s with name "%s"' % (key, self.__class__, str(self)))
|
|
610
|
+
setattr(self, key, kwargs[key])
|
|
611
|
+
|
|
612
|
+
def copy(self):
|
|
613
|
+
"""doc"""
|
|
614
|
+
return self.__class__(
|
|
615
|
+
id=self.id,
|
|
616
|
+
name=self.name,
|
|
617
|
+
description=self.description,
|
|
618
|
+
offset=self.offset,
|
|
619
|
+
layers=[layer.copy() for layer in self.layers],
|
|
620
|
+
_savedModuli=self._savedModuli,
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
@property
|
|
624
|
+
def layerBounds(self):
|
|
625
|
+
"""Calculates the bounds of all layers"""
|
|
626
|
+
bounds = np.zeros((self.numberOfLayers + 1,))
|
|
627
|
+
bounds[1:] = np.cumsum(self.thicknesses)
|
|
628
|
+
bounds -= bounds[-1] / 2
|
|
629
|
+
offset = self.offset if self.offset else 0.0
|
|
630
|
+
return bounds + offset
|
|
631
|
+
|
|
632
|
+
def getOffsetForSubLaminate(self, layers):
|
|
633
|
+
"""Calculates the actual z-coordinate of the specified layer (which belongs to the current laminate) wrt.
|
|
634
|
+
the mid-plane of the laminate.
|
|
635
|
+
|
|
636
|
+
:param layers: list, containing continuous instances of type Layer() or
|
|
637
|
+
VariableStiffnessLayer() representing the sub-laminate
|
|
638
|
+
|
|
639
|
+
:return: float, specifying the offset of the layer from the mid-plane of the laminate
|
|
640
|
+
"""
|
|
641
|
+
layerStartIndex = self.layers.index(layers[0])
|
|
642
|
+
layerEndIndex = self.layers.index(layers[-1])
|
|
643
|
+
|
|
644
|
+
lowerZ = -self.thickness / 2
|
|
645
|
+
distanceToLowerBorder = sum(self.layerThicknesses[:layerStartIndex])
|
|
646
|
+
subLaminateThickness = sum(self.layerThicknesses[layerStartIndex : layerEndIndex + 1])
|
|
647
|
+
return lowerZ + distanceToLowerBorder + subLaminateThickness / 2.0
|
|
648
|
+
|
|
649
|
+
@property
|
|
650
|
+
def thicknesses(self):
|
|
651
|
+
return np.array([layer.thickness for layer in self.layers])
|
|
652
|
+
|
|
653
|
+
def _getThickness(self):
|
|
654
|
+
"""doc"""
|
|
655
|
+
thickness = sum(self.thicknesses, 0.0)
|
|
656
|
+
|
|
657
|
+
if thickness < epsilon:
|
|
658
|
+
log.warning(f'thickness of composite with id "{self.id}" is zero. Resetting it to {epsilon}')
|
|
659
|
+
thickness = epsilon
|
|
660
|
+
return thickness
|
|
661
|
+
|
|
662
|
+
def _getLayerOrientations(self):
|
|
663
|
+
"""doc"""
|
|
664
|
+
return [layer.phi for layer in self.layers]
|
|
665
|
+
|
|
666
|
+
def _getLayerThicknesses(self):
|
|
667
|
+
"""return layer thicknesses as list"""
|
|
668
|
+
return np.array([layer.thickness for layer in self.layers])
|
|
669
|
+
|
|
670
|
+
def _getRho(self):
|
|
671
|
+
"""doc"""
|
|
672
|
+
rho = sum((layer.materialDefinition.rho * layer.thickness for layer in self.layers), 0.0)
|
|
673
|
+
return rho / self.thickness
|
|
674
|
+
|
|
675
|
+
def getABD(self, scaling=None, offset=None):
|
|
676
|
+
"""calculates ABD-matrix - see a composite book for explanation
|
|
677
|
+
|
|
678
|
+
:param scaling: optional parameter to use a custom scaling of the thickness of the composites layers
|
|
679
|
+
:param offset: optional parameter can set another offset. The reference plane
|
|
680
|
+
is the composites middle plane. If offset is not given, the class property
|
|
681
|
+
offset is used instead. If none of these are given, offset=0 which means the mid-thickness
|
|
682
|
+
surface is the reference surface.
|
|
683
|
+
"""
|
|
684
|
+
if self.layers:
|
|
685
|
+
return self.getAbdByLayers(scaling, offset)
|
|
686
|
+
|
|
687
|
+
if self.laminationParameters is not None:
|
|
688
|
+
return self.getAbdByLaminationParameters()
|
|
689
|
+
|
|
690
|
+
raise InternalError("There are no layers or lamination parameters defined")
|
|
691
|
+
|
|
692
|
+
def getAbdByLayers(self, scaling=None, offset=None):
|
|
693
|
+
"""calculates ABD-matrix - see a composite book for explanation
|
|
694
|
+
|
|
695
|
+
:param scaling: optional parameter to use a custom scaling of the thickness of the composites layers
|
|
696
|
+
:param offset: optional parameter can set another offset. The reference plane
|
|
697
|
+
is the composites middle plane. If offset is not given, the class property
|
|
698
|
+
offset is used instead. If none of these are given, offset=0 which means the mid-thickness
|
|
699
|
+
surface is the reference surface.
|
|
700
|
+
|
|
701
|
+
abd = ABD matrix
|
|
702
|
+
zk = offset of layer from the middle of the laminate
|
|
703
|
+
0 -> of previous layer, 1-> of present layer
|
|
704
|
+
|
|
705
|
+
com = compliance matrix
|
|
706
|
+
redCom = reduced compliance matrix
|
|
707
|
+
redStiff = reduced stiffness matrix
|
|
708
|
+
redStiffVec = reduced stiffness matrix in vector form
|
|
709
|
+
transM = transformation matrix
|
|
710
|
+
redStiffVecGlo = reduced stiffness vector in the global coordinate system
|
|
711
|
+
"""
|
|
712
|
+
|
|
713
|
+
if scaling is None:
|
|
714
|
+
scaling = 1.0
|
|
715
|
+
if offset is None:
|
|
716
|
+
offset = 0.0 if self.offset is None else self.offset
|
|
717
|
+
|
|
718
|
+
abd = np.zeros((6, 6))
|
|
719
|
+
zk1 = -(self.thickness * scaling) / 2 - offset
|
|
720
|
+
for layer in self.layers:
|
|
721
|
+
redStiff = MaterialDefinition.getReducedStiffnessMatrixStatic(layer.materialDefinition.stiffnessMatrix)
|
|
722
|
+
redStiffMatGlo = MaterialDefinition.rotateReducedStiffnessMatrix(redStiff, np.radians(layer.phi))
|
|
723
|
+
|
|
724
|
+
zk0 = zk1
|
|
725
|
+
zk1 = zk0 + layer.thickness * scaling
|
|
726
|
+
|
|
727
|
+
zkA = np.zeros((3, 3))
|
|
728
|
+
zkA[:, :] = zk1 - zk0
|
|
729
|
+
|
|
730
|
+
zkB = np.zeros((3, 3))
|
|
731
|
+
zkB[:, :] = zk1**2 - zk0**2
|
|
732
|
+
|
|
733
|
+
zkD = np.zeros((3, 3))
|
|
734
|
+
zkD[:, :] = zk1**3 - zk0**3
|
|
735
|
+
|
|
736
|
+
abd[:3, :3] += redStiffMatGlo * zkA
|
|
737
|
+
abd[3:, :3] += redStiffMatGlo * zkB
|
|
738
|
+
abd[:3, 3:] += redStiffMatGlo * zkB
|
|
739
|
+
abd[3:, 3:] += redStiffMatGlo * zkD
|
|
740
|
+
|
|
741
|
+
abd[3:, :3] = abd[3:, :3] * 1 / 2
|
|
742
|
+
abd[:3, 3:] = abd[:3, 3:] * 1 / 2
|
|
743
|
+
abd[3:, 3:] = abd[3:, 3:] * 1 / 3
|
|
744
|
+
# ABD-matrix now fully calculated
|
|
745
|
+
|
|
746
|
+
return abd
|
|
747
|
+
|
|
748
|
+
def setLaminationParametersOfLayers(self, scaling=None, offset=None):
|
|
749
|
+
"""This method sets for each layer the lamination parameters according to the actual stacking sequence."""
|
|
750
|
+
if scaling is None:
|
|
751
|
+
scaling = 1.0
|
|
752
|
+
if offset is None:
|
|
753
|
+
offset = 0.0 if self.offset is None else self.offset
|
|
754
|
+
|
|
755
|
+
actualZ = -(self.thickness * scaling) / 2 + offset
|
|
756
|
+
for layer in self.layers:
|
|
757
|
+
xsiAList = np.zeros(4)
|
|
758
|
+
xsiBList = np.zeros(4)
|
|
759
|
+
xsiDList = np.zeros(4)
|
|
760
|
+
actualAngle = layer.phi * np.pi / 180.0
|
|
761
|
+
actualThickness = layer.thickness
|
|
762
|
+
|
|
763
|
+
previousZ = actualZ
|
|
764
|
+
actualZ = previousZ + actualThickness
|
|
765
|
+
|
|
766
|
+
cos2 = np.cos(2.0 * actualAngle)
|
|
767
|
+
cos4 = np.cos(4.0 * actualAngle)
|
|
768
|
+
sin2 = np.sin(2.0 * actualAngle)
|
|
769
|
+
sin4 = np.sin(4.0 * actualAngle)
|
|
770
|
+
|
|
771
|
+
xsiAList[0] += 1.0 / 2.0 * cos2 * (actualZ - previousZ)
|
|
772
|
+
xsiAList[1] += 1.0 / 2.0 * cos4 * (actualZ - previousZ)
|
|
773
|
+
xsiAList[2] += 1.0 / 2.0 * sin2 * (actualZ - previousZ)
|
|
774
|
+
xsiAList[3] += 1.0 / 2.0 * sin4 * (actualZ - previousZ)
|
|
775
|
+
|
|
776
|
+
xsiBList[0] += cos2 * (actualZ**2 - previousZ**2)
|
|
777
|
+
xsiBList[1] += cos4 * (actualZ**2 - previousZ**2)
|
|
778
|
+
xsiBList[2] += sin2 * (actualZ**2 - previousZ**2)
|
|
779
|
+
xsiBList[3] += sin4 * (actualZ**2 - previousZ**2)
|
|
780
|
+
|
|
781
|
+
xsiDList[0] += 3.0 / 2.0 * cos2 * (actualZ**3 - previousZ**3)
|
|
782
|
+
xsiDList[1] += 3.0 / 2.0 * cos4 * (actualZ**3 - previousZ**3)
|
|
783
|
+
xsiDList[2] += 3.0 / 2.0 * sin2 * (actualZ**3 - previousZ**3)
|
|
784
|
+
xsiDList[3] += 3.0 / 2.0 * sin4 * (actualZ**3 - previousZ**3)
|
|
785
|
+
|
|
786
|
+
layer.laminationParameters = [xsiAList, xsiBList, xsiDList]
|
|
787
|
+
|
|
788
|
+
def getAbdByLaminationParameters(self):
|
|
789
|
+
"""doc"""
|
|
790
|
+
if self._laminationParameters:
|
|
791
|
+
# there are lamination parameters defined for the whole composite
|
|
792
|
+
if self._stiffnessInvariants is None:
|
|
793
|
+
raise Exception()
|
|
794
|
+
return abdByLaminationParameters(self._laminationParameters, self._stiffnessInvariants, self.thickness)
|
|
795
|
+
|
|
796
|
+
else:
|
|
797
|
+
# calculate layerwise
|
|
798
|
+
abdMatrix = np.zeros((6, 6))
|
|
799
|
+
for layerIndex, layer in enumerate(self.layers):
|
|
800
|
+
stiffnessInvariants = layer.stiffnessInvariants
|
|
801
|
+
laminationParameters = np.array(self._getLamParmsCoeffMatOfLayer(layerIndex + 1)).flatten()
|
|
802
|
+
abd = abdByLaminationParameters(laminationParameters, stiffnessInvariants, layer.thickness)
|
|
803
|
+
abdMatrix += abd
|
|
804
|
+
return abdMatrix
|
|
805
|
+
|
|
806
|
+
def _getLamParmsCoeffMatOfLayer(self, layerNumber):
|
|
807
|
+
"""This method returns the lamination parameters of the specifed ply.
|
|
808
|
+
|
|
809
|
+
:param layerNumber: int, the number of the layer within the actual stacking sequence (starting with 1)
|
|
810
|
+
|
|
811
|
+
:return: list, containing three arrays (lamination parameter matrixes for a-Matrix, b-Matrix and d-Matrix)
|
|
812
|
+
"""
|
|
813
|
+
laminationParameters = self.layers[layerNumber - 1].laminationParameters
|
|
814
|
+
plyThickness = self.layers[layerNumber - 1].thickness
|
|
815
|
+
ratio = plyThickness / self.thickness
|
|
816
|
+
|
|
817
|
+
xsiAList = laminationParameters[0]
|
|
818
|
+
xsiAMatrix = np.array(
|
|
819
|
+
[
|
|
820
|
+
[1.0 * ratio, xsiAList[0], xsiAList[1], 0.0, 0.0],
|
|
821
|
+
[1.0 * ratio, -xsiAList[0], xsiAList[1], 0.0, 0.0],
|
|
822
|
+
[0.0, 0.0, -xsiAList[1], 1.0 * ratio, 0.0],
|
|
823
|
+
[0.0, 0.0, -xsiAList[1], 0.0, 1.0 * ratio],
|
|
824
|
+
[0.0, xsiAList[2] / 2.0, xsiAList[3], 0.0, 0.0],
|
|
825
|
+
[0.0, xsiAList[2] / 2.0, -xsiAList[3], 0.0, 0.0],
|
|
826
|
+
]
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
xsiBList = laminationParameters[1]
|
|
830
|
+
xsiBMatrix = np.array(
|
|
831
|
+
[
|
|
832
|
+
[0.0, xsiBList[0], xsiBList[1], 0.0, 0.0],
|
|
833
|
+
[0.0, -xsiBList[0], xsiBList[1], 0.0, 0.0],
|
|
834
|
+
[0.0, 0.0, -xsiBList[1], 0.0, 0.0],
|
|
835
|
+
[0.0, 0.0, -xsiBList[1], 0.0, 0.0],
|
|
836
|
+
[0.0, xsiBList[2] / 2.0, xsiBList[3], 0.0, 0.0],
|
|
837
|
+
[0.0, xsiBList[2] / 2.0, -xsiBList[3], 0.0, 0.0],
|
|
838
|
+
]
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
xsiDList = laminationParameters[2]
|
|
842
|
+
xsiDMatrix = np.array(
|
|
843
|
+
[
|
|
844
|
+
[1.0 * ratio, xsiDList[0], xsiDList[1], 0.0, 0.0],
|
|
845
|
+
[1.0 * ratio, -xsiDList[0], xsiDList[1], 0.0, 0.0],
|
|
846
|
+
[0.0, 0.0, -xsiDList[1], 1.0 * ratio, 0.0],
|
|
847
|
+
[0.0, 0.0, -xsiDList[1], 0.0, 1.0 * ratio],
|
|
848
|
+
[0.0, xsiDList[2] / 2.0, xsiDList[3], 0.0, 0.0],
|
|
849
|
+
[0.0, xsiDList[2] / 2.0, -xsiDList[3], 0.0, 0.0],
|
|
850
|
+
]
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
return [xsiAMatrix, xsiBMatrix, xsiDMatrix]
|
|
854
|
+
|
|
855
|
+
@staticmethod
|
|
856
|
+
def getLaminationParameter(thickness, orientation):
|
|
857
|
+
"""Calculates and returns the "Lamination Parameter"
|
|
858
|
+
Source: Shutian Liu , Yupin Hou , Xiannian Sun , Yongcun Zhang: A two-step optimization scheme for
|
|
859
|
+
maximum stiffness design of laminated
|
|
860
|
+
plates based on lamination parameters, 2012
|
|
861
|
+
|
|
862
|
+
p. 3531ff.
|
|
863
|
+
"""
|
|
864
|
+
|
|
865
|
+
# Determine the normalized coordinate of thickness z
|
|
866
|
+
# (origin: plane of symmetrie , direction: from plane of symmetrie to rim]
|
|
867
|
+
|
|
868
|
+
thicknessList = orientation
|
|
869
|
+
for i in range(0, len(thicknessList)):
|
|
870
|
+
thicknessList[i] = thickness
|
|
871
|
+
|
|
872
|
+
# t = [0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001] # test-thicknesses
|
|
873
|
+
h = reduce(lambda x, y: x + y, thicknessList)
|
|
874
|
+
|
|
875
|
+
ori = orientation # list with plyorientations
|
|
876
|
+
|
|
877
|
+
z = [] # list with normalized thickness-coordinates zi
|
|
878
|
+
if len(thicknessList) % 2 == 0:
|
|
879
|
+
for i in range(0, len(thicknessList) // 2):
|
|
880
|
+
p = reduce(lambda k, l: k + l, thicknessList[i : len(thicknessList) / 2], 0)
|
|
881
|
+
z.append(p)
|
|
882
|
+
|
|
883
|
+
# reflect list at the plane of symmetry
|
|
884
|
+
# z.extend(z[::1])
|
|
885
|
+
z.append(0)
|
|
886
|
+
zRezi = [-x for x in z]
|
|
887
|
+
z.extend(zRezi[::-1])
|
|
888
|
+
z.remove(0)
|
|
889
|
+
zges = [x / h for x in z]
|
|
890
|
+
|
|
891
|
+
else:
|
|
892
|
+
|
|
893
|
+
try:
|
|
894
|
+
i = 0
|
|
895
|
+
while i < len(thicknessList) / 2:
|
|
896
|
+
p = (len(thicknessList) / 2 - i) * thickness
|
|
897
|
+
|
|
898
|
+
z.append(p)
|
|
899
|
+
i += 1
|
|
900
|
+
|
|
901
|
+
z.append(0.5 * thickness)
|
|
902
|
+
zRezi = [-x for x in z]
|
|
903
|
+
z.extend(zRezi[::-1])
|
|
904
|
+
zges = [x / h for x in z]
|
|
905
|
+
except:
|
|
906
|
+
z.append(0.5 * thickness)
|
|
907
|
+
zRezi = [-x for x in z]
|
|
908
|
+
z.extend(zRezi[::-1])
|
|
909
|
+
zges = [x / h for x in z]
|
|
910
|
+
|
|
911
|
+
# Calculation of the Lamination Parameter:
|
|
912
|
+
|
|
913
|
+
lpA1 = [] # list of the components of lpA1 calculating to the Lamination Parameter lpA1ges
|
|
914
|
+
lpA2 = [] # list of the components of lpA2 calculating to the Lamination Parameter lpA2ges
|
|
915
|
+
lpA3 = [] # list of the components of lpA3 calculating to the Lamination Parameter lpA3ges
|
|
916
|
+
lpA4 = [] # list of the components of lpA4 calculating to the Lamination Parameter lpA4ges
|
|
917
|
+
lpB1 = [] # list of the components of lpB1 calculating to the Lamination Parameter lpB1ges
|
|
918
|
+
lpB2 = [] # list of the components of lpB2 calculating to the Lamination Parameter lpB2ges
|
|
919
|
+
lpB3 = [] # list of the components of lpB3 calculating to the Lamination Parameter lpB3ges
|
|
920
|
+
lpB4 = [] # list of the components of lpB4 calculating to the Lamination Parameter lpB4ges
|
|
921
|
+
lpD1 = [] # list of the components of lpD1 calculating to the Lamination Parameter lpD1ges
|
|
922
|
+
lpD2 = [] # list of the components of lpD2 calculating to the Lamination Parameter lpD2ges
|
|
923
|
+
lpD3 = [] # list of the components of lpD3 calculating to the Lamination Parameter lpD3ges
|
|
924
|
+
lpD4 = [] # list of the components of lpD4 calculating to the Lamination Parameter lpD4ges
|
|
925
|
+
lenThickness = len(thicknessList) # number of plies of a stack
|
|
926
|
+
# print 'lenThickness:', lenThickness
|
|
927
|
+
for n in range(0, lenThickness - 1):
|
|
928
|
+
# Lamination Parameter for matrix A
|
|
929
|
+
|
|
930
|
+
lpA1n = (zges[n + 1] - zges[n]) * (math.cos(2 * ori[n] * math.pi / 180))
|
|
931
|
+
lpA1.append(lpA1n)
|
|
932
|
+
|
|
933
|
+
lpA2n = (zges[n + 1] - zges[n]) * (math.sin(2 * ori[n] * math.pi / 180))
|
|
934
|
+
lpA2.append(lpA2n)
|
|
935
|
+
|
|
936
|
+
lpA3n = (zges[n + 1] - zges[n]) * (math.cos(4 * ori[n] * math.pi / 180))
|
|
937
|
+
lpA3.append(lpA3n)
|
|
938
|
+
|
|
939
|
+
lpA4n = (zges[n + 1] - zges[n]) * (math.sin(4 * ori[n] * math.pi / 180))
|
|
940
|
+
lpA4.append(lpA4n)
|
|
941
|
+
|
|
942
|
+
# Lamination Parameter for matrix B
|
|
943
|
+
|
|
944
|
+
lpB1n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.cos(2 * ori[n] * math.pi / 180))
|
|
945
|
+
lpB1.append(lpB1n)
|
|
946
|
+
|
|
947
|
+
lpB2n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.sin(2 * ori[n] * math.pi / 180))
|
|
948
|
+
lpB2.append(lpB2n)
|
|
949
|
+
|
|
950
|
+
lpB3n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.cos(4 * ori[n] * math.pi / 180))
|
|
951
|
+
lpB3.append(lpB3n)
|
|
952
|
+
|
|
953
|
+
lpB4n = (zges[n + 1] ** 2 - zges[n] ** 2) * (math.sin(4 * ori[n] * math.pi / 180))
|
|
954
|
+
lpB4.append(lpB4n)
|
|
955
|
+
|
|
956
|
+
# Lamination Parameter for matrix D
|
|
957
|
+
|
|
958
|
+
lpD1n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.cos(2 * ori[n] * math.pi / 180))
|
|
959
|
+
lpD1.append(lpD1n)
|
|
960
|
+
|
|
961
|
+
lpD2n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.sin(2 * ori[n] * math.pi / 180))
|
|
962
|
+
lpD2.append(lpD2n)
|
|
963
|
+
|
|
964
|
+
lpD3n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.cos(4 * ori[n] * math.pi / 180))
|
|
965
|
+
lpD3.append(lpD3n)
|
|
966
|
+
|
|
967
|
+
lpD4n = (zges[n + 1] ** 3 - zges[n] ** 3) * (math.sin(4 * ori[n] * math.pi / 180))
|
|
968
|
+
lpD4.append(lpD4n)
|
|
969
|
+
|
|
970
|
+
truncToZero = lambda x: 0 if x < epsilon else x
|
|
971
|
+
|
|
972
|
+
# Lamination Parameter for A:
|
|
973
|
+
lpA = []
|
|
974
|
+
for lp_a in [lpA1, lpA2, lpA3, lpA4]:
|
|
975
|
+
res = truncToZero(reduce(lambda i, j: i + j, lp_a, 0))
|
|
976
|
+
lpA.append(res)
|
|
977
|
+
|
|
978
|
+
# Lamination Parameter for B:
|
|
979
|
+
lpB = []
|
|
980
|
+
for lp_b in [lpB1, lpB2, lpB3, lpB4]:
|
|
981
|
+
res = truncToZero(reduce(lambda i, j: i + 2 * j, lp_b, 0))
|
|
982
|
+
lpB.append(res)
|
|
983
|
+
|
|
984
|
+
# Lamination Parameter for D:
|
|
985
|
+
lpD = []
|
|
986
|
+
for lp_d in [lpD1, lpD2, lpD3, lpD4]:
|
|
987
|
+
res = truncToZero(reduce(lambda i, j: i + 4 * j, lp_d, 0))
|
|
988
|
+
lpD.append(res)
|
|
989
|
+
|
|
990
|
+
# =========================================================
|
|
991
|
+
|
|
992
|
+
def getPolarParameter(self, Q11, Q12, Q22, Q16, Q26, Q66):
|
|
993
|
+
"""Calculates and returns the "Polar Parameter" -> calculation according to: Optimal Orthotropy for Minimum Elastic Energy
|
|
994
|
+
by the Polar Method by A. Vincenti , B. Desmorat"""
|
|
995
|
+
|
|
996
|
+
# stiffnesses of the A- matrix were needed
|
|
997
|
+
|
|
998
|
+
T0 = (Q11 + Q22 - 2 * Q12 + 4 * Q66) / 8
|
|
999
|
+
T1 = (Q11 + Q22 + 2 * Q12) / 8
|
|
1000
|
+
R0 = (
|
|
1001
|
+
math.sqrt(
|
|
1002
|
+
(Q11 + Q22 - 2 * Q12 - 4 * Q66) * (Q11 + Q22 - 2 * Q12 - 4 * Q66)
|
|
1003
|
+
+ (4 * (Q16 - Q26)) * (4 * (Q16 - Q26))
|
|
1004
|
+
)
|
|
1005
|
+
) / 8
|
|
1006
|
+
R1 = (math.sqrt((Q11 - Q22) * (Q11 - Q22) + (2 * (Q16 + Q26)) * (2 * (Q16 + Q26)))) / 8
|
|
1007
|
+
|
|
1008
|
+
if (Q16 - Q26) == 0:
|
|
1009
|
+
phi0 = 0
|
|
1010
|
+
else:
|
|
1011
|
+
try:
|
|
1012
|
+
phi0 = math.atan(4 * (Q16 - Q26) / (Q11 + Q22 - 2 * Q12 - 4 * Q66)) / 4
|
|
1013
|
+
except:
|
|
1014
|
+
phi0 = math.pi / 8
|
|
1015
|
+
|
|
1016
|
+
if (Q16 + Q26) == 0:
|
|
1017
|
+
phi1 = 0
|
|
1018
|
+
else:
|
|
1019
|
+
try:
|
|
1020
|
+
phi1 = math.atan(2 * (Q16 + Q26) / (Q11 - Q22)) / 2
|
|
1021
|
+
except:
|
|
1022
|
+
phi1 = math.pi / 4
|
|
1023
|
+
|
|
1024
|
+
return {"T0": T0, "T1": T1, "R0": R0, "R1": R1, "phi0": phi0, "phi1": phi1}
|
|
1025
|
+
|
|
1026
|
+
def getThermalForces(self, dT=0, Q=None):
|
|
1027
|
+
"""Calculates thermal force due to temperature variations.
|
|
1028
|
+
|
|
1029
|
+
:param dT: delta temperature: scalar or vector with temperatures for top, each interface and bottom.
|
|
1030
|
+
length: number of layers + 1
|
|
1031
|
+
:return: force vector of len 6 in force/length etc.
|
|
1032
|
+
"""
|
|
1033
|
+
if not hasattr(dT, "__iter__"):
|
|
1034
|
+
# dT is scalar
|
|
1035
|
+
dT = [dT] * (self.numberOfLayers + 1)
|
|
1036
|
+
|
|
1037
|
+
forceThermalLcs = np.zeros(6)
|
|
1038
|
+
layerBounds = self.layerBounds
|
|
1039
|
+
layBoundsInf = layerBounds[:-1]
|
|
1040
|
+
layBoundsSup = layerBounds[1:]
|
|
1041
|
+
|
|
1042
|
+
for layer, dTPlyInf, dTPlySup, boundInf, boundSup in zip(
|
|
1043
|
+
self.layers, dT[:-1], dT[1:], layBoundsInf, layBoundsSup
|
|
1044
|
+
):
|
|
1045
|
+
phi = np.radians(layer.phi)
|
|
1046
|
+
materialDef = layer.materialDefinition
|
|
1047
|
+
T = layer.materialDefinition.getTransformationMatrix(phi)
|
|
1048
|
+
# LCS: laminate coordinate system
|
|
1049
|
+
if Q is None:
|
|
1050
|
+
Q = materialDef.reducedStiffnessMatrix
|
|
1051
|
+
stressThermalInfMcs = np.dot(Q, materialDef.reducedThermalExpansionCoeff) * dTPlyInf
|
|
1052
|
+
stressThermalSupMcs = np.dot(Q, materialDef.reducedThermalExpansionCoeff) * dTPlySup
|
|
1053
|
+
stressThermalInfLcs = np.matmul(np.linalg.inv(T), stressThermalInfMcs)
|
|
1054
|
+
stressThermalSupLcs = np.matmul(np.linalg.inv(T), stressThermalSupMcs)
|
|
1055
|
+
stressThermalMeanLcs = np.mean([stressThermalInfLcs, stressThermalSupLcs], axis=0)
|
|
1056
|
+
forceThermalLcs[:3] = forceThermalLcs[:3] + stressThermalMeanLcs * (boundSup - boundInf)
|
|
1057
|
+
forceThermalLcs[3:] = forceThermalLcs[3:] + stressThermalMeanLcs * (1 / 2) * (boundSup**2 - boundInf**2)
|
|
1058
|
+
|
|
1059
|
+
return forceThermalLcs
|
|
1060
|
+
|
|
1061
|
+
def getThermalStrains(self, dT=0):
|
|
1062
|
+
"""Calculates thermal strains due to temperature variations.
|
|
1063
|
+
:param dT: delta temperature: scalar or vector with temperatures for top, each interface and bottom.
|
|
1064
|
+
length: number of layers + 1
|
|
1065
|
+
|
|
1066
|
+
:return: strain vector of len 6
|
|
1067
|
+
"""
|
|
1068
|
+
forceThermal = self.getThermalForces(dT)
|
|
1069
|
+
strains = np.linalg.solve(self.abd, forceThermal)
|
|
1070
|
+
|
|
1071
|
+
return strains
|
|
1072
|
+
|
|
1073
|
+
def getThermalLayerStresses(self, dT=0, clamped=False, outputCS="mcs"):
|
|
1074
|
+
"""Calculates the Stresses in each layer due to CTE
|
|
1075
|
+
|
|
1076
|
+
:param dT: delta temperature: scalar or vector with temperatures for top, each interface and bottom.
|
|
1077
|
+
length: number of layers + 1
|
|
1078
|
+
:param clamped: laminate is clamped or unconstrained
|
|
1079
|
+
:param outputCS: (mcs, lcs)
|
|
1080
|
+
:return: tuple: (stressInferior, stressSuperior) denotes the stresses on the upper and lower border
|
|
1081
|
+
of the composite. Each parameter has shape (len(layerCount), 3)
|
|
1082
|
+
"""
|
|
1083
|
+
|
|
1084
|
+
# Lcs = laminate coordinate system
|
|
1085
|
+
# Mcs = material coordinate system
|
|
1086
|
+
allowedCoordSystems = ["mcs", "lcs"]
|
|
1087
|
+
if outputCS not in allowedCoordSystems:
|
|
1088
|
+
raise ImproperParameterError(f"Parameter outputCS must be one of {allowedCoordSystems}")
|
|
1089
|
+
if not hasattr(dT, "__iter__"):
|
|
1090
|
+
# dT is scalar
|
|
1091
|
+
dT = [dT] * (self.numberOfLayers + 1)
|
|
1092
|
+
|
|
1093
|
+
layerBounds = self.layerBounds
|
|
1094
|
+
layBoundsInf = layerBounds[:-1]
|
|
1095
|
+
layBoundsSup = layerBounds[1:]
|
|
1096
|
+
|
|
1097
|
+
strainsLcs = self.getThermalStrains(dT)
|
|
1098
|
+
strains = strainsLcs[:3]
|
|
1099
|
+
curvatures = strainsLcs[3:6]
|
|
1100
|
+
|
|
1101
|
+
strainsInfLcs = []
|
|
1102
|
+
strainsSupLcs = []
|
|
1103
|
+
for layer, boundInf, boundSup in zip(self.layers, layBoundsInf, layBoundsSup):
|
|
1104
|
+
strainsInfLcs.append(strains + curvatures * boundInf)
|
|
1105
|
+
strainsSupLcs.append(strains + curvatures * boundSup)
|
|
1106
|
+
|
|
1107
|
+
stressesInf = []
|
|
1108
|
+
stressesSup = []
|
|
1109
|
+
for layer, dTPlyInf, dTPlySup, strainInfLcs, strainSupLcs in zip(
|
|
1110
|
+
self.layers, dT[:-1], dT[1:], strainsInfLcs, strainsSupLcs
|
|
1111
|
+
):
|
|
1112
|
+
phi = np.radians(layer.phi)
|
|
1113
|
+
materialDef = layer.materialDefinition
|
|
1114
|
+
T = materialDef.getTransformationMatrix(phi)
|
|
1115
|
+
Ti = materialDef.getTransformationInverseMatrix(phi)
|
|
1116
|
+
Q = materialDef.reducedStiffnessMatrix
|
|
1117
|
+
Qbar = materialDef.rotateReducedStiffnessMatrix(Q, phi)
|
|
1118
|
+
|
|
1119
|
+
stressInfLcs = np.matmul(Qbar, strainInfLcs)
|
|
1120
|
+
stressSupLcs = np.matmul(Qbar, strainSupLcs)
|
|
1121
|
+
stressInfMcs = np.matmul(T, stressInfLcs)
|
|
1122
|
+
stressSupMcs = np.matmul(T, stressSupLcs)
|
|
1123
|
+
|
|
1124
|
+
if not clamped:
|
|
1125
|
+
# subtract thermal layer strains from laminate strains
|
|
1126
|
+
layDeltaTStressInfMcs = np.matmul(Q, materialDef.reducedThermalExpansionCoeff * dTPlyInf)
|
|
1127
|
+
layDeltaTStressSupMcs = np.matmul(Q, materialDef.reducedThermalExpansionCoeff * dTPlySup)
|
|
1128
|
+
stressInfMcs = stressInfMcs - layDeltaTStressInfMcs
|
|
1129
|
+
stressSupMcs = stressSupMcs - layDeltaTStressSupMcs
|
|
1130
|
+
|
|
1131
|
+
if outputCS == "lcs":
|
|
1132
|
+
stressesInf.append(np.dot(Ti, stressInfMcs))
|
|
1133
|
+
stressesSup.append(np.dot(Ti, stressSupMcs))
|
|
1134
|
+
else:
|
|
1135
|
+
stressesInf.append(stressInfMcs)
|
|
1136
|
+
stressesSup.append(stressSupMcs)
|
|
1137
|
+
|
|
1138
|
+
return np.array(stressesInf), np.array(stressesSup)
|
|
1139
|
+
|
|
1140
|
+
def _getModuli(self):
|
|
1141
|
+
"""
|
|
1142
|
+
abdCompliance = inverse of (ABD matrix divided by the laminate thickness)
|
|
1143
|
+
"""
|
|
1144
|
+
if self._savedModuli != {}:
|
|
1145
|
+
return self._savedModuli
|
|
1146
|
+
abd = self.getABD()
|
|
1147
|
+
self._savedModuli = self.getModuliStatic(abd, self.thickness)
|
|
1148
|
+
return self._savedModuli
|
|
1149
|
+
|
|
1150
|
+
@staticmethod
|
|
1151
|
+
def getModuliStatic(abd, thickness):
|
|
1152
|
+
"""doc"""
|
|
1153
|
+
abdCompliance = nplin.inv(abd[:, :] / thickness)
|
|
1154
|
+
e11 = abdCompliance[0, 0] ** -1
|
|
1155
|
+
e22 = abdCompliance[1, 1] ** -1
|
|
1156
|
+
g12 = abdCompliance[2, 2] ** -1
|
|
1157
|
+
nu12 = -abdCompliance[1, 0] * e11
|
|
1158
|
+
nu21 = -abdCompliance[0, 1] * e22
|
|
1159
|
+
|
|
1160
|
+
return {"e11": e11, "e22": e22, "g12": g12, "nu12": nu12, "nu21": nu21}
|
|
1161
|
+
|
|
1162
|
+
def _isComposite(self):
|
|
1163
|
+
"""doc"""
|
|
1164
|
+
return self.numberOfLayers != 1 or not self.layers[0].materialDefinition.isIsotrop
|
|
1165
|
+
|
|
1166
|
+
def getInfoString(self):
|
|
1167
|
+
"""doc"""
|
|
1168
|
+
retStr = "Composite with materialname, orientation, thickness\n"
|
|
1169
|
+
retStr += "\n".join(layer.getInfoString() for layer in self.layers)
|
|
1170
|
+
return retStr
|
|
1171
|
+
|
|
1172
|
+
def _getNumberOfLayers(self):
|
|
1173
|
+
"""doc"""
|
|
1174
|
+
return len(self.layers)
|
|
1175
|
+
|
|
1176
|
+
def _getLaminationParameters(self):
|
|
1177
|
+
"""doc"""
|
|
1178
|
+
laminationParameters = []
|
|
1179
|
+
xsiA = np.zeros(4)
|
|
1180
|
+
xsiB = np.zeros(4)
|
|
1181
|
+
xsiD = np.zeros(4)
|
|
1182
|
+
|
|
1183
|
+
for layer in self.layers:
|
|
1184
|
+
# in case that not for all layers lamination parameters are set (e.g. when a layer was changed)
|
|
1185
|
+
if layer.laminationParameters is None:
|
|
1186
|
+
return None
|
|
1187
|
+
|
|
1188
|
+
xsiA += layer.laminationParameters[0]
|
|
1189
|
+
xsiB += layer.laminationParameters[1]
|
|
1190
|
+
xsiD += layer.laminationParameters[2]
|
|
1191
|
+
|
|
1192
|
+
laminationParameters += [xsiA, xsiB, xsiD]
|
|
1193
|
+
return laminationParameters
|
|
1194
|
+
|
|
1195
|
+
rho = property(fget=_getRho)
|
|
1196
|
+
"""homogenized density"""
|
|
1197
|
+
|
|
1198
|
+
thickness = property(fget=_getThickness)
|
|
1199
|
+
|
|
1200
|
+
layerorientation = property(fget=_getLayerOrientations)
|
|
1201
|
+
|
|
1202
|
+
layerThicknesses = property(fget=_getLayerThicknesses)
|
|
1203
|
+
|
|
1204
|
+
moduli = property(fget=_getModuli)
|
|
1205
|
+
|
|
1206
|
+
isComposite = property(fget=_isComposite)
|
|
1207
|
+
|
|
1208
|
+
abd = property(fget=getABD)
|
|
1209
|
+
|
|
1210
|
+
numberOfLayers = property(fget=_getNumberOfLayers)
|
|
1211
|
+
|
|
1212
|
+
laminationParameters = property(fget=_getLaminationParameters)
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
class Layer:
|
|
1216
|
+
"""This class describes one layer of a composite"""
|
|
1217
|
+
|
|
1218
|
+
def __init__(self, **kwargs):
|
|
1219
|
+
"""doc"""
|
|
1220
|
+
self.id = None
|
|
1221
|
+
self.name = None
|
|
1222
|
+
self.description = None
|
|
1223
|
+
self.phi = None
|
|
1224
|
+
"""region between [0,180] degrees"""
|
|
1225
|
+
self.xPath = None
|
|
1226
|
+
self.laminationParameters = None
|
|
1227
|
+
self.thickness = None
|
|
1228
|
+
self._materialDefinition = None
|
|
1229
|
+
|
|
1230
|
+
for key in kwargs:
|
|
1231
|
+
if not hasattr(self, key):
|
|
1232
|
+
log.warning(f'Setting unknown key "{key}" in class {self.__class__} with name "{str(self)}"')
|
|
1233
|
+
setattr(self, key, kwargs[key])
|
|
1234
|
+
|
|
1235
|
+
def copy(self):
|
|
1236
|
+
"""Returns a copy of self"""
|
|
1237
|
+
return Layer(
|
|
1238
|
+
id=self.id,
|
|
1239
|
+
name=self.name,
|
|
1240
|
+
description=self.description,
|
|
1241
|
+
phi=self.phi,
|
|
1242
|
+
thickness=self.thickness,
|
|
1243
|
+
_materialDefinition=self._materialDefinition,
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
def _setMaterialDefinition(self, materialDefinition):
|
|
1247
|
+
"""doc"""
|
|
1248
|
+
if self.phi:
|
|
1249
|
+
materialDefinition.usedAngles.add(int(self.phi))
|
|
1250
|
+
self._materialDefinition = materialDefinition
|
|
1251
|
+
|
|
1252
|
+
def _getMaterialDefinition(self):
|
|
1253
|
+
"""doc"""
|
|
1254
|
+
return self._materialDefinition
|
|
1255
|
+
|
|
1256
|
+
def getInfoString(self):
|
|
1257
|
+
"""doc"""
|
|
1258
|
+
return f"{self.materialDefinition.name},\t{self.phi},\t{self.thickness}"
|
|
1259
|
+
|
|
1260
|
+
materialDefinition = property(fset=_setMaterialDefinition, fget=_getMaterialDefinition)
|