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.

Files changed (53) hide show
  1. ourocode/__init__.py +0 -0
  2. ourocode/__version__.py +1 -0
  3. ourocode/data/caracteristique_meca_acier.csv +33 -0
  4. ourocode/data/caracteristique_meca_bois.csv +59 -0
  5. ourocode/data/carte_action_region.csv +36722 -0
  6. ourocode/data/coeff_psy.csv +13 -0
  7. ourocode/data/exploitation.csv +17 -0
  8. ourocode/data/gammaM.csv +14 -0
  9. ourocode/data/kdef.csv +11 -0
  10. ourocode/data/kfi.csv +10 -0
  11. ourocode/data/kmod.csv +16 -0
  12. ourocode/data/limite_fleche.csv +9 -0
  13. ourocode/data/qualite_acier.csv +18 -0
  14. ourocode/data/screenshot/C90_def.png +0 -0
  15. ourocode/data/screenshot/Kv_def.png +0 -0
  16. ourocode/data/screenshot/fleche.png +0 -0
  17. ourocode/data/screenshot/sign_convention.png +0 -0
  18. ourocode/data/section_boulon.csv +12 -0
  19. ourocode/data/vent/vent_Cp_toiture_isolee_1_versant.csv +22 -0
  20. ourocode/data/vent/vent_Cp_toiture_isolee_1_versant.png +0 -0
  21. ourocode/data/vent/vent_Cp_toiture_isolee_2_versants.csv +31 -0
  22. ourocode/data/vent/vent_Cp_toiture_isolee_2_versants.png +0 -0
  23. ourocode/data/vent/vent_Cpe_mur_verticaux.csv +11 -0
  24. ourocode/data/vent/vent_Cpe_mur_verticaux.png +0 -0
  25. ourocode/data/vent/vent_Cpe_toiture_1_versant.png +0 -0
  26. ourocode/data/vent/vent_Cpe_toiture_1_versant_0_degres.csv +13 -0
  27. ourocode/data/vent/vent_Cpe_toiture_1_versant_180_degres.csv +13 -0
  28. ourocode/data/vent/vent_Cpe_toiture_1_versant_90_degres.csv +13 -0
  29. ourocode/data/vent/vent_Cpe_toiture_2_versants.png +0 -0
  30. ourocode/data/vent/vent_Cpe_toiture_2_versants_0_degres.csv +21 -0
  31. ourocode/data/vent/vent_Cpe_toiture_2_versants_90_degres.csv +21 -0
  32. ourocode/data/vent/vent_Cpe_toiture_terrasse.csv +21 -0
  33. ourocode/data/vent/vent_Cpe_toiture_terrasse.png +0 -0
  34. ourocode/data/vent/vent_Ffr.png +0 -0
  35. ourocode/eurocode/A0_Projet.py +1408 -0
  36. ourocode/eurocode/EC0_Combinaison.py +1763 -0
  37. ourocode/eurocode/EC1_Exploitation.py +103 -0
  38. ourocode/eurocode/EC1_Neige.py +385 -0
  39. ourocode/eurocode/EC1_Vent.py +1388 -0
  40. ourocode/eurocode/EC3_Assemblage.py +466 -0
  41. ourocode/eurocode/EC3_Element_droit.py +302 -0
  42. ourocode/eurocode/EC5_Assemblage.py +1760 -0
  43. ourocode/eurocode/EC5_Element_droit.py +1740 -0
  44. ourocode/eurocode/EC5_Feu.py +922 -0
  45. ourocode/eurocode/EC8_Sismique.py +2 -0
  46. ourocode/eurocode/__init__.py +0 -0
  47. ourocode/eurocode/logical.py +83 -0
  48. ourocode/eurocode/objet.py +263 -0
  49. ourocode-1.6.0.dist-info/METADATA +331 -0
  50. ourocode-1.6.0.dist-info/RECORD +53 -0
  51. ourocode-1.6.0.dist-info/WHEEL +5 -0
  52. ourocode-1.6.0.dist-info/licenses/LICENSE +201 -0
  53. 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)