diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..055bcc2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: pip install ruff + - run: ruff check ourocode/ tests/ + + test: + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies + run: | + pip install --upgrade pip + pip install -e ".[test]" + - name: Run tests + run: pytest tests/ -v --tb=short diff --git a/.windsurf/agents.md b/.windsurf/agents.md new file mode 100644 index 0000000..78f19db --- /dev/null +++ b/.windsurf/agents.md @@ -0,0 +1,227 @@ +# Ourocode — Contexte agent Cascade + +## Vue d'ensemble du projet + +**Ourocode** est une bibliothèque Python (v1.9.0, Python ≥ 3.12) de calcul de structure selon les Eurocodes avec Annexes Nationales Françaises. +Auteur : Anthony PARISOT — [OUREA STRUCTURE](https://ourea-structure.fr) +Licence : Apache 2.0 + +--- + +## Architecture du code + +``` +ourocode/ +├── ourocode/ +│ ├── __version__.py +│ ├── data/ # Données CSV/JSON de référence normative +│ │ ├── caracteristique_meca_acier.csv +│ │ ├── caracteristique_meca_bois.csv +│ │ ├── caracteristique_meca_panel.csv +│ │ ├── caracteristique_meca_beton.csv +│ │ ├── carte_action_region.csv # Zones neige/vent/sismique par code INSEE +│ │ ├── coeff_psy.csv +│ │ ├── kmod.csv / kdef.csv / kfi.csv / gammaM.csv +│ │ ├── limite_fleche.csv +│ │ ├── exploitation.csv +│ │ ├── section_boulon.csv +│ │ ├── qualite_acier.csv +│ │ ├── sismique/ # Données sismiques (catégories, sols…) +│ │ └── vent/ # Données vent par zones +│ └── eurocode/ +│ ├── objet.py # Classe de base Objet +│ ├── A0_Projet.py # Classes Projet, Batiment, Model_generator +│ ├── EC0_Combinaison.py # EN 1990 – Combinaisons d'actions +│ ├── EC1_Exploitation.py # EN 1991 – Charges d'exploitation +│ ├── EC1_Neige.py # EN 1991 – Action neige +│ ├── EC1_Vent.py # EN 1991 – Action vent +│ ├── EC3_Element_droit.py # EN 1993 – Éléments droits acier +│ ├── EC3_Assemblage.py # EN 1993 – Assemblages acier +│ ├── EC3_Feu.py # EN 1993 – Vérification feu acier +│ ├── EC5_Element_droit.py # EN 1995 – Éléments droits bois +│ ├── EC5_Assemblage.py # EN 1995 – Assemblages bois +│ ├── EC5_CVT.py # EN 1995 – Murs à ossature bois (MOB/CVT) +│ ├── EC5_Feu.py # EN 1995 – Vérification feu bois +│ └── EC8_Sismique.py # EN 1998 – Action sismique +└── tests/ + ├── test_A0_Projet.py + ├── test_EC0_Combinaison.py + ├── test_EC1_Neige.py + ├── test_EC3_Assemblage.py + ├── test_EC3_Element_droit.py + ├── test_EC5_Assemblage.py + ├── test_EC5_CVT.py + ├── test_EC5_Element_droit.py + ├── test_EC5_Feu.py + └── test_EC8_Sismique.py +``` + +--- + +## Hiérarchie des classes (héritage) + +``` +Objet (objet.py) +└── Projet (A0_Projet.py) + ├── Batiment (A0_Projet.py) + │ ├── Neige (EC1_Neige.py) + │ ├── Sismique (EC8_Sismique.py) + │ └── MOB (EC5_CVT.py) + ├── Combinaison (EC0_Combinaison.py) + ├── Barre (EC5_Element_droit.py) + │ ├── Flexion, Traction, Compression, Cisaillement, + │ │ Compression_perpendiculaire, Compression_inclinees, Deformation… + │ └── Feu (EC5_Feu.py) + ├── Plat (EC3_Element_droit.py) ← acier + └── Assemblage (EC5_Assemblage.py) + └── Pointe, Agrafe, Boulon, Tirefond, Broche… +``` + +--- + +## Classe de base : `Objet` + +Toutes les classes héritent de `Objet` (`objet.py`). Points clés : + +- **`PATH_CATALOG`** : résolution automatique du chemin vers `ourocode/data/`. +- **`_data_from_csv(data_file)`** : lecture des CSV de référence via pandas. +- **`_from_parent_class(objet, **kwargs)`** : instanciation d'une sous-classe à partir d'une instance parente existante (pattern central du catalogue). +- **`_from_dict(dictionary)`** : instanciation depuis un dictionnaire. +- **`_assign_handcalcs_value(handcalc_value, args)`** : assigne les résultats `handcalcs` aux attributs d'instance. +- **`_physical_to_dict` / `_dict_to_physical`** : sérialisation/désérialisation des objets `forallpeople.Physical`. +- **`save_data` / `load_data`** : persistence JSON ou CSV (ouvre un `QFileDialog` si pas de chemin fourni). +- **`save_object` / `_open_object`** : sérialisation pickle vers fichier `.oco`. +- **`synthese_taux_travail()` / `_add_synthese_taux_travail()`** : tableau pandas de synthèse des taux de travail (situation normale + incendie). +- **`JUPYTER_DISPLAY`** : booléen de classe, contrôle l'affichage LaTeX dans Jupyter. + +--- + +## Unités : `forallpeople` (si) + +Toutes les grandeurs physiques utilisent `forallpeople` avec l'environnement `"structural"`. + +| Entrée utilisateur | Unité interne | +|--------------------|---------------| +| dimensions (b, h, t) | `si.mm` | +| longueurs de projet | `si.m` | +| forces | `si.kN` ou `si.N` | +| contraintes | `si.MPa` ou `si.N/si.mm**2` | + +La méthode `_convert_unit_physical` gère les conversions entre unités SI. +Toujours fournir les valeurs numériques brutes au constructeur (ex : `b=100` pour 100 mm) — la conversion `* si.mm` est faite **dans** le constructeur. + +--- + +## Génération LaTeX avec `handcalcs` + +Chaque méthode de vérification retourne un tuple `(latex_str, valeur)` via le décorateur `@handcalc`. +Exemple d'usage dans Jupyter : + +```python +from IPython.display import display, Latex +latex_taux, taux = panne_flexion.taux_m_d() +display(Latex(latex_taux)) +``` + +--- + +## Classes et modules clés + +### `A0_Projet.py` — Base projet +- **`Projet`** : définit ingénieur, numéro, adresse, code INSEE, altitude. +- **`Batiment`** : ajoute dimensions (h, d, b), angles de toiture. +- **`Model_generator`** : modèle éléments finis via `PyNiteFEA` (FEModel3D). + +### `EC0_Combinaison.py` — EN 1990 +- **`Combinaison`** : génère les combinaisons ELU_STR, ELU_STR_ACC, ELS_C, ELS_QP à partir d'un `Model_generator`. +- Actions : G, Q, Sn, W+, W-, Sx, Ae. + +### `EC1_Neige.py` — EN 1991-1-3 +- **`Neige`** → hérite de `Batiment`. +- Récupère la zone neige via code INSEE (`carte_action_region.csv`). + +### `EC1_Vent.py` — EN 1991-1-4 +- Classes vent héritant de `Batiment`. + +### `EC3_Element_droit.py` — EN 1993-1-1 +- **`Plat`** : plaque acier (t, h, b, classe_acier S235/S355…, classe transversale 1-3). +- Classe 4 non implémentée → `ValueError`. + +### `EC3_Assemblage.py` — EN 1993 assemblages acier + +### `EC3_Feu.py` — EN 1993 feu + +### `EC5_Element_droit.py` — EN 1995-1-1 +- **`Barre`** : pièce bois (b, h, section, classe C24/GL24h/OSB…, cs 1-3, humidité Hi/Hf). +- Sous-classes de vérification : `Flexion`, `Traction`, `Compression`, `Cisaillement`, `Compression_perpendiculaire`, `Compression_inclinees`, `Deformation`. + +### `EC5_Assemblage.py` — EN 1995 assemblages bois +- **`Assemblage`** : bois/bois ou bois/métal, prend deux objets `Barre` ou `Plat`. +- Sous-classes : `Pointe`, `Agrafe`, `Boulon`, `Tirefond`, `Broche`. + +### `EC5_CVT.py` — EN 1995 §9.2.4 Murs à ossature bois +- **`MOB`** → hérite de `Batiment`. +- Méthode A : systèmes de murs, panneaux, connecteurs, calcul efforts CVT + déformations. + +### `EC5_Feu.py` — EN 1995-1-2 +- **`Feu`** → hérite de `Barre`. +- Vitesse de carbonisation, section résiduelle, vérifications ELU incendie. + +### `EC8_Sismique.py` — EN 1998 +- **`Sismique`** → hérite de `Batiment`. +- Méthode des forces latérales, classes de ductilité (DCL/DCM/DCH), spectre de réponse. + +--- + +## Données de référence (`ourocode/data/`) + +| Fichier | Contenu | +|---------|---------| +| `caracteristique_meca_bois.csv` | fm, ft, fc, E… pour C14→GL32h, LVL, OSB | +| `caracteristique_meca_panel.csv` | Caractéristiques panneaux OSB/CP | +| `caracteristique_meca_acier.csv` | fy, fu pour S235, S275, S355, S420, S460 | +| `carte_action_region.csv` | Zone neige/vent/sismique par code INSEE (5 chiffres) | +| `kmod.csv` | Coefficients kmod par classe de service et durée de chargement | +| `kdef.csv` | Coefficients de fluage kdef | +| `gammaM.csv` | Coefficients partiels γM par type de bois | +| `limite_fleche.csv` | Limites de flèche par type d'élément | +| `sismique/` | Catégories d'importance, classes de sol | + +--- + +## Dépendances principales + +| Package | Usage | +|---------|-------| +| `forallpeople` | Calcul avec unités physiques SI | +| `handcalcs` | Génération de LaTeX depuis du Python | +| `PyNiteFEA` | Modèle éléments finis 3D | +| `pyvista` + `PySide6` | Visualisation 3D et dialogues fichiers | +| `pandas` | Lecture des CSV de référence | +| `Pillow` | Affichage des images de schémas | + +--- + +## Tests + +```bash +pytest --cov=. --cov-report html +``` + +Fichiers de test dans `tests/`, nommés `test_.py`. +Couvrent les modules principaux. Pas de test dédié à `EC1_Vent.py` ni `EC3_Feu.py` pour l'instant. + +--- + +## Conventions et patterns importants + +1. **Pattern `_from_parent_class`** : pour enchaîner les vérifications, on passe un objet déjà instancié à une sous-classe : + ```python + barre = Barre(b=100, h=200, classe="C24", cs=2, ...) + flexion = Flexion._from_parent_class(barre, lo_rel_y=5000, ...) + ``` +2. **`**kwargs` remontants** : tous les constructeurs acceptent `**kwargs` transmis à `super().__init__()`, ce qui permet de passer les paramètres `Projet` directement lors de la création d'une `Barre`. +3. **Arguments à choix multiples** : les paramètres dont la valeur est un tuple/liste dans la signature sont des énumérations de valeurs acceptées (ex : `section=["Rectangulaire","Circulaire"]`). +4. **Unités en entrée** : toujours des valeurs numériques brutes, la conversion est faite dans le constructeur (`self.b = b * si.mm`). +5. **Résultats des méthodes de vérification** : tuple `(latex_str, valeur_numérique)`. +6. **`QFileDialog`** est requis pour `save_data`/`load_data` sans argument `path` → nécessite un environnement Qt actif. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..66fe10c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +Toutes les modifications notables de ce projet seront documentées dans ce fichier. + +Le format est basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), +et ce projet adhère au [Semantic Versioning](https://semver.org/lang/fr/). + +## [Unreleased] + +### Changed +- PySide6, PyNiteFEA et pyvista sont désormais des dépendances optionnelles (`pip install "ourocode[full]"`). +- Les messages utilisateur (`print`) convertis en `warnings.warn` dans EC5_Element_droit, EC5_Feu, EC5_Assemblage. +- Import wildcard `from math import *` supprimé dans EC1_Vent.py. + +### Fixed +- Suppression des `print` de debug dans EC5_Element_droit, EC1_Vent, EC3_Feu, EC5_Assemblage. +- Correction du shadow de la variable `si` (module forallpeople) dans `objet.py` `_add_synthese_taux_travail`. +- Remplacement du `bare except` par `except ImportError` dans `objet.py`. +- Correction des badges du README (licence, CI, release). + +### Added +- CI GitHub Actions : lint (ruff) + tests (pytest) sur push/PR vers main. +- CHANGELOG.md. +- Documentation des dépendances optionnelles dans le README. + +## [1.9.0] - 2025 + +### Added +- Module EC3_Feu : calcul température acier au feu selon EN 1993-1-2. +- Module EC8_Sismique : spectre élastique selon EN 1998-1. diff --git a/README.md b/README.md index c303d0c..e1586a3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # 📐 Ourocode - Bibliothèque Python pour le calcul de structure selon les Eurocodes -[![License: Apache License 2.0](https://img.shields.io/badge/License-A-blue.svg)](LICENSE) - -[![Tests](https://img.shields.io/github/v/release/AnthonyPrst/ourocode)](https://github.com/AnthonyPrst/ourocode/pyptoject.toml) -[![Coverage](https://img.shields.io/codecov/c/github/ton-org/eurocode)](https://codecov.io/gh/ton-org/ourocode) +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) +[![CI](https://github.com/AnthonyPrst/ourocode/actions/workflows/ci.yml/badge.svg)](https://github.com/AnthonyPrst/ourocode/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/AnthonyPrst/ourocode)](https://github.com/AnthonyPrst/ourocode/releases) --- @@ -40,6 +39,13 @@ pip install ourocode pip install git+https://github.com/AnthonyPrst/ourocode.git ``` +> Avec les dépendances optionnelles (GUI PySide6 et/ou MEF Pynite) : +```bash +pip install "ourocode[full]" # GUI + MEF +pip install "ourocode[gui]" # PySide6 uniquement +pip install "ourocode[mef]" # PyNiteFEA + pyvista uniquement +``` + --- ## ✨ Fonctionnalités diff --git a/ourocode/__version__.py b/ourocode/__version__.py index 0a0a43a..7776e34 100644 --- a/ourocode/__version__.py +++ b/ourocode/__version__.py @@ -1 +1,18 @@ -__version__ = "1.9.0" +from pathlib import Path + +try: + import tomllib +except ImportError: + try: + import tomli as tomllib + except ImportError: + tomllib = None + +if tomllib is not None: + _pyproject_path = Path(__file__).parent.parent / "pyproject.toml" + with open(_pyproject_path, "rb") as f: + _data = tomllib.load(f) + __version__ = _data["project"]["version"] +else: + # Fallback si tomllib/tomli non disponible (Python < 3.11 sans tomli) + __version__ = "1.9.0" diff --git a/ourocode/eurocode/A0_Projet.py b/ourocode/eurocode/A0_Projet.py index b331826..5876dfd 100644 --- a/ourocode/eurocode/A0_Projet.py +++ b/ourocode/eurocode/A0_Projet.py @@ -6,11 +6,8 @@ from matplotlib import pyplot as plt from PIL import Image -from PySide6.QtWidgets import QFileDialog, QMessageBox import forallpeople as si from handcalcs.decorator import handcalc -from Pynite import FEModel3D -from Pynite.Rendering import Renderer # sys.path.append(os.path.join(os.getcwd(), "ourocode")) # from eurocode.objet import Objet @@ -19,6 +16,13 @@ class Projet(Objet): + """Classe définissant les informations générales d'un projet de structure. + + Cette classe est la racine de la hiérarchie des objets ourocode. + Elle contient les informations administratives et géographiques du projet, + nécessaires pour l'application des annexes nationales françaises des Eurocodes. + """ + DICO_COMBI_ACTION = { "Permanente G": "G", "Exploitation Q": "Q", @@ -52,17 +56,30 @@ def __init__( alt: si.m = 0, **kwargs, ): - """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. + """Initialise un projet avec ses informations générales. + + Cette classe définit le contexte du projet, indispensable pour + l'application correcte des annexes nationales françaises des Eurocodes. + Tous les objets de calcul (éléments de structure, charges, etc.) + héritent indirectement de cette classe. Args: - ingenieur (str, optional): nom de l'ingénieur Defaults to None. - num_project (str, optional): numéro du projet. Defaults to None. - name (str, optional): nom du projet. Defaults to None. - adresse (str, optional): adresse du projet. Defaults to None. - region (int, optional): numéro INSEE départementale du projet en 5 chiffres. Defaults to None. - pays (str, optional): pays ou ce situe le projet. Defaults to "France". - Attention, ce package est conçu pour être utilisé en France, il n'intègre que les annexes nationales Françaises. - alt (int, optional): altitude du projet en m. Defaults to 0. + ingenieur (str, optional): Nom de l'ingénieur responsable. + Defaults to None. + num_project (str, optional): Numéro de référence du projet. + Defaults to None. + name (str, optional): Nom ou désignation du projet. + Defaults to None. + adresse (str, optional): Adresse géographique du chantier. + Defaults to None. + code_INSEE (int, optional): Code INSEE à 5 chiffres du département + ou de la commune pour les données climatiques. Defaults to None. + pays (str, optional): Pays où se situe le projet. Defaults to "France". + Attention : ce package intègre uniquement les annexes nationales + françaises des Eurocodes. + alt (si.m, optional): Altitude du projet en mètres, utilisée pour + le calcul de la neige et du vent. Defaults to 0. + **kwargs: Arguments supplémentaires ajoutés dynamiquement. """ super().__init__() for key, val in kwargs.items(): @@ -76,7 +93,18 @@ def __init__( self.alt = alt * si.m class Batiment(Projet): + """Classe définissant la géométrie d'un bâtiment pour les calculs de structure. + + Cette classe décrit les dimensions principales du bâtiment et les + caractéristiques de sa toiture, nécessaires pour le calcul des + charges climatiques (neige, vent) et sismiques. + + Attributs de classe: + ETAGE (tuple): Liste des niveaux courants (RDC à Toiture). + """ + ETAGE = ("RDC", "R+1", "R+2", "R+3", "R+4", "Toiture") + def __init__( self, h_bat: si.m, @@ -87,16 +115,22 @@ def __init__( *args, **kwargs, ): - """ - Créer une classe Batiment héritée de Projet, cette classe définit les dimension du bâtiment + """Initialise les dimensions du bâtiment. Args: - h_bat (float): hauteur du bâtiment en m depuis le soubassement rigide - ou les fondations dans le cas d'un calcul sismique. - d_bat (float): largeur du bâtiment en m. - b_bat (float): longueur du bâtiment en m. - alpha_toit (float): angle de toiture en ° du versant 1. - alpha_toit2 (float): angle de toiture en ° du versant 2 si il existe sinon 0. + h_bat (si.m): Hauteur totale du bâtiment en mètres, mesurée depuis + le soubassement rigide ou les fondations (référence sismique). + d_bat (si.m): Largeur du bâtiment en mètres (dimension perpendiculaire + au vent dominant pour le calcul du vent). + b_bat (si.m): Longueur du bâtiment en mètres. + alpha_toit (float): Pente du premier versant de toiture en degrés. + 0° pour un toit plat, valeur positive pour un versant. + alpha_toit2 (float, optional): Pente du second versant pour les + toits à deux pans (0° si toit à un seul versant ou plat). + Defaults to 0. + *args: Arguments positionnels transmis à la classe parent Projet. + **kwargs: Arguments nommés transmis à la classe parent Projet + (ingenieur, code_INSEE, alt, etc.). """ super().__init__(*args, **kwargs) self.h_bat = h_bat * si.m @@ -107,6 +141,23 @@ def __init__( class Model_generator(Projet): + """Générateur de modèles de calcul par éléments finis (MEF). + + Cette classe permet de construire un modèle de structure complet pour + le calcul par éléments finis avec Pynite. Elle gère la définition des + nœuds, barres, sections, matériaux, appuis et chargements. + + Le flux de travail typique est : + 1. Définir les sections avec `add_section` + 2. Définir les matériaux avec `add_material_by_class` ou `add_material_by_mechanical_properties` + 3. Créer les nœuds avec `add_node` + 4. Créer les barres avec `add_member` + 5. Définir les appuis avec `add_support` ou `add_support_spring` + 6. Appliquer les charges avec `create_dist_load` et `create_point_load` + 7. Lancer les combinaisons via la classe `Combinaison` + 8. Analyser les résultats avec `Model_result` + """ + ACTION = ( "Permanente G", "Exploitation Q", @@ -122,18 +173,11 @@ class Model_generator(Projet): )[2:] def __init__(self, *args, **kwargs): - """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 - puis une modélisation MEF après la combinaison des dites charges à l'EC0. - - Voici les étapes de modélisation: - 1) Créer les sections avec la méthode "add_section" - 2) Créer les matériaux avec la méthode "add_material" - 3) Créer les noeuds avec la méthode "add_node" - 4) Créer les barres avec la méthode "add_member" - 5) Créer les appuis avec la méthode "create_support" - 6) Créer les charges sur les barres avec les méthodes "create_dist_load" et "create_point_load" - 7) Transmettre cette classe dans l'argument model_generator de la classe Combinaison du module EC0_Combinaison pour le calcul des combinaisons. - 8) Récupérer les efforts internes , les déformations et afficher les graphiques associés avec les méthodes correspondante de la classe Model_result du même module. + """Initialise un générateur de modèle MEF vide. + + Args: + *args: Arguments positionnels transmis à Projet. + **kwargs: Arguments nommés transmis à Projet. """ super().__init__(*args, **kwargs) self._data = { @@ -147,14 +191,29 @@ def __init__(self, *args, **kwargs): self._model = None def get_all_data(self) -> dict: - """Retourne l'ensemble des données du model""" + """Retourne l'ensemble des données du modèle MEF. + + Returns: + dict: Dictionnaire contenant toutes les données structurées : + - "nodes": dictionnaire des nœuds + - "sections": dictionnaire des sections + - "materials": dictionnaire des matériaux + - "members": dictionnaire des barres + - "supports": dictionnaire des appuis (classiques et ressorts) + - "loads": dictionnaire des chargements + """ return self._data def export_data(self): - """Export les données du model au format JSON + """Exporte les données du modèle au format JSON via boîte de dialogue. - Args: - filename (str, optional): nom du fichier à créer. Defaults to "model.json". + Ouvre une boîte de dialogue Qt pour choisir l'emplacement du fichier JSON. + Le fichier contient l'intégralité des données du modèle (nœuds, barres, + matériaux, sections, appuis et charges). + + Note: + L'export est utile pour sauvegarder un modèle ou le transférer + vers une autre application. """ import json from PySide6.QtWidgets import QFileDialog, QApplication @@ -170,7 +229,15 @@ def export_data(self): json.dump(self._data, f, indent=4) def show_sign_convention(self): - """Affiche l'image de la convention de signe d'une barre'""" + """Affiche l'image de la convention de signe pour les efforts sur barre. + + Ouvre une fenêtre affichant le schéma de la convention de signes + utilisée par Pynite pour les efforts internes (efforts normaux, + tranchants, moments fléchissants) et les déplacements. + + Note: + La convention de signe suit le repère local de chaque barre. + """ file = os.path.join( self.PATH_CATALOG, "data", "screenshot", "sign_convention.png" ) @@ -180,12 +247,24 @@ def show_sign_convention(self): ################## Noeud ################## def add_node(self, X: si.mm, Y: si.mm, Z: si.mm, comment: str = None) -> str: - """Ajoute un noeud au model MEF + """Ajoute un nœud au modèle MEF. + + Crée un nœud dans le système de coordonnées globales (X, Y, Z). + L'identifiant est généré automatiquement sous la forme "N1", "N2", etc. Args: - X (int): position en X global en mm - Y (int): position en Y global en mm - Z (int): position en Z global en mm + X (si.mm): Coordonnée X dans le repère global, en millimètres. + Y (si.mm): Coordonnée Y dans le repère global, en millimètres. + Z (si.mm): Coordonnée Z dans le repère global, en millimètres. + comment (str, optional): Commentaire descriptif pour le nœud. + + Returns: + str: Identifiant unique du nœud créé (ex: "N1"). + + Exemple: + >>> model = Model_generator() + >>> n1 = model.add_node(0, 0, 0, comment="Appui gauche") + >>> n2 = model.add_node(5000, 0, 0, comment="Appui droit") """ node_id = "N" + str(len(self._data["nodes"]) + 1) self._data["nodes"][node_id] = { @@ -218,17 +297,28 @@ def _add_nodes_to_model(self): return self._model.nodes def get_node(self, node_id: str) -> dict: - """Retourne les coordonnée du noeud + """Retourne les coordonnées et informations d'un nœud. Args: - node_id (str): id du noeud à récupérer + node_id (str): Identifiant du nœud à récupérer (ex: "N1"). Returns: - dict: dictionnaire contenant les coordonnées du noeud + dict: Dictionnaire contenant : + - "X", "Y", "Z": coordonnées avec unités (si.mm) + - "Commentaire": texte descriptif (ou None) + + Raises: + KeyError: Si le nœud n'existe pas. """ return self._data["nodes"][node_id] def get_all_nodes(self) -> dict: + """Retourne l'ensemble des nœuds du modèle. + + Returns: + dict: Dictionnaire de tous les nœuds avec leurs coordonnées, + indexé par les identifiants de nœuds. + """ return self._data["nodes"] ################## Barre ################## @@ -311,20 +401,37 @@ def add_member( name: str = None, comment: str = None, ): - """Ajoute une poutre au model MEF + """Ajoute une barre (poutre ou colonne) au modèle MEF. + + Crée une barre entre deux nœuds existants, avec un matériau et une section + prédéfinis. La longueur est calculée automatiquement d'après les coordonnées. Args: - node1 (str): id du noeud 1 - node2 (str): id du noeud 2 - material (str): id du matériaux - section (str): id de la section - poids_propre (bool, optional): Défini si le poids propre de la poutre doit être généré. Defaults to True. - rotation (float, optional): angle de rotation de la poutre en °. Defaults to 0. - tension_only (bool, optional): si True, la poutre ne peut que subir des efforts de traction. Defaults to False. - compression_only (bool, optional): si True, la poutre ne peut que subir des efforts de compression. Defaults to False. - name (str, optional): Nom de la membrure (doit être unique!), si vous remplissez cet argument alors c'est le nom de la membrure, - sinon la fonction en génère un automatique. - comment (str, optional): commentaire sur la poutre. Defaults to None. + node1 (str): Identifiant du nœud de départ (ex: "N1"). + node2 (str): Identifiant du nœud d'arrivée (ex: "N2"). + material (str): Identifiant du matériau (créé via add_material_by_class). + section (str): Identifiant de la section (créé via add_section). + poids_propre (bool, optional): Si True, génère automatiquement une + charge répartie correspondant au poids propre de la barre. + Defaults to True. + rotation (float, optional): Angle de rotation de la section en degrés + autour de l'axe longitudinal. Defaults to 0. + tension_only (bool, optional): Si True, la barre travaille uniquement + en traction (ex: tirant). Defaults to False. + compression_only (bool, optional): Si True, la barre travaille uniquement + en compression (ex: étai). Defaults to False. + name (str, optional): Nom personnalisé de la barre (doit être unique). + Si None, un identifiant automatique "M1", "M2"... est généré. + comment (str, optional): Commentaire descriptif pour la barre. + + Returns: + str: Identifiant unique de la barre créée. + + Raises: + KeyError: Si les nœuds, le matériau ou la section n'existent pas. + + Note: + Les arguments tension_only et compression_only sont mutuellement exclusifs. """ if name: member_id = name @@ -432,31 +539,71 @@ def _add_members_to_model(self): return self._model.members def get_member(self, member_id: str) -> dict: - """Retourne une membrure par son id + """Retourne les informations d'une barre par son identifiant. Args: - member_id (str): id de la membrure à récupérer + member_id (str): Identifiant de la barre à récupérer (ex: "M1"). + + Returns: + dict: Dictionnaire contenant les propriétés de la barre : + - "Noeuds": liste [node1_id, node2_id] + - "Longueur": longueur calculée en mm + - "Section": identifiant de la section + - "Matériaux": identifiant du matériau + - "Rotation": angle de rotation en degrés + - "Relaxation": dictionnaire des relâchements + + Raises: + KeyError: Si la barre n'existe pas. """ return self._data["members"][member_id] - def get_member_length(self, member_id: str): - """Retourne la longueur du membrure par son id + def get_member_length(self, member_id: str) -> si.mm: + """Retourne la longueur d'une barre par son identifiant. Args: - member_id (str): id de la membrure à récupérer + member_id (str): Identifiant de la barre (ex: "M1"). + + Returns: + si.mm: Longueur de la barre avec unité. """ return self._data["members"][member_id]["Longueur"] def get_all_members(self) -> dict: + """Retourne l'ensemble des barres du modèle. + + Returns: + dict: Dictionnaire de toutes les barres, indexé par leurs identifiants. + """ return self._data["members"] ################## Matériaux ################## def add_material_by_class(self, classe: str = CLASSE_WOOD) -> str: - """Ajoute un matériau bois au model MEF par sa classe de résitance. + """Ajoute un matériau bois au modèle par sa classe de résistance. + + Charge les caractéristiques mécaniques depuis les données normatives + (caracteristique_meca_bois.csv) et crée le matériau avec : + - Module de Young E (E0mean) + - Module de cisaillement G (Gmoy) + - Coefficient de Poisson nu (calculé) + - Masse volumique rho (rhomean) Args: - classe (str): classe du matériau ex: "C24", "GL24h". + classe (str): Classe de résistance du bois selon l'EC5. + Valeurs courantes : "C14", "C16", "C18", "C20", "C24", "C27", "C30", + "D30", "D35", "D40", "D50", "D60", "D70", + "GL20h", "GL24h", "GL28h", "GL32h", etc. + + Returns: + str: Identifiant du matériau créé (égal à la classe fournie). + + Raises: + KeyError: Si la classe n'existe pas dans la base de données. + + Note: + Si le matériau existe déjà, la méthode retourne simplement son identifiant + sans recréer les propriétés. """ if not self._data["materials"].get(classe): data_csv_meca = self._data_from_csv("caracteristique_meca_bois.csv") @@ -481,14 +628,28 @@ def add_material_by_mechanical_properties( nu: float, rho: float, ): - """Ajoute un matériau à une barre par ces caractéristiques mécaniques. + """Ajoute un matériau défini manuellement par ses propriétés mécaniques. + + Permet de créer un matériau personnalisé en spécifiant directement + les caractéristiques mécaniques, utile pour les matériaux non standard + ou les matériaux autres que le bois. Args: - name (str): nom du matériau - 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 ! - G (float): Module de cisaillement en MPa - nu (float): Coefficient de Poisson - rho (float): Masse volumique en kg/m3 + name (str): Nom unique du matériau. + E (si.MPa): Module de Young (module d'élasticité longitudinal). + Important : fournir E0mean, pas E0mean,fin (le facteur de fluage + sera appliqué automatiquement si nécessaire par ailleurs). + G (si.MPa): Module de cisaillement (module d'élasticité transversal). + nu (float): Coefficient de Poisson (rapport des déformations transversale + et longitudinale). + rho (float): Masse volumique en kg/m³. + + Returns: + str: Identifiant du matériau créé (égal au nom fourni). + + Note: + Pour le bois, il est recommandé d'utiliser `add_material_by_class` + qui garantit la cohérence avec les valeurs normatives. """ self._data["materials"][name] = { "classe": "Manuel", @@ -521,9 +682,27 @@ def _add_materials_to_model(self): return self._model.materials def get_material(self, material_id: str) -> dict: + """Retourne les propriétés d'un matériau par son identifiant. + + Args: + material_id (str): Identifiant du matériau (ex: "C24"). + + Returns: + dict: Dictionnaire contenant : + - "classe": type de matériau (nom de classe ou "Manuel") + - "E": Module de Young avec unité (si.MPa) + - "G": Module de cisaillement avec unité (si.MPa) + - "nu": Coefficient de Poisson + - "rho": Masse volumique avec unité (si.kg/si.m**3) + """ return self._data["materials"][material_id] def get_all_materials(self) -> dict: + """Retourne l'ensemble des matériaux du modèle. + + Returns: + dict: Dictionnaire de tous les matériaux, indexé par leurs identifiants. + """ return self._data["materials"] ################## Section ################## @@ -551,13 +730,33 @@ def inertie(self, b: si.mm, h: si.mm, section: str = LIST_SECTION): return {"Iy": I_y, "Iz": I_z} def add_section(self, b: si.mm, h: si.mm, J: si.mm**4, section: str = LIST_SECTION): - """Ajoute une section à la liste de section + """Ajoute une section transversale au modèle. + + Crée une section rectangulaire ou circulaire avec calcul automatique + de l'aire et des moments quadratiques d'inertie. Args: - b (int): largeur de la section en mm - h (int): hauteur de la section en mm - J (float): Module de torsion en mm4 - section (str): type de section "Circulaire" ou "Rectangulaire". + b (si.mm): Largeur de la section (ou diamètre pour section circulaire) + en millimètres. + h (si.mm): Hauteur de la section en millimètres. + Ignoré pour les sections circulaires. + J (si.mm**4): Module de torsion (constante de torsion) en mm⁴. + Pour une section rectangulaire pleine : J ≈ k * b * h³ où k ≈ 0.33 + Pour une section circulaire pleine : J = π * d⁴ / 32 + section (str): Type de section. Valeurs acceptées : + - "Rectangulaire" : section rectangulaire pleine + - "Circulaire" : section circulaire pleine (b = diamètre) + + Returns: + str: Identifiant unique de la section créé : + - "R{b}X{h}" pour une section rectangulaire (ex: "R100X200") + - "C{b}" pour une section circulaire (ex: "C300") + + Raises: + ValueError: Si le type de section n'est pas reconnu. + + Note: + L'identifiant est généré automatiquement à partir des dimensions. """ if section not in self.LIST_SECTION: raise ValueError( @@ -583,17 +782,22 @@ def add_section(self, b: si.mm, h: si.mm, J: si.mm**4, section: str = LIST_SECTI def add_section_by_property( self, name: str, aire: si.mm**2, Iy: si.mm**4, Iz: si.mm**4, J: si.mm**4 ): - """Ajoute une section à la liste de section + """Ajoute une section personnalisée définie par ses propriétés mécaniques. - Args: - name (str): nom de la section - aire (float): aire de la section en mm² + Permet de créer une section sans géométrie prédéfinie (IPE, HEA, etc.) + en spécifiant directement l'aire et les moments d'inertie. - ATTENTION: Iy est la petite inertie et Iz est la grande inertie ! - Iy (float): Inertie quadratique autour de y en mm4 - Iz (float): Inertie quadratique autour de z en mm4 + Args: + name (str): Nom unique de la section (ex: "IPE200", "HEA140"). + aire (si.mm**2): Aire de la section en mm². + Iy (si.mm**4): Moment quadratique d'inertie autour de l'axe Y (faible inertie pour une section rectangulaire dans le logiciel). + Note : Pour les sections rectangulaires, Iy = h × b³ / 12. + Iz (si.mm**4): Moment quadratique d'inertie autour de l'axe Z (forte inertie pour une section rectangulaire dans le logiciel). + Note : Pour les sections rectangulaires, Iz = b × h³ / 12. + J (si.mm**4): Module de torsion (constante de torsion) en mm⁴. - J (float): Module de torsion en mm4 + Returns: + dict: Dictionnaire des propriétés de la section créée. """ self._data["sections"][name] = { "Section": "Manuel", @@ -626,9 +830,27 @@ def _add_sections_to_model(self): return self._model.sections def get_section(self, section_id: str) -> dict: + """Retourne les propriétés d'une section par son identifiant. + + Args: + section_id (str): Identifiant de la section (ex: "R100X200", "C300", "IPE200"). + + Returns: + dict: Dictionnaire contenant les propriétés de la section : + - "Section": type de section ("Rectangulaire", "Circulaire", "Manuel") + - "b", "h": dimensions pour les sections prédéfinies + - "Aire": aire avec unité (si.mm**2) + - "Iy", "Iz": inerties avec unités (si.mm**4) + - "J": module de torsion avec unité (si.mm**4) + """ return self._data["sections"][section_id] def get_all_sections(self) -> dict: + """Retourne l'ensemble des sections du modèle. + + Returns: + dict: Dictionnaire de toutes les sections, indexé par leurs identifiants. + """ return self._data["sections"] ################## Relachement ################## @@ -644,18 +866,38 @@ def add_release( teta_y: bool = ("False", "True"), teta_z: bool = ("False", "True"), ): - """Ajoute une relaxation sur une membrure soit au début, soit à la fin. - Ceci est considéré dans les MEF par une matrice de rigidité spécifique avec les éléments relaché égale à 0. + """Ajoute un relâchement (libération de degrés de liberté) sur une barre. + + Définit des conditions de dégagement aux extrémités d'une barre pour + créer des rotules, des articulations ou des glissements partiels. + La matrice de rigidité locale est modifiée en conséquence dans Pynite. Args: - member_id (int): numéro de la membrure à relacher - position (str, optional): position du relachement sur la barre soit au début soit à la fin. Defaults to ("start", "end"). - u (bool, optional): relachement de l'axe x local, si oui alors True. - v (bool, optional): relachement de l'axe y local, si oui alors True. - w (bool, optional): relachement de l'axe z local, si oui alors True. - 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é. - teta_y (bool, optional): relachement de l'axe de rotation y local, si oui alors True. - teta_z (bool, optional): relachement de l'axe de rotation z local, si oui alors True. + member_id (str): Identifiant de la barre à relâcher (ex: "M1"). + position (str, optional): Position du relâchement sur la barre. + "start" = nœud de départ (i), "end" = nœud d'arrivée (j). + Defaults to "start". + u (bool, optional): Relâchement de la translation selon l'axe x local + (longitudinal). Defaults to False. + v (bool, optional): Relâchement de la translation selon l'axe y local. + Defaults to False. + w (bool, optional): Relâchement de la translation selon l'axe z local. + Defaults to False. + teta_x (bool, optional): Relâchement de la rotation selon l'axe x local + (torsion). Par défaut toujours bloquée pour éviter les modes rigides. + Defaults to False. + teta_y (bool, optional): Relâchement de la rotation selon l'axe y local + (moment fléchissant My). Defaults to False. + teta_z (bool, optional): Relâchement de la rotation selon l'axe z local + (moment fléchissant Mz). Defaults to False. + + Returns: + dict: Configuration du relâchement créé pour la position spécifiée. + + Note: + Un relâchement complet de la rotation (teta_y=True, teta_z=True) + crée une articulation parfaite. Toutes les translations relâchées + créent un glisseur. """ self._data["members"][member_id]["Relaxation"][position] = { "u": u, @@ -680,17 +922,31 @@ def add_support( RZ: bool = ("True", "False"), l_appuis: int = 0, ): - """Ajoute un appuis dans la liste d'appuis de la classe MEF + """Ajoute un appui classique (encastrement, rotule, glisseur) sur un nœud. + + Définit les conditions de déplacement (translation et rotation) bloquées + ou libres en chaque nœud selon le repère global (X, Y, Z). Args: - node_id (int): Numéro du noeud sur lequel positionner l'appuis. - DX (bool, optional): Blocage en translation de l'axe X global, si oui alors True. - DY (bool, optional): Blocage en translation de l'axe Y global, si oui alors True. - DZ (bool, optional): Blocage en translation de l'axe Z global, si oui alors True. - RX (bool, optional): Blocage en rotation de l'axe X global, si oui alors True. - RY (bool, optional): Blocage en translation de l'axe X global, si oui alors True. - RZ (bool, optional): Blocage en translation de l'axe X global, si oui alors True. - l_appuis (int, optional): longueur d'appuis sur la poutre en mm. Defaults to 0. + node_id (str): Identifiant du nœud d'appui (ex: "N1"). + DX (bool, optional): Bloque la translation selon l'axe X global. + True = bloqué, False = libre. Defaults to True. + DY (bool, optional): Bloque la translation selon l'axe Y global. + Defaults to True. + DZ (bool, optional): Bloque la translation selon l'axe Z global. + Defaults to True. + RX (bool, optional): Bloque la rotation selon l'axe X global (torsion). + Defaults to True. + RY (bool, optional): Bloque la rotation selon l'axe Y global. + Defaults to True. + RZ (bool, optional): Bloque la rotation selon l'axe Z global. + Defaults to True. + l_appuis (int, optional): Longueur d'appui sur la poutre en mm, + utilisée pour la vérification à la compression perpendiculaire. + Defaults to 0. + + Returns: + dict: Configuration de l'appui créé avec son identifiant généré. """ support_id = "S" + str(len(self._data["supports"]["classic"]) + 1) self._data["supports"]["classic"][support_id] = { @@ -711,13 +967,29 @@ def add_support_spring(self, stiffness: si.kN/si.m = 0, limit_direction: str = ("Aucune limitation", "Tension uniquement", "Compression uniquement") ): - """Ajoute un appuis avec une raideur spécifique dans une direction donnée + """Ajoute un appui élastique (ressort) sur un nœud dans une direction donnée. + + Modélise un appui avec raideur finie (sol élastique, appui flexible, + suspension) ou un tirant/compression unidirectionnel. Args: - node_id (str): Numéro du noeud sur lequel positionner l'appuis. - stiffness (si.kN): Raideur dans la direction donné en kN/m pour DX, DY, DZ et en N/radians pour RX, RY, RZ - dof (str, optional): Degrée de liberté ou appliquer la raideur. Defaults to "DX". - limit_direction (str, optional): Limitation du sens d'action de la raideur. Defaults to "Aucune limitation". + node_id (str): Identifiant du nœud d'appui (ex: "N1"). + dof (str, optional): Degré de liberté sur lequel appliquer la raideur. + "DX", "DY", "DZ" pour les translations, "RX", "RY", "RZ" pour les rotations. + Defaults to "DX". + stiffness (si.kN/si.m): Raideur de l'appui. + - Pour DX/DY/DZ : kN/m (translation) + - Pour RX/RY/RZ : kN·m/rad (rotation) + Defaults to 0. + limit_direction (str, optional): Limite le comportement du ressort + à un seul sens de sollicitation. + - "Aucune limitation" : ressort bidirectionnel + - "Tension uniquement" : ne travaille qu'en traction (ex: tirant) + - "Compression uniquement" : ne travaille qu'en compression (ex: étai) + Defaults to "Aucune limitation". + + Returns: + dict: Configuration de l'appui ressort créé. """ support_id = "SSpring" + str(len(self._data["supports"]["spring"]) + 1) self._data["supports"]["spring"][support_id] = { @@ -738,10 +1010,13 @@ def create_supports_by_list(self, list_supports: list): self.add_support(*support) def del_support(self, support_id: str): - """Supprime un appui par son id + """Supprime un appui classique par son identifiant. Args: - support_id (int): id de l'appuis à supprimer. + support_id (str): Identifiant de l'appui à supprimer (ex: "S1"). + + Returns: + str: Message de confirmation avec les détails de l'appui supprimé. """ return f"L'appui à été supprimé: {self._data["supports"]["classic"].pop(support_id)}" @@ -782,18 +1057,39 @@ def _add_supports_to_model(self): return "Appuis ajoutés" def get_all_supports(self) -> dict: + """Retourne l'ensemble des appuis du modèle. + + Returns: + dict: Dictionnaire contenant les appuis classiques et ressorts : + - "classic": dictionnaire des appuis classiques + - "spring": dictionnaire des appuis ressorts + """ return self._data["supports"] ################## Chargements ################## def _convert_pos(self, pos_index: int | str, member_id: str) -> int: - """Converti la position en valeur recevable par la fonction create_load + """Convertit une position textuelle ou relative en valeur numérique absolue. + + Transforme les notations symboliques en position réelle sur la barre + pour le calcul des charges distribuées et ponctuelles. Args: - pos_index (int | str): position de la charge sur la barre + pos_index (int | str): Position de la charge. Formats acceptés : + - "start" : début de la barre (0 mm) + - "end" : fin de la barre (longueur totale) + - "middle" : milieu de la barre (L/2) + - "XX%" : pourcentage de la longueur (ex: "25%") + - int : position directe en millimètres + member_id (str): Identifiant de la barre concernée. Returns: - int: la position numérique sur la barre + int: Position numérique absolue en millimètres sur la barre. + + Exemples: + >>> _convert_pos("start", "M1") -> 0 + >>> _convert_pos("50%", "M1") -> 2500 (sur une barre de 5000 mm) + >>> _convert_pos(1000, "M1") -> 1000 """ match pos_index: case "start": @@ -825,20 +1121,38 @@ def create_dist_load( direction: str = ("Fx", "Fy", "Fz", "FX", "FY", "FZ"), comment: str = None, ): - """Ajoute une charge distribuée sur la barre + """Ajoute une charge répartie linéairement sur une barre. + + Crée une charge distribuée (poids propre, neige, vent, etc.) appliquée + sur une portion ou la totalité d'une barre. La charge peut être + uniforme (start_load = end_load) ou trapézoïdale. Args: - member_id (str): id de la barre à charger. - name (str): nom de la charge. - start_load (int): effort de départ en kN/m. - end_load (int): effort de fin en kN/m. - 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" - ou un pourcentage pour définir la position de la charge. - 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" - ou un pourcentage pour définir la position de la charge. - action (str): type d'action de l'effort. - direction (str): sens de l'effort sur la barre. - comment (str, optional): commentaire sur la charge. + member_id (str): Identifiant de la barre à charger (ex: "M1"). + name (str): Nom descriptif de la charge (ex: "Neige", "PP poutre"). + start_load (float): Intensité de charge au point de départ en kN/m. + Valeur positive selon le sens de l'axe choisi. + end_load (float): Intensité de charge au point d'arrivée en kN/m. + Pour une charge uniforme, end_load = start_load. + start_pos (str, optional): Position de début de la charge. + Formats acceptés : "start" (début), "middle" (milieu), + "XX%" (pourcentage), ou valeur numérique en mm. + Defaults to "start" (toute la barre). + end_pos (str, optional): Position de fin de charge. Mêmes formats. + Defaults to "end" (toute la barre). + action (str): Type d'action selon DICO_COMBI_ACTION. + Ex: "Permanente G", "Neige normale Sn", "Vent pression W+". + direction (str): Direction et sens de l'effort dans le repère local. + Forces : "Fx", "Fy", "Fz" (local) ou "FX", "FY", "FZ" (global). + Defaults to "Fy" (vertical vers le bas en local). + comment (str, optional): Commentaire descriptif sur la charge. + + Returns: + dict: Configuration de la charge créée avec son identifiant généré. + + Note: + La convention de signe suit Pynite : Fy négatif = charge vers le bas + pour une barre horizontale. """ load_id = "L" + str(len(self._data["loads"]) + 1) type_load = "Distribuée" @@ -887,17 +1201,32 @@ def create_point_load( ), comment: str = None, ): - """Ajoute une charge nodale sur la barre + """Ajoute une charge ponctuelle (force ou moment) sur une barre. + + Crée une force concentrée ou un moment concentré appliqué à une position + précise sur une barre (charge en trémie, point d'application d'une poutre, + moment d'encastrement équivalent, etc.). Args: - member_id (str): id de la barre à charger. - name (str): nom de la charge. - load (int): effort de départ en kN ou kN.m. - pos (str, optional): position de la charge sur la barre en mm. En complément il est possible de mettre "start", "middle", "end" - ou un pourcentage pour définir la position de la charge. - action (str): type d'action de l'effort. - direction (str): sens de l'effort sur la barre. - comment (str, optional): commentaire sur la charge. + member_id (str): Identifiant de la barre à charger (ex: "M1"). + name (str): Nom descriptif de la charge (ex: "Charge ponctuelle P1"). + load (float): Valeur de l'effort. + - Force : en kN si direction commence par "F" + - Moment : en kN·m si direction commence par "M" + pos (str, optional): Position de la charge sur la barre. + Formats acceptés : "start" (début), "end" (fin), "middle" (milieu), + "XX%" (pourcentage), ou valeur numérique en mm. + Defaults to "middle". + action (str): Type d'action selon DICO_COMBI_ACTION. + Ex: "Permanente G", "Exploitation Q". + direction (str): Direction et type d'effort. + Forces : "Fx", "Fy", "Fz" (local) ou "FX", "FY", "FZ" (global). + Moments : "Mx", "My", "Mz" (local) ou "MX", "MY", "MZ" (global). + Defaults to "Fy". + comment (str, optional): Commentaire descriptif sur la charge. + + Returns: + dict: Configuration de la charge ponctuelle créée. """ load_id = "L" + str(len(self._data["loads"]) + 1) if "F" in direction: @@ -924,10 +1253,30 @@ def create_point_load( def create_load_by_list( self, list_loads: list, type_load: str = ("Distribuée", "Autre") ): - """Ajoute les charges d'une liste pré-définit dans la liste de chargement + """Ajoute plusieurs charges en batch depuis une liste. + + Permet de créer rapidement plusieurs charges similaires à partir d'une + liste de paramètres, utile pour les modèles répétitifs ou les imports. Args: - list_loads (list): liste de charge. + list_loads (list): Liste de tuples contenant les arguments de charge. + Chaque tuple doit correspondre aux arguments de create_dist_load + ou create_point_load selon le type_load. + Ex: [("M1", "PP", -0.5, -0.5, "start", "end", "Permanente G", "FY"), ...] + type_load (str, optional): Type de charges dans la liste. + "Distribuée" : utilise create_dist_load pour chaque élément. + "Autre" : utilise create_point_load pour chaque élément. + Defaults to "Distribuée". + + Returns: + None: Les charges sont ajoutées directement au modèle. + + Exemple: + >>> charges = [ + ... ("M1", "Neige", -1.5, -1.5, "start", "end", "Neige normale Sn", "FY"), + ... ("M2", "Neige", -1.5, -1.5, "start", "end", "Neige normale Sn", "FY"), + ... ] + >>> model.create_load_by_list(charges, "Distribuée") """ for load in list_loads: if type_load == "Distribuée": @@ -936,12 +1285,15 @@ def create_load_by_list( self.create_point_load(*load) def del_load(self, load_id: str): - """Supprime une charge de l'attribut _data["loads"] par son index + """Supprime une charge par son identifiant. Args: - index_load (int): index de la charge à supprimer. + load_id (str): Identifiant de la charge à supprimer (ex: "L1"). + + Returns: + dict: Dictionnaire de la charge supprimée. """ - self._data["loads"].pop(load_id) + return self._data["loads"].pop(load_id) def _add_load_to_model(self, load_id: str): load = self._data["loads"][load_id] @@ -986,12 +1338,32 @@ def _add_loads_to_model(self): self._add_load_to_model(load_id) return self._model.load_cases - def get_all_loads(self): - """Retourne la liste des charges définits initialement.""" + def get_all_loads(self) -> dict: + """Retourne l'ensemble des charges définies dans le modèle. + + Returns: + dict: Dictionnaire de toutes les charges, indexé par leurs identifiants + ("L1", "L2", etc.). Chaque entrée contient les propriétés complètes + de la charge (type, intensité, position, barre concernée). + """ return self._data["loads"] def get_member_loads(self, member_id: str) -> list: - """Retourne la liste des charges définits initialement.""" + """Retourne les charges appliquées sur une barre spécifique. + + Filtre l'ensemble des charges du modèle pour ne retourner que celles + appliquées sur la barre identifiée. + + Args: + member_id (str): Identifiant de la barre (ex: "M1"). + + Returns: + list: Liste des dictionnaires de charges appliquées à cette barre. + + Exemple: + >>> model.get_member_loads("M1") + [{'N° barre': 'M1', 'Nom': 'PP', 'Action': 'Permanente G', ...}, ...] + """ return [ load for load in self._data["loads"].values() @@ -999,6 +1371,21 @@ def get_member_loads(self, member_id: str) -> list: ] def generate_model(self): + """Génère et assemble le modèle MEF complet dans Pynite. + + Cette méthode interne (appelée automatiquement par Combinaison) : + 1. Crée l'objet FEModel3D de Pynite + 2. Ajoute tous les nœuds, matériaux, sections + 3. Ajoute toutes les barres avec leurs relâchements + 4. Ajoute tous les appuis (classiques et ressorts) + 5. Ajoute tous les chargements + + Note: + Cette méthode est généralement appelée automatiquement par la + classe Combinaison. Elle n'a pas besoin d'être invoquée manuellement + dans un flux de travail standard. + """ + from Pynite import FEModel3D self._model = FEModel3D() self._add_nodes_to_model() self._add_materials_to_model() @@ -1012,6 +1399,30 @@ def _add_load_combos_to_model(self, combos: dict, tag: str): self._model.add_load_combo(combo, factor, tag) class Wood_beam_model(Model_generator): + """Générateur de modèle MEF simplifié pour poutres bois continues. + + Cette classe spécialisée crée automatiquement un modèle de poutre + sur appuis multiples avec les hypothèses suivantes : + - Barre isostatique + - Distance entre appuis égale (pas de porte-à-faux) + - Possibilité d'inclinaison et de dévers + + Elle est particulièrement adaptée pour : + - Poutres, pannes, chevrons + - Poteaux verticaux (inclinaison = 90°) + - Pannes à dévers (dévers ≠ 0) + + Flux de travail : + 1. Créer le modèle avec Wood_beam_model + 2. Ajouter les charges avec create_dist_load / create_point_load + 3. Passer à la classe Combinaison pour les combinaisons + 4. Analyser les résultats avec Model_result + + Note: + Cette classe surcharge certaines méthodes de Model_generator pour + gérer automatiquement le poids propre des barres multiples. + """ + def __init__( self, longueur: si.mm, @@ -1027,31 +1438,33 @@ def __init__( *args, **kwargs, ): - """Génère un modèle MEF poutre 1D simplifié d'une barre bois avec comme hypothèses: - - appuis rotulés - - distance entre appuis identique - - pas de porte à faux - On peut gérer l'inclinaison et le dévers de la barre. Ce qui permet avec une inclinaison de 90° de calculer un poteau vertical pour exemple. - Cette classe est hérité de la classe projet du module A0_Projet.py - - Une fois le modèle généré, il faut: - 1) Créer les charges sur les barres avec les méthodes "create_dist_load" et "create_point_load" - 2) Transmettre cette classe dans l'argument model_generator de la classe Combinaison du module EC0_Combinaison pour le calcul des combinaisons. - 3) Récupérer les efforts internes , les déformations et afficher les graphiques associés avec les méthodes correspondante de la classe Model_result du même module. + """Initialise un modèle de poutre bois simplifié. + + Crée automatiquement les nœuds, barres et appuis répartis uniformément + sur la longueur totale. Génère autant de barres que de travées + (nbr_appuis - 1). Args: - longueur (si.mm): longueur de la barre en mm. - b (si.mm): épaisseur de la barre en mm. - h (si.mm): hauteur de la barre en mm. - section (str, optional): type de section. - classe (str, optional): classe de la barre. - poids_propre (bool, optional): Défini si le poids propre de la poutre doit être généré. Defaults to True. - nbr_appuis (int, optional): nombre d'appuis sur la barre. Defaults to 2. - l_appuis (float, optional): longueur des appuis en mm. Defaults to 0. - devers (float, optional): rotation en ° de la barre autour de l'axe de la longueur (X) - pour le calcul d'une panne à dévers par exemple. Defaults to 0. - inclinaison (float, optional): inclinaison en ° de la barre autour de l'axe de la largeur (Z) - pour le calcul d'un chevrons ou d'un poteau vertical pour exemple. Defaults to 0. + longueur (si.mm): Longueur totale de la barre en millimètres. + b (si.mm): Épaisseur de la section en millimètres (largeur). + h (si.mm): Hauteur de la section en millimètres. + section (str, optional): Type de section transversale. + "Rectangulaire" ou "Circulaire". Defaults to "Rectangulaire". + classe (str, optional): Classe de résistance du bois selon l'EC5. + Ex: "C24", "GL28h". Defaults to "C24". + poids_propre (bool, optional): Générer automatiquement le poids + propre de chaque barre comme charge répartie. Defaults to True. + nbr_appuis (int, optional): Nombre d'appuis rotulés (≥ 2). + 2 = poutre simple sur deux appuis, 3 = poutre continue sur 3 appuis. + Defaults to 2. + l_appuis (float, optional): Longueur d'appui en mm pour la vérification + à la compression perpendiculaire. Defaults to 0. + devers (float, optional): Angle de dévers en degrés (rotation autour + de l'axe longitudinal X). Utilisé pour les pannes inclinées + transversalement. Defaults to 0. + inclinaison (float, optional): Angle d'inclinaison en degrés (rotation + autour de l'axe Z). 0° = poutre horizontale, 90° = poteau vertical. + Utile pour les chevrons ou poteaux inclinés. Defaults to 0. """ super().__init__(*args, **kwargs) self.longueur = longueur * si.mm @@ -1213,6 +1626,27 @@ def create_point_load( class Model_result(Projet): + """Classe d'analyse et d'extraction des résultats MEF. + + Cette classe permet de lancer l'analyse aux éléments finis et de récupérer + les résultats (efforts, déformées, réactions) du modèle généré par + Model_generator et résolu par Combinaison. + + Elle hérite de Projet pour maintenir le contexte du projet. + L'analyse est lancée automatiquement lors de l'instanciation. + + Flux de travail : + 1. Créer un modèle avec Model_generator + 2. Définir les combinaisons avec Combinaison + 3. Instancier Model_result pour analyser et extraire les résultats + 4. Utiliser les méthodes get_* ou show_* pour visualiser + + Types d'analyse disponibles: + - "Général": Analyse complète (linéaire + P-Delta si nécessaire) + - "Linéaire": Analyse linéaire élastique uniquement + - "Second ordre": Analyse P-Delta (effets du second ordre) + """ + ANALYZE_TYPE = ("Général", "Linéaire", "Second ordre") def __init__( @@ -1223,17 +1657,24 @@ def __init__( *args, **kwargs, ): - """ - 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. - Cette classe est héritée de Projet dans le module A0_Projet.py. - Elle nécessite toutefois la création d'un modèle de calcul avant d'être instanciée. - Le modèle de calcul est créé par la classe Model_generator dans le module A0_Projet.py. + """Initialise l'analyseur de résultats MEF. + + Lance automatiquement l'analyse du modèle après initialisation. + Vérifie que tous les nœuds sont connectés avant de résoudre. Args: - model_generator (Model_generator): le modèle de calcul à utiliser pour la génération des combinaisons d'action. - analize_type (str): Définit le type d'analyse à réaliser - check_stability (bool, optional): Définit si vous voulez vérifier la stabilité du modèle. - Ceci ralentit le calcul, à utiliser donc quand cas de débuguage. Defaults to False. + model_generator (Model_generator): Instance du modèle généré + contenant les nœuds, barres, matériaux et chargements. + analyze_type (str): Type d'analyse à réaliser. + "Général", "Linéaire" ou "Second ordre". Defaults to "Général". + check_stability (bool, optional): Active la vérification de stabilité + du modèle. Ralentit le calcul, utile pour le débogage. + Defaults to False. + *args: Arguments transmis à la classe parent Projet. + **kwargs: Arguments nommés transmis à Projet. + + Raises: + ValueError: Si des nœuds orphelins (non connectés) sont détectés. """ super().__init__(*args, **kwargs) self._model_generator = model_generator @@ -1254,7 +1695,33 @@ def _base_graph( savefig: bool = False, filepath: str=None ): - """Retourne un diagramme""" + """Génère un graphique matplotlib de base pour les résultats. + + Méthode interne utilisée par show_internal_force_of_member et + show_deflection_of_member pour créer des diagrammes cohérents. + + Args: + title (str): Titre principal du graphique. + combo_name (str): Nom de la combinaison affichée (sous-titre). + x_values (array): Valeurs pour l'axe horizontal (position le long de la barre). + y_values (array): Valeurs pour l'axe vertical (effort ou déplacement). + x_label (str): Label de l'axe X. + y_label (str): Label de l'axe Y avec unité. + color (str): Couleur matplotlib du tracé (ex: "r", "b", "g", "orange"). + fill_between (bool, optional): Remplit l'aire sous la courbe si True. + Defaults to True. + savefig (bool, optional): Sauvegarde automatique si True, affichage interactif sinon. + Defaults to False. + filepath (str, optional): Chemin de sauvegarde si savefig=True. + Ouvre une boîte de dialogue si None. + + Returns: + str: Chemin du fichier sauvegardé si savefig=True. + None: Affiche le graphique si savefig=False. + + Note: + Cette méthode est interne et ne devrait pas être appelée directement. + """ # plt.clf() # Effacer le graphique précédent plt.figure(self.name, figsize=(11, 4)) plt.gcf().subplots_adjust( @@ -1274,6 +1741,7 @@ def _base_graph( plt.grid() if savefig: if not filepath: + from PySide6.QtWidgets import QFileDialog filepath = QFileDialog.getSaveFileName( filter="PNG (*.png)", selectedFilter=".png", @@ -1285,7 +1753,14 @@ def _base_graph( def _analyze(self): - """Lance l'analyse du modèle aux éléments finis.""" + """Lance l'analyse du modèle aux éléments finis. + + Méthode interne appelée automatiquement lors de l'instanciation. + Détecte les nœuds orphelins et lance le solveur adapté au type d'analyse. + + Raises: + RuntimeError: Si des nœuds orphelins sont détectés dans le modèle. + """ orphaned_nodes = self._model_generator._model.orphaned_nodes() if orphaned_nodes: return f"Les noeuds suivants ne sont pas connectés: {orphaned_nodes}" @@ -1303,11 +1778,14 @@ def _analyze(self): check_stability=self.check_stability ) - def get_member_length(self, member_id: str): - """Retourne la longueur du membrure par son id + def get_member_length(self, member_id: str) -> float: + """Retourne la longueur d'une barre par son identifiant. Args: - member_id (str): id de la membrure à récupérer + member_id (str): Identifiant de la barre (ex: "M1"). + + Returns: + float: Longueur de la barre en millimètres avec unité (si.mm). """ return self._model_generator._data["members"][member_id]["Longueur"] @@ -1318,13 +1796,30 @@ def get_internal_force( type: str = ("Nx", "Vy", "Vz", "Mx", "My", "Mz"), n_points: int = 20, ) -> np.array: - """Retourne une table des efforts internes d'une membrure pour le type d'effort donné. + """Retourne les efforts internes le long d'une barre pour une combinaison donnée. + + Extrait les valeurs d'efforts (effort normal, cisaillement, moment) + répartis le long de la barre avec un nombre de points défini. Args: - member_id (str): Le nom de la membrure à analyser - combination (str): Le nom de la combinaison à récupérer - type (str): Le type d'effort interne à retourner. Defaults to ("Nx", "Vy", "Vz", "Mx", "My", "Mz"). - n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20. + member_id (str): Identifiant de la barre (ex: "M1"). + combination (str): Nom de la combinaison de charges à analyser + (ex: "ELU_STR", "ELS_QP"). + type (str): Type d'effort interne à récupérer. + "Nx" = effort normal, "Vy"/"Vz" = effort tranchant, + "Mx" = moment de torsion, "My"/"Mz" = moment fléchissant. + Defaults to "Nx". + n_points (int, optional): Nombre de points de discrétisation + le long de la barre. Plus ce nombre est élevé, plus la courbe + est lisse. Defaults to 20. + + Returns: + tuple: (positions, valeurs) où : + - positions (array): Abscisses le long de la barre en mm + - valeurs (array): Valeurs de l'effort correspondant + + Note: + Les valeurs sont retournées dans les unités de base de Pynite (N ou N·mm). """ match type: case "Nx": @@ -1352,15 +1847,32 @@ def get_internal_force( "Mz", n_points=n_points, combo_name=combination ) - def get_min_max_internal_force(self, member_id: str, combination: str|list) -> dict: - """Retourne le maximum et minimum des efforts internes d'une membrure donnée. + def get_min_max_internal_force(self, member_id: str|list, combination: str|list) -> dict: + """Retourne les valeurs minimales et maximales des efforts internes. + + Analyse tous les types d'efforts (Nx, Vy, Vz, Mx, My, Mz) et retourne + les valeurs extrêmes avec la combinaison correspondante. Supporte les + barres continues (liste de barres) et les tags de combinaisons. Args: - member_id (str): Le nom de la membrure à analyser. On peut rentrer plusieurs membrures en créant une liste de membrures, - ex: ["M1", "M2", "M3"] dans le cas par exemple d'une barre continue. - combination (str): Le nom de la combinaison à récupérer. On peut également rentrer un ou des tags de combinaisons, - ex: ["ELU_STR", "ELU_ACC"] dans ce cas le résultat récupéré est - le maximum/minimum de toute les combinaisons inclus dans ces tags. + member_id (str | list): Identifiant de la barre ou liste de barres + pour une barre continue (ex: "M1" ou ["M1", "M2", "M3"]). + combination (str | list): Nom de la combinaison ou liste de tags + de combinaisons (ex: "ELU_STR" ou ["ELU_STR", "ELU_ACC"]). + Si des tags sont fournis, le max/min est cherché parmi toutes + les combinaisons correspondantes. + + Returns: + dict: Dictionnaire structuré par type d'effort : + { + "Nx": {"Min": (valeur, combinaison), "Max": (valeur, combinaison)}, + "Vy": {"Min": (...), "Max": (...)}, ... + } + Les valeurs incluent les unités (N pour efforts, N·mm pour moments). + + Raises: + ValueError: Si une barre de la liste n'existe pas dans le modèle. + """ dict_internal_forces = {} for type in ("Nx", "Vy", "Vz", "Mx", "My", "Mz"): @@ -1441,18 +1953,25 @@ def get_min_max_internal_force(self, member_id: str, combination: str|list) -> d dict_internal_forces[type] = {"Min": (min_value * si_unit, combi_min), "Max": (max_value * si_unit, combi_max)} return dict_internal_forces - def get_absolute_internal_force(self, member_id: str, combination: str|list, type: str = ("Nx", "Vy", "Vz", "Mx", "My", "Mz"), get_combo_name: bool = ("False", "True")): - """Retourne la valeur de d'effort absolue pour le type d'effort donné. + def get_absolute_internal_force(self, member_id: str|list, combination: str|list, type: str = ("Nx", "Vy", "Vz", "Mx", "My", "Mz"), get_combo_name: bool = ("False", "True")): + """Retourne la valeur absolue maximale d'un type d'effort spécifique. + + Détermine automatiquement si le maximum absolu est la valeur minimale + (la plus négative) ou maximale (la plus positive) et retourne sa + valeur absolue. Utile pour les vérifications de dimensionnement. Args: - member_id (str): Le nom de la membrure à analyser. On peut rentrer plusieurs membrures en créant une liste de membrures, - ex: ["M1", "M2", "M3"] dans le cas par exemple d'une barre continue. - combination (str): Le nom de la combinaison à récupérer. On peut également rentrer un ou des tags de combinaisons, - ex: ["ELU_STR", "ELU_ACC"] dans ce cas le résultat récupéré est - le maximum/minimum de toute les combinaisons inclus dans ces tags. - type (str): Le type d'effort interne à retourner. Defaults to ("Nx", "Vy", "Vz", "Mx", "My", "Mz"). - get_combo_name (bool): Si True, retourne également le nom de la combinaison associée à la valeur. Defaults to False. + member_id (str | list): Identifiant de la barre ou liste de barres. + combination (str | list): Nom de la combinaison ou tags de combinaisons. + type (str): Type d'effort à analyser ("Nx", "Vy", "Vz", "Mx", "My", "Mz"). + Defaults to "Nx". + get_combo_name (bool, optional): Si True, retourne également le nom + de la combinaison associée. Defaults to False. + Returns: + float | dict: Valeur absolue de l'effort maximal. + Si get_combo_name=True, retourne un dict : + {"Effort": valeur, "Combinaison": nom_combinaison} """ ei = self.get_min_max_internal_force(member_id, combination) max = "Max" @@ -1466,24 +1985,42 @@ def get_absolute_internal_force(self, member_id: str, combination: str|list, typ def show_internal_force_of_member( self, - member_id: str, + member_id: str|list, combination: str, type: str = ("Nx", "Vy", "Vz", "Mx", "My", "Mz"), n_points: int = 20, screenshot: bool = ("False", "True"), filepath: str=None, ): - """Retourne un graphique des efforts internes d'une membrure suivant le type d'effort et la combinaison choisit. + """Affiche le diagramme des efforts internes d'une barre. + + Génère un graphique matplotlib montrant la distribution de l'effort + interne choisi le long de la barre (ou des barres continues). + Les valeurs sont automatiquement converties en kN ou kN·m. Args: - member_id (str): Le nom de la membrure à analyser. On peut rentrer plusieurs membrures en créant une liste de membrures, - ex: ["M1", "M2", "M3"] dans le cas par exemple d'une barre continue. - combination (str): Le nom de la combinaison à récupérer. - type (str): Le type d'effort interne à retourner. Defaults to ("Nx", "Vy", "Vz", "Mx", "My", "Mz"). - n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20. - screenshot (bool, optional): Définit si l'on souhaite enregistrer un screenshot du graph, si oui alors True. Defaults to False - 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. - Sinon le logiciel vous demande à chaque fois le chemin de sauvegarde. + member_id (str | list): Identifiant de la barre ou liste de barres + pour une barre continue. + combination (str): Nom de la combinaison à afficher (ex: "ELU_STR"). + type (str): Type d'effort à diagrammer. + - "Nx": Effort normal (orange) + - "Vy", "Vz": Effort tranchant (bleu) + - "Mx", "My", "Mz": Moments (rouge) + Defaults to "Nx". + n_points (int, optional): Nombre de points pour le tracé. + Defaults to 20. + screenshot (bool, optional): Si True, sauvegarde l'image sans + afficher la fenêtre interactive. Defaults to False. + filepath (str, optional): Chemin de sauvegarde si screenshot=True. + Ouvre une boîte de dialogue si None. + + Returns: + str: Chemin du fichier sauvegardé si screenshot=True. + None: Affiche le graphique interactif si screenshot=False. + + Note: + Les barres sont concaténées pour former un diagramme continu + dans le cas d'une barre continue (ex: ["M1", "M2", "M3"]). """ x_label = "Longueur (mm)" if "[" in member_id: @@ -1541,27 +2078,62 @@ def get_deflection( direction: str = ("dx", "dy", "dz"), n_points: int = 20, ) -> np.array: - """Retourne une table des déformation d'une membrure pour la direction locale donnée. + """Retourne les déplacements/déformées le long d'une barre. + + Extrait les valeurs de déplacement aux nœuds intermédiaires de la barre + pour une direction donnée et une combinaison spécifique. Args: - member_id (str): Le nom de la membrure à analyser - combination (str): Le nom de la combinaison à récupérer - direction (str): La direction locale à retourner. Defaults to ("dx", "dy", "dz"). - n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20. + member_id (str): Identifiant de la barre (ex: "M1"). + combination (str): Nom de la combinaison à analyser + (ex: "ELS_QP", "W_inst_Q"). + direction (str): Direction du déplacement. + "dx" = selon l'axe longitudinal (allongement/raccourcissement) + "dy" = flèche dans le plan vertical local + "dz" = flèche dans le plan horizontal local + Defaults to "dy" (flèche verticale usuelle). + n_points (int, optional): Nombre de points de discrétisation. + Defaults to 20. + + Returns: + tuple: (positions, déplacements) où : + - positions (array): Abscisses le long de la barre en mm + - déplacements (array): Valeurs de déplacement en mm + + Note: + Les déplacements sont relatifs aux conditions d'appui (la ligne + élastique est calculée par rapport à la position déformée des appuis). """ return self._model_generator._model.members[member_id].deflection_array( direction, n_points=n_points, combo_name=combination ) - def get_min_max_deflection(self, member_id: str, combination: str|list) -> np.array: - """Retourne le maximum et minimum des efforts internes d'une membrure donnée. + def get_min_max_deflection(self, member_id: str|list, combination: str|list) -> dict: + """Retourne les déplacements minimaux et maximaux d'une barre. + + Analyse les trois directions de déplacement (dx, dy, dz) et retourne + les valeurs extrêmes avec la combinaison correspondante. Supporte les + barres continues et les tags de combinaisons ELS. Args: - member_id (str): Le nom de la membrure à analyser. On peut rentrer plusieurs membrures en créant une liste de membrures, - ex: ["M1", "M2", "M3"] dans le cas par exemple d'une barre continue. - combination (str): Le nom de la combinaison à récupérer. On peut également rentrer un ou des tags de combinaisons, - ex: ["ELS_C", "ELS_QP" "W_inst_Q", "W_net_fin"] dans ce cas le résultat récupéré est - le maximum/minimum de toute les combinaisons inclus dans ces tags. + member_id (str | list): Identifiant de la barre ou liste de barres + pour une barre continue. + combination (str | list): Nom de la combinaison ou tags ELS + (ex: "ELS_QP", ["W_inst_Q", "W_net_fin"]). + + Returns: + dict: Dictionnaire structuré par direction : + { + "dx": {"Min": (valeur, combinaison), "Max": (valeur, combinaison)}, + "dy": {"Min": (...), "Max": (...)}, + "dz": {"Min": (...), "Max": (...)} + } + Les valeurs incluent l'unité (si.mm). + + Note: + Les valeurs "Min" peuvent être négatives (déplacement dans le sens + opposé à l'axe). La flèche maximale est généralement la valeur absolue + la plus grande en valeur absolue dans la direction verticale (dy). """ dict_deflection = {} for type in ("dx", "dy", "dz"): @@ -1602,17 +2174,26 @@ def get_min_max_deflection(self, member_id: str, combination: str|list) -> np.ar dict_deflection[type] = {"Min": (min_value * si.mm, combi_min), "Max": (max_value * si.mm, combi_max)} return dict_deflection - def get_absolute_max_deflection(self, member_id: str, combination: str|list, direction: str = ("dx", "dy", "dz"), get_combo_name: bool=("False", "True")): - """Retourne la valeur de déplacement absolue pour la direction de la flèche donnée. + def get_absolute_max_deflection(self, member_id: str|list, combination: str|list, direction: str = ("dx", "dy", "dz"), get_combo_name: bool=("False", "True")): + """Retourne la valeur absolue maximale de déplacement pour une direction donnée. + + Détermine automatiquement si le maximum absolu est la valeur minimale + ou maximale et retourne sa valeur absolue. Méthode standard pour + récupérer la flèche maximale à vérifier. Args: - member_id (str): Le nom de la membrure à analyser. On peut rentrer plusieurs membrures en créant une liste de membrures, - ex: ["M1", "M2", "M3"] dans le cas par exemple d'une barre continue. - combination (str): Le nom de la combinaison à récupérer. On peut également rentrer un ou des tags de combinaisons, - ex: ["ELS_QP" "W_inst_Q", "W_net_fin"] dans ce cas le résultat récupéré est - le maximum/minimum de toute les combinaisons inclus dans ces tags. - direction (str): La direction locale à retourner. Defaults to ("dx", "dy", "dz"). - get_combo_name (bool): Si True, retourne également le nom de la combinaison associée à la valeur. Defaults to False. + member_id (str | list): Identifiant de la barre ou liste de barres. + combination (str | list): Nom de la combinaison ou tags de combinaisons. + (ex: "ELS_QP", ["W_inst_Q", "W_net_fin"]). + direction (str): Direction à analyser ("dx", "dy", "dz"). + Defaults to "dy" (flèche verticale). + get_combo_name (bool, optional): Si True, retourne également le nom + de la combinaison associée. Defaults to False. + + Returns: + float | dict: Valeur absolue du déplacement maximal en mm. + Si get_combo_name=True, retourne un dict : + {"Flèche": valeur, "Combinaison": nom_combinaison} """ deflection = self.get_min_max_deflection(member_id, combination) max = "Max" @@ -1626,24 +2207,39 @@ def get_absolute_max_deflection(self, member_id: str, combination: str|list, dir def show_deflection_of_member( self, - member_id: str, + member_id: str|list, combination: str, direction: str = ("dx", "dy", "dz"), n_points: int = 20, screenshot: bool = ("False", "True"), filepath: str=None, ): - """Retourne un graphique des efforts internes d'une membrure suivant le type d'effort et la combinaison choisit. + """Affiche le diagramme de déformée (flèche) d'une barre. + + Génère un graphique matplotlib montrant la ligne élastique de la barre + pour la direction et combinaison spécifiées. Utile pour visualiser + la déformée et identifier les zones de flèche maximale. Args: - member_id (str): Le nom de la membrure à analyser. On peut rentrer plusieurs membrures en créant une liste de membrures, - ex: ["M1", "M2", "M3"] dans le cas par exemple d'une barre continue. - combination (str): Le nom de la combinaison à récupérer - direction (str): La direction locale à retourner. Defaults to ("dx", "dy", "dz"). - n_points (int, optional): le nombre de valeur à retrouner le long de la membrure. Defaults to 20. - screenshot (bool, optional): Définit si l'on souhaite enregistrer un screenshot du graph, si oui alors True. Defaults to False - 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. - Sinon le logiciel vous demande à chaque fois le chemin de sauvegarde. + member_id (str | list): Identifiant de la barre ou liste de barres + pour une barre continue. + combination (str): Nom de la combinaison à afficher (ex: "W_inst_Q"). + direction (str): Direction de la déformée à afficher. + "dx" = allongement, "dy" = flèche verticale, "dz" = flèche horizontale. + Defaults to "dy". + n_points (int, optional): Nombre de points pour le tracé. + Defaults to 20. + screenshot (bool, optional): Si True, sauvegarde l'image. + Defaults to False. + filepath (str, optional): Chemin de sauvegarde si screenshot=True. + + Returns: + str: Chemin du fichier sauvegardé si screenshot=True. + None: Affiche le graphique interactif si screenshot=False. + + Note: + La déformée est tracée en vert avec remplissage pour visualiser + l'amplitude des déplacements. """ title = f'Barre {member_id}: Flèche {direction}' x_label = "Longueur (mm)" @@ -1693,18 +2289,41 @@ def show_model( screenshot: bool = ("False", "True"), filepath: str=None, ): - """Retourne le model dans un graphique 3D. + """Affiche une visualisation 3D interactive du modèle MEF. + + Rendu 3D complet du modèle avec possibilité d'afficher les charges, + les efforts internes colorés, ou la déformée. Utilise le renderer + Pynite avec interaction utilisateur. Args: combination (str): Nom de la combinaison à afficher. - annotation_size (int, optional): Tailles des annotations. Defaults to 70. - show_loads (bool, optional): Détermine si les charges doivent être afficher. Defaults to True. - diagrams (bool, optional): détermine quel type de diagramme afficher. Defaults to True. - scale (int, optional): Définit l'effet d'échelle sur les diagrammes. Defaults to 20. - screenshot (bool, optional): Définit si l'on souhaite enregistrer un screenshot du graph, si oui alors True. Defaults to False - 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. - Sinon le logiciel vous demande à chaque fois le chemin de sauvegarde. + annotation_size (int, optional): Taille des annotations de nœuds. + Defaults to 70. + show_loads (bool, optional): Affiche les charges appliquées. + Defaults to True. + diagrams (str, optional): Type de diagramme à superposer. + - "Aucun": Modèle fil de fer seul + - "Fx", "Fy", "Fz", "My", "Mz", "Tx": Diagramme d'efforts coloré + - "Flèche": Déformée amplifiée + Defaults to "Aucun". + scale (int, optional): Facteur d'échelle pour les diagrammes et + la déformée. Defaults to 1000. + screenshot (bool, optional): Si True, capture l'image sans interaction. + Defaults to False. + filepath (str, optional): Chemin de sauvegarde. Ouvre une boîte de + dialogue si None et screenshot=True. + + Returns: + str: Chemin du fichier sauvegardé si screenshot=True. + None: Affiche la fenêtre interactive si screenshot=False. + + Note: + En mode interactif (screenshot=False), utilisez la souris pour + tourner le modèle (clic gauche), zoomer (molette), paner (clic droit). + Appuyez sur Q pour fermer. """ + from PySide6.QtWidgets import QFileDialog, QMessageBox + from Pynite.Rendering import Renderer renderer = Renderer(self._model_generator._model) renderer.combo_name = combination renderer.annotation_size = annotation_size @@ -1735,12 +2354,26 @@ def show_model( else: renderer.render_model() - def get_global_displacement_of_node(self, node_id: str, combination: str): - """Retourne un dictionnaire des coordonnées de déplacment global d'un noeud dans le model pour la combinaison définit. + def get_global_displacement_of_node(self, node_id: str, combination: str) -> dict: + """Retourne les déplacements/rotations globaux d'un nœud. + + Extrait les valeurs de déplacement et rotation dans le repère global + (X, Y, Z) pour un nœud spécifique et une combinaison donnée. Args: - node_id (str): id du noeud à récupérer. + node_id (str): Identifiant du nœud (ex: "N1"). combination (str): Nom de la combinaison à analyser. + + Returns: + dict: Déplacements et rotations du nœud : + { + "DX", "DY", "DZ": translations en mm (si.mm) + "RX", "RY", "RZ": rotations en radians (sans unité) + } + + Note: + Les déplacements sont relatifs à la position initiale du nœud. + Les rotations positives suivent la convention de la main droite. """ DX = self._model_generator._model.nodes[node_id].DX[combination] * si.mm DY = self._model_generator._model.nodes[node_id].DY[combination] * si.mm @@ -1750,12 +2383,27 @@ def get_global_displacement_of_node(self, node_id: str, combination: str): RZ = self._model_generator._model.nodes[node_id].RZ[combination] return {"DX": DX, "DY": DY, "DZ": DZ, "RX": RX, "RY": RY, "RZ": RZ} - def _get_node_reaction(self, node_id: str, combination: str): - """Retourne un dictionnaire des réaction d'un noeud. + def _get_node_reaction(self, node_id: str, combination: str) -> dict: + """Retourne les réactions d'appui en un nœud (méthode interne). + + Extrait les forces et moments de réaction dans le repère global + pour un nœud d'appui. Cette méthode est principalement utilisée + par get_supports_reactions. Args: - node_id (str): id du noeud à récupérer. + node_id (str): Identifiant du nœud d'appui (ex: "N1"). combination (str): Nom de la combinaison à analyser. + + Returns: + dict: Réactions au nœud : + { + "FX", "FY", "FZ": forces en N (si.N) + "MX", "MY", "MZ": moments en N·mm (si.N*si.mm) + } + + Note: + Cette méthode est interne. Pour récupérer toutes les réactions + d'appui, utilisez plutôt get_supports_reactions(). """ FX = self._model_generator._model.nodes[node_id].RxnFX[combination] * si.N FY = self._model_generator._model.nodes[node_id].RxnFY[combination] * si.N @@ -1777,11 +2425,27 @@ def _get_node_reaction(self, node_id: str, combination: str): ) return {"FX": FX, "FY": FY, "FZ": FZ, "MX": MX, "MY": MY, "MZ": MZ} - def get_supports_reactions(self, combination: str): - """Retourne un dictionnaire des réaction aux appuis. + def get_supports_reactions(self, combination: str) -> dict: + """Retourne les réactions d'appui pour tous les appuis du modèle. + + Parcourt tous les appuis définis dans le modèle et récupère les + forces et moments de réaction pour la combinaison spécifiée. Args: - combination (str): Nom de la combinaison à analyser. + combination (str): Nom de la combinaison à analyser (ex: "ELU_STR"). + + Returns: + dict: Dictionnaire indexé par identifiant d'appui : + { + "S1": {"FX": ..., "FY": ..., "FZ": ..., "MX": ..., ...}, + "S2": {...}, + ... + } + Forces en N, moments en N·mm. + + Note: + Un appui rotulé retournera des moments nuls (MX=MY=MZ=0). + Un glisseur retournera une force nulle dans la direction libre. """ reaction = {} supports = self._model_generator.get_all_supports() diff --git a/ourocode/eurocode/EC0_Combinaison.py b/ourocode/eurocode/EC0_Combinaison.py index d85b98c..dabe157 100644 --- a/ourocode/eurocode/EC0_Combinaison.py +++ b/ourocode/eurocode/EC0_Combinaison.py @@ -10,6 +10,22 @@ class Combinaison(Projet): + """Générateur de combinaisons d'actions selon l'EC0 (Eurocode 0). + + Cette classe génère automatiquement toutes les combinaisons d'actions + nécessaires selon l'Annexe Nationale française de l'EC0, à partir des + chargements définis dans un modèle MEF. + + Elle supporte : + - Les combinaisons ELU STR (EQU, STR, GEO) selon l'EC0 §6.4.3.2 + - Les combinaisons ELU accidentelles selon l'EC0 §6.4.3.3 + - Les combinaisons ELS caractéristiques selon l'EC0 §6.5.3 + - Les combinaisons ELS quasi-permanentes selon l'EC0 §6.5.3 + + Pour les structures bois, elle peut également calculer les psi_2 pour + le fluage et les flèches finales. + """ + COEF_G = (1, 1.35) # Ginf, Gsup COEF_Q = 1.5 # Qsup ANALYZE_TYPE = ("Général", "Linéaire", "Second ordre") @@ -26,22 +42,42 @@ def __init__( type_psy_2: str = ["Court terme", "Moyen terme", "Long terme"], **kwargs, ): - """Créer une classe Combinaison qui génère les combinaisons d'action suivant les actions données. - Cette classe est hérité de la classe Projet du module A0_Projet.py. - Elle nécessite toutefois la création d'un modèle de calcul avant d'être instanciée. - Le modèle de calcul est créé par la classe Model_generator dans le module A0_Projet.py. + """Initialise le générateur de combinaisons d'actions. + + Un modèle MEF doit être créé préalablement avec Model_generator et + transmis via l'argument model_generator. Ce modèle doit contenir + au moins une charge pour générer des combinaisons. Args: - model_generator (Model_generator): le modèle de calcul à utiliser pour la génération des combinaisons d'action. - ELU_STR (bool): combiner les charges à l'ELU structure, si vrai -> True, sinon False. - ELU_STR_ACC (bool): combiner les charges à l'ELU accidentel, si vrai -> True, sinon False. - ELS_C (bool): combiner les charges à l'ELS carctéristique, si vrai -> True, sinon False. - ELS_QP (bool): combiner les charges à l'ELS quasi permananent, si vrai -> True, sinon False. - cat (str): catégorie d'exploitation de la zone considéré. Defaults to "Aucune". - kdef (float): Coefficient permettant de prendre en compte le fluage du bois en fonction de sa classe de service. - Si le matériaux est autre que du bois laisser vide. - type_psy_2: détermine le type de psy 2 à récupérer notamment pour le calcul de la flèche dans le bois. - Court terme = 0, Moyen terme = calcul du psy en fonction de l'action donnant le psy le plus élevé, Long terme = 1. + model_generator (Model_generator): Modèle de calcul MEF contenant + les nœuds, barres, matériaux, sections et chargements. + ELU_STR (bool, optional): Générer les combinaisons ELU structure + selon l'EC0 §6.4.3.2 (1.35G + 1.5Q, etc.). Defaults to True. + ELU_STR_ACC (bool, optional): Générer les combinaisons ELU + accidentelles selon l'EC0 §6.4.3.3 (Gk + Ad + psi_1 Qk). + Defaults to False. + ELS_C (bool, optional): Générer les combinaisons ELS caractéristiques + selon l'EC0 §6.5.3 (Gk + Qk + psi_0 Qi). Defaults to False. + ELS_QP (bool, optional): Générer les combinaisons ELS quasi-permanentes + selon l'EC0 §6.5.3 (Gk + psi_2 Qi). Defaults to False. + cat (str, optional): Catégorie d'exploitation selon l'EC1-1-1 + pour la détermination des coefficients psi. + Ex: "Cat A : habitation", "Cat B : bureaux". + Defaults to "Aucune". + kdef (float, optional): Coefficient de déformation différée pour + le calcul des flèches finales en bois (EC5). Laisser None + pour les matériaux autres que le bois. + type_psy_2 (str, optional): Méthode de calcul du psi_2 effectif + pour les flèches en bois. + - "Court terme": psi_2 = 0 (pas de fluage) + - "Moyen terme": psi_2 calculé selon l'action dominante + - "Long terme": psi_2 = 1 (fluage complet) + **kwargs: Arguments transmis à la classe parent Projet. + + Note: + La génération des combinaisons est effectuée automatiquement + lors de l'instanciation. Le modèle MEF est résolu pour chaque + combinaison. """ super().__init__(**kwargs) self._combo_tags = [] diff --git a/ourocode/eurocode/EC1_Vent.py b/ourocode/eurocode/EC1_Vent.py index 57533a2..7753c8b 100644 --- a/ourocode/eurocode/EC1_Vent.py +++ b/ourocode/eurocode/EC1_Vent.py @@ -5,14 +5,11 @@ from PIL import Image import math as mt -from math import * from copy import copy import pandas as pd import forallpeople as si from handcalcs.decorator import handcalc -from PySide6.QtWidgets import QApplication, QInputDialog -from PySide6.QtCore import Qt # sys.path.append(os.path.join(os.getcwd(), "ourocode")) # from eurocode.A0_Projet import Batiment @@ -112,6 +109,8 @@ def _calc_delta_AC(self): "Ao2": "", } # Demande des altitudes via des boîtes de dialogue QInputDialog (PySide6), autonome si aucune QApplication n'existe + from PySide6.QtWidgets import QApplication, QInputDialog + from PySide6.QtCore import Qt app = QApplication.instance() owns_app = False if app is None: @@ -712,7 +711,6 @@ def _Cpe(self): self.load_area, 1, 10, df.iloc[i + 1, j], df.iloc[i, j] ) ) - print(row) df.loc[df.shape[0]] = row df.reset_index(drop=True, inplace=True) @@ -1108,7 +1106,6 @@ def _Cpe(self, direction: str): df_max.iloc[i, j], ) ) - print(row) df.loc[df.shape[0]] = row if self.load_area > 1 and self.load_area < 10: @@ -1310,8 +1307,7 @@ def show_zonage(self): class Toiture_isolee_2_pants(Vent): def __init__(self, phi: float, load_area: float, *args, **kwargs): - """Créer une classe permetant le calcul d_bat'une toiture isolée à deux versants au vent selon l'EN 1991-1-4 §7.3 - ATTENTION : Il ne semble pas y avoir d'inversion de zonage quand le vent est à 0° ou 90° mais une inversion des longueurs de surface A VALIDER ! + """Créer une classe permetant le calcul d'une toiture isolée à deux versants au vent selon l'EN 1991-1-4 §7.3 Args: phi (int | float): le degré d'obstruction sous une toiture isolée entre 0 et 1. diff --git a/ourocode/eurocode/EC3_Assemblage.py b/ourocode/eurocode/EC3_Assemblage.py index 2208417..62c11dd 100644 --- a/ourocode/eurocode/EC3_Assemblage.py +++ b/ourocode/eurocode/EC3_Assemblage.py @@ -7,9 +7,6 @@ import matplotlib.pyplot as plt from matplotlib.patches import Rectangle, Circle -from PySide6.QtWidgets import QApplication, QInputDialog -from PySide6.QtCore import Qt - # Calculs structurels import forallpeople as si si.environment("structural") diff --git a/ourocode/eurocode/EC3_Element_droit.py b/ourocode/eurocode/EC3_Element_droit.py index c105515..44aade4 100644 --- a/ourocode/eurocode/EC3_Element_droit.py +++ b/ourocode/eurocode/EC3_Element_droit.py @@ -28,13 +28,20 @@ def __init__(self, t: si.mm=0, h: si.mm=0, b: si.mm=0, classe_acier: str=CLASSE_ classe_transv (int, optional): classe transversale de la section en fonction de sa capacité de plastification. Defaults to 1. """ super().__init__(**kwargs) + if classe_acier not in self.CLASSE_STEEL: + raise ValueError( + f"Classe d'acier '{classe_acier}' invalide. Valeurs acceptées : {self.CLASSE_STEEL}" + ) + if int(classe_transv) not in (1, 2, 3): + raise ValueError( + f"Classe transversale '{classe_transv}' invalide. Valeurs acceptées : 1, 2, 3 " + "(la classe 4 n'est pas développée)" + ) self.t = t * si.mm self.h = h * si.mm self.b = b * si.mm self.classe_acier = classe_acier - if classe_transv == 4: - raise ValueError("La classe transversale 4 n'est pas développée, merci de choisir une classe transversale entre 1 et 3") - self.classe_transv = classe_transv + self.classe_transv = int(classe_transv) self.__fy_fu() diff --git a/ourocode/eurocode/EC3_Feu.py b/ourocode/eurocode/EC3_Feu.py index 84159fc..2a997aa 100644 --- a/ourocode/eurocode/EC3_Feu.py +++ b/ourocode/eurocode/EC3_Feu.py @@ -2,7 +2,6 @@ # Encoding in UTF-8 by Anthony PARISOT import math as mt import pandas as pd -from PySide6.QtWidgets import QFileDialog from matplotlib import pyplot as plt import forallpeople as si @@ -381,6 +380,7 @@ def show_temperatures(self, screenshot: bool = ("False", "True"), filepath: str= plt.tight_layout() if screenshot: if not filepath: + from PySide6.QtWidgets import QFileDialog filepath = QFileDialog.getSaveFileName( filter="PNG (*.png)", selectedFilter=".png", @@ -411,6 +411,7 @@ def show_reductions_factors(self, screenshot: bool = ("False", "True"), filepath plt.tight_layout() if screenshot: if not filepath: + from PySide6.QtWidgets import QFileDialog filepath = QFileDialog.getSaveFileName( filter="PNG (*.png)", selectedFilter=".png", diff --git a/ourocode/eurocode/EC5_Assemblage.py b/ourocode/eurocode/EC5_Assemblage.py index fc98465..3e27e17 100644 --- a/ourocode/eurocode/EC5_Assemblage.py +++ b/ourocode/eurocode/EC5_Assemblage.py @@ -3,6 +3,7 @@ ############# Le but de ce fichier est de regrouper toute les fonctions d'assemblage par organe métalique dans l'EN-1995 ############# from copy import deepcopy +import warnings import math as mt from math import sin, cos, radians, sqrt, pi @@ -111,7 +112,7 @@ def _min_nef(self, list_nef: list): n_file = deepcopy(self.nfile) self.nfile = self.n self.n = n_file - print("Le sens de traitement de l'assemblage a été changé car le nef mini ce trouve sur la pièce 2 et non sur la pièce 1.\nAttention aux efforts de calcul à prendre en compte.") + warnings.warn("Le sens de traitement de l'assemblage a été changé car le nef mini ce trouve sur la pièce 2 et non sur la pièce 1.\nAttention aux efforts de calcul à prendre en compte.") return list_nef[1] @@ -1540,7 +1541,6 @@ def __t1_t2(self): else: b_beam_2 = self.beam_2.b_calcul l_pointe = self.d_vis - print(self.l , self.beam_1.b_calcul , b_beam_2 , l_pointe) self.t1 = min(self.beam_1.b_calcul, self.l - self.beam_1.b_calcul - b_beam_2 - l_pointe) continue self.t1 = self.beam_1.b_calcul @@ -1916,7 +1916,7 @@ def pince_tirefond_axial(self, t: int): a1CG = 10 * self.d_vis a2CG = 4 * self.d_vis else: - print("L'épaisseur de bois n'est pas suffisante, il faut un bois de {0} mm minimum !".format( + warnings.warn("L'épaisseur de bois n'est pas suffisante, il faut un bois de {0} mm minimum !".format( 12*self.d_vis)) return {"a1": a1, "a2": a2, "a1CG": a1CG, "a2CG": a2CG} @@ -1962,7 +1962,7 @@ def val(): return val() else: - print( + warnings.warn( "le diamètre ne répond pas aux spécifications demandées en 8.7.2(4) de l'EN 1995 partie assemblage") diff --git a/ourocode/eurocode/EC5_Element_droit.py b/ourocode/eurocode/EC5_Element_droit.py index 6303374..4ac8947 100644 --- a/ourocode/eurocode/EC5_Element_droit.py +++ b/ourocode/eurocode/EC5_Element_droit.py @@ -1,6 +1,7 @@ #! env\Scripts\python.exe # Encoding in UTF-8 by Anthony PARISOT from copy import deepcopy +import warnings import matplotlib.pyplot as plt import math as mt @@ -17,6 +18,16 @@ # ================================ GLOBAL ================================== class Barre(Projet): + """Classe définissant les caractéristiques d'un élément droit en bois. + + Cette classe décrit la géométrie, la classe de résistance et les conditions + d'exploitation d'une barre (poutre, colonne, liteau) selon l'EN 1995. + + Elle calcule automatiquement les dimensions de section en fonction de + l'humidité de pose (retrait/gonflement) et donne accès aux caractéristiques + mécaniques normatives du matériau. + """ + LIST_SECTION = ["Rectangulaire","Circulaire"] LIST_TYPE_B = ["Massif", "BLC", "LVL", "OSB 2", "OSB 3/4", "CP"] CLASSE_WOOD = list(Projet._data_from_csv(Projet, "caracteristique_meca_bois.csv").index)[2:] @@ -31,37 +42,75 @@ class Barre(Projet): B90 = 0.25 def __init__(self, b:si.mm, h:si.mm, section: str=LIST_SECTION, Hi: int=12, Hf: int=12, classe: str=CLASSE, cs: int=CS, effet_systeme: bool=("False", "True"), **kwargs): - """Classe qui définit les caractéristiques d'un élément droit. - Cette classe est hérité de la classe Projet du module A0_Projet.py. + """Initialise un élément droit en bois avec ses caractéristiques. Args: - b (int): largeur de pose de la pièce en mm - h (int): hauteur de pose de la pièce en mm - section (str, optional): Type de section. Defaults to "Rectangulaire". - Hi (int, optional): Humidité initiale de pose en %. Defaults to 12. - Hf (int, optional): Humidité finale de pose en %. Defaults to 12. - classe (str, optional): Classe mécanique du bois. Defaults to 'C24'. - cs (int, optional): Classe de service de l'élément. Defaults to 1. - effet_systeme: Détermine si l'effet système s'applique. + b (si.mm): Largeur de pose de la pièce en millimètres (dimension brute). + h (si.mm): Hauteur de pose de la pièce en millimètres (dimension brute). + section (str, optional): Type de section transversale. + "Rectangulaire" ou "Circulaire". Defaults to "Rectangulaire". + Hi (int, optional): Humidité initiale de pose en %. + Humidité au moment de la fabrication/pose. Defaults to 12. + Hf (int, optional): Humidité finale d'équilibre en % selon l'AN (Hf = 12). + Humidité en service. Defaults to 12. + classe (str, optional): Classe de résistance du bois selon l'EC5. + Ex: "C24", "GL28h", "LVL". Defaults to "C24". + cs (int, optional): Classe de service selon l'EC5 §2.3.1.3. + 1 = intérieur chauffé, 2 = couvert non chauffé, 3 = extérieur. + Defaults to 1. + effet_systeme (bool, optional): Active l'effet de système (k_sys = 1.1) + pour les éléments permettant une redistribution des charges + (solives avec répartition continue). Defaults to False. + **kwargs: Arguments supplémentaires transmis à Projet. + + Note: + Les dimensions de calcul (b_calcul, h_calcul) sont automatiquement + ajustées pour tenir compte du retrait si Hi > Hf (AN B90 = 0.25%). + + Raises: + ValueError: Si la section, la classe ou la classe de service est invalide. """ super().__init__(**kwargs) + if section not in self.LIST_SECTION: + raise ValueError( + f"Section '{section}' invalide. Valeurs acceptées : {self.LIST_SECTION}" + ) + if classe not in self.CLASSE: + raise ValueError( + f"Classe '{classe}' invalide. Valeurs acceptées : {self.CLASSE}" + ) + if int(cs) not in (1, 2, 3): + raise ValueError( + f"Classe de service '{cs}' invalide. Valeurs acceptées : 1, 2, 3" + ) self.b = b * si.mm self.h = h * si.mm self.section = section self.Hi = Hi self.Hf = Hf self.classe = classe - self.cs = cs + self.cs = int(cs) self.effet_systeme = effet_systeme self._sectionCalcul() def _sectionCalcul(self): - """ Retourne la section de calcul en fonction de l'humidité de pose et celle d'utilisation avec pour argument: - Hi : Humidité de pose en % - Hf : Humidité finale en % selon AN Hf = 12% - B90 : Coefficient de correction de section selon AN B90 = 0.25 % - cote : Largeur ou hauteur de la section initiale en mm """ + """Calcule les dimensions de section de calcul avec correction humidité. + + Ajuste les dimensions brutes (b, h) en fonction de la variation + d'humidité entre la pose et l'équilibre en service selon la formule + de l'Annexe Nationale française (coefficient B90 = 0.25 %). + + Formule : dimension_calcul = dimension_pose × (1 - B90/100 × (Hi - Hf)) + + Cette correction est appliquée pour le calcul des aires et inerties, + mais les résistances caractéristiques sont basées sur les dimensions + de pose selon les règles de l'EC5. + + Attributs modifiés: + b_calcul (si.mm): Largeur corrigée pour le calcul des inerties. + h_calcul (si.mm): Hauteur corrigée pour le calcul des inerties. + """ self.b_calcul = self.b * (1 - self.B90 / 100 * (self.Hi - self.Hf)) self.h_calcul = self.h * (1 - self.B90 / 100 * (self.Hi - self.Hf)) @@ -176,16 +225,30 @@ def __convert_latex_ftyped(self, latex: str, type_caract: str): return latex - def _f_type_d(self,typeCarac=CARACTERISTIQUE[0:6], loadtype=LOAD_TIME, typecombi=TYPE_ACTION): - """Méthode donnant la résistance de calcul de l'élément fonction de la vérification + def _f_type_d(self, typeCarac=CARACTERISTIQUE[0:6], loadtype=LOAD_TIME, typecombi=TYPE_ACTION): + """Calcule la résistance de calcul f_d selon l'EC5 §2.4.1. + + Applique la formule : f_d = k_mod × k_sys × f_k / gamma_M + où k_mod dépend de la classe de service et de la durée de chargement, + et k_sys est le coefficient d'effet de système (1.1 si activé, 1.0 sinon). Args: - typeCarac (str, optional): Type de résistance caractéristique (flexion = "fm0k", compression = "fc0k" etc.). Defaults to "fm0k". - loadtype (str, optional): Durée de chargement (Permanente, Court terme etc.). Defaults to "Permanente". - typecombi (str, optional): Type de combinaison étudiée ("Fondamentales" ou " Accidentelles"). Defaults to "Fondamentales". + typeCarac (str, optional): Type de résistance caractéristique. + Valeurs possibles : "fm0k" (flexion), "fc0k" (compression), + "ft0k" (traction), "fvk" (cisaillement), etc. + Defaults to "fm0k". + loadtype (str, optional): Durée de chargement selon l'EC5 Tableau 3.1. + Valeurs : "Permanente", "Long terme", "Moyen terme", "Court terme", + "Instantanée". + typecombi (str, optional): Type de combinaison. + "Fondamentales" ou "Accidentelles". Defaults to "Fondamentales". Returns: - float: Résistance de calcul en N/mm2 du type de vérification étudié. + tuple: (latex_string, valeur) où valeur est la résistance de calcul + f_d en N/mm² (MPa) prête à être comparée aux contraintes. + + Note: + Cette méthode utilise @handcalc pour générer la justification LaTeX. """ gamma_M = self._get_gamma_M(typecombi) K_mod = self._get_k_mod(loadtype) @@ -209,7 +272,25 @@ def val(): def _K_h(self): - """ Retourne le coef. Kh qui peut augmenter la resistance caractéristique fm,k et ft,k """ + """Calcule le coefficient de taille K_h selon l'EC5 §3.2 et §3.3. + + Ce coefficient majore la résistance caractéristique à la flexion et + à la traction pour les petites sections, selon le type de bois : + + - Bois massif : K_h = min((150/h)^0.2, 1.3) où h < 150 mm + - BLC : K_h = min((600/h)^0.1, 1.1) où h < 600 mm + - LVL : K_h = 1.0 (non applicable) + + Le coefficient s'applique séparément aux deux dimensions (hauteur + et largeur) pour les sections rectangulaires. + + Returns: + dict: Dictionnaire {'y': K_h_y, 'z': K_h_z} avec les coefficients + pour chaque direction de flexion. + + Note: + Ce coefficient ne s'applique qu'à fm,k et ft,k selon l'EC5. + """ kh = {} dim = {'y': self.h_calcul.value *10**3, 'z': self.b_calcul.value *10**3} @@ -225,13 +306,31 @@ def _K_h(self): else : kh[cle] = 1 else: - print("LVL non pris en compte dans cette fonction") + warnings.warn("LVL non pris en compte dans cette fonction") kh[cle] = 1 return kh def Emean_fin(self, psy_2: float): - """Renvoie le E,mean,fin en fonction du Kdef et du psy2""" + """Calcule le module de Young final E_mean,fin selon l'EC5 §2.3.2.2. + + Le module final tient compte du fluage par la formule : + E_mean,fin = E_0,mean / (1 + psi_2 × k_def) + + où psi_2 est le coefficient de combinaison quasi-permanente et + k_def dépend de la classe de service et du type de bois. + + Args: + psy_2 (float): Coefficient psi_2 de la combinaison quasi-permanente. + 0 pour le court terme, 1 pour le long terme, ou valeur calculée. + + Returns: + tuple: (latex_string, valeur) où valeur est E_mean,fin en MPa. + + Note: + Ce module final est utilisé pour les calculs de flèche en ELS + selon l'EC5 §2.2.3 et §7.2. + """ self.psy_2 = psy_2 E0_mean = int(self.caract_meca.loc["E0mean"]) * si.MPa K_def = self.K_def @@ -247,20 +346,33 @@ def val(): def fleche(self, long:si.mm, Ed_WinstQ:si.mm=0, Ed_Wnetfin:si.mm=0, Ed_Wfin:si.mm=0, Ed_W2:si.mm=0, limit_W2:int=500, type_ele=TYPE_ELE, type_bat=TYPE_BAT): - """Retourne le taux de travail de la flèche avec pour argument: + """Vérifie les taux de travail des flèches selon l'EC5 §7.2. + + Compare les flèches calculées aux limites normatives pour différents + critères ELS. Génère automatiquement un tableau de synthèse avec + _add_synthese_taux_travail. Args: - long (int): La longueur entre appuis à vérifier en mm - Ed_WinstQ (float, optional): La flèche instanténée sous charge variable Q en mm. Defaults to 0. - Ed_Wnetfin (float, optional): La flèche net finale en mm. Defaults to 0. - Ed_Wfin (float, optional): La flèche finale en mm. Defaults to 0. - Ed_W2 (float, optional): La flèche w2 en mm qui est la flèche fragile tenant compte du phasage de pose des éléments fragiles. Defaults to 0. - limit_W2 (int, optional): La limite de flèche w2 des éléments fragiles. Defaults to 500. - type_ele (_type_, optional): Le type d'élément à vérifier. Defaults to TYPE_ELE. - type_bat (_type_, optional): Le type de bâtiment sur lequel on vérifie notre élémennt. Defaults to TYPE_BAT. + long (si.mm): Portée entre appuis à vérifier en millimètres. + Ed_WinstQ (si.mm, optional): Flèche instantanée sous charge variable Q seule. + Defaults to 0. + Ed_Wnetfin (si.mm, optional): Flèche nette finale (sous combinaison quasi-permanente). + Defaults to 0. + Ed_Wfin (si.mm, optional): Flèche finale totale (y compris fluage). + Defaults to 0. + Ed_W2 (si.mm, optional): Flèche w2 tenant compte du phasage de pose + pour éléments fragiles (cloisons, platrerie). Defaults to 0. + limit_W2 (int, optional): Limite de flèche w2 pour éléments fragiles. + Valeur courante : 500 (L/500). Defaults to 500. + type_ele (str, optional): Type d'élément selon limite_fleche.csv. + type_bat (str, optional): Type de bâtiment. Returns: - dict: Retourne le dictionnaire des taux de travails. + tuple: (latex_string, valeurs) où valeurs contient les taux de travail. + + Note: + Les limites de flèche sont définies dans le fichier limite_fleche.csv + selon les recommandations de l'Annexe Nationale française. """ data_csv_fleche = self._data_from_csv("limite_fleche.csv") self.data_fleche= data_csv_fleche.loc[type_ele] @@ -340,6 +452,16 @@ def val(): # ================================ FLEXION ================================== class Flexion(Barre): + """Classe de vérification à la flexion selon l'EN 1995-1-1 §6.1.6, §6.2.3, §6.2.4 et §6.3.3. + + Cette classe effectue les vérifications de résistance à la flexion et + de stabilité au déversement (flambement latéral) pour des poutres en bois. + + Elle hérite de la classe Barre pour récupérer les caractéristiques + géométriques et mécaniques, et utilise le pattern _from_parent_class + pour l'enchaînement des vérifications. + """ + COEF_LEF = {"Appuis simple" : [1, 0.9, 0.8], "Porte à faux": [0.5, 0.8]} LOAD_POS = ( @@ -355,19 +477,31 @@ def __init__(self, coeflef_z: float=0.9, pos: str=LOAD_POS, *args, **kwargs): - """Classe permettant le calcul de la flexion d'une poutre bois selon l'EN 1995 §6.1.6, §6.2.3, §6.2.4 et §6.3.3. - Cette classe est hérité de la classe Barre, provenant du module EC5_Element_droit.py. + """Initialise une vérification en flexion avec paramètres de déversement. Args: - lo_rel_y/z (int): longueur de déversemment autour de l'axe défini en mm - coeflef_y/z (float): appuis simple : - Moment constant : 1 - Charge répartie constante : 0.9 - Charge concentrée au milieu de la portée : 0.8 - porte à faux : - Charge répartie constante : 0.5 - Charge concentrée agissant à l'extrémité libre : 0.8. - pos (str): positionnement de la charge sur la hauteur de poutre + lo_rel_y (si.mm): Longueur de déversement effective autour de l'axe Y + (entre appuis latéraux), en millimètres. + lo_rel_z (si.mm): Longueur de déversement effective autour de l'axe Z, + en millimètres. + coeflef_y (float, optional): Coefficient de longueur efficace selon l'EC5. + - Appuis simple : 1.0 (moment constant), 0.9 (charge répartie), + 0.8 (charge concentrée centrale) + - Porte-à-faux : 0.5 (charge répartie), 0.8 (charge concentrée bout) + Defaults to 0.9. + coeflef_z (float, optional): Idem pour l'axe Z. Defaults to 0.9. + pos (str, optional): Position de la charge verticale sur la hauteur. + "Charge sur fibre comprimée": charge au-dessus de l'axe neutre + (aggrave le déversement, +2h sur l_ef) + "Charge sur fibre neutre": charge au centre de gravité + "Charge sur fibre tendue": charge en dessous de l'axe neutre + (favorise la stabilité, -0.5h sur l_ef) + *args: Arguments transmis à la classe parent Barre. + **kwargs: Arguments nommés transmis à Barre (b, h, classe, etc.). + + Note: + La longueur efficace de déversement l_ef est calculée par : + l_ef = lo_rel × coeflef (+ correction selon pos) """ super().__init__(*args, **kwargs) self.lo_rel_y = lo_rel_y* si.mm @@ -385,7 +519,17 @@ def K_h(self): @property def K_m(self): - """ Retourne le coef. Km qui reduit les contrainte d'une poutre scié en flexion """ + """Coefficient de distribution des contraintes K_m selon l'EC5 §6.1.6. + + Ce coefficient réduit la contrainte de flexion calculée pour les + sections rectangulaires en bois massif, BLC ou LVL afin de tenir + compte de la redistribution plastique des contraintes. + + Returns: + float: Valeur de K_m. + - 0.7 pour les sections rectangulaires en bois massif, BLC, LVL + - 1.0 pour les sections circulaires ou les panneaux dérivés + """ if self.type_bois == "Massif" or self.type_bois == "BLC" or self.type_bois == "LVL": if self.section == "Rectangulaire": km = 0.7 @@ -397,7 +541,17 @@ def K_m(self): @property def sigma_m_crit(self): - """ Retourne sigma m,crit pour la prise en compte du déversement d'une poutre """ + """Contrainte critique de déversement sigma_m,crit selon l'EC5 §6.3.3. + + Calculée par la formule de l'EC5 : sigma_m,crit = (0.78 × b² × E_0,05) / (h × l_ef) + + Cette contrainte caractérise la stabilité latérale de la poutre. + Elle est corrigée en fonction de la position de la charge (pos). + + Returns: + tuple: (latex_string, valeurs) où valeurs est un dict {'y': ..., 'z': ...} + avec les contraintes critiques pour chaque direction. + """ self.l_ef_y = self.lo_rel_y * self.coeflef['y'] self.l_ef_z = self.lo_rel_z * self.coeflef['z'] if self.pos == "Charge sur fibre comprimée": @@ -423,7 +577,20 @@ def val(): @property def lamb_rel_m(self): - """ Retourne l'élancement relatif de la section avec pour argument """ + """Élancement relatif en flexion lambda_rel,m selon l'EC5 §6.3.3. + + Rapport entre la résistance caractéristique et la contrainte critique : + lambda_rel,m = sqrt(f_m,k / sigma_m,crit) + + Cet élancement caractérise le risque de déversement : + - lambda_rel,m <= 0.75 : pas de risque de déversement (K_crit = 1) + - 0.75 < lambda_rel,m <= 1.4 : zone de transition + - lambda_rel,m > 1.4 : risque élevé de déversement + + Returns: + tuple: (latex_string, valeurs) où valeurs est un dict {'y': ..., 'z': ...} + avec les élancements relatifs pour chaque direction. + """ f_m0k = float(self.caract_meca.loc['fm0k']) *si.MPa sigma_m_crit_y = self.sigma_m_crit[1]['y'] sigma_m_crit_z = self.sigma_m_crit[1]['z'] @@ -437,7 +604,22 @@ def val(): @property def K_crit(self): - """ Retourne K,crit le coef. de minoration de la résistance en flexion au déversement""" + """Coefficient de déversement K_crit selon l'EC5 §6.3.3. + + Ce coefficient minore la résistance à la flexion pour tenir compte +du risque de déversement latéral. Il dépend de l'élancement relatif : + + - lambda_rel,m <= 0.75 : K_crit = 1 (pas de déversement) + - 0.75 < lambda_rel,m <= 1.4 : K_crit = 1.56 - 0.75 × lambda_rel,m + - lambda_rel,m > 1.4 : K_crit = 1 / lambda_rel,m² + + Returns: + tuple: (latex_string, valeurs) où valeurs est un dict {'y': ..., 'z': ...} + avec les coefficients de déversement pour chaque direction. + + Note: + La vérification finale utilise : sigma_m,d <= K_crit × f_m,d + """ lamb_rel_m_y = self.lamb_rel_m[1]['y'] lamb_rel_m_z = self.lamb_rel_m[1]['z'] result = [None, {"y": None, "z": None}] @@ -472,21 +654,42 @@ def val(): return result def f_m_d(self, loadtype=Barre.LOAD_TIME, typecombi=Barre.TYPE_ACTION): - """Retourne la résistance f,m,d de l'élément en MPa + """Calcule la résistance de calcul en flexion f_m,d selon l'EC5 §6.1.6. + + La résistance est déterminée à partir de la résistance caractéristique fm,0,k + et des coefficients de modification (kmod, γM). Args: - loadtype (str): chargement de plus courte durée sur l'élément. - typecombi (str): type de combinaison, fondamentale ou accidentelle. + loadtype (str): Classe de durée de chargement (permanent, long terme, etc.). + Voir Barre.LOAD_TIME pour les valeurs possibles. + typecombi (str): Type de combinaison d'actions. + "fondamentale" ou "accidentelle". Defaults to "fondamentale". Returns: - float: f,m,d en MPa + float: Résistance de calcul fm,d en MPa avec unité (si.MPa). """ return self._f_type_d("fm0k", loadtype, typecombi) def sigma_m_d(self, My: si.kN*si.m, Mz: si.kN*si.m): - """ Retourne la contrainte sigma,m,d suivant sont axes de flexion avec : - My/z : Moment autour de l'axe y et/ou z dans la barre en kN.m + """Calcule les contraintes de flexion sigma_m,d selon l'EC5 §6.1.6. + + Détermine les contraintes normales dues aux moments fléchissants My et Mz + en utilisant la formule de Navier : σ = M·y/I + + Args: + My (si.kN*m): Moment fléchissant autour de l'axe y (moment vertical) + en kN·m. Mettre 0 si pas de flexion selon cet axe. + Mz (si.kN*m): Moment fléchissant autour de l'axe z (moment horizontal) + en kN·m. Mettre 0 si pas de flexion selon cet axe. + + Returns: + tuple: (latex_string, valeurs) où valeurs est un dictionnaire : + {"y": sigma_my_d, "z": sigma_mz_d} en MPa avec unités. + + Note: + Les valeurs sont stockées dans l'attribut sigma_m_rd. + Pour une section rectangulaire : sigma = M·h/(2·I) = 6·M/(b·h²) """ self.Md = {'y': My * si.kN*si.m, 'z': Mz * si.kN*si.m} self.sigma_m_rd = {'y': 0 * si.MPa, 'z': 0 * si.MPa} @@ -511,16 +714,34 @@ def val(): def taux_m_d(self, compression: object=None, traction: object=None): - """Retourne les différents taux de travaux en flexion. - Si l'élement est une poutre (donc avec un travail principalement en flexion) et de la compression (EN 1995-1-1 §6.3.3) ou de la traction (EN 1995-1-1 §6.2.3) combinée, - il est possible d'ajouter l'objet Compression et Traction et de vérifier ces combinaisons. + """Calcule les taux de travail en flexion selon l'EC5 §6.1.6, §6.2.3 et §6.3.3. + + Vérifie les critères de résistance en flexion pure, flexion déviée, + flexo-compression et flexo-traction selon les équations : + - 6.11 et 6.12 : Flexion déviée avec K_m (facteur de distribution) + - 6.33 : Flexion avec déversement (K_crit) + - 6.17-6.20 : Combinaisons flexion + traction/compression + - 6.35 : Flexo-compression avec risque de déversement Args: - compression (object, optional): L'objet Compression avec ces taux de travaux préalablement calculés. Defaults to None. - traction (object, optional): L'objet Traction avec ces taux de travaux préalablement calculés. Defaults to None. + compression (Compression, optional): Objet Compression déjà calculé + pour les combinaisons flexo-compression. Defaults to None. + traction (Traction, optional): Objet Traction déjà calculé + pour les combinaisons flexo-traction. Defaults to None. Returns: - list: retourne la liste des taux de travaux en %""" + tuple: (latex_string, taux_dict) où taux_dict contient : + - "equ6.11", "equ6.12" : Flexion déviée + - "equ6.33y", "equ6.33z" : Flexion avec déversement + - "equ6.17", "equ6.18" : Flexion + traction (si traction fournie) + - "equ6.19", "equ6.20" : Flexion + compression (si compression fournie) + - "equ6.23-6.35" : Combinaisons avancées (si compression fournie) + Valeurs en pourcentage (0.85 = 85%). + + Note: + Cette méthode met à jour automatiquement la synthèse des taux + de travail via _add_synthese_taux_travail. + """ self.taux_m_rd = {} sigma_my_d = self.sigma_m_rd['y'] @@ -597,14 +818,26 @@ def tract(taux_6_11, taux_6_12): self._add_synthese_taux_travail(synthese) return (latex, self.taux_m_rd) +class Traction(Barre): + """Classe de vérification des éléments bois en traction axiale selon l'EC5 §6.1.2. + Effectue les calculs de résistance et de contrainte en traction axiale selon + l'Eurocode 5 - Partie 1-1. Hérite de Barre pour les caractéristiques + géométriques et mécaniques. -# ================================ Traction ================================== + La vérification principale est le taux de travail en traction (équation 6.1): + σ_t,0,d / (f_t,0,d · k_h) ≤ 1 + """ -class Traction(Barre): def __init__(self, *args, **kwargs): - """Classe permettant le calcul de la Traction d'un élément bois selon l'EN 1995. - Cette classe est hérité de la classe Barre, provenant du module EC5_Element_droit.py. + """Initialise un objet de vérification en traction axiale. + + Hérite de toutes les caractéristiques de Barre (section, classe de bois, + etc.). Aucun paramètre supplémentaire requis à l'initialisation. + + Args: + *args: Arguments positionnels transmis à Barre. + **kwargs: Arguments nommés transmis à Barre (b, h, classe, etc.). """ super().__init__(*args, **kwargs) @@ -613,27 +846,46 @@ def __init__(self, *args, **kwargs): def K_h(self): """ Retourne le coef. Kh qui peut augmenter la resistance caractéristique fm,k et ft,k """ return self._K_h() - - + + def f_t_0_d(self, loadtype=Barre.LOAD_TIME, typecombi=Barre.TYPE_ACTION): - """Retourne la résistance f,t,0,d de l'élément en MPa + """Calcule la résistance de calcul en traction axiale f_t,0,d selon l'EC5 §6.1.2. + + Détermine la résistance à partir de la résistance caractéristique ft,0,k + et des coefficients de modification (kmod, γM). Args: - loadtype (str): chargement de plus courte durée sur l'élément. - typecombi (str): type de combinaison, fondamentale ou accidentelle. + loadtype (str): Classe de durée de chargement. + Voir Barre.LOAD_TIME pour les valeurs possibles. + typecombi (str): Type de combinaison d'actions. + "fondamentale" ou "accidentelle". Defaults to "fondamentale". Returns: - float: f,t,0,d en MPa + float: Résistance de calcul ft,0,d en MPa avec unité (si.MPa). """ return super()._f_type_d("ft0k", loadtype, typecombi) - - + + def sigma_t_0_d(self, Ft0d: si.kN, Anet: si.mm**2=None): - """Retourne la contrainte de traxion axial en MPa avec: + """Calcule la contrainte de traction axiale sigma_t,0,d selon l'EC5 §6.1.2. + + Détermine la contrainte normale due à l'effort de traction axial. + Prend en compte une section nette réduite (perçages, entailles) si spécifiée. Args: - Ft0d (float): la charge en kN de compression - Anet (float|optional): si il y a une réduction de la section en traction alors renseigner l'aire nette de traction en mm2 + Ft0d (si.kN): Effort de traction axial en kN. + Anet (si.mm**2, optional): Aire nette de la section en mm² si réduction + (perçages, entailles). Doit être ≤ aire brute. Defaults to None. + + Returns: + tuple: (latex_string, valeur) où valeur est sigma_t,0,d en MPa avec unité. + + Raises: + ValueError: Si Anet > aire brute de la section. + + Note: + La valeur est stockée dans l'attribut sigma_t_0_rd. + Pour les assemblages boulonnés, utiliser Anet pour tenir compte des trous. """ self.Ft_0_d = Ft0d * si.kN Ft_0_d = self.Ft_0_d @@ -652,10 +904,20 @@ def val(): def taux_t_0_d(self): - """Retourne le taux de travail en traction axial. + """Calcule le taux de travail en traction axiale selon l'EC5 §6.1.2 (Eq. 6.1). + + Vérifie le critère : σ_t,0,d / (k_h · f_t,0,d) ≤ 1 + + Le coefficient k_h (effet de hauteur) est pris comme le minimum des + valeurs selon y et z pour être conservateur. Returns: - float: taux de travail en % + tuple: (latex_string, valeur) où valeur est le taux en pourcentage + (0.75 = 75%). Stocké dans taux_t_0_rd['equ6.1']. + + Note: + Cette méthode met à jour automatiquement la synthèse des taux + de travail via _add_synthese_taux_travail. """ self.taux_t_0_rd = {} K_h_y = self.K_h['y'] @@ -679,28 +941,53 @@ def val(): # ================================ Compression ================================== - + class Compression(Barre): + """Classe de vérification des éléments bois en compression axiale selon l'EC5 §6.2 et §6.3.2. + + Effectue les calculs de résistance, élancement et flambement selon + l'Eurocode 5 - Partie 1-1. Hérite de Barre pour les caractéristiques + géométriques et mécaniques. + + Vérifie : + - La résistance en compression axiale (§6.2.2, Eq. 6.2) + - Le flambement avec coefficient kc (§6.3.2, Eq. 6.23-6.24) + - Les combinaisons flexo-compression (si objet Flexion fourni) + """ + COEF_LF = {"Encastré 1 côté" : 2, "Rotule - Rotule" : 1, "Encastré - Rotule" : 0.7, "Encastré - Encastré" : 0.5, "Encastré - Rouleau" : 1} + def __init__(self, lo_y: si.mm, lo_z: si.mm, type_appuis: str=COEF_LF, *args, **kwargs): - """ Classe permettant le calcul de la Compression d'un élément bois selon l'EN 1995. - Cette classe est hérité de la classe Barre, provenant du module EC5_Element_droit.py. - + """Initialise un objet de vérification en compression axiale. + + Définit les longueurs de flambement et le coefficient de longueur efficace + selon les conditions d'appui pour le calcul du flambement. + Args: - lo : Longueur de flambement suivant l'axe de rotation (y ou z) en mm si pas de flambement alors 0 - type_appuis : Coefficient multiplicateur de la longueur pour obtenir la longeur efficace de flambement en - fonction des types d'appui : - Encastré 1 côté : 2 - Rotule - Rotule : 1 - Encastré - Rotule : 0.7 - Encastré - Encastré : 0.5 - Encastré - Rouleau : 1 + lo_y (si.mm): Longueur de flambement suivant l'axe y en mm. + Mettre 0 si pas de risque de flambement selon cet axe. + lo_z (si.mm): Longueur de flambement suivant l'axe z en mm. + Mettre 0 si pas de risque de flambement selon cet axe. + type_appuis (str): Type de conditions d'appui pour le coefficient β. + Détermine la longueur efficace lf = β · lo. + Valeurs possibles (voir COEF_LF): + - "Encastré 1 côté" : β = 2.0 (console) + - "Rotule - Rotule" : β = 1.0 (articulé-articulé) + - "Encastré - Rotule" : β = 0.7 + - "Encastré - Encastré" : β = 0.5 + - "Encastré - Rouleau" : β = 1.0 (encastré-glissière) + Defaults to "Rotule - Rotule". + *args: Arguments positionnels transmis à Barre. + **kwargs: Arguments nommés transmis à Barre (b, h, classe, etc.). + + Note: + La longueur efficace de flambement lf est calculée par : + lf = lo × coef_lef """ - super().__init__(*args, **kwargs) self.lo_comp = {"y":lo_y * si.mm, "z":lo_z * si.mm} self.lo_y = self.lo_comp['y'] @@ -784,24 +1071,45 @@ def val(): def f_c_0_d(self, loadtype=Barre.LOAD_TIME, typecombi=Barre.TYPE_ACTION): - """Retourne la résistance f,c,0,d de l'élément en MPa + """Calcule la résistance de calcul en compression axiale f_c,0,d selon l'EC5 §6.2.2. + + Détermine la résistance à partir de la résistance caractéristique fc,0,k + et des coefficients de modification (kmod, γM). Args: - loadtype (str): chargement de plus courte durée sur l'élément. - typecombi (str): type de combinaison, fondamentale ou accidentelle. + loadtype (str): Classe de durée de chargement. + Voir Barre.LOAD_TIME pour les valeurs possibles. + typecombi (str): Type de combinaison d'actions. + "fondamentale" ou "accidentelle". Defaults to "fondamentale". Returns: - float: f,c,0,d en MPa + float: Résistance de calcul fc,0,d en MPa avec unité (si.MPa). + + Note: + Cette valeur est réduite par le coefficient kc en cas de flambement. """ return super()._f_type_d("fc0k", loadtype, typecombi) def sigma_c_0_d(self, Fc0d: si.kN, Anet: si.mm**2=None): - """Retourne la contrainte de compression axial en MPa avec: + """Calcule la contrainte de compression axiale sigma_c,0,d selon l'EC5 §6.2.2. + + Détermine la contrainte normale due à l'effort de compression axial. + Prend en compte une section nette réduite si spécifiée. Args: - Fc0d (float): la charge en kN de compression - Anet (float|optional): si il y a une réduction de la section en compression alors renseigner l'aire nette de compression en mm2 + Fc0d (si.kN): Effort de compression axial en kN. + Anet (si.mm**2, optional): Aire nette de la section en mm² si réduction + (entailles, perçages). Doit être ≤ aire brute. Defaults to None. + + Returns: + tuple: (latex_string, valeur) où valeur est sigma_c,0,d en MPa avec unité. + + Raises: + ValueError: Si Anet > aire brute de la section. + + Note: + La valeur est stockée dans l'attribut sigma_c_0_rd. """ self.Fc_0_d = Fc0d * si.kN Fc_0_d = self.Fc_0_d @@ -819,15 +1127,27 @@ def val(): def taux_c_0_d(self, flexion: object=None): - """Retourne les taux de travaux de la compression axial. - Si l'élement est un poteau (donc avec un travail principalement en compression) et de la flexion combinée (EN 1995-1-1 §6.3.2), - il est possible d'ajouter l'objet flexion et de vérifier cette combinaison. + """Calcule les taux de travail en compression axiale selon l'EC5 §6.2.2 et §6.3.2. + + Vérifie les critères de résistance : + - Equ. 6.2 : Compression simple (sigma_c,0,d / f_c,0,d) + - Equ. 6.23-6.24 : Flambement (sigma_c,0,d / (kc · f_c,0,d)) + - Equ. 6.19-6.20 : Flexo-compression (si objet Flexion fourni) Args: - flexion (object, optional): L'objet Flexion avec ces taux de travaux préalablement calculés. Default to None. + flexion (Flexion, optional): Objet Flexion déjà calculé + pour les combinaisons flexo-compression. Defaults to None. Returns: - list: retourne la liste des taux de travaux en % + tuple: (latex_string, taux_dict) où taux_dict contient : + - "equ6.2" : Compression simple sans flambement + - "equ6.23", "equ6.24" : Compression avec flambement selon y et z + - "equ6.19", "equ6.20" : Flexo-compression (si flexion fournie) + Valeurs en pourcentage (0.85 = 85%). + + Note: + Cette méthode met à jour automatiquement la synthèse des taux + de travail via _add_synthese_taux_travail. """ self.taux_c_0_rd = {} sigma_c_0_d = self.sigma_c_0_rd @@ -873,11 +1193,17 @@ def val(): self._add_synthese_taux_travail(synthese) return value - +class Compression_perpendiculaire(Barre): + """Classe de vérification des éléments bois en compression perpendiculaire selon l'EC5 §6.1.5. - # ================================ COMPRESSION PERPENDICULAIRE ================================== + Effectue les calculs de résistance et de contrainte en compression perpendiculaire + au fil du bois (appuis de poutres, abouts de pieux, etc.) selon l'Eurocode 5. + Hérite de Barre pour les caractéristiques géométriques et mécaniques. + + La vérification utilise l'équation 6.3 avec le coefficient K_c,90 : + σ_c,90,d / (K_c,90 · f_c,90,d) ≤ 1 + """ -class Compression_perpendiculaire(Barre): TYPE_APPUIS = ("Appuis discret", "Appuis continu") def __init__( self, @@ -891,19 +1217,23 @@ def __init__( *args, **kwargs ): - """Classe intégrant les formules de compression perpendiculaire selon l'EN 1995 §6.1.5. - Cette classe est hérité de la classe Barre, provenant du module EC5_Element_droit.py. + """Initialise un objet de vérification en compression perpendiculaire. Args: - b_appuis(int): largeur d'appuis en mm. - l_appuis(int): longeur de l'appuis en mm. - l1d(int) : Distance entre les charges en mm (l et l) (si pas de l1d ne rien mettre). - l1g(int) : Distance entre les charges en mm (l et l) (si pas de l1g ne rien mettre). - ad(int) : Distance depuis le bord jusqu'à l'appuis à droite (l) en mm (si pas de ad et au bord ne rien mettre). - ag(int) : Distance depuis le bord jusqu'à l'appuis à gauche (l) en mm (si pas de ad et au bord ne rien mettre). - type_appuis_90(str) : Type d'appuis (Appui continu, Appui discret) + b_appuis (si.mm): Largeur d'appuis en mm. + l_appuis (si.mm): Longueur de l'appuis en mm. + l1d (si.mm, optional): Distance entre les charges en mm (l et l) (si pas de l1d ne rien mettre). Defaults to 10000. + l1g (si.mm, optional): Distance entre les charges en mm (l et l) (si pas de l1g ne rien mettre). Defaults to 10000. + ad (si.mm, optional): Distance depuis le bord jusqu'à l'appuis à droite (l) en mm (si pas de ad et au bord ne rien mettre). Defaults to 0. + ag (si.mm, optional): Distance depuis le bord jusqu'à l'appuis à gauche (l) en mm (si pas de ad et au bord ne rien mettre). Defaults to 0. + type_appuis_90 (str, optional): Type d'appuis (Appui continu, Appui discret). Defaults to TYPE_APPUIS. + *args: Arguments transmis à Barre (classe, etc.). + **kwargs: Arguments transmis à Barre (b, h, etc.). + + Note: + La longueur efficace de compression perpendiculaire l_ef est calculée + en fonction de la configuration des appuis et des distances entre charges. """ - super().__init__(*args, **kwargs) self.b_appuis = b_appuis * si.mm self.l_appuis = l_appuis * si.mm @@ -915,13 +1245,12 @@ def __init__( @property def K_c90(self): - """ Retourne le facteur Kc,90 qui tient compte de la configuration de chargement, du fendage et de la déformation + """Retourne le facteur K_c,90 qui tient compte de la configuration de chargement, du fendage et de la déformation en compression avec pour argument : h : Hauteur de l'élement subissant la compression en mm lO : Longeur de l'appuis en compression en mm l1 : Distance la plus petite entre deux appuis en mm (l et l) - """ - + """ try: return self._setter_K_c90 except AttributeError: @@ -988,22 +1317,38 @@ def K_c90(self, value): def f_c_90_d(self, loadtype: str=Barre.LOAD_TIME, typecombi: str=Barre.TYPE_ACTION): - """Retourne la résistance f,c,90,d de l'élément en MPa + """Calcule la résistance de calcul en compression perpendiculaire f_c,90,d selon l'EC5 §6.1.5. + + Détermine la résistance à partir de la résistance caractéristique fc,90,k + et des coefficients de modification (kmod, γM). Args: - loadtype (str): chargement de plus courte durée sur l'élément. - typecombi (str): type de combinaison, fondamentale ou accidentelle. + loadtype (str): Classe de durée de chargement. + Voir Barre.LOAD_TIME pour les valeurs possibles. + typecombi (str): Type de combinaison d'actions. + "fondamentale" ou "accidentelle". Defaults to "fondamentale". Returns: - float: f,c,90,d en MPa + float: Résistance de calcul fc,90,d en MPa avec unité (si.MPa). """ return super()._f_type_d("fc90k", loadtype, typecombi) def sigma_c_90_d(self, Fc90d: si.kN): - """ Retourne la contrainte normal de compression à 90 degrés en MPa avec pour argument: + """Calcule la contrainte de compression perpendiculaire sigma_c,90,d selon l'EC5 §6.1.5. + + Détermine la contrainte en tenant compte de l'aire effective d'appui, + qui inclut une diffusion des efforts sur 30 mm ou jusqu'aux bords/entraxe. - Fc90d : Charge en compression perpendiculaire en kN + Args: + Fc90d (si.kN): Effort de compression perpendiculaire en kN. + + Returns: + tuple: (latex_string, valeur) où valeur est sigma_c,90,d en MPa avec unité. + + Note: + L'aire effective a_ef = (l_appuis + min(30mm, distance_bord, l_appuis, 0.5×entraxe)) × b_appuis + La valeur est stockée dans l'attribut sigma_c_90_rd. """ self.Fc90d = Fc90d * si.kN @@ -1027,7 +1372,23 @@ def val(): def taux_c_90_d(self): - """ Retourne le taux de travail de la compression perpendiculaire """ + """Calcule le taux de travail en compression perpendiculaire selon l'EC5 §6.1.5 (Eq. 6.3). + + Vérifie le critère : σ_c,90,d / (K_c,90 · f_c,90,d) ≤ 1 + + Le coefficient K_c,90 (déterminé par la propriété K_c90) tient compte : + - De la configuration des appuis (discrets ou continus) + - Du type de bois (massif, BLC, etc.) + - De la hauteur de l'élément et des distances entre appuis + + Returns: + tuple: (latex_string, valeur) où valeur est le taux en pourcentage + (0.75 = 75%). Stocké dans taux_c_90_rd['equ6.3']. + + Note: + Cette méthode met à jour automatiquement la synthèse des taux + de travail via _add_synthese_taux_travail. + """ self.taux_c_90_rd = {} sigma_c_90_d = self.sigma_c_90_rd K_c90 = self.K_c90 @@ -1055,19 +1416,44 @@ def show_c90(self): class Compression_inclinees(Compression_perpendiculaire): + """Classe de vérification des éléments bois en compression inclinée selon l'EC5 §6.2.2. + + Effectue les calculs de résistance et de contrainte en compression inclinée + par rapport au fil du bois selon l'Eurocode 5 - Partie 1-1, article 6.2.2. + Hérite de Compression_perpendiculaire pour la gestion des appuis et des coefficients. + + La vérification utilise l'équation 6.16 avec la formule de Hankinson : + σ_c,α,d ≤ f_c,0,d / [(f_c,0,d/(K_c,90·f_c,90,d))·sin²(α) + cos²(α)] + """ + def __init__(self, alpha: float=45, **kwargs): - """Classe qui permet de calculer la compression inclinées par rapport au fil comme décrit à l'EN 1995 §6.2.2. - Cette classe est hérité de la classe Compression_perpendiculaire provenant du module EC5_Element_droit.py. + """Initialise un objet de vérification en compression inclinée. Args: - alpha (float, optional): angle d'inclinaison en degrés de la compression. Defaults to 0. + alpha (float, optional): Angle d'inclinaison de la compression par rapport + au fil du bois en degrés. 0° = compression axiale, 90° = compression + perpendiculaire. Defaults to 45°. + **kwargs: Arguments transmis à Compression_perpendiculaire + (b_appuis, l_appuis, etc.). """ super().__init__(**kwargs) self.alpha = alpha def sigma_c_alpha_d(self, Fcad: si.kN): - """ Retourne la contrainte de compression inclinée en MPa avec: - Fcad : la charge en kN de compression inclinée """ + """Calcule la contrainte de compression inclinée sigma_c,alpha,d. + + Détermine la contrainte normale due à l'effort de compression inclinée, + en utilisant l'aire brute d'appui (sans diffusion). + + Args: + Fcad (si.kN): Effort de compression inclinée en kN. + + Returns: + tuple: (latex_string, valeur) où valeur est sigma_c,alpha,d en MPa avec unité. + + Note: + La valeur est stockée dans l'attribut sigma_c_alpha_rd. + """ b_appuis = self.b_appuis l_appuis = self.l_appuis self.Fc_alpha_d = Fcad * si.kN @@ -1085,11 +1471,24 @@ def val(): def taux_c_alpha_d(self, loadtype=Barre.LOAD_TIME, typecombi=Barre.TYPE_ACTION): - """ Retourne le taux de travail de la compression inclinées par rapport au fil + """Calcule le taux de travail en compression inclinée selon l'EC5 §6.2.2 (Eq. 6.16). + + Vérifie le critère de Hankinson : σ_c,α,d ≤ f_c,α,d + où f_c,α,d = f_c,0,d / [(f_c,0,d/(K_c,90·f_c,90,d))·sin²(α) + cos²(α)] Args: - loadtype (str): chargement de plus courte durée sur l'élément. - typecombi (str): type de combinaison, fondamentale ou accidentelle. + loadtype (str): Classe de durée de chargement. + Voir Barre.LOAD_TIME pour les valeurs possibles. + typecombi (str): Type de combinaison d'actions. + "fondamentale" ou "accidentelle". Defaults to "fondamentale". + + Returns: + tuple: (latex_string, valeur) où valeur est le taux en pourcentage + (0.75 = 75%). Stocké dans taux_c_alpha_rd['equ6.16']. + + Note: + Cette méthode met à jour automatiquement la synthèse des taux + de travail via _add_synthese_taux_travail. """ self.taux_c_alpha_rd = {} f_c_0_d = self._f_type_d("fc0k", loadtype, typecombi)[1] @@ -1116,10 +1515,26 @@ def val(): # ================================ CISAILLEMENT ================================== class Cisaillement(Barre): + """Classe de vérification des éléments bois au cisaillement selon l'EC5 §6.1.7 et §6.5. + + Effectue les calculs de contrainte et taux de travail au cisaillement + longitudinal pour des poutres en bois selon l'Eurocode 5. + Hérite de Barre pour les caractéristiques géométriques et mécaniques. + + Vérifie : + - La résistance au cisaillement (§6.1.7, Eq. 6.13) + - Le cisaillement avec entaille (§6.5, Eq. 6.60) avec facteur K_v + """ + DICT_KN = {"Massif": 5,"BLC": 6.5, "LVL": 4.5} def __init__(self, **kwargs): - """Classe qui permet de calculer le cisaillement d'une poutre comme décrit à l'EN 1995 §6.1.7 et §6.5. - Cette classe est hérité de la classe Barre, provenant du module EC5_Element_droit.py. + """Initialise un objet de vérification au cisaillement. + + Hérite de toutes les caractéristiques de Barre. Initialise K_v à 1 + (pas d'entaille) et h_ef à la hauteur totale. + + Args: + **kwargs: Arguments transmis à Barre (classe, etc.). """ super().__init__(**kwargs) self.K_v = 1 @@ -1127,16 +1542,21 @@ def __init__(self, **kwargs): @property def K_cr(self): - """ Retourne le facteur de réduction de largeur Kcr avec pour argument: - cs: Classe de service de la poutre - CS 1 : 1 - CS 2 : 2 - CS 3 : 3 - h : Hauteur en mm - type_bois : Type de bois - Massif : 0 - BLC : 1 - Autre : 2 """ + """Facteur de réduction de largeur K_cr selon l'EC5 §6.1.7. + + Tient compte des fissures de séchage dans le bois massif et BLC. + Réduction de 33% (K_cr = 0.67) pour les sections fragilisées. + + Returns: + float: Valeur de K_cr. + - 1.0 : Pas de réduction (bois sans fissuration significative) + - 0.67 : Réduction pour bois massif h > 150mm (CS1/CS2) ou BLC (CS2/CS3) + + Note: + CS1/CS2 : K_cr = 0.67 si h > 150mm et bois massif + CS2 : K_cr = 0.67 pour BLC + CS3 : K_cr = 0.67 pour tout bois + """ if self.cs == 1: if self.h_calcul.value * 10**3 > 150 and self.type_bois == "Massif": return 0.67 @@ -1154,23 +1574,34 @@ def K_cr(self): def Kv(self, hef:si.mm, x:si.mm, i_lo:si.mm, ent=("Dessous", "Dessus")): - """Retourne le facteur d'entaille Kv pour une entaille au niveau d'un appuis + """Calcule le facteur de réduction d'entaille K_v selon l'EC5 §6.5. + + Ce coefficient réduit la résistance au cisaillement en présence d'une + entaille au niveau d'un appui (entaille en dessous ou au dessus de la poutre). + + Formule EC5 : K_v = min(1, [K_n(1 + 1.1·i^1.5/√h)] / [√h·(√(α(1-α)) + 0.8·x/h·√(1/α - α²))]) + où α = h_ef/h et i = i_lo/h_ef Args: - hef (int): Hauteur efficace de la poutre (hauteur - hauteur de l'entaille) en mm - x (int):Distance entre le centre de réaction à l'appuis et le coin de l'entaille en mm - i_lo (float): longueur horizontal de l'entaille en mm - ent (tuple, optional): Entaille sur le dessus ou dessous de la poutre. + hef (si.mm): Hauteur efficace de la poutre (hauteur - profondeur entaille) en mm. + x (si.mm): Distance entre le centre de réaction à l'appui et le coin de l'entaille en mm. + i_lo (si.mm): Longueur horizontale de l'entaille en mm. + ent (str, optional): Position de l'entaille : "Dessous" ou "Dessus". + Defaults to "Dessous". Returns: - float: facteur Kv + tuple: (latex_string, valeur) où valeur est le facteur K_v (≤ 1). + Si ent="Dessus", retourne K_v = 1 (pas de réduction). + + Note: + La valeur est stockée dans l'attribut K_v. + K_n dépend du type de bois (voir DICT_KN). """ K_n = self.DICT_KN[self.type_bois] x = x * si.mm h_ef = hef * si.mm i = i_lo * si.mm / h_ef h_calcul = self.h_calcul - print(h_ef, h_calcul) self.h_ef = h_ef if ent == "Dessus": @@ -1186,23 +1617,39 @@ def val(): self.K_v = value[1] return value - def f_v_d(self, loadtype=Barre.LOAD_TIME, typecombi=Barre.TYPE_ACTION): - """Retourne la résistance f,v,d de l'élément en MPa + """Calcule la résistance de calcul au cisaillement f_v,d selon l'EC5 §6.1.7. + + Détermine la résistance à partir de la résistance caractéristique f_v,k + et des coefficients de modification (kmod, γM). Args: - loadtype (str): chargement de plus courte durée sur l'élément. - typecombi (str): type de combinaison, fondamentale ou accidentelle. + loadtype (str): Classe de durée de chargement. + Voir Barre.LOAD_TIME pour les valeurs possibles. + typecombi (str): Type de combinaison d'actions. + "fondamentale" ou "accidentelle". Defaults to "fondamentale". Returns: - float: f,v,d en MPa + float: Résistance de calcul f_v,d en MPa avec unité (si.MPa). """ return super()._f_type_d("fvk", loadtype, typecombi) - - + def tau_d(self, Vd:si.kN): - """ Retourne la contrainte tau en MPa pour le cisaillement longitudinale d'une poutre rectangulaire - Vd : Effort de cisaillement sur la poutre en kN""" + """Calcule la contrainte de cisaillement tau_d selon l'EC5 §6.1.7. + + Détermine la contrainte de cisaillement longitudinal pour une poutre + rectangulaire : τ = 1.5·V/(b_ef·h_ef) + + Args: + Vd (si.kN): Effort tranchant (cisaillement) sur la poutre en kN. + + Returns: + tuple: (latex_string, valeur) où valeur est tau_d en MPa avec unité. + + Note: + La valeur est stockée dans l'attribut tau_rd. + Prend en compte K_cr (fissuration) et K_v (entaille si défini). + """ self.V_d = Vd * si.kN V_d = self.V_d K_cr = self.K_cr @@ -1214,14 +1661,27 @@ def val(): b_ef = K_cr * b_calcul tau_d = (1.5 * V_d) / (b_ef * h_ef) return tau_d - + value = val() self.tau_rd = value[1] return value - def taux_tau_d(self): - """ Retourne le taux de travail en cisaillement en % """ + """Calcule les taux de travail au cisaillement selon l'EC5 §6.1.7 et §6.5. + + Vérifie les critères : + - Equ. 6.13 : Cisaillement simple (tau_d / f_v,d) + - Equ. 6.60 : Cisaillement avec entaille (tau_d / (K_v · f_v,d)) + + Returns: + tuple: (latex_string, valeurs) où valeurs contient les taux pour + equ6.13 (sans entaille) et equ6.60 (avec entaille) en pourcentage. + Stockés dans taux_tau_rd['equ6.13'] et ['equ6.60']. + + Note: + Cette méthode met à jour automatiquement la synthèse des taux + de travail via _add_synthese_taux_travail. + """ self.taux_tau_rd = {} tau_d = self.tau_rd f_v_d = self.f_type_rd @@ -1230,9 +1690,9 @@ def taux_tau_d(self): @handcalc(override="short", precision=3, jupyter_display=self.JUPYTER_DISPLAY, left="\\[", right="\\]") def val(): taux_6_13 = tau_d / f_v_d - taux_6_60 = tau_d/ (K_v * f_v_d) + taux_6_60 = tau_d / (K_v * f_v_d) return taux_6_13, taux_6_60 - + value = val() self.taux_tau_rd['equ6.13'] = value[1][0] self.taux_tau_rd['equ6.60'] = value[1][1] diff --git a/ourocode/eurocode/EC5_Feu.py b/ourocode/eurocode/EC5_Feu.py index 681426d..27c92f7 100644 --- a/ourocode/eurocode/EC5_Feu.py +++ b/ourocode/eurocode/EC5_Feu.py @@ -2,6 +2,7 @@ # Encoding in UTF-8 by Anthony PARISOT import sys import os +import warnings import math as mt from math import sqrt, pi import pandas as pd @@ -471,7 +472,7 @@ def _K_h(self): else: kh[cle] = 1 else: - print("LVL non pris en compte dans cette fonction") + warnings.warn("LVL non pris en compte dans cette fonction") kh[cle] = 1 return kh diff --git a/ourocode/eurocode/EC8_Sismique.py b/ourocode/eurocode/EC8_Sismique.py index 43c1063..ce6f57e 100644 --- a/ourocode/eurocode/EC8_Sismique.py +++ b/ourocode/eurocode/EC8_Sismique.py @@ -1,7 +1,6 @@ #! env\Scripts\python.exe # Encoding in UTF-8 by Anthony PARISOT import os -from PySide6.QtWidgets import QFileDialog import matplotlib.pyplot as plt from math import sqrt import numpy as np @@ -415,6 +414,7 @@ def show_spectre_elastique_calcul( plt.grid() if screenshot: if not filepath: + from PySide6.QtWidgets import QFileDialog filepath = QFileDialog.getSaveFileName( filter="PNG (*.png)", selectedFilter=".png", diff --git a/ourocode/eurocode/objet.py b/ourocode/eurocode/objet.py index 60817c2..ee5861c 100644 --- a/ourocode/eurocode/objet.py +++ b/ourocode/eurocode/objet.py @@ -11,7 +11,6 @@ import pickle import inspect from IPython.display import display, Latex -from PySide6.QtWidgets import QFileDialog import forallpeople as si si.environment("structural") @@ -24,38 +23,89 @@ def get_package_path(package): class Objet(object): - """Classe permetant la sauvegarde ou l'ouverture d'un objet ou de plusieur sous un fichier .ec + """Classe permettant la sauvegarde et l'ouverture d'objets dans des fichiers .oco (format pickle). + + Cette classe de base fournit les fonctionnalités essentielles pour : + - La sérialisation/désérialisation des objets avec gestion des unités physiques + - L'accès aux données normatives (CSV, JSON) + - Les opérations mathématiques et de synthèse de résultats + - La conversion des unités SI + + Toutes les classes du package ourocode héritent de cette classe. """ JUPYTER_DISPLAY = False OPERATOR = ("+", "-", "x", "/") + _csv_cache: dict[tuple, pd.DataFrame] = {} + _json_cache: dict[str, dict] = {} try: import ourocode PATH_CATALOG = os.path.join(get_package_path(ourocode)) - except: + except ImportError: PATH_CATALOG = os.path.join(os.getcwd(), "ourocode") def _data_from_csv(self, data_file: str, index_col=0): - """ Retourne un dataframe d'un fichier CSV """ - repertory = os.path.join(self.PATH_CATALOG, "data", data_file) - data_csv = pd.read_csv(repertory, sep=';', header=0, index_col=index_col) - return data_csv + """Charge et retourne les données normatives depuis un fichier CSV. + + Cette méthode permet d'accéder aux tables de données Eurocode stockées + dans le répertoire data/ du package (caractéristiques mécaniques, kmod, etc.) + Les résultats sont mis en cache au niveau de la classe pour éviter + de relire le disque à chaque appel. + + Args: + data_file (str): Nom du fichier CSV à charger (ex: "kmod.csv", "gammaM.csv") + index_col (int, optional): Colonne à utiliser comme index. Defaults to 0. + + Returns: + pd.DataFrame: DataFrame pandas contenant les données normatives. + """ + cache_key = (data_file, index_col) + if cache_key not in Objet._csv_cache: + repertory = os.path.join(self.PATH_CATALOG, "data", data_file) + Objet._csv_cache[cache_key] = pd.read_csv(repertory, sep=';', header=0, index_col=index_col) + return Objet._csv_cache[cache_key].copy() def _data_from_json(self, data_file: str): - """ Retourne un dataframe d'un fichier JSON """ + """Charge et retourne les données depuis un fichier JSON sous forme de DataFrame. + + Args: + data_file (str): Nom du fichier JSON à charger. + + Returns: + pd.DataFrame: DataFrame pandas contenant les données. + """ repertory = os.path.join(self.PATH_CATALOG, "data", data_file) data_json = pd.read_json(repertory) return data_json def _load_json(self, data_file: str): - """ Retourne un dict d'un fichier JSON """ - repertory = os.path.join(self.PATH_CATALOG, "data", data_file) - with open(repertory, "r", encoding="utf-8") as json_file: - data = json.load(json_file) - return data + """Charge et retourne les données depuis un fichier JSON sous forme de dictionnaire. + + Les résultats sont mis en cache au niveau de la classe. + + Args: + data_file (str): Nom du fichier JSON à charger. + + Returns: + dict: Dictionnaire contenant les données du fichier. + """ + if data_file not in Objet._json_cache: + repertory = os.path.join(self.PATH_CATALOG, "data", data_file) + with open(repertory, "r", encoding="utf-8") as json_file: + Objet._json_cache[data_file] = json.load(json_file) + return Objet._json_cache[data_file] def _assign_handcalcs_value(self, handcalc_value: tuple, args: list[str]): - """Assigne les valeurs des calculs handcalc aux arguments de l'objet. - Les arguments doivent être dans le même ordre que les valeurs des calculs handcalc.""" + """Assigne les valeurs calculées par handcalcs aux attributs de l'objet. + + Cette méthode extrait la valeur numérique du résultat handcalcs (qui peut + contenir du LaTeX) et l'assigne aux attributs spécifiés. + + Args: + handcalc_value (tuple): Tuple retourné par le décorateur @handcalc + contenant (latex_string, numeric_result) ou (result,) selon le mode + args (list[str]): Liste des noms d'attributs à assigner, dans le même + ordre que les valeurs retournées par le calcul. + """ if isinstance(handcalc_value, tuple): if self.JUPYTER_DISPLAY: for i, value in enumerate(handcalc_value): @@ -72,7 +122,19 @@ def objet(self): return self def _physical_to_dict(self, obj): - """Convertit un objet Physical en dictionnaire sérialisable.""" + """Convertit un objet Physical (forallpeople) en dictionnaire sérialisable. + + Cette méthode permet de sérialiser les objets contenant des unités physiques + (m, mm, MPa, kN, etc.) pour la sauvegarde JSON ou pickle. + La conversion préserve la valeur numérique et l'unité sous forme de chaîne. + + Args: + obj: Objet à convertir (dict, list, tuple, ou Physical) + + Returns: + Objet converti avec les valeurs Physical transformées en dictionnaires + contenant '_physical_value' et '_physical_unit'. + """ if isinstance(obj, dict): return {k: self._physical_to_dict(v) for k, v in obj.items()} elif isinstance(obj, (list, tuple)): @@ -85,8 +147,56 @@ def _physical_to_dict(self, obj): } return obj + @staticmethod + def _resolve_unit_expr(unit_expr: str): + """Résout une expression d'unité de manière sécurisée sans eval(). + + Gère les unités simples (m, mm, kN), les puissances (mm**2, m**4) + et les produits (kN*m, N*mm**2) par parsing explicite. + + Args: + unit_expr (str): Expression d'unité normalisée. + + Returns: + Physical: Objet unité forallpeople correspondant. + + Raises: + ValueError: Si l'unité est inconnue. + """ + si_ns = vars(si) + # Produit : kN*m, N*mm**2, etc. + if "*" in unit_expr and "**" not in unit_expr.split("*")[0]: + parts = unit_expr.split("*", 1) + return Objet._resolve_unit_expr(parts[0]) * Objet._resolve_unit_expr(parts[1]) + # Puissance : mm**2, m**4, etc. + if "**" in unit_expr: + base, exp_str = unit_expr.split("**", 1) + # Gérer le cas kN*m**2 déjà splité à gauche + if not exp_str.lstrip("-").isdigit(): + raise ValueError(f"Exposant non numérique: {exp_str}") + base_unit = si_ns.get(base) + if base_unit is None: + raise ValueError(f"Unité de base inconnue: {base}") + return base_unit ** int(exp_str) + # Unité simple : m, mm, kN, MPa, etc. + unit_obj = si_ns.get(unit_expr) + if unit_obj is None: + raise ValueError(f"Unité inconnue: {unit_expr}") + return unit_obj + def _dict_to_physical(self, data): - """Reconstruit un objet Physical à partir d'un dictionnaire.""" + """Reconstruit un objet Physical (forallpeople) à partir d'un dictionnaire. + + Cette méthode est l'inverse de _physical_to_dict. Elle restaure les + objets Physical à partir de leur représentation sérialisée. + Gère les conversions d'unités avec différentes notations (², ³, ·, etc.) + + Args: + data: Données sérialisées (dict, list, ou valeur simple) + + Returns: + Objet avec les valeurs physiques restaurées. + """ if isinstance(data, dict): if '_physical_value' in data: # Reconstruire l'objet Physical @@ -103,15 +213,10 @@ def _dict_to_physical(self, data): ) if "/" in unit_expr: unit_expr = unit_expr.replace("/", "_") - # Évaluer l'expression d'unité dans l'espace de noms de forallpeople try: - unit_obj = eval(unit_expr, {"__builtins__": {}}, vars(si)) - except Exception: - # En dernier recours, tenter un getattr direct si c'est un symbole simple - try: - unit_obj = getattr(si, unit_expr) - except Exception as e: - raise ValueError(f"Unité inconnue ou non prise en charge: {unit_str}") from e + unit_obj = self._resolve_unit_expr(unit_expr) + except ValueError as e: + raise ValueError(f"Unité inconnue ou non prise en charge: {unit_str}") from e return value * unit_obj return {k: self._dict_to_physical(v) for k, v in data.items()} elif isinstance(data, (list, tuple)): @@ -119,14 +224,30 @@ def _dict_to_physical(self, data): return data def get_value(self, value: dict|list|str, index: int=None, key: str=None, get_keys: bool=("False", "True"),): - """Retourne l'argument transmit. + """Extrait et retourne une valeur depuis une structure de données complexe. + + Cette méthode utilitaire permet de naviguer dans les dictionnaires, + listes, ou chaînes JSON pour extraire des valeurs spécifiques. Args: - value (dict|list|str): la valeur à retourner. - index (int, optional): index à retourner dans une liste python. - Attention sous pyhon le premier élément d'une liste ce trouve à l'index 0. - key (str, optional): clé à renvoyer dans un dictionnaire python. - get_keys (bool, optional): permet de retourner les clés d'un dictionnaire python. + value (dict|list|str): La structure de données source. + index (int, optional): Index à extraire dans une liste Python. + Note : sous Python, le premier élément est à l'index 0. + key (str, optional): Clé à extraire d'un dictionnaire Python, + ou clé JSON à extraire d'une chaîne. + get_keys (bool, optional): Si True, retourne la liste des clés + d'un dictionnaire au lieu d'une valeur. + + Returns: + La valeur extraite selon les critères spécifiés. + + Exemple: + >>> obj.get_value({"a": 1, "b": 2}, key="a") + 1 + >>> obj.get_value([10, 20, 30], index=1) + 20 + >>> obj.get_value({"x": 1, "y": 2}, get_keys=True) + ["x", "y"] """ if index and isinstance(value, list): value = value[index] @@ -141,8 +262,22 @@ def get_value(self, value: dict|list|str, index: int=None, key: str=None, get_ke return value def operation_between_values(self, value1: float, value2: float, operator: str=OPERATOR): - """Retourne l'opération donnée entre la valeur 1 et la valeur 2. - Pour les calculs trigonométrique, la valeur 2 est en degré.""" + """Effectue une opération arithmétique entre deux valeurs. + + Opérateurs supportés : "+" (addition), "-" (soustraction), + "x" (multiplication), "/" (division). + + Args: + value1 (float): Première opérande. + value2 (float): Deuxième opérande. + operator (str): Opérateur à appliquer. Defaults to "+". + + Returns: + float: Résultat de l'opération. + + Raises: + ValueError: Si l'opérateur n'est pas reconnu. + """ if operator not in self.OPERATOR: raise ValueError(f"Invalid operator: {operator}") match operator: @@ -157,23 +292,55 @@ def operation_between_values(self, value1: float, value2: float, operator: str=O return result def abs_value(self, value: float): - """Retourne la valeur absolue. + """Retourne la valeur absolue (module) d'un nombre. + + Args: + value (float): Valeur dont on veut la valeur absolue. + + Returns: + float: Valeur absolue (toujours positive ou nulle). """ return abs(float(value)) def max(self, value1: float, value2: float): - """Retourne la valeur max entre la valeur 1 et valeur 2. + """Retourne la valeur maximale entre deux nombres. + + Args: + value1 (float): Première valeur à comparer. + value2 (float): Deuxième valeur à comparer. + + Returns: + float: La plus grande des deux valeurs. """ return max(float(value1), float(value2)) def min(self, value1: float, value2: float): - """Retourne la valeur min entre la valeur 1 et valeur 2. + """Retourne la valeur minimale entre deux nombres. + + Args: + value1 (float): Première valeur à comparer. + value2 (float): Deuxième valeur à comparer. + + Returns: + float: La plus petite des deux valeurs. """ return min(float(value1), float(value2)) def _extract_numbers(self, value, absolute: bool=True): - """Fonction récursive pour extraire tous les nombres d'une structure de données imbriquée.""" + """Extrait récursivement tous les nombres d'une structure de données imbriquée. + + Parcourt les dictionnaires, listes et tuples pour extraire toutes les + valeurs numériques, y compris celles encapsulées dans des objets Physical. + + Args: + value: Structure de données à parcourir (dict, list, tuple, ou valeur simple) + absolute (bool, optional): Si True, retourne les valeurs absolues. + Defaults to True. + + Returns: + list: Liste de toutes les valeurs numériques extraites. + """ numbers = [] if isinstance(value, (int, float, Physical)): if isinstance(value, Physical): @@ -190,11 +357,24 @@ def _extract_numbers(self, value, absolute: bool=True): return numbers def max_list(self, iterable: dict|list|tuple, absolute: bool=("False", "True")): - """Retourne la valeur max d'une liste, ou d'un dictionnaire. - + """Retourne la valeur maximale d'une liste ou d'un dictionnaire. + + Cette méthode parcourt récursivement la structure pour extraire + toutes les valeurs numériques et retourne la maximum. + Args: - iterable (dict|list|tuple): la liste ou le dictionnaire à parcourir. - absolute (bool, optional): permet de retourner la valeur absolue. Defaults to False. + iterable (dict|list|tuple): Structure de données contenant des valeurs numériques. + absolute (bool, optional): Si True, compare les valeurs absolues. + Defaults to False. + + Returns: + float: La valeur maximale trouvée. + + Exemple: + >>> obj.max_list([1, -5, 3], absolute=True) + 5 + >>> obj.max_list({"a": 10, "b": [20, 5]}) + 20 """ result = self.get_value(iterable, get_keys=False) result = self._extract_numbers(result, absolute) @@ -202,18 +382,51 @@ def max_list(self, iterable: dict|list|tuple, absolute: bool=("False", "True")): def min_list(self, iterable: dict|list|tuple|str, absolute: bool=("False", "True")): - """Retourne la valeur min d'une liste, ou d'un dictionnaire. - + """Retourne la valeur minimale d'une liste ou d'un dictionnaire. + + Cette méthode parcourt récursivement la structure pour extraire + toutes les valeurs numériques et retourne le minimum. + Args: - iterable (dict|list|tuple|str): la liste ou le dictionnaire à parcourir. - absolute (bool, optional): permet de retourner la valeur absolue. Defaults to False. + iterable (dict|list|tuple|str): Structure de données contenant des valeurs numériques. + absolute (bool, optional): Si True, compare les valeurs absolues. + Defaults to False. + + Returns: + float: La valeur minimale trouvée. + + Exemple: + >>> obj.min_list([1, -5, 3], absolute=True) + 1 + >>> obj.min_list({"a": 10, "b": [20, 5]}) + 5 """ result = self.get_value(iterable, get_keys=False) result = self._extract_numbers(result, absolute) return min(result) def get_trigonometric_value(self, value: float, operator: str=("COS", "SIN", "TAN", "ACOS", "ASIN", "ATAN")): - """Retourne la valeur trigonométrique donnée en degré.""" + """Calcule une fonction trigonométrique avec un angle exprimé en degrés. + + Convertit automatiquement l'angle de degrés en radians avant le calcul. + + Args: + value (float): Angle en degrés. + operator (str): Fonction trigonométrique à appliquer. + Choix possibles : "COS", "SIN", "TAN", "ACOS", "ASIN", "ATAN" + + Returns: + float: Résultat de la fonction trigonométrique. + + Raises: + ValueError: Si la fonction trigonométrique n'est pas reconnue. + + Exemple: + >>> obj.get_trigonometric_value(90, "SIN") + 1.0 + >>> obj.get_trigonometric_value(0, "COS") + 1.0 + """ if operator not in ("COS", "SIN", "TAN", "ACOS", "ASIN", "ATAN"): raise ValueError(f"La fonction trigonométrique {operator} n'est pas reconnue.") match operator: @@ -240,16 +453,24 @@ def _add_synthese_taux_travail( ) -> pd.DataFrame: """Construit un tableau pandas de synthèse des taux de travail. + Cette méthode agrège les résultats de vérification (taux de travail) + pour la situation normale et la situation d'incendie dans un DataFrame + partagé entre toutes les méthodes de vérification. + Args: - lignes (Iterable): éléments de synthèse sous forme de: - - dict avec clés (effort/Effort), (SN/situation_normale) et (SI/situation_incendie) - - tuple/list (effort, taux_situation_normale, taux_situation_incendie) - col_normale (str, optional): nom de la colonne pour la situation normale. Defaults to "Situation normale". - col_incendie (str, optional): nom de la colonne pour la situation d'incendie. Defaults to "Situation d'incendie". - arrondi (int|None, optional): nombre de décimales à conserver. None pour ne pas arrondir. Defaults to 3. + lignes (Iterable): Éléments de synthèse sous forme de : + - dict avec clés : "effort"/"Effort", "SN"/"situation_normale", + "SI"/"situation_incendie" + - tuple/list : (effort, taux_situation_normale, taux_situation_incendie) + col_normale (str, optional): Nom de la colonne pour la situation normale. + Defaults to "Situation normale". + col_incendie (str, optional): Nom de la colonne pour la situation d'incendie. + Defaults to "Situation d'incendie". + arrondi (int|None, optional): Nombre de décimales à conserver. + None pour ne pas arrondir. Defaults to 3. Returns: - pd.DataFrame: tableau de synthèse prêt à être exporté ou affiché. + pd.DataFrame: Tableau de synthèse cumulatif avec les taux de travail. """ colonnes = ["Effort agissant dimensionnant", col_normale, col_incendie] # DataFrame commun stocké sur l'instance @@ -274,21 +495,21 @@ def _arrondi(val): or ligne.get(col_normale) or ligne.get("normale") ) - si = ( + si_val = ( ligne.get("SI") or ligne.get("situation_incendie") or ligne.get(col_incendie) or ligne.get("incendie") ) elif isinstance(ligne, (list, tuple)): - effort, sn, si = (list(ligne) + [None] * 3)[:3] + effort, sn, si_val = (list(ligne) + [None] * 3)[:3] else: raise TypeError("Chaque ligne doit être un dict, une liste ou un tuple.") lignes_preparees.append({ "Effort agissant dimensionnant": effort, col_normale: _arrondi(sn), - col_incendie: _arrondi(si), + col_incendie: _arrondi(si_val), }) df_nouveau = pd.DataFrame(lignes_preparees, columns=colonnes) @@ -304,7 +525,19 @@ def _arrondi(val): return df_final def synthese_taux_travail(self): - """Retourne le DataFrame de synthèse partagé entre toutes les instances.""" + """Construit un tableau pandas de synthèse des taux de travail. + + Cette méthode agrège les résultats de vérification (taux de travail) + pour la situation normale et la situation d'incendie dans un DataFrame + partagé entre toutes les méthodes de vérification. + + Returns: + pd.DataFrame: Tableau de synthèse final avec la ligne des maximums. + + Note: + Le DataFrame est partagé entre toutes les méthodes de vérification + d'une même instance via l'attribut _synthese_taux_df. + """ df = getattr(self, "_synthese_taux_df", None) if df is None or df.empty: return df @@ -322,14 +555,23 @@ def synthese_taux_travail(self): def save_data(self, data: dict, type_data: str=("JSON", "CSV"), path: str=None): - """Sauvegarde les données dans un fichier JSON ou CSV. + """Sauvegarde les données dans un fichier JSON ou CSV via boîte de dialogue. + + Convertit automatiquement les unités physiques (Physical) en valeurs + sérialisables avant la sauvegarde. Args: - type_data (str): le type de données à sauvegarder (JSON, CSV). - data (dict): les données à sauvegarder sous forme de dictionnaire. - path (str, optional): Chemin du fichier à créer, s'il n'est pas fourni, une boite de dialogue s'ouvre pour choisir le fichier. - Defaults to None. + data (dict): Données à sauvegarder sous forme de dictionnaire. + Les valeurs Physical sont automatiquement converties. + type_data (str): Format de sauvegarde : "JSON" ou "CSV". + path (str, optional): Chemin du fichier à créer. Si None, une boîte + de dialogue Qt s'ouvre pour choisir l'emplacement. + + Note: + Pour JSON : sauvegarde indentée avec encodage UTF-8. + Pour CSV : format simple avec une ligne d'en-tête. """ + from PySide6.QtWidgets import QFileDialog data = self._physical_to_dict(data) if type_data == "JSON": save_file_path = path if path else QFileDialog.getSaveFileName( @@ -349,13 +591,21 @@ def save_data(self, data: dict, type_data: str=("JSON", "CSV"), path: str=None): w.writerow(data) def load_data(self, type_data: str=("JSON", "CSV"), path: str=None): - """Charge les données depuis un fichier JSON ou CSV. + """Charge les données depuis un fichier JSON ou CSV via boîte de dialogue. + + Reconstruit automatiquement les unités physiques (Physical) à partir + des données sérialisées. Args: - type_data (str): le type de données à charger (JSON, CSV). - path (str, optional): Chemin du fichier à charger, s'il n'est pas fourni, une boite de dialogue s'ouvre pour choisir le fichier. - Defaults to None. + type_data (str): Format du fichier : "JSON" ou "CSV". + path (str, optional): Chemin du fichier à charger. Si None, une boîte + de dialogue Qt s'ouvre pour sélectionner le fichier. + + Returns: + dict: Dictionnaire contenant les données chargées avec les unités + physiques restaurées. """ + from PySide6.QtWidgets import QFileDialog if type_data == "JSON": file_path = path if path else QFileDialog.getOpenFileName( filter="JSON (*.json)", @@ -374,12 +624,25 @@ def load_data(self, type_data: str=("JSON", "CSV"), path: str=None): @classmethod def _convert_unit_physical(cls, value: int|float, si_unit: si.Physical, unit_to_convert: si.Physical): - """Convertie l'unité de base dans l'unité nécessaire à l'instanciation de la classe parente + """Convertit une valeur d'une unité SI vers une autre unité. + + Cette méthode gère les conversions entre les unités courantes en + calcul de structure : m/mm, N/kN/daN, Pa/MPa, et les unités + combinées (moments, pressions, etc.). Args: - value (int|float): valeur à convertir - si_unit (si.Physical): unité si de base - unit_to_convert (si.Physical): unité dans la quelle convertir + value (int|float): Valeur numérique à convertir. + si_unit (si.Physical): Unité source (ex: si.m, si.N). + unit_to_convert (si.Physical): Unité cible (ex: si.mm, si.kN). + + Returns: + float: Valeur convertie dans l'unité cible. + + Exemple: + >>> Objet._convert_unit_physical(1.5, si.m, si.mm) + 1500.0 + >>> Objet._convert_unit_physical(5000, si.N, si.kN) + 5.0 """ si_unit, unit_to_convert = str(si_unit), str(unit_to_convert) if si_unit != unit_to_convert: @@ -440,14 +703,20 @@ def _convert_unit_physical(cls, value: int|float, si_unit: si.Physical, unit_to_ @classmethod def _reset_physical_dictionnary(cls, objet: object, dictionnary: dict) -> dict: - """Class méthode permetant de réinitialiser les valeurs physiques d'un dictionnaire d'argument d'une classe parent. + """Réinitialise les valeurs physiques d'un dictionnaire d'arguments. + + Lors du chaînage de classes via _from_parent_class, les unités physiques + peuvent être multipliées par elles-mêmes. Cette méthode réinitialise + les valeurs en extrayant la partie numérique et en réappliquant + l'unité attendue par l'annotation de type de la classe parent. Args: - objet (object): l'objet à réinitailiser - dictionnary (dict): le dictionnaire d'argument de la classe parent + objet (object): Objet dont on veut réinitialiser les valeurs physiques. + dictionnary (dict): Dictionnaire d'arguments contenant potentiellement + des valeurs Physical malformées. Returns: - dict: le dictionnaire d'argument de la classe parent avec les valeurs physiques réinitialisées + dict: Dictionnaire avec les valeurs physiques corrigées. """ dict_physical = {} # Si un argument utilise forallpeople on récupère que la valeur pour ne pas multiplier l'unité par elle même @@ -470,7 +739,16 @@ def _reset_physical_dictionnary(cls, objet: object, dictionnary: dict) -> dict: @classmethod def _reset_physical_object(cls, objet: object): - """Class méthode permetant de réinitialiser les valeurs physiques d'un objet d'une classe parent. + """Réinitialise les valeurs physiques d'un objet en utilisant son __dict__. + + Wrapper de _reset_physical_dictionnary qui extrait automatiquement + le dictionnaire d'attributs de l'objet. + + Args: + objet (object): Objet à réinitialiser. + + Returns: + dict: Dictionnaire des attributs avec valeurs physiques corrigées. """ dictionnary = objet.__dict__ return cls._reset_physical_dictionnary(objet, dictionnary) @@ -479,25 +757,44 @@ def _reset_physical_object(cls, objet: object): @classmethod def _from_dict(cls, dictionary:dict): - """Class méthode permetant l'intanciation des classe hérité de la classe parent, par une classe déjà instanciée. + """Instancie une classe à partir d'un dictionnaire d'arguments. + + Cette méthode de classe permet de créer une nouvelle instance + à partir d'un dictionnaire de paramètres. Args: - object (class object): l'objet Element déjà créer par l'utilisateur - """ + dictionary (dict): Dictionnaire contenant les arguments nommés + pour le constructeur de la classe. + + Returns: + instance: Nouvelle instance de la classe initialisée avec les + arguments du dictionnaire. + """ return cls(**dictionary) @classmethod def _from_parent_class(cls, objet: list|object, **kwargs): - """Class méthode permetant l'intanciation des classes héritées de la classe parent, par une classe déjà instanciée. - - Les clés dans kwargs écrasent les clés existantes dans dict_objet. - + """Instancie une classe en héritant des attributs d'objets existants. + + Cette méthode de classe est le mécanisme central d'enchaînement des + vérifications Eurocode. Elle permet de créer une nouvelle instance + en copiant les attributs d'objets parent(s), avec possibilité de + surcharge via les kwargs. + + Pattern : héritage par composition pour les vérifications en cascade. + Args: - objet (object|list): L'objet ou la liste d'objets à partir desquels créer la nouvelle instance - **kwargs: Arguments additionnels qui écraseront les attributs des objets sources - + objet (object|list): Objet ou liste d'objets sources dont on veut + hériter les attributs. Les derniers objets écrasent les précédents. + **kwargs: Arguments nommés qui écrasent les attributs hérités. + Returns: - Une nouvelle instance de la classe avec les attributs des objets sources et des kwargs + instance: Nouvelle instance de la classe avec les attributs fusionnés. + + Exemple: + >>> barre = Barre(b=100, h=200, classe="C24") + >>> flexion = Flexion._from_parent_class(barre, lo_rel_y=3000) + # Flexion hérite de b, h, classe depuis Barre """ dict_objet = {} @@ -519,6 +816,7 @@ def _from_parent_class(cls, objet: list|object, **kwargs): def _save_muliple_objects(self, object: list): + from PySide6.QtWidgets import QFileDialog save_file_path = QFileDialog.getSaveFileName( filter="Ourea catalog object (*.oco);;'Text Document' (*.txt)", selectedFilter=".oco", @@ -530,6 +828,7 @@ def _save_muliple_objects(self, object: list): def save_object(self): + from PySide6.QtWidgets import QFileDialog save_file_path = QFileDialog.getSaveFileName( filter="Ourea catalog object (*.oco);;'Text Document' (*.txt)", selectedFilter=".oco", @@ -549,6 +848,7 @@ def _show_element(self, picture: str): @classmethod def _open_multiple_objects(cls): + from PySide6.QtWidgets import QFileDialog data = [] # with filedialog.askopenfile('rb', filetypes=(("Ourea catalog object", "*.oco"), ('Text Document', '*.txt')), defaultextension='.oco') as f: file_path = QFileDialog.getOpenFileName( @@ -564,6 +864,7 @@ def _open_multiple_objects(cls): @classmethod def _open_object(cls, path: str=None): + from PySide6.QtWidgets import QFileDialog if not path: file_path = QFileDialog.getOpenFileName( filter="Ourea catalog object (*.oco);;'Text Document' (*.txt)", selectedFilter=".oco" diff --git a/pyproject.toml b/pyproject.toml index 8737598..40dde29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ include = ["ourocode*"] [project] name = "ourocode" -version = "1.9.0" +version = "1.10.0" description = "Ceci est un catalogue de fonction permettant une utilisation rapide pour la réalisation de note de calcul personnalisée." readme = "README.md" authors = [{name = "Anthony PARISOT", email = "contact@ourea-structure.fr"}] @@ -17,9 +17,6 @@ license-files = ["LICEN[CS]E"] dependencies = [ "handcalcs", "forallpeople", - "PyNiteFEA", - "pyvista[all,trame]", - "PySide6", "pandas", "Pillow", ] @@ -33,6 +30,16 @@ classifiers = [ Homepage = "https://github.com/AnthonyPrst/ourocode" [project.optional-dependencies] +gui = [ + "PySide6", +] +mef = [ + "PyNiteFEA", + "pyvista[all,trame]", +] +full = [ + "ourocode[gui,mef]", +] dev = [ "black", ]