ourocode 1.11.3__tar.gz → 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ourocode-1.11.3/ourocode.egg-info → ourocode-2.0.0}/PKG-INFO +6 -5
- ourocode-2.0.0/ourocode/eurocode/core/renderer.py +598 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec1/exploitation.py +4 -4
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec1/neige.py +2 -2
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec1/vent.py +2 -2
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/assemblage/platine.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/assemblage/soudure.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/assemblage/tige.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/element_droit/cisaillement.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/element_droit/compression.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/element_droit/flexion.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/element_droit/plat.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/element_droit/traction.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/feu.py +2 -2
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/agrafe.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/assemblage.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/boulon.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/broche.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/embrevement.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/pointe.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/tirefond.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/cvt.py +2 -2
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/element_droit/barre.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/element_droit/cisaillement.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/element_droit/compression.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/element_droit/flexion.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/element_droit/traction.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/feu/cisaillement_feu.py +5 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/feu/compression_feu.py +6 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/feu/feu.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/feu/flexion_feu.py +6 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/feu/traction_feu.py +5 -2
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec8/sismique.py +2 -2
- {ourocode-1.11.3 → ourocode-2.0.0/ourocode.egg-info}/PKG-INFO +6 -5
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode.egg-info/SOURCES.txt +1 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode.egg-info/requires.txt +3 -5
- {ourocode-1.11.3 → ourocode-2.0.0}/pyproject.toml +6 -6
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC8_Sismique.py +1 -1
- {ourocode-1.11.3 → ourocode-2.0.0}/LICENSE +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/MANIFEST.in +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/README.md +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/__version__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/caracteristique_meca_acier.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/caracteristique_meca_beton.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/caracteristique_meca_bois.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/caracteristique_meca_panel.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/carte_action_region.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/coeff_psy.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/exploitation.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/gammaM.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/kdef.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/kfi.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/kmod.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/limite_fleche.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/qualite_acier.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/screenshot/C90_def.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/screenshot/CNC2M_4.3.3_tab15.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/screenshot/CNC2M_4.3.3_tab16.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/screenshot/EN1993-1-8_fig6_11.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/screenshot/Kv_def.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/screenshot/sign_convention.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/section_boulon.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/sismique/categorie_importance.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/sismique/categorie_importance_ns.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/sismique/classe_sol.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/sismique/spectre_eleastique_h_type1.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/sismique/spectre_eleastique_h_type2.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cp_toiture_isolee_1_versant.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cp_toiture_isolee_1_versant.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cp_toiture_isolee_2_versants.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cp_toiture_isolee_2_versants.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_mur_verticaux.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_mur_verticaux.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_1_versant.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_1_versant_0_degres.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_1_versant_180_degres.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_1_versant_90_degres.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_2_versants.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_2_versants_0_degres.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_2_versants_90_degres.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_terrasse.csv +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Cpe_toiture_terrasse.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/data/vent/vent_Ffr.png +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/core/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/core/batiment.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/core/combinaison.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/core/model_generator.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/core/model_result.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/core/objet.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/core/projet.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec1/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/assemblage/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec3/element_droit/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/assemblage/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/blc.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/element_droit/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec5/feu/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/ec8/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/mixins/__init__.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/mixins/data_loader.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/mixins/math_utils.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/mixins/serialization.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode/eurocode/mixins/synthese.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode.egg-info/dependency_links.txt +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/ourocode.egg-info/top_level.txt +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/setup.cfg +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_A0_Projet.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC0_Combinaison.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC1_Exploitation.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC1_Neige.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC3_Assemblage.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC3_Element_droit.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC3_Feu.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC3_Platine.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC5_Assemblage.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC5_BLC.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC5_CVT.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC5_Element_droit.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC5_Embrevement.py +0 -0
- {ourocode-1.11.3 → ourocode-2.0.0}/tests/test_EC5_Feu.py +0 -0
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ourocode
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Ceci est un catalogue de fonction permettant une utilisation rapide pour la réalisation de note de calcul personnalisée.
|
|
5
5
|
Author-email: Anthony PARISOT <contact@ourea-structure.fr>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/AnthonyPrst/ourocode
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
9
11
|
Classifier: Operating System :: OS Independent
|
|
10
12
|
Requires-Python: >=3.12
|
|
11
13
|
Description-Content-Type: text/markdown
|
|
12
14
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: handcalcs
|
|
14
15
|
Requires-Dist: forallpeople
|
|
15
16
|
Requires-Dist: pandas
|
|
16
17
|
Requires-Dist: Pillow
|
|
18
|
+
Requires-Dist: matplotlib
|
|
19
|
+
Requires-Dist: pyvista[all,trame]
|
|
20
|
+
Requires-Dist: PyNiteFEA
|
|
17
21
|
Provides-Extra: gui
|
|
18
22
|
Requires-Dist: PySide6; extra == "gui"
|
|
19
|
-
Provides-Extra: mef
|
|
20
|
-
Requires-Dist: PyNiteFEA; extra == "mef"
|
|
21
|
-
Requires-Dist: pyvista[all,trame]; extra == "mef"
|
|
22
23
|
Provides-Extra: full
|
|
23
24
|
Requires-Dist: ourocode[gui,mef]; extra == "full"
|
|
24
25
|
Provides-Extra: dev
|
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
# coding in UTF-8
|
|
2
|
+
# by Anthony PARISOT
|
|
3
|
+
"""Remplacement de handcalcs.decorator.handcalc compatible Python 3.13+.
|
|
4
|
+
|
|
5
|
+
Ce module implémente un décorateur @handcalc qui génère du LaTeX à partir
|
|
6
|
+
du code source Python via l'AST (ast module), sans dépendance à innerscope.
|
|
7
|
+
Il reproduit le comportement utilisé dans ourocode : rendu des assignations
|
|
8
|
+
d'une fonction interne avec substitution symbolique et numérique.
|
|
9
|
+
|
|
10
|
+
Retour du décorateur : tuple (latex_str, valeur_retournée)
|
|
11
|
+
- latex_str : chaîne LaTeX prête à injecter dans IPython.display.Latex
|
|
12
|
+
- valeur : résultat numérique de la fonction (scalaire, tuple, dict…)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import ast
|
|
16
|
+
import inspect
|
|
17
|
+
import re
|
|
18
|
+
import textwrap
|
|
19
|
+
from math import sqrt, pi, sin, cos, tan, radians, log, log10, exp, floor, ceil, fabs
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Helpers de conversion AST → LaTeX
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
_GREEK = {
|
|
28
|
+
"alpha": r"\alpha", "beta": r"\beta", "gamma": r"\gamma", "delta": r"\delta",
|
|
29
|
+
"epsilon": r"\epsilon", "zeta": r"\zeta", "eta": r"\eta", "theta": r"\theta",
|
|
30
|
+
"lambda": r"\lambda", "mu": r"\mu", "nu": r"\nu", "xi": r"\xi",
|
|
31
|
+
"pi": r"\pi", "rho": r"\rho", "sigma": r"\sigma", "tau": r"\tau",
|
|
32
|
+
"phi": r"\phi", "chi": r"\chi", "psi": r"\psi", "omega": r"\omega",
|
|
33
|
+
"Gamma": r"\Gamma", "Delta": r"\Delta", "Theta": r"\Theta",
|
|
34
|
+
"Lambda": r"\Lambda", "Xi": r"\Xi", "Pi": r"\Pi", "Sigma": r"\Sigma",
|
|
35
|
+
"Phi": r"\Phi", "Psi": r"\Psi", "Omega": r"\Omega",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_FUNC_MAP = {
|
|
39
|
+
"sqrt": r"\sqrt", "sin": r"\sin", "cos": r"\cos", "tan": r"\tan",
|
|
40
|
+
"log": r"\log", "log10": r"\log_{10}", "exp": r"\exp",
|
|
41
|
+
"abs": r"\left|", "fabs": r"\left|",
|
|
42
|
+
"floor": r"\lfloor", "ceil": r"\lceil",
|
|
43
|
+
"radians": r"\frac{\pi}{180}",
|
|
44
|
+
"min": r"\min", "max": r"\max",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _name_to_latex(name: str) -> str:
|
|
49
|
+
"""Convertit un nom Python en symbole LaTeX (grecs, indices)."""
|
|
50
|
+
if name in _GREEK:
|
|
51
|
+
return _GREEK[name]
|
|
52
|
+
# Gestion des sous-indices : taux_6_11 → taux_{6,11} ; sigma_m_d → \sigma_{m,d}
|
|
53
|
+
parts = name.split("_")
|
|
54
|
+
if len(parts) == 1:
|
|
55
|
+
return name
|
|
56
|
+
base = parts[0]
|
|
57
|
+
subs = parts[1:]
|
|
58
|
+
base_latex = _GREEK.get(base, base)
|
|
59
|
+
sub_str = ",".join(subs)
|
|
60
|
+
return f"{base_latex}_{{{sub_str}}}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _is_physical(value: Any) -> bool:
|
|
64
|
+
"""Retourne True si la valeur est un objet forallpeople.Physical."""
|
|
65
|
+
return hasattr(value, "latex") and hasattr(value, "dimensions")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _num_to_latex(value: Any, precision: int) -> str:
|
|
69
|
+
"""Convertit une valeur numérique en chaîne LaTeX (sans unité)."""
|
|
70
|
+
try:
|
|
71
|
+
if _is_physical(value):
|
|
72
|
+
# str(x) -> "1.500 MPa" : on extrait la partie numérique affichée
|
|
73
|
+
s = str(value)
|
|
74
|
+
parts = s.rsplit(" ", 1)
|
|
75
|
+
num_str = parts[0].strip()
|
|
76
|
+
try:
|
|
77
|
+
v = float(num_str)
|
|
78
|
+
if abs(v) >= 1e6 or (abs(v) < 1e-3 and v != 0):
|
|
79
|
+
return f"{v:.{precision}e}"
|
|
80
|
+
return f"{round(v, precision)}"
|
|
81
|
+
except ValueError:
|
|
82
|
+
return num_str
|
|
83
|
+
v = value
|
|
84
|
+
if isinstance(v, int):
|
|
85
|
+
return str(v)
|
|
86
|
+
if isinstance(v, float):
|
|
87
|
+
if abs(v) >= 1e6 or (abs(v) < 1e-3 and v != 0):
|
|
88
|
+
return f"{v:.{precision}e}"
|
|
89
|
+
return f"{round(v, precision)}"
|
|
90
|
+
return str(round(float(v), precision))
|
|
91
|
+
except Exception:
|
|
92
|
+
return str(value)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class _ASTNumRenderer(ast.NodeVisitor):
|
|
96
|
+
"""Visite l'AST d'une expression et substitue les Name par leurs valeurs numériques.
|
|
97
|
+
|
|
98
|
+
Produit la forme numérique intermédiaire : \\min(0.77 + 3.5/25, 1).
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, local_vars: dict, precision: int):
|
|
102
|
+
self.local_vars = local_vars
|
|
103
|
+
self.precision = precision
|
|
104
|
+
|
|
105
|
+
def render(self, node: ast.AST) -> str:
|
|
106
|
+
return self.visit(node)
|
|
107
|
+
|
|
108
|
+
def visit_Name(self, node: ast.Name) -> str:
|
|
109
|
+
name = node.id
|
|
110
|
+
if name in self.local_vars:
|
|
111
|
+
val = self.local_vars[name]
|
|
112
|
+
num = _num_to_latex(val, self.precision + 1)
|
|
113
|
+
unit = _get_unit_str(val)
|
|
114
|
+
return f"{num}{unit}" if unit else num
|
|
115
|
+
return _name_to_latex(name)
|
|
116
|
+
|
|
117
|
+
def visit_Constant(self, node: ast.Constant) -> str:
|
|
118
|
+
if isinstance(node.value, float):
|
|
119
|
+
return f"{node.value}"
|
|
120
|
+
return str(node.value)
|
|
121
|
+
|
|
122
|
+
def visit_BinOp(self, node: ast.BinOp) -> str:
|
|
123
|
+
sym = _ASTRenderer(self.local_vars, self.precision)
|
|
124
|
+
left = self.visit(node.left)
|
|
125
|
+
right = self.visit(node.right)
|
|
126
|
+
op = node.op
|
|
127
|
+
if isinstance(op, ast.Add):
|
|
128
|
+
return f"{left} + {right}"
|
|
129
|
+
if isinstance(op, ast.Sub):
|
|
130
|
+
return f"{left} - {right}"
|
|
131
|
+
if isinstance(op, ast.Mult):
|
|
132
|
+
return f"{left} \\cdot {right}"
|
|
133
|
+
if isinstance(op, ast.Div):
|
|
134
|
+
return f"\\frac{{{left}}}{{{right}}}"
|
|
135
|
+
if isinstance(op, ast.Pow):
|
|
136
|
+
base = left if _is_simple(node.left) else f"\\left({left}\\right)"
|
|
137
|
+
exp_str = right if _is_simple(node.right) else f"{{{right}}}"
|
|
138
|
+
return f"{base}^{{{exp_str}}}"
|
|
139
|
+
if isinstance(op, ast.FloorDiv):
|
|
140
|
+
return f"\\lfloor\\frac{{{left}}}{{{right}}}\\rfloor"
|
|
141
|
+
return f"{left} \\circ {right}"
|
|
142
|
+
|
|
143
|
+
def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
|
|
144
|
+
operand = self.visit(node.operand)
|
|
145
|
+
if isinstance(node.op, ast.USub):
|
|
146
|
+
return f"-{operand}"
|
|
147
|
+
return operand
|
|
148
|
+
|
|
149
|
+
def visit_Call(self, node: ast.Call) -> str:
|
|
150
|
+
func_name = ""
|
|
151
|
+
if isinstance(node.func, ast.Name):
|
|
152
|
+
func_name = node.func.id
|
|
153
|
+
elif isinstance(node.func, ast.Attribute):
|
|
154
|
+
func_name = node.func.attr
|
|
155
|
+
args = [self.visit(a) for a in node.args]
|
|
156
|
+
if func_name == "sqrt":
|
|
157
|
+
return f"\\sqrt{{{args[0]}}}" if args else r"\sqrt{\cdot}"
|
|
158
|
+
if func_name in ("sin", "cos", "tan"):
|
|
159
|
+
return f"\\{func_name}\\left({args[0]}\\right)" if args else f"\\{func_name}()"
|
|
160
|
+
if func_name in ("min", "max"):
|
|
161
|
+
return f"\\{func_name}\\left({', '.join(args)}\\right)"
|
|
162
|
+
if func_name in ("abs", "fabs"):
|
|
163
|
+
return f"\\left|{args[0]}\\right|" if args else r"\left|\cdot\right|"
|
|
164
|
+
if func_name == "log":
|
|
165
|
+
return f"\\ln\\left({args[0]}\\right)" if args else r"\ln(\cdot)"
|
|
166
|
+
if func_name == "exp":
|
|
167
|
+
return f"e^{{{args[0]}}}" if args else "e^{\\cdot}"
|
|
168
|
+
if func_name == "radians":
|
|
169
|
+
return f"\\frac{{\\pi \\cdot {args[0]}}}{{180}}" if args else ""
|
|
170
|
+
if func_name == "floor":
|
|
171
|
+
return f"\\lfloor {args[0]} \\rfloor" if args else ""
|
|
172
|
+
if func_name == "ceil":
|
|
173
|
+
return f"\\lceil {args[0]} \\rceil" if args else ""
|
|
174
|
+
return f"{_name_to_latex(func_name)}\\left({', '.join(args)}\\right)"
|
|
175
|
+
|
|
176
|
+
def visit_Compare(self, node: ast.Compare) -> str:
|
|
177
|
+
parts = [self.visit(node.left)]
|
|
178
|
+
ops_map = {ast.Lt: "<", ast.LtE: "\\leq", ast.Gt: ">", ast.GtE: "\\geq",
|
|
179
|
+
ast.Eq: "=", ast.NotEq: "\\neq"}
|
|
180
|
+
for op, comp in zip(node.ops, node.comparators):
|
|
181
|
+
parts.append(ops_map.get(type(op), "?"))
|
|
182
|
+
parts.append(self.visit(comp))
|
|
183
|
+
return " ".join(parts)
|
|
184
|
+
|
|
185
|
+
def visit_IfExp(self, node: ast.IfExp) -> str:
|
|
186
|
+
body = self.visit(node.body)
|
|
187
|
+
test = self.visit(node.test)
|
|
188
|
+
orelse = self.visit(node.orelse)
|
|
189
|
+
return f"{body} \\text{{ si }} {test} \\text{{ sinon }} {orelse}"
|
|
190
|
+
|
|
191
|
+
def generic_visit(self, node: ast.AST) -> str:
|
|
192
|
+
return "\\cdot"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class _ASTRenderer(ast.NodeVisitor):
|
|
196
|
+
"""Visite l'AST d'une expression et génère du LaTeX."""
|
|
197
|
+
|
|
198
|
+
def __init__(self, local_vars: dict, precision: int):
|
|
199
|
+
self.local_vars = local_vars
|
|
200
|
+
self.precision = precision
|
|
201
|
+
|
|
202
|
+
def render(self, node: ast.AST) -> str:
|
|
203
|
+
return self.visit(node)
|
|
204
|
+
|
|
205
|
+
# --- Nœuds terminaux ---
|
|
206
|
+
|
|
207
|
+
def visit_Name(self, node: ast.Name) -> str:
|
|
208
|
+
return _name_to_latex(node.id)
|
|
209
|
+
|
|
210
|
+
def visit_Constant(self, node: ast.Constant) -> str:
|
|
211
|
+
if isinstance(node.value, float):
|
|
212
|
+
return f"{node.value}"
|
|
213
|
+
return str(node.value)
|
|
214
|
+
|
|
215
|
+
# --- Opérations binaires ---
|
|
216
|
+
|
|
217
|
+
def visit_BinOp(self, node: ast.BinOp) -> str:
|
|
218
|
+
left = self.visit(node.left)
|
|
219
|
+
right = self.visit(node.right)
|
|
220
|
+
op = node.op
|
|
221
|
+
|
|
222
|
+
if isinstance(op, ast.Add):
|
|
223
|
+
return f"{left} + {right}"
|
|
224
|
+
if isinstance(op, ast.Sub):
|
|
225
|
+
return f"{left} - {right}"
|
|
226
|
+
if isinstance(op, ast.Mult):
|
|
227
|
+
# Évite \cdot entre deux nombres
|
|
228
|
+
return f"{left} \\cdot {right}"
|
|
229
|
+
if isinstance(op, ast.Div):
|
|
230
|
+
return f"\\frac{{{left}}}{{{right}}}"
|
|
231
|
+
if isinstance(op, ast.Pow):
|
|
232
|
+
# Parenthèses si la base est complexe
|
|
233
|
+
base = left if _is_simple(node.left) else f"\\left({left}\\right)"
|
|
234
|
+
exp_str = right if _is_simple(node.right) else f"{{{right}}}"
|
|
235
|
+
return f"{base}^{{{exp_str}}}"
|
|
236
|
+
if isinstance(op, ast.FloorDiv):
|
|
237
|
+
return f"\\lfloor\\frac{{{left}}}{{{right}}}\\rfloor"
|
|
238
|
+
if isinstance(op, ast.Mod):
|
|
239
|
+
return f"{left} \\bmod {right}"
|
|
240
|
+
return f"{left} \\circ {right}"
|
|
241
|
+
|
|
242
|
+
def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
|
|
243
|
+
operand = self.visit(node.operand)
|
|
244
|
+
if isinstance(node.op, ast.USub):
|
|
245
|
+
return f"-{operand}"
|
|
246
|
+
if isinstance(node.op, ast.UAdd):
|
|
247
|
+
return operand
|
|
248
|
+
return operand
|
|
249
|
+
|
|
250
|
+
# --- Appels de fonctions ---
|
|
251
|
+
|
|
252
|
+
def visit_Call(self, node: ast.Call) -> str:
|
|
253
|
+
func_name = ""
|
|
254
|
+
if isinstance(node.func, ast.Name):
|
|
255
|
+
func_name = node.func.id
|
|
256
|
+
elif isinstance(node.func, ast.Attribute):
|
|
257
|
+
func_name = node.func.attr
|
|
258
|
+
|
|
259
|
+
args_latex = [self.visit(a) for a in node.args]
|
|
260
|
+
|
|
261
|
+
if func_name == "sqrt":
|
|
262
|
+
return f"\\sqrt{{{args_latex[0]}}}" if args_latex else r"\sqrt{\cdot}"
|
|
263
|
+
if func_name in ("sin", "cos", "tan"):
|
|
264
|
+
return f"\\{func_name}\\left({args_latex[0]}\\right)" if args_latex else f"\\{func_name}()"
|
|
265
|
+
if func_name in ("min", "max"):
|
|
266
|
+
return f"\\{func_name}\\left({', '.join(args_latex)}\\right)"
|
|
267
|
+
if func_name == "abs" or func_name == "fabs":
|
|
268
|
+
return f"\\left|{args_latex[0]}\\right|" if args_latex else r"\left|\cdot\right|"
|
|
269
|
+
if func_name == "log":
|
|
270
|
+
return f"\\ln\\left({args_latex[0]}\\right)" if args_latex else r"\ln(\cdot)"
|
|
271
|
+
if func_name == "log10":
|
|
272
|
+
return f"\\log_{{10}}\\left({args_latex[0]}\\right)" if args_latex else r"\log_{10}(\cdot)"
|
|
273
|
+
if func_name == "exp":
|
|
274
|
+
return f"e^{{{args_latex[0]}}}" if args_latex else "e^{\\cdot}"
|
|
275
|
+
if func_name == "radians":
|
|
276
|
+
return f"\\frac{{\\pi \\cdot {args_latex[0]}}}{{180}}" if args_latex else ""
|
|
277
|
+
if func_name == "floor":
|
|
278
|
+
return f"\\lfloor {args_latex[0]} \\rfloor" if args_latex else ""
|
|
279
|
+
if func_name == "ceil":
|
|
280
|
+
return f"\\lceil {args_latex[0]} \\rceil" if args_latex else ""
|
|
281
|
+
# Fonction générique
|
|
282
|
+
name_latex = _name_to_latex(func_name)
|
|
283
|
+
return f"{name_latex}\\left({', '.join(args_latex)}\\right)"
|
|
284
|
+
|
|
285
|
+
# --- Comparaisons ---
|
|
286
|
+
|
|
287
|
+
def visit_Compare(self, node: ast.Compare) -> str:
|
|
288
|
+
parts = [self.visit(node.left)]
|
|
289
|
+
ops_map = {
|
|
290
|
+
ast.Lt: "<", ast.LtE: "\\leq", ast.Gt: ">", ast.GtE: "\\geq",
|
|
291
|
+
ast.Eq: "=", ast.NotEq: "\\neq",
|
|
292
|
+
}
|
|
293
|
+
for op, comp in zip(node.ops, node.comparators):
|
|
294
|
+
parts.append(ops_map.get(type(op), "?"))
|
|
295
|
+
parts.append(self.visit(comp))
|
|
296
|
+
return " ".join(parts)
|
|
297
|
+
|
|
298
|
+
# --- Tuple/List/Dict ---
|
|
299
|
+
|
|
300
|
+
def visit_Tuple(self, node: ast.Tuple) -> str:
|
|
301
|
+
return "\\left(" + ", ".join(self.visit(e) for e in node.elts) + "\\right)"
|
|
302
|
+
|
|
303
|
+
def visit_List(self, node: ast.List) -> str:
|
|
304
|
+
return "\\left[" + ", ".join(self.visit(e) for e in node.elts) + "\\right]"
|
|
305
|
+
|
|
306
|
+
def visit_Dict(self, node: ast.Dict) -> str:
|
|
307
|
+
return ""
|
|
308
|
+
|
|
309
|
+
def visit_Subscript(self, node: ast.Subscript) -> str:
|
|
310
|
+
val = self.visit(node.value)
|
|
311
|
+
sl = self.visit(node.slice)
|
|
312
|
+
return f"{val}_{{{sl}}}"
|
|
313
|
+
|
|
314
|
+
def visit_IfExp(self, node: ast.IfExp) -> str:
|
|
315
|
+
body = self.visit(node.body)
|
|
316
|
+
test = self.visit(node.test)
|
|
317
|
+
orelse = self.visit(node.orelse)
|
|
318
|
+
return f"{body} \\text{{ si }} {test} \\text{{ sinon }} {orelse}"
|
|
319
|
+
|
|
320
|
+
def generic_visit(self, node: ast.AST) -> str:
|
|
321
|
+
return "\\cdot"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _is_simple(node: ast.AST) -> bool:
|
|
325
|
+
"""Retourne True si le nœud est suffisamment simple pour ne pas nécessiter de parenthèses."""
|
|
326
|
+
return isinstance(node, (ast.Name, ast.Constant, ast.Attribute))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
# Extraction des lignes d'assignation depuis l'AST de la fonction
|
|
331
|
+
# ---------------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
def _extract_assignments(func_source: str) -> list[ast.Assign]:
|
|
334
|
+
"""Extrait les nœuds Assign et AugAssign du corps de la fonction."""
|
|
335
|
+
tree = ast.parse(func_source)
|
|
336
|
+
assignments = []
|
|
337
|
+
for node in ast.walk(tree):
|
|
338
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
339
|
+
for stmt in node.body:
|
|
340
|
+
if isinstance(stmt, ast.Assign):
|
|
341
|
+
assignments.append(stmt)
|
|
342
|
+
elif isinstance(stmt, ast.AugAssign):
|
|
343
|
+
assignments.append(stmt)
|
|
344
|
+
# Les Expr seuls (noms sans affectation comme `axe` ou `direction`)
|
|
345
|
+
# sont ignorés — c'était des "labels" handcalcs, inutiles en LaTeX
|
|
346
|
+
return assignments
|
|
347
|
+
|
|
348
|
+
# Rendu LaTeX d'une assignation
|
|
349
|
+
# ---------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def _render_assignment(assign: ast.Assign, local_vars: dict, precision: int,
|
|
352
|
+
long: bool = False) -> str:
|
|
353
|
+
"""Génère une ligne LaTeX 'lhs &= rhs_sym = rhs_num = valeur_unité'.
|
|
354
|
+
|
|
355
|
+
Si long=True, chaque étape (sym / num / résultat) est sur sa propre ligne alignée.
|
|
356
|
+
"""
|
|
357
|
+
sym_renderer = _ASTRenderer(local_vars, precision)
|
|
358
|
+
num_renderer = _ASTNumRenderer(local_vars, precision)
|
|
359
|
+
|
|
360
|
+
# Cible (LHS)
|
|
361
|
+
target = assign.targets[0] if isinstance(assign, ast.Assign) else assign.target
|
|
362
|
+
lhs = sym_renderer.visit(target)
|
|
363
|
+
|
|
364
|
+
# Expression symbolique
|
|
365
|
+
rhs_sym = sym_renderer.visit(assign.value)
|
|
366
|
+
|
|
367
|
+
# Expression numérique (substitution des variables connues)
|
|
368
|
+
rhs_num = num_renderer.visit(assign.value)
|
|
369
|
+
|
|
370
|
+
# Valeur finale calculée
|
|
371
|
+
target_name = None
|
|
372
|
+
if isinstance(target, ast.Name):
|
|
373
|
+
target_name = target.id
|
|
374
|
+
elif isinstance(target, ast.Tuple):
|
|
375
|
+
target_name = None
|
|
376
|
+
|
|
377
|
+
has_num_sub = rhs_num != rhs_sym
|
|
378
|
+
|
|
379
|
+
if target_name and target_name in local_vars:
|
|
380
|
+
val = local_vars[target_name]
|
|
381
|
+
val_str = _num_to_latex(val, precision)
|
|
382
|
+
unit_str = _get_unit_str(val)
|
|
383
|
+
if unit_str:
|
|
384
|
+
val_str = f"{val_str}{unit_str}"
|
|
385
|
+
if long and has_num_sub:
|
|
386
|
+
return (
|
|
387
|
+
f"{lhs} &= {rhs_sym} \\\\\n"
|
|
388
|
+
f" &= {rhs_num} \\\\\n"
|
|
389
|
+
f" &= {val_str} \\\\\n"
|
|
390
|
+
)
|
|
391
|
+
elif long:
|
|
392
|
+
return (
|
|
393
|
+
f"{lhs} &= {rhs_sym} \\\\\n"
|
|
394
|
+
f" &= {val_str} \\\\\n"
|
|
395
|
+
)
|
|
396
|
+
elif has_num_sub:
|
|
397
|
+
return f"{lhs} &= {rhs_sym} = {rhs_num} = {val_str} \\\\\n"
|
|
398
|
+
else:
|
|
399
|
+
return f"{lhs} &= {rhs_sym} = {val_str} \\\\\n"
|
|
400
|
+
else:
|
|
401
|
+
if long and has_num_sub:
|
|
402
|
+
return (
|
|
403
|
+
f"{lhs} &= {rhs_sym} \\\\\n"
|
|
404
|
+
f" &= {rhs_num} \\\\\n"
|
|
405
|
+
)
|
|
406
|
+
elif has_num_sub:
|
|
407
|
+
return f"{lhs} &= {rhs_sym} = {rhs_num} \\\\\n"
|
|
408
|
+
return f"{lhs} &= {rhs_sym} \\\\\n"
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _get_unit_str(value: Any) -> str:
|
|
412
|
+
"""Extrait l'unité d'une grandeur forallpeople en LaTeX (ex: \\mathrm{MPa})."""
|
|
413
|
+
try:
|
|
414
|
+
if _is_physical(value):
|
|
415
|
+
s = str(value)
|
|
416
|
+
parts = s.rsplit(" ", 1)
|
|
417
|
+
if len(parts) == 2:
|
|
418
|
+
unit = parts[1].strip()
|
|
419
|
+
unit = unit.replace("**", "^").replace("*", " \\cdot ")
|
|
420
|
+
return f"\\,\\mathrm{{{unit}}}"
|
|
421
|
+
except Exception:
|
|
422
|
+
pass
|
|
423
|
+
return ""
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
# ---------------------------------------------------------------------------
|
|
427
|
+
# Décorateur principal
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def handcalc(
|
|
432
|
+
override: str = "short",
|
|
433
|
+
precision: int = 2,
|
|
434
|
+
jupyter_display: bool = False,
|
|
435
|
+
left: str = "\\[",
|
|
436
|
+
right: str = "\\]",
|
|
437
|
+
):
|
|
438
|
+
"""Remplace handcalcs.decorator.handcalc — compatible Python 3.13+.
|
|
439
|
+
|
|
440
|
+
Usage identique à l'original :
|
|
441
|
+
|
|
442
|
+
@handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY,
|
|
443
|
+
left="\\\\[", right="\\\\]")
|
|
444
|
+
def val():
|
|
445
|
+
x = a + b
|
|
446
|
+
return x
|
|
447
|
+
|
|
448
|
+
latex_str, result = val()
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
override: "short" (défaut) ou "long".
|
|
452
|
+
precision: Nombre de décimales pour les valeurs numériques.
|
|
453
|
+
jupyter_display: Si True, affiche le LaTeX dans Jupyter via IPython.display.
|
|
454
|
+
left / right: Délimiteurs LaTeX du bloc (ex. "$$", "\\\\[").
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
Décorateur qui transforme la fonction en un appelable retournant
|
|
458
|
+
``(latex_str, résultat_numérique)``.
|
|
459
|
+
"""
|
|
460
|
+
def decorator(func):
|
|
461
|
+
def wrapper(*args, **kwargs):
|
|
462
|
+
# 1. Exécuter la fonction pour obtenir les valeurs numériques
|
|
463
|
+
result = func(*args, **kwargs)
|
|
464
|
+
|
|
465
|
+
# 2. Récupérer le code source de la fonction
|
|
466
|
+
try:
|
|
467
|
+
raw_source = inspect.getsource(func)
|
|
468
|
+
func_source = textwrap.dedent(raw_source)
|
|
469
|
+
except (OSError, TypeError):
|
|
470
|
+
# Impossible de récupérer le source → retour sans LaTeX
|
|
471
|
+
return ("", result)
|
|
472
|
+
|
|
473
|
+
# 3. Construire le dict des variables locales disponibles
|
|
474
|
+
# = globals du scope + fermeture (closure) + arguments
|
|
475
|
+
local_vars: dict = {}
|
|
476
|
+
|
|
477
|
+
# Variables du scope module (globals) — permet la substitution numérique
|
|
478
|
+
if func.__globals__:
|
|
479
|
+
local_vars.update({
|
|
480
|
+
k: v for k, v in func.__globals__.items()
|
|
481
|
+
if not k.startswith("__") and not callable(v)
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
# Variables de fermeture (closures)
|
|
485
|
+
if func.__code__.co_freevars and func.__closure__:
|
|
486
|
+
for name, cell in zip(func.__code__.co_freevars, func.__closure__):
|
|
487
|
+
try:
|
|
488
|
+
local_vars[name] = cell.cell_contents
|
|
489
|
+
except ValueError:
|
|
490
|
+
pass
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
# Arguments positionnels (cas `def comp(arg1, arg2, ...)`)
|
|
494
|
+
arg_names = func.__code__.co_varnames[: func.__code__.co_argcount]
|
|
495
|
+
for name, val in zip(arg_names, args):
|
|
496
|
+
local_vars[name] = val
|
|
497
|
+
local_vars.update(kwargs)
|
|
498
|
+
|
|
499
|
+
# Résultats de la fonction — injecte les valeurs calculées
|
|
500
|
+
# On récupère les noms des variables locales via co_varnames
|
|
501
|
+
# En exécutant la fonction une seconde fois dans un namespace capturé
|
|
502
|
+
local_capture: dict = dict(local_vars)
|
|
503
|
+
try:
|
|
504
|
+
_exec_and_capture(func, args, kwargs, local_capture)
|
|
505
|
+
except Exception:
|
|
506
|
+
pass
|
|
507
|
+
local_vars.update(local_capture)
|
|
508
|
+
|
|
509
|
+
# 4. Extraire les assignations de l'AST
|
|
510
|
+
try:
|
|
511
|
+
assignments = _extract_assignments(func_source)
|
|
512
|
+
except SyntaxError:
|
|
513
|
+
return ("", result)
|
|
514
|
+
|
|
515
|
+
# 5. Construire le LaTeX
|
|
516
|
+
is_long = override == "long"
|
|
517
|
+
lines = []
|
|
518
|
+
for assign in assignments:
|
|
519
|
+
line = _render_assignment(assign, local_vars, precision, long=is_long)
|
|
520
|
+
lines.append(line)
|
|
521
|
+
|
|
522
|
+
if not lines:
|
|
523
|
+
latex_body = ""
|
|
524
|
+
elif override == "long" or len(lines) > 1:
|
|
525
|
+
inner = "".join(lines)
|
|
526
|
+
latex_body = f"\\begin{{aligned}}\n{inner}\\end{{aligned}}"
|
|
527
|
+
else:
|
|
528
|
+
# short : une seule ligne, format compact
|
|
529
|
+
latex_body = "".join(lines).replace(" &=", " =").replace("\\\\\n", "")
|
|
530
|
+
|
|
531
|
+
latex_str = f"{left}\n{latex_body}\n{right}"
|
|
532
|
+
|
|
533
|
+
# 6. Affichage Jupyter si demandé
|
|
534
|
+
if jupyter_display:
|
|
535
|
+
try:
|
|
536
|
+
from IPython.display import display, Latex as IPyLatex
|
|
537
|
+
if display and IPyLatex:
|
|
538
|
+
display(IPyLatex(latex_str))
|
|
539
|
+
except ImportError:
|
|
540
|
+
pass
|
|
541
|
+
|
|
542
|
+
return (latex_str, result)
|
|
543
|
+
|
|
544
|
+
return wrapper
|
|
545
|
+
return decorator
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
# ---------------------------------------------------------------------------
|
|
549
|
+
# Capture des variables locales sans innerscope
|
|
550
|
+
# ---------------------------------------------------------------------------
|
|
551
|
+
|
|
552
|
+
def _exec_and_capture(func, args, kwargs, capture: dict) -> None:
|
|
553
|
+
"""Exécute func dans un namespace étendu et capture les variables locales.
|
|
554
|
+
|
|
555
|
+
Utilise exec() sur le corps de la fonction avec les variables de fermeture
|
|
556
|
+
et arguments déjà présents dans `capture`. Met à jour `capture` in-place.
|
|
557
|
+
"""
|
|
558
|
+
try:
|
|
559
|
+
raw = inspect.getsource(func)
|
|
560
|
+
source = textwrap.dedent(raw)
|
|
561
|
+
except (OSError, TypeError):
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
tree = ast.parse(source)
|
|
565
|
+
|
|
566
|
+
# On cherche le premier FunctionDef
|
|
567
|
+
func_def = None
|
|
568
|
+
for node in ast.walk(tree):
|
|
569
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
570
|
+
func_def = node
|
|
571
|
+
break
|
|
572
|
+
if func_def is None:
|
|
573
|
+
return
|
|
574
|
+
|
|
575
|
+
# Reconstruire le corps sans le 'return' final comme module exécutable
|
|
576
|
+
body_stmts = [s for s in func_def.body if not isinstance(s, ast.Return)]
|
|
577
|
+
if not body_stmts:
|
|
578
|
+
return
|
|
579
|
+
|
|
580
|
+
# Créer un nouveau module AST avec ces instructions
|
|
581
|
+
new_module = ast.Module(body=body_stmts, type_ignores=[])
|
|
582
|
+
ast.fix_missing_locations(new_module)
|
|
583
|
+
|
|
584
|
+
# Injecter les fonctions math disponibles + globals de la fonction (scope module)
|
|
585
|
+
exec_globals = {
|
|
586
|
+
"sqrt": sqrt, "pi": pi, "sin": sin, "cos": cos, "tan": tan,
|
|
587
|
+
"radians": radians, "log": log, "log10": log10, "exp": exp,
|
|
588
|
+
"floor": floor, "ceil": ceil, "fabs": fabs, "abs": abs,
|
|
589
|
+
"min": min, "max": max, "round": round,
|
|
590
|
+
}
|
|
591
|
+
if func.__globals__:
|
|
592
|
+
exec_globals.update(func.__globals__)
|
|
593
|
+
exec_globals.update(capture)
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
exec(compile(new_module, "<handcalc>", "exec"), exec_globals, capture)
|
|
597
|
+
except Exception:
|
|
598
|
+
pass
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from math import *
|
|
2
2
|
|
|
3
3
|
import forallpeople as si
|
|
4
|
-
from
|
|
4
|
+
from ourocode.eurocode.core.renderer import handcalc
|
|
5
5
|
|
|
6
6
|
from ourocode.eurocode.core.projet import Projet
|
|
7
7
|
|
|
@@ -56,7 +56,7 @@ class Exploitation(Projet):
|
|
|
56
56
|
q_k = self.qk
|
|
57
57
|
|
|
58
58
|
if self.categorie in ("A_plancher", "A_escalier", "A_balcons", "B", "C3", "D1", "F"):
|
|
59
|
-
@handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="
|
|
59
|
+
@handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\\[", right="\\]")
|
|
60
60
|
def val():
|
|
61
61
|
alpha_A = min(0.77 + A_0 / A, 1) #EN 1991-1-1 et son AN §6.2.1(4) et 6.3.1.2(10)
|
|
62
62
|
q_k_alpha_A = q_k * alpha_A
|
|
@@ -80,14 +80,14 @@ class Exploitation(Projet):
|
|
|
80
80
|
"""
|
|
81
81
|
if n > 2:
|
|
82
82
|
if self.categorie in ("B", "F"):
|
|
83
|
-
@handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="
|
|
83
|
+
@handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\\[", right="\\]")
|
|
84
84
|
def val():
|
|
85
85
|
alpha_n = 0.7 + 0.8 / n #EN 1991-1-1 §6.2.2(2) et 6.3.1.2(11)
|
|
86
86
|
return alpha_n
|
|
87
87
|
return val()
|
|
88
88
|
|
|
89
89
|
elif self.categorie in ("A_plancher", "A_escalier", "A_balcons"):
|
|
90
|
-
@handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="
|
|
90
|
+
@handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\\[", right="\\]")
|
|
91
91
|
def val():
|
|
92
92
|
alpha_n = 0.5 + 1.36 / n #EN 1991-1-1 §6.2.2(2) et 6.3.1.2(11)
|
|
93
93
|
return alpha_n
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#! env\Scripts\python.exe
|
|
1
|
+
#! env\Scripts\python.exe
|
|
2
2
|
# Encoding in UTF-8 by Anthony PARISOT
|
|
3
3
|
from math import sin, radians
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import warnings
|
|
6
6
|
|
|
7
7
|
import forallpeople as si
|
|
8
|
-
from
|
|
8
|
+
from ourocode.eurocode.core.renderer import handcalc
|
|
9
9
|
|
|
10
10
|
from ourocode.eurocode.core.batiment import Batiment
|
|
11
11
|
|