elasticipy 4.2.0__py3-none-any.whl → 6.0.0__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.
- {Elasticipy → elasticipy}/FourthOrderTensor.py +4 -4
- elasticipy/StressStrainTensors.py +16 -0
- {Elasticipy → elasticipy}/ThermalExpansion.py +3 -3
- elasticipy/gui/__init__.py +3 -0
- elasticipy/gui/about.py +49 -0
- {Elasticipy → elasticipy/gui}/gui.py +218 -42
- elasticipy/gui/rotate_window.py +79 -0
- {Elasticipy → elasticipy}/interfaces/FEPX.py +5 -5
- {Elasticipy → elasticipy}/interfaces/PRISMS.py +2 -2
- {Elasticipy → elasticipy}/plasticity.py +180 -35
- elasticipy/resources/favicon.png +0 -0
- elasticipy/resources/logo_text.svg +126 -0
- {Elasticipy → elasticipy}/spherical_function.py +44 -5
- elasticipy/tensors/__init__.py +0 -0
- {Elasticipy → elasticipy}/tensors/elasticity.py +678 -152
- elasticipy/tensors/fourth_order.py +1385 -0
- elasticipy/tensors/mapping.py +92 -0
- {Elasticipy → elasticipy}/tensors/second_order.py +56 -14
- elasticipy/tensors/stress_strain.py +360 -0
- {Elasticipy → elasticipy}/tensors/thermal_expansion.py +8 -3
- {elasticipy-4.2.0.dist-info → elasticipy-6.0.0.dist-info}/METADATA +41 -22
- elasticipy-6.0.0.dist-info/RECORD +30 -0
- elasticipy-6.0.0.dist-info/entry_points.txt +2 -0
- elasticipy-6.0.0.dist-info/top_level.txt +1 -0
- Elasticipy/StressStrainTensors.py +0 -16
- Elasticipy/tensors/fourth_order.py +0 -662
- Elasticipy/tensors/mapping.py +0 -44
- Elasticipy/tensors/stress_strain.py +0 -154
- elasticipy-4.2.0.dist-info/RECORD +0 -23
- elasticipy-4.2.0.dist-info/top_level.txt +0 -1
- {Elasticipy → elasticipy}/__init__.py +0 -0
- {Elasticipy → elasticipy}/crystal_symmetries.py +0 -0
- {Elasticipy/tensors → elasticipy/interfaces}/__init__.py +0 -0
- {Elasticipy → elasticipy}/polefigure.py +0 -0
- {elasticipy-4.2.0.dist-info → elasticipy-6.0.0.dist-info}/WHEEL +0 -0
- {elasticipy-4.2.0.dist-info → elasticipy-6.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import warnings
|
|
2
|
-
from
|
|
3
|
-
from
|
|
2
|
+
from elasticipy.tensors.elasticity import StiffnessTensor as NewStiffnessTensor
|
|
3
|
+
from elasticipy.tensors.elasticity import ComplianceTensor as NewComplianceTensor
|
|
4
4
|
|
|
5
5
|
warnings.warn(
|
|
6
|
-
"The module '
|
|
7
|
-
"Please use '
|
|
6
|
+
"The module 'elasticipy.FourthOrderTensor' is deprecated and will be removed in a future release. "
|
|
7
|
+
"Please use 'elasticipy.tensors.elasticity' instead.",
|
|
8
8
|
DeprecationWarning,
|
|
9
9
|
stacklevel=2
|
|
10
10
|
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from elasticipy.tensors.stress_strain import StressTensor as NewStressTensor
|
|
3
|
+
from elasticipy.tensors.stress_strain import StrainTensor as NewStrainTensor
|
|
4
|
+
|
|
5
|
+
warnings.warn(
|
|
6
|
+
"The module 'elasticipy.StressStrainTensors' is deprecated and will be removed in a future release. "
|
|
7
|
+
"Please use 'elasticipy.tensors.stress_strain' instead.",
|
|
8
|
+
DeprecationWarning,
|
|
9
|
+
stacklevel=2
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
class StressTensor(NewStressTensor):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
class StrainTensor(NewStrainTensor):
|
|
16
|
+
pass
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import warnings
|
|
2
|
-
from
|
|
2
|
+
from elasticipy.tensors.thermal_expansion import ThermalExpansionTensor as NewThermalExpansionTensor
|
|
3
3
|
|
|
4
4
|
warnings.warn(
|
|
5
|
-
"The module '
|
|
6
|
-
"Please use '
|
|
5
|
+
"The module 'elasticipy.ThermalExpansion' is deprecated and will be removed in a future release. "
|
|
6
|
+
"Please use 'elasticipy.tensors.thermal_expansion' instead.",
|
|
7
7
|
DeprecationWarning,
|
|
8
8
|
stacklevel=2
|
|
9
9
|
)
|
elasticipy/gui/about.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from qtpy.QtWidgets import QVBoxLayout, QLabel, QPushButton
|
|
2
|
+
from qtpy.QtGui import QPixmap
|
|
3
|
+
from qtpy.QtCore import Qt
|
|
4
|
+
|
|
5
|
+
def about(dialog, logo_path):
|
|
6
|
+
dialog.setWindowTitle("About elasticipy")
|
|
7
|
+
dialog.setFixedWidth(400)
|
|
8
|
+
|
|
9
|
+
layout = QVBoxLayout(dialog)
|
|
10
|
+
|
|
11
|
+
if logo_path.exists():
|
|
12
|
+
logo = QLabel()
|
|
13
|
+
pixmap = QPixmap(str(logo_path))
|
|
14
|
+
pixmap = pixmap.scaledToWidth(250, Qt.SmoothTransformation)
|
|
15
|
+
logo.setPixmap(pixmap)
|
|
16
|
+
logo.setAlignment(Qt.AlignCenter)
|
|
17
|
+
layout.addWidget(logo)
|
|
18
|
+
|
|
19
|
+
# --- Text ---
|
|
20
|
+
text = QLabel(
|
|
21
|
+
"A Python library for elasticity tensors computations<br><br>"
|
|
22
|
+
"© 2024–2025 Dorian Depriester, MIT Licence"
|
|
23
|
+
)
|
|
24
|
+
text.setAlignment(Qt.AlignCenter)
|
|
25
|
+
layout.addWidget(text)
|
|
26
|
+
|
|
27
|
+
# --- Link ---
|
|
28
|
+
link = QLabel(
|
|
29
|
+
'<a href="https://elasticipy.readthedocs.io/">'
|
|
30
|
+
'https://elasticipy.readthedocs.io/</a>'
|
|
31
|
+
)
|
|
32
|
+
link.setAlignment(Qt.AlignCenter)
|
|
33
|
+
link.setOpenExternalLinks(True)
|
|
34
|
+
layout.addWidget(link)
|
|
35
|
+
|
|
36
|
+
# --- Bug report ---
|
|
37
|
+
link = QLabel(
|
|
38
|
+
'<a href="https://github.com/DorianDepriester/Elasticipy/issues">'
|
|
39
|
+
'Report a bug</a>'
|
|
40
|
+
)
|
|
41
|
+
link.setAlignment(Qt.AlignCenter)
|
|
42
|
+
link.setOpenExternalLinks(True)
|
|
43
|
+
layout.addWidget(link)
|
|
44
|
+
|
|
45
|
+
# --- Close button ---
|
|
46
|
+
close_btn = QPushButton("Close")
|
|
47
|
+
close_btn.clicked.connect(dialog.close)
|
|
48
|
+
layout.addWidget(close_btn)
|
|
49
|
+
return dialog
|
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
import sys
|
|
2
|
-
|
|
3
2
|
import numpy as np
|
|
4
3
|
from qtpy.QtWidgets import (
|
|
5
|
-
QApplication, QMainWindow, QComboBox, QGridLayout,
|
|
6
|
-
QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFrame, QMessageBox
|
|
4
|
+
QApplication, QMainWindow, QComboBox, QGridLayout, QLineEdit, QWidget, QFrame, QMessageBox
|
|
7
5
|
)
|
|
6
|
+
from qtpy.QtGui import QIcon
|
|
8
7
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
9
8
|
from matplotlib.figure import Figure
|
|
10
9
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
10
|
+
from elasticipy.crystal_symmetries import SYMMETRIES
|
|
11
|
+
from elasticipy.gui.rotate_window import EulerBungeDialog
|
|
12
|
+
from elasticipy.gui.about import about
|
|
13
|
+
from elasticipy.tensors.elasticity import StiffnessTensor
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from scipy.spatial.transform import Rotation
|
|
13
17
|
|
|
14
18
|
WHICH_OPTIONS = {'Mean': 'mean', 'Max': 'max', 'Min': 'min', 'Std. dev.': 'std'}
|
|
15
19
|
|
|
20
|
+
# --- Logo ---
|
|
21
|
+
here = Path(__file__).resolve().parent
|
|
22
|
+
LOGO_PATH = here / ".." / "resources" / "logo_text.svg"
|
|
23
|
+
ICON_PATH = here / ".." / "resources" / "favicon.png"
|
|
24
|
+
|
|
16
25
|
class ElasticityGUI(QMainWindow):
|
|
17
26
|
def __init__(self):
|
|
18
27
|
super().__init__()
|
|
28
|
+
self.C_stiff = None
|
|
19
29
|
self.coefficient_fields = {}
|
|
20
|
-
self.setWindowTitle("
|
|
30
|
+
self.setWindowTitle("elasticipy - GUI")
|
|
21
31
|
self.initUI()
|
|
32
|
+
self.C_invar= np.zeros(7)
|
|
22
33
|
|
|
23
34
|
def selected_symmetry(self):
|
|
24
35
|
symmetry_name = self.symmetry_selector.currentText()
|
|
@@ -53,6 +64,7 @@ class ElasticityGUI(QMainWindow):
|
|
|
53
64
|
|
|
54
65
|
# Space Group selection
|
|
55
66
|
self.point_group_selector = QComboBox()
|
|
67
|
+
self.point_group_selector.setMinimumWidth(140)
|
|
56
68
|
self.point_group_selector.addItems(['', ''])
|
|
57
69
|
self.point_group_selector.currentIndexChanged.connect(self.update_fields)
|
|
58
70
|
selectors_layout.addWidget(QLabel("Point group:"))
|
|
@@ -65,6 +77,14 @@ class ElasticityGUI(QMainWindow):
|
|
|
65
77
|
selectors_layout.addWidget(QLabel("Diad convention:"))
|
|
66
78
|
selectors_layout.addWidget(self.diag_selector)
|
|
67
79
|
|
|
80
|
+
# About button
|
|
81
|
+
self.about_button = QPushButton("About")
|
|
82
|
+
self.about_button.setFixedHeight(24)
|
|
83
|
+
self.about_button.setMaximumWidth(60)
|
|
84
|
+
self.about_button.clicked.connect(self.show_about)
|
|
85
|
+
selectors_layout.addStretch()
|
|
86
|
+
selectors_layout.addWidget(self.about_button)
|
|
87
|
+
|
|
68
88
|
# Add horizontal separator
|
|
69
89
|
separator = QFrame()
|
|
70
90
|
separator.setFrameShape(QFrame.HLine)
|
|
@@ -125,12 +145,96 @@ class ElasticityGUI(QMainWindow):
|
|
|
125
145
|
|
|
126
146
|
# Plot button
|
|
127
147
|
self.calculate_button = QPushButton("Plot")
|
|
148
|
+
self.calculate_button.setEnabled(False)
|
|
128
149
|
self.calculate_button.clicked.connect(self.calculate_and_plot)
|
|
129
150
|
left_panel_layout.addWidget(self.calculate_button)
|
|
130
151
|
|
|
152
|
+
self.euler_button = QPushButton("Apply rotation")
|
|
153
|
+
self.euler_button.setEnabled(False)
|
|
154
|
+
self.euler_button.setToolTip("Rotate stiffness tensor (Bunge ZXZ)")
|
|
155
|
+
self.euler_button.clicked.connect(self.open_euler_dialog)
|
|
156
|
+
left_panel_layout.addWidget(self.euler_button)
|
|
157
|
+
|
|
158
|
+
# Add horizontal separator
|
|
159
|
+
separator = QFrame()
|
|
160
|
+
separator.setFrameShape(QFrame.HLine)
|
|
161
|
+
separator.setFrameShadow(QFrame.Sunken)
|
|
162
|
+
left_panel_layout.addWidget(separator)
|
|
163
|
+
|
|
164
|
+
############################################
|
|
165
|
+
# Numeric results
|
|
166
|
+
############################################
|
|
167
|
+
self.result_labels = {}
|
|
168
|
+
RESULT_GROUPS = {
|
|
169
|
+
"Young modulus": [
|
|
170
|
+
("E_mean", "Mean"),
|
|
171
|
+
("E_voigt", "Voigt"),
|
|
172
|
+
("E_reuss", "Reuss"),
|
|
173
|
+
("E_hill", "Hill"),
|
|
174
|
+
],
|
|
175
|
+
"Shear modulus": [
|
|
176
|
+
("G_mean", "Mean"),
|
|
177
|
+
("G_voigt", "Voigt"),
|
|
178
|
+
("G_reuss", "Reuss"),
|
|
179
|
+
("G_hill", "Hill"),
|
|
180
|
+
],
|
|
181
|
+
"Poisson ratio": [
|
|
182
|
+
("nu_mean", "Mean"),
|
|
183
|
+
("nu_voigt", "Voigt"),
|
|
184
|
+
("nu_reuss", "Reuss"),
|
|
185
|
+
("nu_hill", "Hill"),
|
|
186
|
+
],
|
|
187
|
+
"Linear compressibility (x1000)": [
|
|
188
|
+
("Beta_mean", "Mean"),
|
|
189
|
+
("Beta_voigt", "Voigt"),
|
|
190
|
+
("Beta_reuss", "Reuss"),
|
|
191
|
+
("Beta_hill", "Hill"),
|
|
192
|
+
],
|
|
193
|
+
"Other": [
|
|
194
|
+
("K", "Bulk modulus"),
|
|
195
|
+
("Z", "Zener ratio"),
|
|
196
|
+
("A", "Univ. anisotropy factor"),
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
############################################
|
|
201
|
+
# Numeric results (grouped)
|
|
202
|
+
############################################
|
|
203
|
+
self.result_labels = {}
|
|
204
|
+
for group_name, items in RESULT_GROUPS.items():
|
|
205
|
+
|
|
206
|
+
# Group title
|
|
207
|
+
group_label = QLabel(group_name + ":")
|
|
208
|
+
group_label.setStyleSheet("font-weight: bold; margin-top: 6px;")
|
|
209
|
+
left_panel_layout.addWidget(group_label)
|
|
210
|
+
|
|
211
|
+
# Indented layout
|
|
212
|
+
indent_layout = QVBoxLayout()
|
|
213
|
+
indent_layout.setContentsMargins(15, 0, 0, 0)
|
|
214
|
+
|
|
215
|
+
for key, label_text in items:
|
|
216
|
+
row = QHBoxLayout()
|
|
217
|
+
|
|
218
|
+
label_name = QLabel(f"{label_text}:")
|
|
219
|
+
label_value = QLabel("—")
|
|
220
|
+
label_value.setMinimumWidth(100)
|
|
221
|
+
label_value.setStyleSheet("font-family: Consolas, Courier;")
|
|
222
|
+
|
|
223
|
+
self.result_labels[key] = label_value
|
|
224
|
+
|
|
225
|
+
row.addWidget(label_name)
|
|
226
|
+
row.addStretch()
|
|
227
|
+
row.addWidget(label_value)
|
|
228
|
+
|
|
229
|
+
indent_layout.addLayout(row)
|
|
230
|
+
|
|
231
|
+
left_panel_layout.addLayout(indent_layout)
|
|
232
|
+
|
|
131
233
|
# Fill space
|
|
132
234
|
left_panel_layout.addStretch()
|
|
133
|
-
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
bottom_layout.addLayout(left_panel_layout,1)
|
|
134
238
|
|
|
135
239
|
############################################
|
|
136
240
|
# Plotting area
|
|
@@ -143,7 +247,7 @@ class ElasticityGUI(QMainWindow):
|
|
|
143
247
|
|
|
144
248
|
self.figure = Figure()
|
|
145
249
|
self.canvas = FigureCanvas(self.figure)
|
|
146
|
-
bottom_layout.addWidget(self.canvas)
|
|
250
|
+
bottom_layout.addWidget(self.canvas,4)
|
|
147
251
|
|
|
148
252
|
#######################################################################################
|
|
149
253
|
# Main widget
|
|
@@ -160,6 +264,7 @@ class ElasticityGUI(QMainWindow):
|
|
|
160
264
|
self.plotting_selector.setCurrentText('Young modulus')
|
|
161
265
|
self.which_selector.setEnabled(False)
|
|
162
266
|
|
|
267
|
+
|
|
163
268
|
def update_fields(self):
|
|
164
269
|
# Deactivate unused fields
|
|
165
270
|
active_fields = self.selected_symmetry().required
|
|
@@ -193,42 +298,48 @@ class ElasticityGUI(QMainWindow):
|
|
|
193
298
|
self.which_selector.setEnabled(True)
|
|
194
299
|
|
|
195
300
|
def calculate_and_plot(self):
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
301
|
+
stiff = self.C_stiff
|
|
302
|
+
self.figure.clear()
|
|
303
|
+
requested_value = self.plotting_selector.currentText()
|
|
304
|
+
if requested_value == "Young modulus":
|
|
305
|
+
value = stiff.Young_modulus
|
|
306
|
+
plot_kwargs = {}
|
|
307
|
+
elif requested_value == 'Linear compressibility':
|
|
308
|
+
value = stiff.linear_compressibility
|
|
309
|
+
plot_kwargs = {}
|
|
310
|
+
else:
|
|
311
|
+
if requested_value == 'Shear modulus':
|
|
312
|
+
value = stiff.shear_modulus
|
|
313
|
+
else:
|
|
314
|
+
value = stiff.Poisson_ratio
|
|
315
|
+
plot_kwargs = {'which': WHICH_OPTIONS[self.which_selector.currentText()]}
|
|
316
|
+
if self.plot_style_selector.currentIndex() == 0:
|
|
317
|
+
value.plot3D(fig=self.figure, **plot_kwargs)
|
|
318
|
+
elif self.plot_style_selector.currentIndex() == 1:
|
|
319
|
+
value.plot_xyz_sections(fig=self.figure)
|
|
320
|
+
else:
|
|
321
|
+
value.plot_as_pole_figure(fig=self.figure, **plot_kwargs)
|
|
322
|
+
self.canvas.draw()
|
|
323
|
+
invariants = np.array(stiff.linear_invariants() + stiff.quadratic_invariants())
|
|
324
|
+
if not np.all(np.isclose(invariants, self.C_invar)):
|
|
325
|
+
self.C_invar = invariants
|
|
326
|
+
self.result_labels["E_mean"].setText(f"{stiff.Young_modulus.mean():.3f}")
|
|
327
|
+
self.result_labels["G_mean"].setText(f"{stiff.shear_modulus.mean():.3f}")
|
|
328
|
+
self.result_labels["nu_mean"].setText(f"{stiff.Poisson_ratio.mean():.3f}")
|
|
329
|
+
self.result_labels["Beta_mean"].setText(f"{stiff.linear_compressibility.mean()*1000:.3f}")
|
|
330
|
+
for method in ['voigt', 'reuss', 'hill']:
|
|
331
|
+
C = stiff.average(method=method)
|
|
332
|
+
self.result_labels[f"E_{method}"].setText(f"{C.Young_modulus.eval([1,0,0]):.3f}")
|
|
333
|
+
self.result_labels[f"G_{method}"].setText(f"{C.shear_modulus.eval([1, 0, 0],[0,1,0]):.3f}")
|
|
334
|
+
self.result_labels[f"nu_{method}"].setText(f"{C.Poisson_ratio.eval([1, 0, 0], [0, 1, 0]):.3f}")
|
|
335
|
+
self.result_labels[f"Beta_{method}"].setText(f"{C.linear_compressibility.eval([1, 0, 0])*1000:.3f}")
|
|
336
|
+
self.result_labels["K"].setText(f"{stiff.bulk_modulus:.3f}")
|
|
199
337
|
try:
|
|
200
|
-
|
|
338
|
+
Z = stiff.Zener_ratio()
|
|
339
|
+
self.result_labels["Z"].setText(f"{stiff.Zener_ratio():.3f}")
|
|
201
340
|
except ValueError:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
Csym = C + np.tril(C.T, -1) # Rebuild the lower triangular part
|
|
205
|
-
|
|
206
|
-
try:
|
|
207
|
-
stiff = StiffnessTensor(Csym)
|
|
208
|
-
self.figure.clear()
|
|
209
|
-
requested_value = self.plotting_selector.currentText()
|
|
210
|
-
if requested_value == "Young modulus":
|
|
211
|
-
value = stiff.Young_modulus
|
|
212
|
-
plot_kwargs = {}
|
|
213
|
-
elif requested_value == 'Linear compressibility':
|
|
214
|
-
value = stiff.linear_compressibility
|
|
215
|
-
plot_kwargs = {}
|
|
216
|
-
else:
|
|
217
|
-
if requested_value == 'Shear modulus':
|
|
218
|
-
value = stiff.shear_modulus
|
|
219
|
-
else:
|
|
220
|
-
value = stiff.Poisson_ratio
|
|
221
|
-
plot_kwargs = {'which': WHICH_OPTIONS[self.which_selector.currentText()]}
|
|
222
|
-
if self.plot_style_selector.currentIndex() == 0:
|
|
223
|
-
value.plot3D(fig=self.figure, **plot_kwargs)
|
|
224
|
-
elif self.plot_style_selector.currentIndex() == 1:
|
|
225
|
-
value.plot_xyz_sections(fig=self.figure)
|
|
226
|
-
else:
|
|
227
|
-
value.plot_as_pole_figure(fig=self.figure, **plot_kwargs)
|
|
228
|
-
self.canvas.draw()
|
|
229
|
-
|
|
230
|
-
except ValueError as inst:
|
|
231
|
-
QMessageBox.critical(self, "Singular stiffness", inst.__str__(), QMessageBox.Ok)
|
|
341
|
+
self.result_labels["Z"].setText("—")
|
|
342
|
+
self.result_labels["A"].setText(f"{stiff.universal_anisotropy:.3f}")
|
|
232
343
|
|
|
233
344
|
|
|
234
345
|
def update_dependent_fields(self):
|
|
@@ -255,9 +366,74 @@ class ElasticityGUI(QMainWindow):
|
|
|
255
366
|
self.coefficient_fields[index].setText(f"{0.5*(C11-C12)}")
|
|
256
367
|
except ValueError:
|
|
257
368
|
pass
|
|
369
|
+
coefficients = np.zeros((6, 6))
|
|
370
|
+
for (i, j), field in self.coefficient_fields.items():
|
|
371
|
+
try:
|
|
372
|
+
coefficients[i, j] = float(field.text())
|
|
373
|
+
except ValueError:
|
|
374
|
+
coefficients[i, j] = 0
|
|
375
|
+
Csym = coefficients + np.tril(coefficients.T, -1) # Rebuild the lower triangular part
|
|
376
|
+
try:
|
|
377
|
+
self.C_stiff = StiffnessTensor(Csym)
|
|
378
|
+
self.calculate_button.setEnabled(True)
|
|
379
|
+
self.euler_button.setToolTip("Plot directional dependence")
|
|
380
|
+
self.euler_button.setEnabled(True)
|
|
381
|
+
self.euler_button.setToolTip("Rotate stiffness tensor (Bunge ZXZ)")
|
|
382
|
+
except ValueError:
|
|
383
|
+
self.calculate_button.setEnabled(False)
|
|
384
|
+
error_msg = "The stiffness tensor is not definite positive!"
|
|
385
|
+
self.calculate_button.setToolTip(error_msg)
|
|
386
|
+
self.euler_button.setEnabled(False)
|
|
387
|
+
self.euler_button.setToolTip(error_msg)
|
|
388
|
+
|
|
389
|
+
def show_about(self):
|
|
390
|
+
dialog = QDialog(self)
|
|
391
|
+
dialog = about(dialog, LOGO_PATH)
|
|
392
|
+
dialog.exec_()
|
|
393
|
+
|
|
394
|
+
def open_euler_dialog(self):
|
|
395
|
+
if not hasattr(self, "euler_dialog"):
|
|
396
|
+
self.current_config = {'sym': self.symmetry_selector.currentText(),
|
|
397
|
+
'pg': self.point_group_selector.currentText(),
|
|
398
|
+
'diag': self.diag_selector.currentText()}
|
|
399
|
+
self.euler_dialog = EulerBungeDialog(self, C=self.C_stiff.matrix())
|
|
400
|
+
self.euler_dialog.anglesChanged.connect(self.update_from_euler)
|
|
401
|
+
self.euler_dialog.show()
|
|
402
|
+
|
|
403
|
+
def update_from_euler(self, phi1, Phi, phi2):
|
|
404
|
+
C0 = StiffnessTensor(self.euler_dialog.C)
|
|
405
|
+
rot = Rotation.from_euler(seq='ZXZ', angles=[phi1, Phi, phi2], degrees=True)
|
|
406
|
+
if np.any(np.array([phi1, Phi, phi2])) != 0.:
|
|
407
|
+
self.symmetry_selector.setCurrentText('Triclinic')
|
|
408
|
+
C_new = C0.rotate(rot).matrix()
|
|
409
|
+
for (i, j), field in self.coefficient_fields.items():
|
|
410
|
+
self.coefficient_fields[(i,j)].setText(f"{C_new[i,j]}")
|
|
411
|
+
else:
|
|
412
|
+
for (i, j), field in self.coefficient_fields.items():
|
|
413
|
+
self.coefficient_fields[(i,j)].setText(f"{self.euler_dialog.C[i,j]}")
|
|
414
|
+
self.symmetry_selector.setCurrentText(self.current_config['sym'])
|
|
415
|
+
self.point_group_selector.setCurrentText(self.current_config['pg'])
|
|
416
|
+
self.diag_selector.setCurrentText(self.current_config['diag'])
|
|
417
|
+
if self.euler_dialog.live_button.isChecked():
|
|
418
|
+
self.calculate_and_plot()
|
|
258
419
|
|
|
259
420
|
def crystal_elastic_plotter():
|
|
260
421
|
app = QApplication(sys.argv)
|
|
422
|
+
try:
|
|
423
|
+
icon = QIcon(str(ICON_PATH))
|
|
424
|
+
except Exception:
|
|
425
|
+
icon = QIcon()
|
|
426
|
+
app.setWindowIcon(icon)
|
|
261
427
|
window = ElasticityGUI()
|
|
428
|
+
window.setWindowIcon(icon)
|
|
262
429
|
window.show()
|
|
263
430
|
sys.exit(app.exec_())
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
from qtpy.QtWidgets import (
|
|
434
|
+
QDialog, QVBoxLayout, QHBoxLayout,
|
|
435
|
+
QLabel, QPushButton
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
if __name__ == "__main__":
|
|
439
|
+
crystal_elastic_plotter()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PyQt5.QtCore import Qt
|
|
3
|
+
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QSlider, QDoubleSpinBox, QPushButton, QCheckBox
|
|
4
|
+
from qtpy.QtCore import Qt, Signal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EulerBungeDialog(QDialog):
|
|
8
|
+
anglesChanged = Signal(float, float, float)
|
|
9
|
+
|
|
10
|
+
def __init__(self, parent=None, C=np.zeros((6, 6))):
|
|
11
|
+
super().__init__(parent)
|
|
12
|
+
self.setWindowTitle("Euler angles (Bunge ZXZ)")
|
|
13
|
+
self._build_ui()
|
|
14
|
+
self.reset()
|
|
15
|
+
self.C = C
|
|
16
|
+
|
|
17
|
+
def _build_ui(self):
|
|
18
|
+
self.layout = QVBoxLayout(self)
|
|
19
|
+
|
|
20
|
+
self.sliders = []
|
|
21
|
+
self.spins = []
|
|
22
|
+
|
|
23
|
+
labels = ["φ₁ (deg)", "Φ (deg)", "φ₂ (deg)"]
|
|
24
|
+
ranges = [(0, 360), (0, 180), (0, 360)]
|
|
25
|
+
|
|
26
|
+
for label, (vmin, vmax) in zip(labels, ranges):
|
|
27
|
+
row = QHBoxLayout()
|
|
28
|
+
|
|
29
|
+
row.addWidget(QLabel(label))
|
|
30
|
+
|
|
31
|
+
slider = QSlider(Qt.Horizontal)
|
|
32
|
+
slider.setRange(vmin * 10, vmax * 10)
|
|
33
|
+
slider.setSingleStep(1)
|
|
34
|
+
|
|
35
|
+
spin = QDoubleSpinBox()
|
|
36
|
+
spin.setRange(vmin, vmax)
|
|
37
|
+
spin.setDecimals(1)
|
|
38
|
+
spin.setSingleStep(0.1)
|
|
39
|
+
|
|
40
|
+
slider.valueChanged.connect(
|
|
41
|
+
lambda v, s=spin: s.setValue(v / 10)
|
|
42
|
+
)
|
|
43
|
+
spin.valueChanged.connect(
|
|
44
|
+
lambda v, s=slider: s.setValue(int(v * 10))
|
|
45
|
+
)
|
|
46
|
+
spin.valueChanged.connect(self._emit_angles)
|
|
47
|
+
|
|
48
|
+
row.addWidget(slider, stretch=1)
|
|
49
|
+
row.addWidget(spin)
|
|
50
|
+
|
|
51
|
+
self.sliders.append(slider)
|
|
52
|
+
self.spins.append(spin)
|
|
53
|
+
|
|
54
|
+
self.layout.addLayout(row)
|
|
55
|
+
|
|
56
|
+
# Live update
|
|
57
|
+
self.live_button = QCheckBox("Live-update plotting")
|
|
58
|
+
self.layout.addWidget(self.live_button)
|
|
59
|
+
|
|
60
|
+
# Reset button
|
|
61
|
+
reset_button = QPushButton("Reset orientation")
|
|
62
|
+
reset_button.clicked.connect(self.reset)
|
|
63
|
+
self.layout.addWidget(reset_button)
|
|
64
|
+
|
|
65
|
+
def reset(self):
|
|
66
|
+
self._set_angles(0.0, 0.0, 0.0)
|
|
67
|
+
for slider in self.sliders:
|
|
68
|
+
slider.setValue(0)
|
|
69
|
+
|
|
70
|
+
def _set_angles(self, phi1, Phi, phi2):
|
|
71
|
+
for spin, val in zip(self.spins, (phi1, Phi, phi2)):
|
|
72
|
+
spin.blockSignals(True)
|
|
73
|
+
spin.setValue(val)
|
|
74
|
+
spin.blockSignals(False)
|
|
75
|
+
self._emit_angles()
|
|
76
|
+
|
|
77
|
+
def _emit_angles(self):
|
|
78
|
+
angles = [spin.value() for spin in self.spins]
|
|
79
|
+
self.anglesChanged.emit(*angles)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from elasticipy.tensors.second_order import SymmetricSecondOrderTensor, SecondOrderTensor, \
|
|
2
2
|
SkewSymmetricSecondOrderTensor
|
|
3
|
-
from
|
|
3
|
+
from elasticipy.tensors.stress_strain import StressTensor, StrainTensor
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import numpy as np
|
|
6
6
|
import os
|
|
@@ -90,14 +90,14 @@ def from_results_folder(folder, dtype=None):
|
|
|
90
90
|
- SecondOrderTensor
|
|
91
91
|
- SymmetricSecondOrderTensor
|
|
92
92
|
- SkewSymmetricSecondOrderTensor
|
|
93
|
-
-
|
|
94
|
-
-
|
|
93
|
+
- StressTensor
|
|
94
|
+
- StrainTensor
|
|
95
95
|
|
|
96
96
|
Returns
|
|
97
97
|
-------
|
|
98
98
|
SecondOrderTensor or numpy.ndarray
|
|
99
99
|
Array of second-order tensors built from the read data. The array will be of shape (m, n), where m is the number
|
|
100
|
-
|
|
100
|
+
of time increment n is the number of elements in the mesh.
|
|
101
101
|
"""
|
|
102
102
|
dir_path = Path(folder)
|
|
103
103
|
folder_name = dir_path.name
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
1
|
+
from elasticipy.tensors.second_order import SecondOrderTensor, SymmetricSecondOrderTensor
|
|
2
|
+
from elasticipy.tensors.stress_strain import StressTensor
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import numpy as np
|
|
5
5
|
|