ourocode 1.6.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.
Potentially problematic release.
This version of ourocode might be problematic. Click here for more details.
- ourocode/__init__.py +0 -0
- ourocode/__version__.py +1 -0
- ourocode/data/caracteristique_meca_acier.csv +33 -0
- ourocode/data/caracteristique_meca_bois.csv +59 -0
- ourocode/data/carte_action_region.csv +36722 -0
- ourocode/data/coeff_psy.csv +13 -0
- ourocode/data/exploitation.csv +17 -0
- ourocode/data/gammaM.csv +14 -0
- ourocode/data/kdef.csv +11 -0
- ourocode/data/kfi.csv +10 -0
- ourocode/data/kmod.csv +16 -0
- ourocode/data/limite_fleche.csv +9 -0
- ourocode/data/qualite_acier.csv +18 -0
- ourocode/data/screenshot/C90_def.png +0 -0
- ourocode/data/screenshot/Kv_def.png +0 -0
- ourocode/data/screenshot/fleche.png +0 -0
- ourocode/data/screenshot/sign_convention.png +0 -0
- ourocode/data/section_boulon.csv +12 -0
- ourocode/data/vent/vent_Cp_toiture_isolee_1_versant.csv +22 -0
- ourocode/data/vent/vent_Cp_toiture_isolee_1_versant.png +0 -0
- ourocode/data/vent/vent_Cp_toiture_isolee_2_versants.csv +31 -0
- ourocode/data/vent/vent_Cp_toiture_isolee_2_versants.png +0 -0
- ourocode/data/vent/vent_Cpe_mur_verticaux.csv +11 -0
- ourocode/data/vent/vent_Cpe_mur_verticaux.png +0 -0
- ourocode/data/vent/vent_Cpe_toiture_1_versant.png +0 -0
- ourocode/data/vent/vent_Cpe_toiture_1_versant_0_degres.csv +13 -0
- ourocode/data/vent/vent_Cpe_toiture_1_versant_180_degres.csv +13 -0
- ourocode/data/vent/vent_Cpe_toiture_1_versant_90_degres.csv +13 -0
- ourocode/data/vent/vent_Cpe_toiture_2_versants.png +0 -0
- ourocode/data/vent/vent_Cpe_toiture_2_versants_0_degres.csv +21 -0
- ourocode/data/vent/vent_Cpe_toiture_2_versants_90_degres.csv +21 -0
- ourocode/data/vent/vent_Cpe_toiture_terrasse.csv +21 -0
- ourocode/data/vent/vent_Cpe_toiture_terrasse.png +0 -0
- ourocode/data/vent/vent_Ffr.png +0 -0
- ourocode/eurocode/A0_Projet.py +1408 -0
- ourocode/eurocode/EC0_Combinaison.py +1763 -0
- ourocode/eurocode/EC1_Exploitation.py +103 -0
- ourocode/eurocode/EC1_Neige.py +385 -0
- ourocode/eurocode/EC1_Vent.py +1388 -0
- ourocode/eurocode/EC3_Assemblage.py +466 -0
- ourocode/eurocode/EC3_Element_droit.py +302 -0
- ourocode/eurocode/EC5_Assemblage.py +1760 -0
- ourocode/eurocode/EC5_Element_droit.py +1740 -0
- ourocode/eurocode/EC5_Feu.py +922 -0
- ourocode/eurocode/EC8_Sismique.py +2 -0
- ourocode/eurocode/__init__.py +0 -0
- ourocode/eurocode/logical.py +83 -0
- ourocode/eurocode/objet.py +263 -0
- ourocode-1.6.0.dist-info/METADATA +331 -0
- ourocode-1.6.0.dist-info/RECORD +53 -0
- ourocode-1.6.0.dist-info/WHEEL +5 -0
- ourocode-1.6.0.dist-info/licenses/LICENSE +201 -0
- ourocode-1.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1408 @@
|
|
|
1
|
+
#! env\Scripts\python.exe
|
|
2
|
+
# Encoding in UTF-8 by Anthony PARISOT
|
|
3
|
+
import os, sys
|
|
4
|
+
import math as mt
|
|
5
|
+
import numpy as np
|
|
6
|
+
from matplotlib import pyplot as plt
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
from PySide6.QtWidgets import QFileDialog, QMessageBox
|
|
10
|
+
import forallpeople as si
|
|
11
|
+
from handcalcs.decorator import handcalc
|
|
12
|
+
from Pynite import FEModel3D
|
|
13
|
+
from Pynite.Rendering import Renderer
|
|
14
|
+
|
|
15
|
+
# sys.path.append(os.path.join(os.getcwd(), "ourocode"))
|
|
16
|
+
# from eurocode.objet import Objet
|
|
17
|
+
|
|
18
|
+
from ourocode.eurocode.objet import Objet
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Projet(Objet):
|
|
22
|
+
JUPYTER_DISPLAY = False
|
|
23
|
+
DICO_COMBI_ACTION = {
|
|
24
|
+
"Permanente G": "G",
|
|
25
|
+
"Exploitation Q": "Q",
|
|
26
|
+
"Neige normale Sn": "Sn",
|
|
27
|
+
"Vent pression W+": "W+",
|
|
28
|
+
"Vent dépression W-": "W-",
|
|
29
|
+
"Neige accidentelle Sx": "Sx",
|
|
30
|
+
"Sismique Ae": "Ae",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
ingenieur: str = None,
|
|
36
|
+
num_project: str = None,
|
|
37
|
+
name: str = None,
|
|
38
|
+
adresse: str = None,
|
|
39
|
+
code_INSEE: int = None,
|
|
40
|
+
pays: str = "France",
|
|
41
|
+
alt: si.m = 0,
|
|
42
|
+
**kwargs,
|
|
43
|
+
):
|
|
44
|
+
"""Créer une classe Projet hérité de la classe Objet du fichier objet.py. Cette classe définit le projet, d'ou découle l'ensemble des objets du catalogue.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
ingenieur (str, optional): nom de l'ingénieur Defaults to None.
|
|
48
|
+
num_project (str, optional): numéro du projet. Defaults to None.
|
|
49
|
+
name (str, optional): nom du projet. Defaults to None.
|
|
50
|
+
adresse (str, optional): adresse du projet. Defaults to None.
|
|
51
|
+
region (int, optional): numéro INSEE départementale du projet en 5 chiffres. Defaults to None.
|
|
52
|
+
pays (str, optional): pays ou ce situe le projet. Defaults to "France".
|
|
53
|
+
alt (int, optional): altitude du projet en m. Defaults to 0.
|
|
54
|
+
"""
|
|
55
|
+
super().__init__()
|
|
56
|
+
for key, val in kwargs.items():
|
|
57
|
+
setattr(self, key, val)
|
|
58
|
+
self.ingenieur = ingenieur
|
|
59
|
+
self.num_project = num_project
|
|
60
|
+
self.name = name
|
|
61
|
+
self.adresse = adresse
|
|
62
|
+
self.code_INSEE = code_INSEE
|
|
63
|
+
self.pays = pays
|
|
64
|
+
self.alt = alt * si.m
|
|
65
|
+
|
|
66
|
+
def __str__(self) -> str:
|
|
67
|
+
return "Créer une classe Projet hérité de la classe Objet du fichier objet.py. Cette classe définit le projet, d'ou découle l'ensemble des objets du catalogue."
|
|
68
|
+
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
|
+
return super().__repr__()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Batiment(Projet):
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
h_bat: float,
|
|
77
|
+
d_bat: float,
|
|
78
|
+
b_bat: float,
|
|
79
|
+
alpha_toit: float,
|
|
80
|
+
alpha_toit2: float = 0,
|
|
81
|
+
*args,
|
|
82
|
+
**kwargs,
|
|
83
|
+
):
|
|
84
|
+
"""Créer une classe Batiment héritée de Projet, cette classe définit les dimension du bâtiment
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
h_bat (float): hauteur du bâtiment en m.
|
|
88
|
+
d_bat (float): largeur du bâtiment en m.
|
|
89
|
+
b_bat (float): longueur du bâtiment en m.
|
|
90
|
+
alpha_toit (float): angle de toiture en ° du versant 1.
|
|
91
|
+
alpha_toit2 (float): angle de toiture en ° du versant 2 si il existe sinon 0.
|
|
92
|
+
"""
|
|
93
|
+
super().__init__(*args, **kwargs)
|
|
94
|
+
self.h_bat = h_bat
|
|
95
|
+
self.d_bat = d_bat
|
|
96
|
+
self.b_bat = b_bat # coté perpendiculaire au vent longpant
|
|
97
|
+
self.alpha_toit = alpha_toit
|
|
98
|
+
self.alpha_toit2 = alpha_toit2
|
|
99
|
+
|
|
100
|
+
def height_ref(self):
|
|
101
|
+
pass # 7.2.1 bâtiment de grande hauteur
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class Model_generator(Projet):
|
|
105
|
+
ACTION = (
|
|
106
|
+
"Permanente G",
|
|
107
|
+
"Exploitation Q",
|
|
108
|
+
"Neige normale Sn",
|
|
109
|
+
"Vent pression W+",
|
|
110
|
+
"Vent dépression W-",
|
|
111
|
+
"Neige accidentelle Sx",
|
|
112
|
+
"Sismique Ae",
|
|
113
|
+
)
|
|
114
|
+
LIST_SECTION = ["Rectangulaire", "Circulaire"]
|
|
115
|
+
CLASSE_WOOD = tuple(
|
|
116
|
+
Projet._data_from_csv(Projet, "caracteristique_meca_bois.csv").index
|
|
117
|
+
)[2:]
|
|
118
|
+
|
|
119
|
+
def __init__(self, *args, **kwargs):
|
|
120
|
+
"""Créer une classe héritée de Projet, permettant de générer des barres pour générer tout d'abords des charges
|
|
121
|
+
puis une modélisation MEF après la combinaison des dites charges à l'EC0.
|
|
122
|
+
|
|
123
|
+
Voici les étapes de modélisation:
|
|
124
|
+
1) Créer les sections avec la méthode "add_section"
|
|
125
|
+
2) Créer les matériaux avec la méthode "add_material"
|
|
126
|
+
3) Créer les barres avec la méthode "add_member"
|
|
127
|
+
4) Créer les appuis avec la méthode "create_support"
|
|
128
|
+
5) Créer les charges sur les barres avec les méthodes "create_dist_load" et "create_point_load"
|
|
129
|
+
7) Transmettre cette classe dans les argument de la classe Combinaison du module EC0_Combinaison pour le calcul des combinaisons.
|
|
130
|
+
8) Exécuté la méthode "generate_model" avec la classe Combinaison.
|
|
131
|
+
9) Récupérer les efforts internes , les déformations et afficher les graphiques associés avec les méthodes correspondante.
|
|
132
|
+
"""
|
|
133
|
+
super().__init__(*args, **kwargs)
|
|
134
|
+
self._data = {
|
|
135
|
+
"nodes": {},
|
|
136
|
+
"sections": {},
|
|
137
|
+
"materials": {},
|
|
138
|
+
"members": {},
|
|
139
|
+
"supports": {},
|
|
140
|
+
"loads": {},
|
|
141
|
+
}
|
|
142
|
+
self._model = None
|
|
143
|
+
|
|
144
|
+
def get_all_data(self) -> dict:
|
|
145
|
+
"""Retourne l'ensemble des données du model"""
|
|
146
|
+
return self._data
|
|
147
|
+
|
|
148
|
+
def export_data(self):
|
|
149
|
+
"""Export les données du model au format JSON
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
filename (str, optional): nom du fichier à créer. Defaults to "model.json".
|
|
153
|
+
"""
|
|
154
|
+
import json
|
|
155
|
+
from PySide6.QtWidgets import QFileDialog, QApplication
|
|
156
|
+
from PySide6.QtCore import Qt
|
|
157
|
+
|
|
158
|
+
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
159
|
+
app = QApplication(sys.argv)
|
|
160
|
+
filename = QFileDialog.getSaveFileName(
|
|
161
|
+
filter="'JSON' (*.json)",
|
|
162
|
+
selectedFilter=".json",
|
|
163
|
+
)[0]
|
|
164
|
+
with open(filename, "w") as f:
|
|
165
|
+
json.dump(self._data, f, indent=4)
|
|
166
|
+
|
|
167
|
+
def show_sign_convention(self):
|
|
168
|
+
"""Affiche l'image de la convention de signe d'une barre'"""
|
|
169
|
+
file = os.path.join(
|
|
170
|
+
self.PATH_CATALOG, "data", "screenshot", "sign_convention.png"
|
|
171
|
+
)
|
|
172
|
+
image = Image.open(file)
|
|
173
|
+
image.show()
|
|
174
|
+
|
|
175
|
+
################## Noeud ##################
|
|
176
|
+
|
|
177
|
+
def add_node(self, X: si.mm, Y: si.mm, Z: si.mm, comment: str = None) -> str:
|
|
178
|
+
"""Ajoute un noeud au model MEF
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
X (int): position en X global en mm
|
|
182
|
+
Y (int): position en Y global en mm
|
|
183
|
+
Z (int): position en Z global en mm
|
|
184
|
+
"""
|
|
185
|
+
node_id = "N" + str(len(self._data["nodes"]) + 1)
|
|
186
|
+
self._data["nodes"][node_id] = {
|
|
187
|
+
"X": X * si.mm,
|
|
188
|
+
"Y": Y * si.mm,
|
|
189
|
+
"Z": Z * si.mm,
|
|
190
|
+
"Commentaire": comment,
|
|
191
|
+
}
|
|
192
|
+
return node_id
|
|
193
|
+
|
|
194
|
+
def _add_node_to_model(self, node_id: str):
|
|
195
|
+
"""Ajoute un noeud au model MEF
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
node_id (str): id du noeud à ajouter
|
|
199
|
+
"""
|
|
200
|
+
node = self._data["nodes"][node_id]
|
|
201
|
+
self._model.add_node(
|
|
202
|
+
node_id,
|
|
203
|
+
node["X"].value * 10**3,
|
|
204
|
+
node["Y"].value * 10**3,
|
|
205
|
+
node["Z"].value * 10**3,
|
|
206
|
+
)
|
|
207
|
+
return node_id
|
|
208
|
+
|
|
209
|
+
def _add_nodes_to_model(self):
|
|
210
|
+
"""Ajoute tous les noeuds au model MEF"""
|
|
211
|
+
for node_id in self._data["nodes"].keys():
|
|
212
|
+
self._add_node_to_model(node_id)
|
|
213
|
+
return self._model.nodes
|
|
214
|
+
|
|
215
|
+
def get_node(self, node_id: str) -> dict:
|
|
216
|
+
"""Retourne les coordonnée du noeud
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
node_id (str): id du noeud à récupérer
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
dict: dictionnaire contenant les coordonnées du noeud
|
|
223
|
+
"""
|
|
224
|
+
return self._data["nodes"][node_id]
|
|
225
|
+
|
|
226
|
+
def get_all_nodes(self) -> dict:
|
|
227
|
+
return self._data["nodes"]
|
|
228
|
+
|
|
229
|
+
################## Barre ##################
|
|
230
|
+
|
|
231
|
+
def _get_angle_of_bar(self, vector: tuple):
|
|
232
|
+
"""Retourne les angles de la barre par rapport aux plans XY, XZ et YZ.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
vector (tuple): vecteur 3D représentant la barre (x2-x1, y2-y1, z2-z1)
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
dict: Dictionnaire contenant les angles par rapport aux plans XY, XZ et YZ en degrés.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def _get_local_angle_of_bar(vector: tuple):
|
|
242
|
+
"""
|
|
243
|
+
Retourne l'angle dans le plan local de la barre, autour de l'axe longitudinal.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
vector (tuple): vecteur 3D représentant la barre (x2-x1, y2-y1, z2-z1)
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
float: L'angle de rotation autour de l'axe longitudinal en degrés.
|
|
250
|
+
"""
|
|
251
|
+
# Calcul de l'axe longitudinal de la barre (normalisation du vecteur)
|
|
252
|
+
L = np.array(vector) / np.linalg.norm(vector)
|
|
253
|
+
|
|
254
|
+
# Choisir un vecteur de référence global, par exemple (1, 0, 0) pour l'axe X
|
|
255
|
+
ref_vec = np.array([1, 0, 0])
|
|
256
|
+
|
|
257
|
+
# Calculer le produit vectoriel pour obtenir un vecteur perpendiculaire à L
|
|
258
|
+
T = np.cross(L, ref_vec) # Axe transverse
|
|
259
|
+
if np.linalg.norm(T) == 0:
|
|
260
|
+
# Si L est parallèle à l'axe de référence, on choisit un autre vecteur de référence (par exemple, l'axe Y)
|
|
261
|
+
ref_vec = np.array([0, 1, 0])
|
|
262
|
+
T = np.cross(L, ref_vec)
|
|
263
|
+
|
|
264
|
+
# Normaliser l'axe transverse
|
|
265
|
+
T = T / np.linalg.norm(T)
|
|
266
|
+
|
|
267
|
+
# Calculer l'angle entre l'axe de référence projeté et l'axe transverse
|
|
268
|
+
angle_local = np.arctan2(
|
|
269
|
+
np.linalg.norm(np.cross(ref_vec, T)), np.dot(ref_vec, T)
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
return np.rad2deg(angle_local)
|
|
273
|
+
|
|
274
|
+
dx, dy, dz = vector
|
|
275
|
+
|
|
276
|
+
# Angle dans le plan XY
|
|
277
|
+
angle_xy = np.arctan2(dy.value(), dx.value())
|
|
278
|
+
|
|
279
|
+
# Angle dans le plan XZ
|
|
280
|
+
angle_xz = np.arctan2(dz.value(), dx.value())
|
|
281
|
+
|
|
282
|
+
# Angle dans le plan YZ
|
|
283
|
+
angle_yz = np.arctan2(dz.value(), dy.value())
|
|
284
|
+
|
|
285
|
+
# Convertir les angles en degrés
|
|
286
|
+
angles = {
|
|
287
|
+
"XY": np.rad2deg(angle_xy % (2 * np.pi)),
|
|
288
|
+
"XZ": np.rad2deg(angle_xz % (2 * np.pi)),
|
|
289
|
+
"YZ": np.rad2deg(angle_yz % (2 * np.pi)),
|
|
290
|
+
"local": _get_local_angle_of_bar(vector),
|
|
291
|
+
}
|
|
292
|
+
# ang1 = np.arctan2(*v1[::-1])
|
|
293
|
+
# return np.rad2deg(ang1 % (2 * np.pi))
|
|
294
|
+
return angles
|
|
295
|
+
|
|
296
|
+
def add_member(
|
|
297
|
+
self,
|
|
298
|
+
node1: str,
|
|
299
|
+
node2: str,
|
|
300
|
+
material: str,
|
|
301
|
+
section: str,
|
|
302
|
+
rotation: float = 0,
|
|
303
|
+
tension_only: bool = ("False", "True"),
|
|
304
|
+
compression_only: bool = ("False", "True"),
|
|
305
|
+
name: str = None,
|
|
306
|
+
comment: str = None,
|
|
307
|
+
):
|
|
308
|
+
"""Ajoute une poutre au model MEF
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
node1 (str): id du noeud 1
|
|
312
|
+
node2 (str): id du noeud 2
|
|
313
|
+
material (str): id du matériaux
|
|
314
|
+
section (str): id de la section
|
|
315
|
+
rotation (float, optional): angle de rotation de la poutre en °. Defaults to 0.
|
|
316
|
+
tension_only (bool, optional): si True, la poutre ne peut que subir des efforts de traction. Defaults to False.
|
|
317
|
+
compression_only (bool, optional): si True, la poutre ne peut que subir des efforts de compression. Defaults to False.
|
|
318
|
+
name (str, optional): Nom de la membrure, si vous remplissez cet argument alors c'est le nom de la membrure,
|
|
319
|
+
sinon la fonction en génère un automatique.
|
|
320
|
+
comment (str, optional): commentaire sur la poutre. Defaults to None.
|
|
321
|
+
"""
|
|
322
|
+
if name:
|
|
323
|
+
member_id = name
|
|
324
|
+
else:
|
|
325
|
+
member_id = "M" + str(len(self._data["members"]) + 1)
|
|
326
|
+
node_coor_1 = self.get_node(node1)
|
|
327
|
+
node_coor_2 = self.get_node(node2)
|
|
328
|
+
x1, y1, z1 = (
|
|
329
|
+
node_coor_1["X"].value * 10**3,
|
|
330
|
+
node_coor_1["Y"].value * 10**3,
|
|
331
|
+
node_coor_1["Z"].value * 10**3,
|
|
332
|
+
)
|
|
333
|
+
x2, y2, z2 = (
|
|
334
|
+
node_coor_2["X"].value * 10**3,
|
|
335
|
+
node_coor_2["Y"].value * 10**3,
|
|
336
|
+
node_coor_2["Z"].value * 10**3,
|
|
337
|
+
)
|
|
338
|
+
vector = (x2 - x1, y2 - y1, z2 - z1)
|
|
339
|
+
length = (
|
|
340
|
+
mt.sqrt(abs(vector[0]) ** 2 + abs(vector[1]) ** 2 + abs(vector[2]) ** 2)
|
|
341
|
+
* si.mm
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
self._data["members"][member_id] = {
|
|
345
|
+
"Noeuds": [node1, node2],
|
|
346
|
+
"Longueur": length,
|
|
347
|
+
"Section": section,
|
|
348
|
+
"Matériaux": material,
|
|
349
|
+
"Rotation": rotation,
|
|
350
|
+
"Relaxation": {"start": None, "end": None},
|
|
351
|
+
"Commentaire": comment,
|
|
352
|
+
}
|
|
353
|
+
if tension_only:
|
|
354
|
+
self._data["members"][member_id]["Tension uniquement"] = True
|
|
355
|
+
elif compression_only:
|
|
356
|
+
self._data["members"][member_id]["Compression uniquement"] = True
|
|
357
|
+
|
|
358
|
+
return member_id
|
|
359
|
+
|
|
360
|
+
def _add_member_to_model(self, member_id: str):
|
|
361
|
+
"""Ajoute une poutre au model MEF
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
member_id (str): id de la poutre à ajouter
|
|
365
|
+
"""
|
|
366
|
+
node1, node2 = self._data["members"][member_id]["Noeuds"]
|
|
367
|
+
tension_only = False
|
|
368
|
+
compression_only = False
|
|
369
|
+
if self._data["members"][member_id].get("Tension uniquement"):
|
|
370
|
+
tension_only = True
|
|
371
|
+
elif self._data["members"][member_id].get("Compression uniquement"):
|
|
372
|
+
compression_only = True
|
|
373
|
+
self._model.add_member(
|
|
374
|
+
member_id,
|
|
375
|
+
node1,
|
|
376
|
+
node2,
|
|
377
|
+
self._data["members"][member_id]["Matériaux"],
|
|
378
|
+
self._data["members"][member_id]["Section"],
|
|
379
|
+
self._data["members"][member_id]["Rotation"],
|
|
380
|
+
tension_only,
|
|
381
|
+
compression_only,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Ajout des releases
|
|
385
|
+
list_releases = []
|
|
386
|
+
has_release = False
|
|
387
|
+
for pos, release in self._data["members"][member_id]["Relaxation"].items():
|
|
388
|
+
if release:
|
|
389
|
+
has_release = True
|
|
390
|
+
list_releases.append([val for val in release.values()])
|
|
391
|
+
else:
|
|
392
|
+
list_releases.append([False] * 6)
|
|
393
|
+
|
|
394
|
+
if has_release:
|
|
395
|
+
list_releases = list_releases[0] + list_releases[1]
|
|
396
|
+
print(list_releases)
|
|
397
|
+
self._model.def_releases(
|
|
398
|
+
member_id,
|
|
399
|
+
*list_releases,
|
|
400
|
+
)
|
|
401
|
+
return member_id
|
|
402
|
+
|
|
403
|
+
def _add_members_to_model(self):
|
|
404
|
+
"""Ajoute toutes les poutres au model MEF"""
|
|
405
|
+
for member_id in self._data["members"].keys():
|
|
406
|
+
self._add_member_to_model(member_id)
|
|
407
|
+
return self._model.members
|
|
408
|
+
|
|
409
|
+
def get_member(self, member_id: str) -> dict:
|
|
410
|
+
"""Retourne une membrure par son id
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
member_id (str): id de la membrure à récupérer
|
|
414
|
+
"""
|
|
415
|
+
return self._data["members"][member_id]
|
|
416
|
+
|
|
417
|
+
def get_member_length(self, member_id: str):
|
|
418
|
+
"""Retourne la longueur du membrure par son id
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
member_id (str): id de la membrure à récupérer
|
|
422
|
+
"""
|
|
423
|
+
return self._data["members"][member_id]["Longueur"]
|
|
424
|
+
|
|
425
|
+
def get_all_members(self) -> dict:
|
|
426
|
+
return self._data["members"]
|
|
427
|
+
|
|
428
|
+
################## Matériaux ##################
|
|
429
|
+
|
|
430
|
+
def add_material_by_class(self, classe: str = CLASSE_WOOD) -> str:
|
|
431
|
+
if not self._data["materials"].get(classe):
|
|
432
|
+
data_csv_meca = self._data_from_csv("caracteristique_meca_bois.csv")
|
|
433
|
+
material_properties = data_csv_meca.loc[classe]
|
|
434
|
+
E = int(material_properties.loc["E0mean"])
|
|
435
|
+
G = int(material_properties.loc["Gmoy"])
|
|
436
|
+
nu = (E / (2 * G)) - 1 # Coefficient de Poisson
|
|
437
|
+
self._data["materials"][classe] = {
|
|
438
|
+
"classe": classe,
|
|
439
|
+
"E": E * si.MPa,
|
|
440
|
+
"G": G * si.MPa,
|
|
441
|
+
"nu": nu,
|
|
442
|
+
"rho": int(material_properties.loc["rhomean"]) * si.kg / si.m**3,
|
|
443
|
+
}
|
|
444
|
+
return classe
|
|
445
|
+
|
|
446
|
+
def add_material_by_mechanical_properties(
|
|
447
|
+
self,
|
|
448
|
+
name: str,
|
|
449
|
+
E: si.MPa,
|
|
450
|
+
G: si.MPa,
|
|
451
|
+
nu: float,
|
|
452
|
+
rho: float,
|
|
453
|
+
):
|
|
454
|
+
"""Ajoute un matériau à une barre par ces caractéristiques mécaniques.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
name (str): nom du matériau
|
|
458
|
+
E (int): Module de young en MPa, ce E est le E,mean. Il ne faut absolument pas donner le E,mean,fin sous peine de réaliser le calcul EC5 §2.3.2.2 equ2.7 deux fois !
|
|
459
|
+
G (float): Module de cisaillement en MPa
|
|
460
|
+
nu (float): Coefficient de Poisson
|
|
461
|
+
rho (float): Masse volumique en kg/m3
|
|
462
|
+
"""
|
|
463
|
+
self._data["materials"][name] = {
|
|
464
|
+
"classe": "Manuel",
|
|
465
|
+
"E": E * si.MPa,
|
|
466
|
+
"G": G * si.MPa,
|
|
467
|
+
"nu": nu,
|
|
468
|
+
"rho": rho * si.kg / si.m**3,
|
|
469
|
+
}
|
|
470
|
+
return name
|
|
471
|
+
|
|
472
|
+
def _add_material_to_model(self, material_id: str):
|
|
473
|
+
"""Ajoute un matériau au model MEF
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
material_id (str): id du matériau à ajouter
|
|
477
|
+
"""
|
|
478
|
+
material = self._data["materials"][material_id]
|
|
479
|
+
self._model.add_material(
|
|
480
|
+
material_id,
|
|
481
|
+
material["E"].value * 10**-6,
|
|
482
|
+
material["G"].value * 10**-6,
|
|
483
|
+
material["nu"],
|
|
484
|
+
material["rho"].value,
|
|
485
|
+
)
|
|
486
|
+
return material_id
|
|
487
|
+
|
|
488
|
+
def _add_materials_to_model(self):
|
|
489
|
+
for mat_id in self._data["materials"].keys():
|
|
490
|
+
self._add_material_to_model(mat_id)
|
|
491
|
+
return self._model.materials
|
|
492
|
+
|
|
493
|
+
def get_material(self, material_id: str) -> dict:
|
|
494
|
+
return self._data["materials"][material_id]
|
|
495
|
+
|
|
496
|
+
def get_all_materials(self) -> dict:
|
|
497
|
+
return self._data["materials"]
|
|
498
|
+
|
|
499
|
+
################## Section ##################
|
|
500
|
+
|
|
501
|
+
def aire(self, b: si.mm, h: si.mm, section: str = LIST_SECTION):
|
|
502
|
+
b = b * si.mm
|
|
503
|
+
h = h * si.mm
|
|
504
|
+
if section == self.LIST_SECTION[0]:
|
|
505
|
+
return b * h
|
|
506
|
+
else:
|
|
507
|
+
return mt.pi * (b / 2) ** 2
|
|
508
|
+
|
|
509
|
+
def inertie(self, b: si.mm, h: si.mm, section: str = LIST_SECTION):
|
|
510
|
+
"""Retourne le moment quadratique d'une section rectangulaire en mm4 avec pour argument :
|
|
511
|
+
b ou d : Largeur ou diamètre de la poutre en mm
|
|
512
|
+
h : Hauteur de la poutre en mm"""
|
|
513
|
+
b = b * si.mm
|
|
514
|
+
h = h * si.mm
|
|
515
|
+
if section == "Rectangulaire":
|
|
516
|
+
I_z = (b * h**3) / 12
|
|
517
|
+
I_y = (h * b**3) / 12
|
|
518
|
+
else:
|
|
519
|
+
I_y = (mt.pi * b**4) / 64
|
|
520
|
+
I_z = I_y
|
|
521
|
+
return {"Iy": I_y, "Iz": I_z}
|
|
522
|
+
|
|
523
|
+
def add_section(self, b: si.mm, h: si.mm, J: si.mm**4, section: str = LIST_SECTION):
|
|
524
|
+
"""Ajoute une section à la liste de section
|
|
525
|
+
|
|
526
|
+
Args:
|
|
527
|
+
b (int): largeur de la section en mm
|
|
528
|
+
h (int): hauteur de la section en mm
|
|
529
|
+
J (float): Module de torsion en mm4
|
|
530
|
+
section (str): type de section "Circulaire" ou "Rectangulaire".
|
|
531
|
+
"""
|
|
532
|
+
if section not in self.LIST_SECTION:
|
|
533
|
+
raise ValueError(
|
|
534
|
+
f"Le type de section {section} n'est pas reconnu. Les types de sections disponibles sont: {self.LIST_SECTION}"
|
|
535
|
+
)
|
|
536
|
+
match section:
|
|
537
|
+
case "Rectangulaire":
|
|
538
|
+
name = "".join(["R", str(b), "X", str(h)])
|
|
539
|
+
case "Circulaire":
|
|
540
|
+
name = "".join(["C", str(b)])
|
|
541
|
+
inertie = self.inertie(b, h, section)
|
|
542
|
+
self._data["sections"][name] = {
|
|
543
|
+
"Section": section,
|
|
544
|
+
"b": b * si.mm,
|
|
545
|
+
"h": h * si.mm,
|
|
546
|
+
"Aire": self.aire(b, h, section),
|
|
547
|
+
"Iy": inertie["Iy"],
|
|
548
|
+
"Iz": inertie["Iz"],
|
|
549
|
+
"J": J * si.mm**4,
|
|
550
|
+
}
|
|
551
|
+
return name
|
|
552
|
+
|
|
553
|
+
def add_section_by_property(
|
|
554
|
+
self, name: str, aire: si.mm**2, Iy: si.mm**4, Iz: si.mm**4, J: si.mm**4
|
|
555
|
+
):
|
|
556
|
+
"""Ajoute une section à la liste de section
|
|
557
|
+
|
|
558
|
+
Args:
|
|
559
|
+
name (str): nom de la section
|
|
560
|
+
aire (float): aire de la section en mm²
|
|
561
|
+
|
|
562
|
+
ATTENTION: Iy est la petite inertie et Iz est la grande inertie !
|
|
563
|
+
Iy (float): Inertie quadratique autour de y en mm4
|
|
564
|
+
Iz (float): Inertie quadratique autour de z en mm4
|
|
565
|
+
|
|
566
|
+
J (float): Module de torsion en mm4
|
|
567
|
+
"""
|
|
568
|
+
self._data["sections"][name] = {
|
|
569
|
+
"Section": "Manuel",
|
|
570
|
+
"Aire": aire * si.mm**2,
|
|
571
|
+
"Iy": Iy * si.mm**4,
|
|
572
|
+
"Iz": Iz * si.mm**4,
|
|
573
|
+
"J": J * si.mm**4,
|
|
574
|
+
}
|
|
575
|
+
return self._data["sections"][name]
|
|
576
|
+
|
|
577
|
+
def _add_section_to_model(self, section_id: str):
|
|
578
|
+
"""Ajoute une section au model MEF
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
section_id (str): id de la section à ajouter
|
|
582
|
+
"""
|
|
583
|
+
section = self._data["sections"][section_id]
|
|
584
|
+
self._model.add_section(
|
|
585
|
+
section_id,
|
|
586
|
+
section["Aire"].value * 10**6,
|
|
587
|
+
section["Iy"].value * 10**12,
|
|
588
|
+
section["Iz"].value * 10**12,
|
|
589
|
+
section["J"].value * 10**12,
|
|
590
|
+
)
|
|
591
|
+
return section_id
|
|
592
|
+
|
|
593
|
+
def _add_sections_to_model(self):
|
|
594
|
+
for section_id in self._data["sections"].keys():
|
|
595
|
+
self._add_section_to_model(section_id)
|
|
596
|
+
return self._model.sections
|
|
597
|
+
|
|
598
|
+
def get_section(self, section_id: str) -> dict:
|
|
599
|
+
return self._data["sections"][section_id]
|
|
600
|
+
|
|
601
|
+
def get_all_sections(self) -> dict:
|
|
602
|
+
return self._data["sections"]
|
|
603
|
+
|
|
604
|
+
################## Relachement ##################
|
|
605
|
+
|
|
606
|
+
def add_release(
|
|
607
|
+
self,
|
|
608
|
+
member_id: str,
|
|
609
|
+
position: str = ("start", "end"),
|
|
610
|
+
u: bool = ("False", "True"),
|
|
611
|
+
v: bool = ("False", "True"),
|
|
612
|
+
w: bool = ("False", "True"),
|
|
613
|
+
teta_x: bool = ("False", "True"),
|
|
614
|
+
teta_y: bool = ("False", "True"),
|
|
615
|
+
teta_z: bool = ("False", "True"),
|
|
616
|
+
):
|
|
617
|
+
"""Ajoute une relaxation sur une membrure soit au début, soit à la fin.
|
|
618
|
+
Ceci est considéré dans les MEF par une matrice de rigidité spécifique avec les éléments relaché égale à 0.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
member_id (int): numéro de la membrure à relacher
|
|
622
|
+
position (str, optional): position du relachement sur la barre soit au début soit à la fin. Defaults to ("start", "end").
|
|
623
|
+
u (bool, optional): relachement de l'axe x local, si oui alors True.
|
|
624
|
+
v (bool, optional): relachement de l'axe y local, si oui alors True.
|
|
625
|
+
w (bool, optional): relachement de l'axe z local, si oui alors True.
|
|
626
|
+
teta_x (bool, optional): relachement de l'axe de rotation x local, si oui alors True. Attention de base cette rotation doit toujours être fixé.
|
|
627
|
+
teta_y (bool, optional): relachement de l'axe de rotation y local, si oui alors True.
|
|
628
|
+
teta_z (bool, optional): relachement de l'axe de rotation z local, si oui alors True.
|
|
629
|
+
"""
|
|
630
|
+
self._data["members"][member_id]["Relaxation"][position] = {
|
|
631
|
+
"u": u,
|
|
632
|
+
"v": v,
|
|
633
|
+
"w": w,
|
|
634
|
+
"teta_x": teta_x,
|
|
635
|
+
"teta_y": teta_y,
|
|
636
|
+
"teta_z": teta_z,
|
|
637
|
+
}
|
|
638
|
+
return self._data["members"][member_id]["Relaxation"][position]
|
|
639
|
+
|
|
640
|
+
################## Appuis ##################
|
|
641
|
+
|
|
642
|
+
def add_support(
|
|
643
|
+
self,
|
|
644
|
+
node_id: str,
|
|
645
|
+
DX: bool = ("True", "False"),
|
|
646
|
+
DY: bool = ("True", "False"),
|
|
647
|
+
DZ: bool = ("True", "False"),
|
|
648
|
+
RX: bool = ("True", "False"),
|
|
649
|
+
RY: bool = ("True", "False"),
|
|
650
|
+
RZ: bool = ("True", "False"),
|
|
651
|
+
l_appuis: int = 0,
|
|
652
|
+
):
|
|
653
|
+
"""Ajoute un appuis dans la liste d'appuis de la classe MEF
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
node_id (int): Numéro du noeud sur lequel positionner l'appuis.
|
|
657
|
+
DX (bool, optional): Blocage en translation de l'axe X global, si oui alors True.
|
|
658
|
+
DY (bool, optional): Blocage en translation de l'axe Y global, si oui alors True.
|
|
659
|
+
DZ (bool, optional): Blocage en translation de l'axe Z global, si oui alors True.
|
|
660
|
+
RX (bool, optional): Blocage en rotation de l'axe X global, si oui alors True.
|
|
661
|
+
RY (bool, optional): Blocage en translation de l'axe X global, si oui alors True.
|
|
662
|
+
RZ (bool, optional): Blocage en translation de l'axe X global, si oui alors True.
|
|
663
|
+
l_appuis (int, optional): longueur d'appuis sur la poutre en mm. Defaults to 0.
|
|
664
|
+
"""
|
|
665
|
+
support_id = "S" + str(len(self._data["supports"]) + 1)
|
|
666
|
+
self._data["supports"][support_id] = {
|
|
667
|
+
"Noeud": node_id,
|
|
668
|
+
"DX": DX,
|
|
669
|
+
"DY": DY,
|
|
670
|
+
"DZ": DZ,
|
|
671
|
+
"RX": RX,
|
|
672
|
+
"RY": RY,
|
|
673
|
+
"RZ": RZ,
|
|
674
|
+
"Longueur d'appui": l_appuis,
|
|
675
|
+
}
|
|
676
|
+
return self._data["supports"][support_id]
|
|
677
|
+
|
|
678
|
+
def create_supports_by_list(self, list_supports: list):
|
|
679
|
+
"""Ajoute les charges d'une liste pré-définit dans la liste de chargement
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
list_supports (list): liste de charge.
|
|
683
|
+
"""
|
|
684
|
+
for support in list_supports:
|
|
685
|
+
self.add_support(*support)
|
|
686
|
+
|
|
687
|
+
def del_support(self, support_id: str):
|
|
688
|
+
"""Supprime un appui de l'attribut list_supports par son index
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
support_id (int): id de l'appuis à supprimer.
|
|
692
|
+
"""
|
|
693
|
+
return f"L'appui à été supprimé: {self._data["supports"].pop(support_id)}"
|
|
694
|
+
|
|
695
|
+
def _add_support_to_model(self, support_id: str):
|
|
696
|
+
"""Ajoute un appui au model MEF
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
support_id (str): id de l'appui à ajouter
|
|
700
|
+
"""
|
|
701
|
+
support = self._data["supports"][support_id]
|
|
702
|
+
self._model.def_support(
|
|
703
|
+
support["Noeud"],
|
|
704
|
+
support["DX"],
|
|
705
|
+
support["DY"],
|
|
706
|
+
support["DZ"],
|
|
707
|
+
support["RX"],
|
|
708
|
+
support["RY"],
|
|
709
|
+
support["RZ"],
|
|
710
|
+
)
|
|
711
|
+
return support_id
|
|
712
|
+
|
|
713
|
+
def _add_supports_to_model(self):
|
|
714
|
+
for support_id in self._data["supports"].keys():
|
|
715
|
+
self._add_support_to_model(support_id)
|
|
716
|
+
return "Appuis ajoutés"
|
|
717
|
+
|
|
718
|
+
def get_all_supports(self) -> dict:
|
|
719
|
+
return self._data["supports"]
|
|
720
|
+
|
|
721
|
+
################## Chargements ##################
|
|
722
|
+
|
|
723
|
+
def _convert_pos(self, pos_index: int | str, member_id: str) -> int:
|
|
724
|
+
"""Converti la position en valeur recevable par la fonction create_load
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
pos_index (int | str): position de la charge sur la barre
|
|
728
|
+
|
|
729
|
+
Returns:
|
|
730
|
+
int: la position numérique sur la barre
|
|
731
|
+
"""
|
|
732
|
+
match pos_index:
|
|
733
|
+
case "start":
|
|
734
|
+
pos_index = 0 * si.mm
|
|
735
|
+
case "end":
|
|
736
|
+
pos_index = self._data["members"][member_id]["Longueur"]
|
|
737
|
+
case "middle":
|
|
738
|
+
pos_index = round(self._data["members"][member_id]["Longueur"] / 2, 0)
|
|
739
|
+
case str(x) if "%" in x:
|
|
740
|
+
pos_index = pos_index.split("%")[0]
|
|
741
|
+
pos_index.replace(" ", "")
|
|
742
|
+
pos_index = round(
|
|
743
|
+
self._data["members"][member_id]["Longueur"] * int(pos_index) / 100,
|
|
744
|
+
0,
|
|
745
|
+
)
|
|
746
|
+
case _:
|
|
747
|
+
pos_index = pos_index * si.mm
|
|
748
|
+
return pos_index
|
|
749
|
+
|
|
750
|
+
def create_dist_load(
|
|
751
|
+
self,
|
|
752
|
+
member_id: str,
|
|
753
|
+
name: str,
|
|
754
|
+
start_load: float,
|
|
755
|
+
end_load: float,
|
|
756
|
+
start_pos: str = None,
|
|
757
|
+
end_pos: str = None,
|
|
758
|
+
action: str = ACTION,
|
|
759
|
+
direction: str = ("Fx", "Fy", "Fz", "FX", "FY", "FZ"),
|
|
760
|
+
comment: str = None,
|
|
761
|
+
):
|
|
762
|
+
"""Ajoute une charge dans la liste de chargement de la classe chargement
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
member_id (str): id de la barre à charger.
|
|
766
|
+
name (str): nom de la charge.
|
|
767
|
+
start_load (int): effort de départ en kN/m.
|
|
768
|
+
end_load (int): effort de fin en kN/m.
|
|
769
|
+
start_pos (str, optional): position de début de la charge sur la barre en mm. En complément il est possible de mettre "start", "middle"
|
|
770
|
+
ou un pourcentage pour définitr la position de la charge.
|
|
771
|
+
end_pos (str, optional): position de début de la charge sur la barre en mm. En complément il est possible de mettre "end", "middle"
|
|
772
|
+
ou un pourcentage pour définitr la position de la charge.
|
|
773
|
+
action (str): type d'action de l'effort.
|
|
774
|
+
direction (str): sens de l'effort sur la barre.
|
|
775
|
+
comment (str, optional): commentaire sur la charge.
|
|
776
|
+
"""
|
|
777
|
+
load_id = "L" + str(len(self._data["loads"]) + 1)
|
|
778
|
+
type_load = "Distribuée"
|
|
779
|
+
if not start_pos:
|
|
780
|
+
start_pos = "start"
|
|
781
|
+
if not end_pos:
|
|
782
|
+
end_pos = "end"
|
|
783
|
+
start_pos = self._convert_pos(start_pos, member_id)
|
|
784
|
+
end_pos = self._convert_pos(end_pos, member_id)
|
|
785
|
+
|
|
786
|
+
self._data["loads"][load_id] = {
|
|
787
|
+
"N° barre": member_id,
|
|
788
|
+
"Nom": name,
|
|
789
|
+
"Action": action,
|
|
790
|
+
"Type de charge": type_load,
|
|
791
|
+
"Charge": {
|
|
792
|
+
"start": start_load * si.kN / si.m,
|
|
793
|
+
"end": end_load * si.kN / si.m,
|
|
794
|
+
},
|
|
795
|
+
"Position": {"start": start_pos, "end": end_pos},
|
|
796
|
+
"Axe": direction,
|
|
797
|
+
"Commentaire": comment,
|
|
798
|
+
}
|
|
799
|
+
return self._data["loads"][load_id]
|
|
800
|
+
|
|
801
|
+
def create_point_load(
|
|
802
|
+
self,
|
|
803
|
+
member_id: str,
|
|
804
|
+
name: str,
|
|
805
|
+
load: int,
|
|
806
|
+
pos: str = None,
|
|
807
|
+
action: str = ACTION,
|
|
808
|
+
direction: str = (
|
|
809
|
+
"Fx",
|
|
810
|
+
"Fy",
|
|
811
|
+
"Fz",
|
|
812
|
+
"Mx",
|
|
813
|
+
"My",
|
|
814
|
+
"Mz",
|
|
815
|
+
"FX",
|
|
816
|
+
"FY",
|
|
817
|
+
"FZ",
|
|
818
|
+
"MX",
|
|
819
|
+
"MY",
|
|
820
|
+
"MZ",
|
|
821
|
+
),
|
|
822
|
+
comment: str = None,
|
|
823
|
+
):
|
|
824
|
+
"""Ajoute une charge dans la liste de chargement de la classe chargement
|
|
825
|
+
|
|
826
|
+
Args:
|
|
827
|
+
member_id (str): id de la barre à charger.
|
|
828
|
+
name (str): nom de la charge.
|
|
829
|
+
load (int): effort de départ en kN ou kN.m.
|
|
830
|
+
pos (str, optional): position de la charge sur la barre en mm. En complément il est possible de mettre "start", "middle", "end"
|
|
831
|
+
ou un pourcentage pour définitr la position de la charge.
|
|
832
|
+
action (str): type d'action de l'effort.
|
|
833
|
+
direction (str): sens de l'effort sur la barre.
|
|
834
|
+
comment (str, optional): commentaire sur la charge.
|
|
835
|
+
"""
|
|
836
|
+
load_id = "L" + str(len(self._data["loads"]) + 1)
|
|
837
|
+
if "F" in direction:
|
|
838
|
+
type_load = "Concentrée"
|
|
839
|
+
load = load * si.kN
|
|
840
|
+
else:
|
|
841
|
+
type_load = "Moment"
|
|
842
|
+
load = load * si.kN * si.m
|
|
843
|
+
|
|
844
|
+
pos = self._convert_pos(pos, member_id)
|
|
845
|
+
|
|
846
|
+
self._data["loads"][load_id] = {
|
|
847
|
+
"N° barre": member_id,
|
|
848
|
+
"Nom": name,
|
|
849
|
+
"Action": action,
|
|
850
|
+
"Type de charge": type_load,
|
|
851
|
+
"Charge": load,
|
|
852
|
+
"Position": pos,
|
|
853
|
+
"Axe": direction,
|
|
854
|
+
"Commentaire": comment,
|
|
855
|
+
}
|
|
856
|
+
return self._data["loads"][load_id]
|
|
857
|
+
|
|
858
|
+
def create_load_by_list(
|
|
859
|
+
self, list_loads: list, type_load: str = ("Distribuée", "Autre")
|
|
860
|
+
):
|
|
861
|
+
"""Ajoute les charges d'une liste pré-définit dans la liste de chargement
|
|
862
|
+
|
|
863
|
+
Args:
|
|
864
|
+
list_loads (list): liste de charge.
|
|
865
|
+
"""
|
|
866
|
+
for load in list_loads:
|
|
867
|
+
if type_load == "Distribuée":
|
|
868
|
+
self.create_dist_load(*load)
|
|
869
|
+
else:
|
|
870
|
+
self.create_point_load(*load)
|
|
871
|
+
|
|
872
|
+
def del_load(self, load_id: str):
|
|
873
|
+
"""Supprime une charge de l'attribut _data["loads"] par son index
|
|
874
|
+
|
|
875
|
+
Args:
|
|
876
|
+
index_load (int): index de la charge à supprimer.
|
|
877
|
+
"""
|
|
878
|
+
self._data["loads"].pop(load_id)
|
|
879
|
+
|
|
880
|
+
def _add_load_to_model(self, load_id: str):
|
|
881
|
+
load = self._data["loads"][load_id]
|
|
882
|
+
case = self.DICO_COMBI_ACTION[load["Action"]]
|
|
883
|
+
match load["Type de charge"]:
|
|
884
|
+
case "Distribuée":
|
|
885
|
+
self._model.add_member_dist_load(
|
|
886
|
+
load["N° barre"],
|
|
887
|
+
load["Axe"],
|
|
888
|
+
load["Charge"]["start"].value * 10**-3,
|
|
889
|
+
load["Charge"]["end"].value * 10**-3,
|
|
890
|
+
load["Position"]["start"].value * 10**3,
|
|
891
|
+
load["Position"]["end"].value * 10**3,
|
|
892
|
+
case,
|
|
893
|
+
)
|
|
894
|
+
case _:
|
|
895
|
+
member = self._data["members"][load["N° barre"]]
|
|
896
|
+
long = member["Longueur"]
|
|
897
|
+
local_axes = ("Fx", "Fy", "Fz", "Mx", "My", "Mz")
|
|
898
|
+
if load["Position"] == 0 and load["Axe"] not in local_axes:
|
|
899
|
+
node1 = member["Noeuds"][0]
|
|
900
|
+
self._model.add_node_load(
|
|
901
|
+
node1, load["Axe"], load["Charge"].value, case
|
|
902
|
+
)
|
|
903
|
+
elif load["Position"] == long and load["Axe"] not in local_axes:
|
|
904
|
+
node2 = member["Noeuds"][1]
|
|
905
|
+
self._model.add_node_load(
|
|
906
|
+
node2, load["Axe"], load["Charge"].value, case
|
|
907
|
+
)
|
|
908
|
+
else:
|
|
909
|
+
self._model.add_member_pt_load(
|
|
910
|
+
load["N° barre"],
|
|
911
|
+
load["Axe"],
|
|
912
|
+
load["Charge"].value,
|
|
913
|
+
load["Position"].value * 10**3,
|
|
914
|
+
case,
|
|
915
|
+
)
|
|
916
|
+
return load_id
|
|
917
|
+
|
|
918
|
+
def _add_loads_to_model(self):
|
|
919
|
+
for load_id in self._data["loads"].keys():
|
|
920
|
+
self._add_load_to_model(load_id)
|
|
921
|
+
return self._model.load_cases
|
|
922
|
+
|
|
923
|
+
def get_all_loads(self):
|
|
924
|
+
"""Retourne la liste des charges définits initialement."""
|
|
925
|
+
return self._data["loads"]
|
|
926
|
+
|
|
927
|
+
def get_member_loads(self, member_id: str) -> list:
|
|
928
|
+
"""Retourne la liste des charges définits initialement."""
|
|
929
|
+
return [
|
|
930
|
+
load
|
|
931
|
+
for load in self._data["loads"].values()
|
|
932
|
+
if load["N° barre"] == member_id
|
|
933
|
+
]
|
|
934
|
+
|
|
935
|
+
def generate_model(self):
|
|
936
|
+
self._model = FEModel3D()
|
|
937
|
+
self._add_nodes_to_model()
|
|
938
|
+
self._add_materials_to_model()
|
|
939
|
+
self._add_sections_to_model()
|
|
940
|
+
self._add_members_to_model()
|
|
941
|
+
self._add_supports_to_model()
|
|
942
|
+
self._add_loads_to_model()
|
|
943
|
+
|
|
944
|
+
def _add_load_combos_to_model(self, combos: dict, tag: str):
|
|
945
|
+
for combo, factor in combos.items():
|
|
946
|
+
self._model.add_load_combo(combo, factor, tag)
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
class Model_result(Projet):
|
|
950
|
+
ANALYZE_TYPE = ("Général", "Linéaire", "Second ordre")
|
|
951
|
+
|
|
952
|
+
def __init__(
|
|
953
|
+
self,
|
|
954
|
+
model_generator: object,
|
|
955
|
+
analyze_type: str = ANALYZE_TYPE,
|
|
956
|
+
check_stability: bool = ("False", "True"),
|
|
957
|
+
*args,
|
|
958
|
+
**kwargs,
|
|
959
|
+
):
|
|
960
|
+
"""
|
|
961
|
+
Cette classe permet de lancer l'analyse aux éléments finis et de récupérer et d'afficher les résultats du modèle.
|
|
962
|
+
Cette classe est héritée de Projet dans le module A0_Projet.py.
|
|
963
|
+
Elle nécessite toutefois la création d'un modèle de calcul avant d'être instanciée.
|
|
964
|
+
Le modèle de calcul est créé par la classe Model_generator dans le module A0_Projet.py.
|
|
965
|
+
|
|
966
|
+
Args:
|
|
967
|
+
model_generator (Model_generator): le modèle de calcul à utiliser pour la génération des combinaisons d'action.
|
|
968
|
+
analize_type (str): Définit le type d'analyse à réaliser
|
|
969
|
+
check_stability (bool, optional): Définit si vous voulez vérifier la stabilité du modèle.
|
|
970
|
+
Ceci ralentit le calcul, à utiliser donc quand cas de débuguage. Defaults to False.
|
|
971
|
+
"""
|
|
972
|
+
super().__init__(*args, **kwargs)
|
|
973
|
+
self._model_generator = model_generator
|
|
974
|
+
self.analyze_type = analyze_type
|
|
975
|
+
self.check_stability = check_stability
|
|
976
|
+
self._analyze()
|
|
977
|
+
|
|
978
|
+
def _base_graph(
|
|
979
|
+
self,
|
|
980
|
+
title: str,
|
|
981
|
+
combo_name: str,
|
|
982
|
+
x_values,
|
|
983
|
+
y_values,
|
|
984
|
+
x_label: str,
|
|
985
|
+
y_label: str,
|
|
986
|
+
color: str,
|
|
987
|
+
fill_between: bool = True,
|
|
988
|
+
savefig: bool = False,
|
|
989
|
+
filepath: str=None
|
|
990
|
+
):
|
|
991
|
+
"""Retourne un diagramme"""
|
|
992
|
+
plt.clf() # Effacer le graphique précédent
|
|
993
|
+
plt.figure(self.name, figsize=(12, 4))
|
|
994
|
+
plt.gcf().subplots_adjust(
|
|
995
|
+
left=0.1, bottom=0.25, right=0.9, top=0.75, wspace=0, hspace=0.95
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
# manager = plt.get_current_fig_manager()
|
|
999
|
+
# manager.resize(*manager.window.maxsize())
|
|
1000
|
+
|
|
1001
|
+
plt.plot(x_values, y_values, color=color)
|
|
1002
|
+
plt.title(f"{title}\n{combo_name}", color=color)
|
|
1003
|
+
plt.ylabel(y_label)
|
|
1004
|
+
plt.xlabel(x_label)
|
|
1005
|
+
|
|
1006
|
+
if fill_between:
|
|
1007
|
+
plt.fill_between(x_values, y_values, 0, color=color, alpha=0.2)
|
|
1008
|
+
plt.grid()
|
|
1009
|
+
if savefig:
|
|
1010
|
+
if not filepath:
|
|
1011
|
+
filepath = QFileDialog.getSaveFileName(
|
|
1012
|
+
filter="PNG (*.png)",
|
|
1013
|
+
selectedFilter=".png",
|
|
1014
|
+
)[0]
|
|
1015
|
+
plt.savefig(filepath)
|
|
1016
|
+
return filepath
|
|
1017
|
+
else:
|
|
1018
|
+
plt.show()
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
def _analyze(self):
|
|
1022
|
+
"""Lance l'analyse du modèle aux éléments finis."""
|
|
1023
|
+
orphaned_nodes = self._model_generator._model.orphaned_nodes()
|
|
1024
|
+
if orphaned_nodes:
|
|
1025
|
+
return f"Les noeuds suivants ne sont pas connectés: {orphaned_nodes}"
|
|
1026
|
+
else:
|
|
1027
|
+
if self.analyze_type == self.ANALYZE_TYPE[0]:
|
|
1028
|
+
self._model_generator._model.analyze(
|
|
1029
|
+
check_stability=self.check_stability
|
|
1030
|
+
)
|
|
1031
|
+
elif self.analyze_type == self.ANALYZE_TYPE[1]:
|
|
1032
|
+
self._model_generator._model.analyze_linear(
|
|
1033
|
+
check_stability=self.check_stability
|
|
1034
|
+
)
|
|
1035
|
+
else:
|
|
1036
|
+
self._model_generator._model.analyze_PDelta(
|
|
1037
|
+
check_stability=self.check_stability
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
def get_internal_force(
|
|
1041
|
+
self,
|
|
1042
|
+
member_id: str,
|
|
1043
|
+
combination: str,
|
|
1044
|
+
type: str = ("Nx", "Vy", "Vz", "Mx", "My", "Mz"),
|
|
1045
|
+
n_points: int = 20,
|
|
1046
|
+
) -> np.array:
|
|
1047
|
+
"""Retourne une table des efforts internes d'une membrure pour le type d'effort donné.
|
|
1048
|
+
|
|
1049
|
+
Args:
|
|
1050
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1051
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1052
|
+
type (str): Le type d'effort interne à retourner. Defaults to ("Nx", "Vy", "Vz", "Mx", "My", "Mz").
|
|
1053
|
+
n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20.
|
|
1054
|
+
"""
|
|
1055
|
+
match type:
|
|
1056
|
+
case "Nx":
|
|
1057
|
+
return self._model_generator._model.members[member_id].axial_array(
|
|
1058
|
+
n_points=n_points, combo_name=combination
|
|
1059
|
+
)
|
|
1060
|
+
case "Vy":
|
|
1061
|
+
return self._model_generator._model.members[member_id].shear_array(
|
|
1062
|
+
"Fy", n_points=n_points, combo_name=combination
|
|
1063
|
+
)
|
|
1064
|
+
case "Vz":
|
|
1065
|
+
return self._model_generator._model.members[member_id].shear_array(
|
|
1066
|
+
"Fz", n_points=n_points, combo_name=combination
|
|
1067
|
+
)
|
|
1068
|
+
case "Mx":
|
|
1069
|
+
return self._model_generator._model.members[member_id].torque_array(
|
|
1070
|
+
n_points=n_points, combo_name=combination
|
|
1071
|
+
)
|
|
1072
|
+
case "My":
|
|
1073
|
+
return self._model_generator._model.members[member_id].moment_array(
|
|
1074
|
+
"My", n_points=n_points, combo_name=combination
|
|
1075
|
+
)
|
|
1076
|
+
case "Mz":
|
|
1077
|
+
return self._model_generator._model.members[member_id].moment_array(
|
|
1078
|
+
"Mz", n_points=n_points, combo_name=combination
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
def get_min_max_internal_force(self, member_id: str, combination: str) -> np.array:
|
|
1082
|
+
"""Retourne le maximum et minimum des efforts internes d'une membrure donnée.
|
|
1083
|
+
|
|
1084
|
+
Args:
|
|
1085
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1086
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1087
|
+
"""
|
|
1088
|
+
dict_internal_forces = {}
|
|
1089
|
+
for type in ("Nx", "Vy", "Vz", "Mx", "My", "Mz"):
|
|
1090
|
+
if type == "Nx":
|
|
1091
|
+
max = self._model_generator._model.members[member_id].max_axial(
|
|
1092
|
+
combo_name=combination
|
|
1093
|
+
)
|
|
1094
|
+
min = self._model_generator._model.members[member_id].min_axial(
|
|
1095
|
+
combo_name=combination
|
|
1096
|
+
)
|
|
1097
|
+
elif type == "Vy":
|
|
1098
|
+
max = self._model_generator._model.members[member_id].max_shear(
|
|
1099
|
+
"Fy", combo_name=combination
|
|
1100
|
+
)
|
|
1101
|
+
min = self._model_generator._model.members[member_id].min_shear(
|
|
1102
|
+
"Fy", combo_name=combination
|
|
1103
|
+
)
|
|
1104
|
+
elif type == "Vz":
|
|
1105
|
+
max = self._model_generator._model.members[member_id].max_shear(
|
|
1106
|
+
"Fz", combo_name=combination
|
|
1107
|
+
)
|
|
1108
|
+
min = self._model_generator._model.members[member_id].min_shear(
|
|
1109
|
+
"Fz", combo_name=combination
|
|
1110
|
+
)
|
|
1111
|
+
elif type == "Mx":
|
|
1112
|
+
max = self._model_generator._model.members[member_id].max_torque(
|
|
1113
|
+
combo_name=combination
|
|
1114
|
+
)
|
|
1115
|
+
min = self._model_generator._model.members[member_id].min_torque(
|
|
1116
|
+
combo_name=combination
|
|
1117
|
+
)
|
|
1118
|
+
elif type == "My":
|
|
1119
|
+
max = self._model_generator._model.members[member_id].max_moment(
|
|
1120
|
+
"My", combo_name=combination
|
|
1121
|
+
)
|
|
1122
|
+
min = self._model_generator._model.members[member_id].min_moment(
|
|
1123
|
+
"My", combo_name=combination
|
|
1124
|
+
)
|
|
1125
|
+
elif type == "Mz":
|
|
1126
|
+
max = self._model_generator._model.members[member_id].max_moment(
|
|
1127
|
+
"Mz", combo_name=combination
|
|
1128
|
+
)
|
|
1129
|
+
min = self._model_generator._model.members[member_id].min_moment(
|
|
1130
|
+
"Mz", combo_name=combination
|
|
1131
|
+
)
|
|
1132
|
+
if "M" in type:
|
|
1133
|
+
si_unit = si.N * si.mm
|
|
1134
|
+
else:
|
|
1135
|
+
si_unit = si.N
|
|
1136
|
+
dict_internal_forces[type] = {"Min": min * si_unit, "Max": max * si_unit}
|
|
1137
|
+
return dict_internal_forces
|
|
1138
|
+
|
|
1139
|
+
def get_absolute_internal_force(self, member_id: str, combination: str, type: str = ("Nx", "Vy", "Vz", "Mx", "My", "Mz")):
|
|
1140
|
+
"""Retourne la valeur de d'effort absolue pour le type d'effort donné.
|
|
1141
|
+
|
|
1142
|
+
Args:
|
|
1143
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1144
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1145
|
+
type (str): Le type d'effort interne à retourner. Defaults to ("Nx", "Vy", "Vz", "Mx", "My", "Mz").
|
|
1146
|
+
"""
|
|
1147
|
+
ei = self.get_min_max_internal_force(member_id, combination)
|
|
1148
|
+
return max(abs(ei[type]["Min"]), ei[type]["Max"])
|
|
1149
|
+
|
|
1150
|
+
def show_internal_force_of_member(
|
|
1151
|
+
self,
|
|
1152
|
+
member_id: str,
|
|
1153
|
+
combination: str,
|
|
1154
|
+
type: str = ("Nx", "Vy", "Vz", "Mx", "My", "Mz"),
|
|
1155
|
+
n_points: int = 20,
|
|
1156
|
+
screenshot: bool = ("False", "True"),
|
|
1157
|
+
filepath: str=None,
|
|
1158
|
+
):
|
|
1159
|
+
"""Retourne un graphique des efforts internes d'une membrure suivant le type d'effort et la combinaison choisit.
|
|
1160
|
+
|
|
1161
|
+
Args:
|
|
1162
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1163
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1164
|
+
type (str): Le type d'effort interne à retourner. Defaults to ("Nx", "Vy", "Vz", "Mx", "My", "Mz").
|
|
1165
|
+
n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20.
|
|
1166
|
+
screenshot (bool, optional): Définit si l'on souhaite enregistrer un screenshot du graph, si oui alors True. Defaults to False
|
|
1167
|
+
filepath (str, optional): Si le screenshot est souhaité, on peut rentrer un chemin de sauvegarde automatique, ce qui permettra je générer dynamiquement un rapport.
|
|
1168
|
+
Sinon le logiciel vous demande à chaque fois le chemin de sauvegarde.
|
|
1169
|
+
"""
|
|
1170
|
+
x_label = "Longueur (mm)"
|
|
1171
|
+
x_value, y_value = self.get_internal_force(
|
|
1172
|
+
member_id, combination, type, n_points
|
|
1173
|
+
)
|
|
1174
|
+
if type.startswith("N"):
|
|
1175
|
+
title = f"Barre {member_id}: Effort normal {type}"
|
|
1176
|
+
color = "orange"
|
|
1177
|
+
y_label = "Effort (kN)"
|
|
1178
|
+
y_value = y_value * 10**-3
|
|
1179
|
+
elif type.startswith("V"):
|
|
1180
|
+
title = f"Barre {member_id}: Cisaillement {type}"
|
|
1181
|
+
color = "b"
|
|
1182
|
+
y_label = "Effort (kN)"
|
|
1183
|
+
y_value = y_value * 10**-3
|
|
1184
|
+
else:
|
|
1185
|
+
title = f"Barre {member_id}: Moments {type}"
|
|
1186
|
+
color = "r"
|
|
1187
|
+
y_label = "Effort (kN.m)"
|
|
1188
|
+
y_value = y_value * 10**-6
|
|
1189
|
+
return self._base_graph(
|
|
1190
|
+
title,
|
|
1191
|
+
combination,
|
|
1192
|
+
x_value,
|
|
1193
|
+
y_value,
|
|
1194
|
+
x_label,
|
|
1195
|
+
y_label,
|
|
1196
|
+
color=color,
|
|
1197
|
+
savefig=screenshot,
|
|
1198
|
+
filepath=filepath
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
def get_deflection(
|
|
1202
|
+
self,
|
|
1203
|
+
member_id: str,
|
|
1204
|
+
combination: str,
|
|
1205
|
+
direction: str = ("dx", "dy", "dz"),
|
|
1206
|
+
n_points: int = 20,
|
|
1207
|
+
) -> np.array:
|
|
1208
|
+
"""Retourne une table des déformation d'une membrure pour la direction locale donnée.
|
|
1209
|
+
|
|
1210
|
+
Args:
|
|
1211
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1212
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1213
|
+
direction (str): La direction locale à retourner. Defaults to ("dx", "dy", "dz").
|
|
1214
|
+
n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20.
|
|
1215
|
+
"""
|
|
1216
|
+
return self._model_generator._model.members[member_id].deflection_array(
|
|
1217
|
+
direction, n_points=n_points, combo_name=combination
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
def get_min_max_deflection(self, member_id: str, combination: str) -> np.array:
|
|
1221
|
+
"""Retourne le maximum et minimum des efforts internes d'une membrure donnée.
|
|
1222
|
+
|
|
1223
|
+
Args:
|
|
1224
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1225
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1226
|
+
"""
|
|
1227
|
+
dict_deflection = {}
|
|
1228
|
+
for type in ("dx", "dy", "dz"):
|
|
1229
|
+
max = self._model_generator._model.members[member_id].max_deflection(
|
|
1230
|
+
type, combo_name=combination
|
|
1231
|
+
)
|
|
1232
|
+
min = self._model_generator._model.members[member_id].min_deflection(
|
|
1233
|
+
type, combo_name=combination
|
|
1234
|
+
)
|
|
1235
|
+
dict_deflection[type] = {"Min": min * si.mm, "Max": max * si.mm}
|
|
1236
|
+
return dict_deflection
|
|
1237
|
+
|
|
1238
|
+
def get_absolute_max_deflection(self, member_id: str, combination: str, direction: str = ("dx", "dy", "dz")):
|
|
1239
|
+
"""Retourne la valeur de déplacement absolue pour la direction de la flèche donnée.
|
|
1240
|
+
|
|
1241
|
+
Args:
|
|
1242
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1243
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1244
|
+
direction (str): La direction locale à retourner. Defaults to ("dx", "dy", "dz").
|
|
1245
|
+
"""
|
|
1246
|
+
deflection = self.get_min_max_deflection(member_id, combination)
|
|
1247
|
+
return max(abs(deflection[direction]["Min"]), deflection[direction]["Max"])
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
def show_deflection_of_member(
|
|
1251
|
+
self,
|
|
1252
|
+
member_id: str,
|
|
1253
|
+
combination: str,
|
|
1254
|
+
direction: str = ("dx", "dy", "dz"),
|
|
1255
|
+
n_points: int = 20,
|
|
1256
|
+
screenshot: bool = ("False", "True"),
|
|
1257
|
+
filepath: str=None,
|
|
1258
|
+
):
|
|
1259
|
+
"""Retourne un graphique des efforts internes d'une membrure suivant le type d'effort et la combinaison choisit.
|
|
1260
|
+
|
|
1261
|
+
Args:
|
|
1262
|
+
member_id (str): Le nom de la membrure à analyser
|
|
1263
|
+
combination (str): Le nom de la combinaison à récupérer
|
|
1264
|
+
direction (str): La direction locale à retourner. Defaults to ("dx", "dy", "dz").
|
|
1265
|
+
n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20.
|
|
1266
|
+
screenshot (bool, optional): Définit si l'on souhaite enregistrer un screenshot du graph, si oui alors True. Defaults to False
|
|
1267
|
+
filepath (str, optional): Si le screenshot est souhaité, on peut rentrer un chemin de sauvegarde automatique, ce qui permettra je générer dynamiquement un rapport.
|
|
1268
|
+
Sinon le logiciel vous demande à chaque fois le chemin de sauvegarde.
|
|
1269
|
+
"""
|
|
1270
|
+
title = f'Barre {member_id}: Flèche {direction}'
|
|
1271
|
+
x_label = "Longueur (mm)"
|
|
1272
|
+
y_label = "Déplacement\n(mm)"
|
|
1273
|
+
color = "g"
|
|
1274
|
+
x_value, y_value = self.get_deflection(
|
|
1275
|
+
member_id, combination, direction, n_points
|
|
1276
|
+
)
|
|
1277
|
+
return self._base_graph(
|
|
1278
|
+
title,
|
|
1279
|
+
combination,
|
|
1280
|
+
x_value,
|
|
1281
|
+
y_value,
|
|
1282
|
+
x_label,
|
|
1283
|
+
y_label,
|
|
1284
|
+
color=color,
|
|
1285
|
+
savefig=screenshot,
|
|
1286
|
+
filepath=filepath
|
|
1287
|
+
)
|
|
1288
|
+
|
|
1289
|
+
|
|
1290
|
+
def show_model(
|
|
1291
|
+
self,
|
|
1292
|
+
combination: str,
|
|
1293
|
+
annotation_size: int = 70,
|
|
1294
|
+
deformed_shape: bool = True,
|
|
1295
|
+
deformed_scale: int = 20,
|
|
1296
|
+
screenshot: bool = ("False", "True"),
|
|
1297
|
+
filepath: str=None,
|
|
1298
|
+
):
|
|
1299
|
+
"""Retourne le model dans un graphique 3D.
|
|
1300
|
+
|
|
1301
|
+
Args:
|
|
1302
|
+
combination (str): Nom de la combinaison à afficher.
|
|
1303
|
+
annotation_size (int, optional): Tailles des annotations. Defaults to 70.
|
|
1304
|
+
deformed_shape (bool, optional): Définit si l'on doit afficher les déformations globales. Defaults to True.
|
|
1305
|
+
deformed_scale (int, optional): Définit l'effet d'échelle des déformations. Defaults to 20.
|
|
1306
|
+
screenshot (bool, optional): Définit si l'on souhaite enregistrer un screenshot du graph, si oui alors True. Defaults to False
|
|
1307
|
+
filepath (str, optional): Si le screenshot est souhaité, on peut rentrer un chemin de sauvegarde automatique, ce qui permettra je générer dynamiquement un rapport.
|
|
1308
|
+
Sinon le logiciel vous demande à chaque fois le chemin de sauvegarde.
|
|
1309
|
+
"""
|
|
1310
|
+
renderer = Renderer(self._model_generator._model)
|
|
1311
|
+
renderer.combo_name = combination
|
|
1312
|
+
renderer.annotation_size = annotation_size
|
|
1313
|
+
renderer.deformed_shape = deformed_shape
|
|
1314
|
+
renderer.deformed_scale = deformed_scale
|
|
1315
|
+
if screenshot:
|
|
1316
|
+
if not filepath:
|
|
1317
|
+
interaction = True
|
|
1318
|
+
filepath = QFileDialog.getSaveFileName(
|
|
1319
|
+
filter="PNG (*.png)",
|
|
1320
|
+
selectedFilter=".png",
|
|
1321
|
+
)[0]
|
|
1322
|
+
QMessageBox.information(
|
|
1323
|
+
None,
|
|
1324
|
+
"Screenshot",
|
|
1325
|
+
"Vous pouvez bouger le modèle pour prendre le screenshot.\nUne fois prêt, cliquer sur Q pour faire le screenshot.",
|
|
1326
|
+
)
|
|
1327
|
+
else:
|
|
1328
|
+
interaction = False
|
|
1329
|
+
renderer.screenshot(filepath, interact=interaction)
|
|
1330
|
+
return filepath
|
|
1331
|
+
else:
|
|
1332
|
+
renderer.render_model()
|
|
1333
|
+
|
|
1334
|
+
def get_global_displacement_of_node(self, node_id: str, combination: str):
|
|
1335
|
+
"""Retourne un dictionnaire des coordonnées de déplacment global d'un noeud dans le model pour la combinaison définit.
|
|
1336
|
+
|
|
1337
|
+
Args:
|
|
1338
|
+
node_id (str): id du noeud à récupérer.
|
|
1339
|
+
combination (str): Nom de la combinaison à analyser.
|
|
1340
|
+
"""
|
|
1341
|
+
DX = self._model_generator._model.nodes[node_id].DX[combination] * si.mm
|
|
1342
|
+
DY = self._model_generator._model.nodes[node_id].DY[combination] * si.mm
|
|
1343
|
+
DZ = self._model_generator._model.nodes[node_id].DZ[combination] * si.mm
|
|
1344
|
+
RX = self._model_generator._model.nodes[node_id].RX[combination]
|
|
1345
|
+
RY = self._model_generator._model.nodes[node_id].RY[combination]
|
|
1346
|
+
RZ = self._model_generator._model.nodes[node_id].RZ[combination]
|
|
1347
|
+
return {"DX": DX, "DY": DY, "DZ": DZ, "RX": RX, "RY": RY, "RZ": RZ}
|
|
1348
|
+
|
|
1349
|
+
def _get_node_reaction(self, node_id: str, combination: str):
|
|
1350
|
+
"""Retourne un dictionnaire des réaction d'un noeud.
|
|
1351
|
+
|
|
1352
|
+
Args:
|
|
1353
|
+
node_id (str): id du noeud à récupérer.
|
|
1354
|
+
combination (str): Nom de la combinaison à analyser.
|
|
1355
|
+
"""
|
|
1356
|
+
FX = self._model_generator._model.nodes[node_id].RxnFX[combination] * si.N
|
|
1357
|
+
FY = self._model_generator._model.nodes[node_id].RxnFY[combination] * si.N
|
|
1358
|
+
FZ = self._model_generator._model.nodes[node_id].RxnFZ[combination] * si.N
|
|
1359
|
+
MX = (
|
|
1360
|
+
self._model_generator._model.nodes[node_id].RxnMX[combination]
|
|
1361
|
+
* si.N
|
|
1362
|
+
* si.mm
|
|
1363
|
+
)
|
|
1364
|
+
MY = (
|
|
1365
|
+
self._model_generator._model.nodes[node_id].RxnMY[combination]
|
|
1366
|
+
* si.N
|
|
1367
|
+
* si.mm
|
|
1368
|
+
)
|
|
1369
|
+
MZ = (
|
|
1370
|
+
self._model_generator._model.nodes[node_id].RxnMZ[combination]
|
|
1371
|
+
* si.N
|
|
1372
|
+
* si.mm
|
|
1373
|
+
)
|
|
1374
|
+
return {"FX": FX, "FY": FY, "FZ": FZ, "MX": MX, "MY": MY, "MZ": MZ}
|
|
1375
|
+
|
|
1376
|
+
def get_supports_reactions(self, combination: str):
|
|
1377
|
+
"""Retourne un dictionnaire des réaction aux appuis.
|
|
1378
|
+
|
|
1379
|
+
Args:
|
|
1380
|
+
combination (str): Nom de la combinaison à analyser.
|
|
1381
|
+
"""
|
|
1382
|
+
reaction = {}
|
|
1383
|
+
supports = self._model_generator.get_all_supports()
|
|
1384
|
+
for support_id, support in supports.items():
|
|
1385
|
+
node_id = support["Noeud"]
|
|
1386
|
+
reaction[support_id] = self._get_node_reaction(node_id, combination)
|
|
1387
|
+
return reaction
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
if __name__ == "__main__":
|
|
1391
|
+
# projet = Projet(num_project="6006.0", commmentaire="c'est mon premier projet")
|
|
1392
|
+
# building = Batiment(h_bat=5, d_bat=15, b_bat=13.1, alpha_toit=15)
|
|
1393
|
+
# print(building.h_bat)
|
|
1394
|
+
beam_gen = Model_generator()
|
|
1395
|
+
node1 = beam_gen.add_node(0, 0, 0)
|
|
1396
|
+
node2 = beam_gen.add_node(5000, 0, 0)
|
|
1397
|
+
inertie = beam_gen.inertie(100, 200, "Rectangulaire")
|
|
1398
|
+
aire = beam_gen.aire(100, 200, "Rectangulaire")
|
|
1399
|
+
memb1 = beam_gen.add_member(node1, node2, aire, inertie["Iy"], inertie["Iz"])
|
|
1400
|
+
beam_gen.add_material_by_class(memb1, "C24")
|
|
1401
|
+
|
|
1402
|
+
listdeplacement = [
|
|
1403
|
+
["N1", "Rotule YZ", 0],
|
|
1404
|
+
["N2", "Rotule YZ", 0],
|
|
1405
|
+
]
|
|
1406
|
+
beam_gen.create_supports_by_list(listdeplacement)
|
|
1407
|
+
beam_gen.export_data()
|
|
1408
|
+
print(beam_gen._data)
|