diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6493b90..322ef05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.14" - run: pip install ruff - run: ruff check ourocode/ tests/ @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.14" - name: Install dependencies run: | pip install --upgrade pip @@ -47,7 +47,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.14" - name: Install doc dependencies run: | pip install --upgrade pip diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index a169d63..0775483 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -46,7 +46,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.12" + python-version: "3.14" - name: Build release distributions run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ff051b..e3c5369 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.14' - name: Install toml library run: pip install toml diff --git a/.windsurf/agents.md b/AGENTS.md similarity index 100% rename from .windsurf/agents.md rename to AGENTS.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ecd48c..a3e49af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,25 @@ et ce projet adhère au [Semantic Versioning](https://semver.org/lang/fr/). ## [Unreleased] +## [2.0.0] - 2026-04-16 + +### Added +- **`ourocode/eurocode/core/renderer.py`** : nouveau module de rendu LaTeX maison, remplaçant `handcalcs` — compatible Python 3.12+. + - Décorateur `@handcalc` basé sur l'AST (`ast` + `inspect`), sans dépendance à `innerscope`. + - Rendu symbolique (noms → symboles LaTeX, grecs, indices), numérique (substitution des valeurs) et résultat final sur une même ligne (`override="short"`) ou sur trois lignes alignées (`override="long"`). + - Extraction correcte des unités `forallpeople` via `str(x).rsplit()` → `\,\mathrm{...}`. + - Support des fonctions `sqrt`, `sin`, `cos`, `tan`, `log`, `log10`, `exp`, `floor`, `ceil`, `abs`, `min`, `max`, `radians`. + - Capture des variables de fermeture (closures), du scope module (globals) et des arguments positionnels. + ### Changed -- **CI/CD** : Migration du déploiement de la documentation vers l'action officielle GitHub Pages (`actions/deploy-pages@v4`) pour une meilleure fiabilité et compatibilité avec les paramètres de sécurité GitHub. +- **Suppression de la dépendance `handcalcs`** dans `pyproject.toml` — incompatible avec Python 3.13+ (bug `FrameLocalsProxy` dans `innerscope`). +- `requires-python` élargi à `>=3.12` (sans borne supérieure). +- Classifiers Python mis à jour : ajout `3.13`, `3.14`. +- Tous les imports `from handcalcs.decorator import handcalc` remplacés par `from ourocode.eurocode.core.renderer import handcalc` dans les modules EC1, EC5. +- **`ourocode/eurocode/ec1/exploitation.py`** : correction des séquences d'échappement invalides `"\["` / `"\]"` → `"\\["` / `"\\]"` dans le décorateur `@handcalc` de `alpha_A` (`SyntaxWarning` Python 3.14+). + +### Changed (CI/CD) +- Migration du déploiement de la documentation vers l'action officielle GitHub Pages (`actions/deploy-pages@v4`). ## [1.11.3] - 2026-04-14 diff --git a/Ourocode.png b/Ourocode.png deleted file mode 100644 index 791c70b..0000000 Binary files a/Ourocode.png and /dev/null differ diff --git a/docs/index.md b/docs/index.md index a1d8f99..d61e9b5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,7 +36,6 @@ Avec les dépendances optionnelles : ```bash pip install "ourocode[full]" # GUI (PySide6) + MEF (PyNiteFEA + pyvista) pip install "ourocode[gui]" # PySide6 uniquement -pip install "ourocode[mef]" # PyNiteFEA + pyvista uniquement ``` --- diff --git a/ourocode/eurocode/core/renderer.py b/ourocode/eurocode/core/renderer.py new file mode 100644 index 0000000..21c37f1 --- /dev/null +++ b/ourocode/eurocode/core/renderer.py @@ -0,0 +1,598 @@ +# coding in UTF-8 +# by Anthony PARISOT +"""Remplacement de handcalcs.decorator.handcalc compatible Python 3.13+. + +Ce module implémente un décorateur @handcalc qui génère du LaTeX à partir +du code source Python via l'AST (ast module), sans dépendance à innerscope. +Il reproduit le comportement utilisé dans ourocode : rendu des assignations +d'une fonction interne avec substitution symbolique et numérique. + +Retour du décorateur : tuple (latex_str, valeur_retournée) + - latex_str : chaîne LaTeX prête à injecter dans IPython.display.Latex + - valeur : résultat numérique de la fonction (scalaire, tuple, dict…) +""" + +import ast +import inspect +import re +import textwrap +from math import sqrt, pi, sin, cos, tan, radians, log, log10, exp, floor, ceil, fabs +from typing import Any + + +# --------------------------------------------------------------------------- +# Helpers de conversion AST → LaTeX +# --------------------------------------------------------------------------- + +_GREEK = { + "alpha": r"\alpha", "beta": r"\beta", "gamma": r"\gamma", "delta": r"\delta", + "epsilon": r"\epsilon", "zeta": r"\zeta", "eta": r"\eta", "theta": r"\theta", + "lambda": r"\lambda", "mu": r"\mu", "nu": r"\nu", "xi": r"\xi", + "pi": r"\pi", "rho": r"\rho", "sigma": r"\sigma", "tau": r"\tau", + "phi": r"\phi", "chi": r"\chi", "psi": r"\psi", "omega": r"\omega", + "Gamma": r"\Gamma", "Delta": r"\Delta", "Theta": r"\Theta", + "Lambda": r"\Lambda", "Xi": r"\Xi", "Pi": r"\Pi", "Sigma": r"\Sigma", + "Phi": r"\Phi", "Psi": r"\Psi", "Omega": r"\Omega", +} + +_FUNC_MAP = { + "sqrt": r"\sqrt", "sin": r"\sin", "cos": r"\cos", "tan": r"\tan", + "log": r"\log", "log10": r"\log_{10}", "exp": r"\exp", + "abs": r"\left|", "fabs": r"\left|", + "floor": r"\lfloor", "ceil": r"\lceil", + "radians": r"\frac{\pi}{180}", + "min": r"\min", "max": r"\max", +} + + +def _name_to_latex(name: str) -> str: + """Convertit un nom Python en symbole LaTeX (grecs, indices).""" + if name in _GREEK: + return _GREEK[name] + # Gestion des sous-indices : taux_6_11 → taux_{6,11} ; sigma_m_d → \sigma_{m,d} + parts = name.split("_") + if len(parts) == 1: + return name + base = parts[0] + subs = parts[1:] + base_latex = _GREEK.get(base, base) + sub_str = ",".join(subs) + return f"{base_latex}_{{{sub_str}}}" + + +def _is_physical(value: Any) -> bool: + """Retourne True si la valeur est un objet forallpeople.Physical.""" + return hasattr(value, "latex") and hasattr(value, "dimensions") + + +def _num_to_latex(value: Any, precision: int) -> str: + """Convertit une valeur numérique en chaîne LaTeX (sans unité).""" + try: + if _is_physical(value): + # str(x) -> "1.500 MPa" : on extrait la partie numérique affichée + s = str(value) + parts = s.rsplit(" ", 1) + num_str = parts[0].strip() + try: + v = float(num_str) + if abs(v) >= 1e6 or (abs(v) < 1e-3 and v != 0): + return f"{v:.{precision}e}" + return f"{round(v, precision)}" + except ValueError: + return num_str + v = value + if isinstance(v, int): + return str(v) + if isinstance(v, float): + if abs(v) >= 1e6 or (abs(v) < 1e-3 and v != 0): + return f"{v:.{precision}e}" + return f"{round(v, precision)}" + return str(round(float(v), precision)) + except Exception: + return str(value) + + +class _ASTNumRenderer(ast.NodeVisitor): + """Visite l'AST d'une expression et substitue les Name par leurs valeurs numériques. + + Produit la forme numérique intermédiaire : \\min(0.77 + 3.5/25, 1). + """ + + def __init__(self, local_vars: dict, precision: int): + self.local_vars = local_vars + self.precision = precision + + def render(self, node: ast.AST) -> str: + return self.visit(node) + + def visit_Name(self, node: ast.Name) -> str: + name = node.id + if name in self.local_vars: + val = self.local_vars[name] + num = _num_to_latex(val, self.precision + 1) + unit = _get_unit_str(val) + return f"{num}{unit}" if unit else num + return _name_to_latex(name) + + def visit_Constant(self, node: ast.Constant) -> str: + if isinstance(node.value, float): + return f"{node.value}" + return str(node.value) + + def visit_BinOp(self, node: ast.BinOp) -> str: + sym = _ASTRenderer(self.local_vars, self.precision) + left = self.visit(node.left) + right = self.visit(node.right) + op = node.op + if isinstance(op, ast.Add): + return f"{left} + {right}" + if isinstance(op, ast.Sub): + return f"{left} - {right}" + if isinstance(op, ast.Mult): + return f"{left} \\cdot {right}" + if isinstance(op, ast.Div): + return f"\\frac{{{left}}}{{{right}}}" + if isinstance(op, ast.Pow): + base = left if _is_simple(node.left) else f"\\left({left}\\right)" + exp_str = right if _is_simple(node.right) else f"{{{right}}}" + return f"{base}^{{{exp_str}}}" + if isinstance(op, ast.FloorDiv): + return f"\\lfloor\\frac{{{left}}}{{{right}}}\\rfloor" + return f"{left} \\circ {right}" + + def visit_UnaryOp(self, node: ast.UnaryOp) -> str: + operand = self.visit(node.operand) + if isinstance(node.op, ast.USub): + return f"-{operand}" + return operand + + def visit_Call(self, node: ast.Call) -> str: + func_name = "" + if isinstance(node.func, ast.Name): + func_name = node.func.id + elif isinstance(node.func, ast.Attribute): + func_name = node.func.attr + args = [self.visit(a) for a in node.args] + if func_name == "sqrt": + return f"\\sqrt{{{args[0]}}}" if args else r"\sqrt{\cdot}" + if func_name in ("sin", "cos", "tan"): + return f"\\{func_name}\\left({args[0]}\\right)" if args else f"\\{func_name}()" + if func_name in ("min", "max"): + return f"\\{func_name}\\left({', '.join(args)}\\right)" + if func_name in ("abs", "fabs"): + return f"\\left|{args[0]}\\right|" if args else r"\left|\cdot\right|" + if func_name == "log": + return f"\\ln\\left({args[0]}\\right)" if args else r"\ln(\cdot)" + if func_name == "exp": + return f"e^{{{args[0]}}}" if args else "e^{\\cdot}" + if func_name == "radians": + return f"\\frac{{\\pi \\cdot {args[0]}}}{{180}}" if args else "" + if func_name == "floor": + return f"\\lfloor {args[0]} \\rfloor" if args else "" + if func_name == "ceil": + return f"\\lceil {args[0]} \\rceil" if args else "" + return f"{_name_to_latex(func_name)}\\left({', '.join(args)}\\right)" + + def visit_Compare(self, node: ast.Compare) -> str: + parts = [self.visit(node.left)] + ops_map = {ast.Lt: "<", ast.LtE: "\\leq", ast.Gt: ">", ast.GtE: "\\geq", + ast.Eq: "=", ast.NotEq: "\\neq"} + for op, comp in zip(node.ops, node.comparators): + parts.append(ops_map.get(type(op), "?")) + parts.append(self.visit(comp)) + return " ".join(parts) + + def visit_IfExp(self, node: ast.IfExp) -> str: + body = self.visit(node.body) + test = self.visit(node.test) + orelse = self.visit(node.orelse) + return f"{body} \\text{{ si }} {test} \\text{{ sinon }} {orelse}" + + def generic_visit(self, node: ast.AST) -> str: + return "\\cdot" + + +class _ASTRenderer(ast.NodeVisitor): + """Visite l'AST d'une expression et génère du LaTeX.""" + + def __init__(self, local_vars: dict, precision: int): + self.local_vars = local_vars + self.precision = precision + + def render(self, node: ast.AST) -> str: + return self.visit(node) + + # --- Nœuds terminaux --- + + def visit_Name(self, node: ast.Name) -> str: + return _name_to_latex(node.id) + + def visit_Constant(self, node: ast.Constant) -> str: + if isinstance(node.value, float): + return f"{node.value}" + return str(node.value) + + # --- Opérations binaires --- + + def visit_BinOp(self, node: ast.BinOp) -> str: + left = self.visit(node.left) + right = self.visit(node.right) + op = node.op + + if isinstance(op, ast.Add): + return f"{left} + {right}" + if isinstance(op, ast.Sub): + return f"{left} - {right}" + if isinstance(op, ast.Mult): + # Évite \cdot entre deux nombres + return f"{left} \\cdot {right}" + if isinstance(op, ast.Div): + return f"\\frac{{{left}}}{{{right}}}" + if isinstance(op, ast.Pow): + # Parenthèses si la base est complexe + base = left if _is_simple(node.left) else f"\\left({left}\\right)" + exp_str = right if _is_simple(node.right) else f"{{{right}}}" + return f"{base}^{{{exp_str}}}" + if isinstance(op, ast.FloorDiv): + return f"\\lfloor\\frac{{{left}}}{{{right}}}\\rfloor" + if isinstance(op, ast.Mod): + return f"{left} \\bmod {right}" + return f"{left} \\circ {right}" + + def visit_UnaryOp(self, node: ast.UnaryOp) -> str: + operand = self.visit(node.operand) + if isinstance(node.op, ast.USub): + return f"-{operand}" + if isinstance(node.op, ast.UAdd): + return operand + return operand + + # --- Appels de fonctions --- + + def visit_Call(self, node: ast.Call) -> str: + func_name = "" + if isinstance(node.func, ast.Name): + func_name = node.func.id + elif isinstance(node.func, ast.Attribute): + func_name = node.func.attr + + args_latex = [self.visit(a) for a in node.args] + + if func_name == "sqrt": + return f"\\sqrt{{{args_latex[0]}}}" if args_latex else r"\sqrt{\cdot}" + if func_name in ("sin", "cos", "tan"): + return f"\\{func_name}\\left({args_latex[0]}\\right)" if args_latex else f"\\{func_name}()" + if func_name in ("min", "max"): + return f"\\{func_name}\\left({', '.join(args_latex)}\\right)" + if func_name == "abs" or func_name == "fabs": + return f"\\left|{args_latex[0]}\\right|" if args_latex else r"\left|\cdot\right|" + if func_name == "log": + return f"\\ln\\left({args_latex[0]}\\right)" if args_latex else r"\ln(\cdot)" + if func_name == "log10": + return f"\\log_{{10}}\\left({args_latex[0]}\\right)" if args_latex else r"\log_{10}(\cdot)" + if func_name == "exp": + return f"e^{{{args_latex[0]}}}" if args_latex else "e^{\\cdot}" + if func_name == "radians": + return f"\\frac{{\\pi \\cdot {args_latex[0]}}}{{180}}" if args_latex else "" + if func_name == "floor": + return f"\\lfloor {args_latex[0]} \\rfloor" if args_latex else "" + if func_name == "ceil": + return f"\\lceil {args_latex[0]} \\rceil" if args_latex else "" + # Fonction générique + name_latex = _name_to_latex(func_name) + return f"{name_latex}\\left({', '.join(args_latex)}\\right)" + + # --- Comparaisons --- + + def visit_Compare(self, node: ast.Compare) -> str: + parts = [self.visit(node.left)] + ops_map = { + ast.Lt: "<", ast.LtE: "\\leq", ast.Gt: ">", ast.GtE: "\\geq", + ast.Eq: "=", ast.NotEq: "\\neq", + } + for op, comp in zip(node.ops, node.comparators): + parts.append(ops_map.get(type(op), "?")) + parts.append(self.visit(comp)) + return " ".join(parts) + + # --- Tuple/List/Dict --- + + def visit_Tuple(self, node: ast.Tuple) -> str: + return "\\left(" + ", ".join(self.visit(e) for e in node.elts) + "\\right)" + + def visit_List(self, node: ast.List) -> str: + return "\\left[" + ", ".join(self.visit(e) for e in node.elts) + "\\right]" + + def visit_Dict(self, node: ast.Dict) -> str: + return "" + + def visit_Subscript(self, node: ast.Subscript) -> str: + val = self.visit(node.value) + sl = self.visit(node.slice) + return f"{val}_{{{sl}}}" + + def visit_IfExp(self, node: ast.IfExp) -> str: + body = self.visit(node.body) + test = self.visit(node.test) + orelse = self.visit(node.orelse) + return f"{body} \\text{{ si }} {test} \\text{{ sinon }} {orelse}" + + def generic_visit(self, node: ast.AST) -> str: + return "\\cdot" + + +def _is_simple(node: ast.AST) -> bool: + """Retourne True si le nœud est suffisamment simple pour ne pas nécessiter de parenthèses.""" + return isinstance(node, (ast.Name, ast.Constant, ast.Attribute)) + + +# --------------------------------------------------------------------------- +# Extraction des lignes d'assignation depuis l'AST de la fonction +# --------------------------------------------------------------------------- + +def _extract_assignments(func_source: str) -> list[ast.Assign]: + """Extrait les nœuds Assign et AugAssign du corps de la fonction.""" + tree = ast.parse(func_source) + assignments = [] + for node in ast.walk(tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + for stmt in node.body: + if isinstance(stmt, ast.Assign): + assignments.append(stmt) + elif isinstance(stmt, ast.AugAssign): + assignments.append(stmt) + # Les Expr seuls (noms sans affectation comme `axe` ou `direction`) + # sont ignorés — c'était des "labels" handcalcs, inutiles en LaTeX + return assignments + +# Rendu LaTeX d'une assignation +# --------------------------------------------------------------------------- + +def _render_assignment(assign: ast.Assign, local_vars: dict, precision: int, + long: bool = False) -> str: + """Génère une ligne LaTeX 'lhs &= rhs_sym = rhs_num = valeur_unité'. + + Si long=True, chaque étape (sym / num / résultat) est sur sa propre ligne alignée. + """ + sym_renderer = _ASTRenderer(local_vars, precision) + num_renderer = _ASTNumRenderer(local_vars, precision) + + # Cible (LHS) + target = assign.targets[0] if isinstance(assign, ast.Assign) else assign.target + lhs = sym_renderer.visit(target) + + # Expression symbolique + rhs_sym = sym_renderer.visit(assign.value) + + # Expression numérique (substitution des variables connues) + rhs_num = num_renderer.visit(assign.value) + + # Valeur finale calculée + target_name = None + if isinstance(target, ast.Name): + target_name = target.id + elif isinstance(target, ast.Tuple): + target_name = None + + has_num_sub = rhs_num != rhs_sym + + if target_name and target_name in local_vars: + val = local_vars[target_name] + val_str = _num_to_latex(val, precision) + unit_str = _get_unit_str(val) + if unit_str: + val_str = f"{val_str}{unit_str}" + if long and has_num_sub: + return ( + f"{lhs} &= {rhs_sym} \\\\\n" + f" &= {rhs_num} \\\\\n" + f" &= {val_str} \\\\\n" + ) + elif long: + return ( + f"{lhs} &= {rhs_sym} \\\\\n" + f" &= {val_str} \\\\\n" + ) + elif has_num_sub: + return f"{lhs} &= {rhs_sym} = {rhs_num} = {val_str} \\\\\n" + else: + return f"{lhs} &= {rhs_sym} = {val_str} \\\\\n" + else: + if long and has_num_sub: + return ( + f"{lhs} &= {rhs_sym} \\\\\n" + f" &= {rhs_num} \\\\\n" + ) + elif has_num_sub: + return f"{lhs} &= {rhs_sym} = {rhs_num} \\\\\n" + return f"{lhs} &= {rhs_sym} \\\\\n" + + +def _get_unit_str(value: Any) -> str: + """Extrait l'unité d'une grandeur forallpeople en LaTeX (ex: \\mathrm{MPa}).""" + try: + if _is_physical(value): + s = str(value) + parts = s.rsplit(" ", 1) + if len(parts) == 2: + unit = parts[1].strip() + unit = unit.replace("**", "^").replace("*", " \\cdot ") + return f"\\,\\mathrm{{{unit}}}" + except Exception: + pass + return "" + + +# --------------------------------------------------------------------------- +# Décorateur principal +# --------------------------------------------------------------------------- + + +def handcalc( + override: str = "short", + precision: int = 2, + jupyter_display: bool = False, + left: str = "\\[", + right: str = "\\]", +): + """Remplace handcalcs.decorator.handcalc — compatible Python 3.13+. + + Usage identique à l'original : + + @handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, + left="\\\\[", right="\\\\]") + def val(): + x = a + b + return x + + latex_str, result = val() + + Args: + override: "short" (défaut) ou "long". + precision: Nombre de décimales pour les valeurs numériques. + jupyter_display: Si True, affiche le LaTeX dans Jupyter via IPython.display. + left / right: Délimiteurs LaTeX du bloc (ex. "$$", "\\\\["). + + Returns: + Décorateur qui transforme la fonction en un appelable retournant + ``(latex_str, résultat_numérique)``. + """ + def decorator(func): + def wrapper(*args, **kwargs): + # 1. Exécuter la fonction pour obtenir les valeurs numériques + result = func(*args, **kwargs) + + # 2. Récupérer le code source de la fonction + try: + raw_source = inspect.getsource(func) + func_source = textwrap.dedent(raw_source) + except (OSError, TypeError): + # Impossible de récupérer le source → retour sans LaTeX + return ("", result) + + # 3. Construire le dict des variables locales disponibles + # = globals du scope + fermeture (closure) + arguments + local_vars: dict = {} + + # Variables du scope module (globals) — permet la substitution numérique + if func.__globals__: + local_vars.update({ + k: v for k, v in func.__globals__.items() + if not k.startswith("__") and not callable(v) + }) + + # Variables de fermeture (closures) + if func.__code__.co_freevars and func.__closure__: + for name, cell in zip(func.__code__.co_freevars, func.__closure__): + try: + local_vars[name] = cell.cell_contents + except ValueError: + pass + + + # Arguments positionnels (cas `def comp(arg1, arg2, ...)`) + arg_names = func.__code__.co_varnames[: func.__code__.co_argcount] + for name, val in zip(arg_names, args): + local_vars[name] = val + local_vars.update(kwargs) + + # Résultats de la fonction — injecte les valeurs calculées + # On récupère les noms des variables locales via co_varnames + # En exécutant la fonction une seconde fois dans un namespace capturé + local_capture: dict = dict(local_vars) + try: + _exec_and_capture(func, args, kwargs, local_capture) + except Exception: + pass + local_vars.update(local_capture) + + # 4. Extraire les assignations de l'AST + try: + assignments = _extract_assignments(func_source) + except SyntaxError: + return ("", result) + + # 5. Construire le LaTeX + is_long = override == "long" + lines = [] + for assign in assignments: + line = _render_assignment(assign, local_vars, precision, long=is_long) + lines.append(line) + + if not lines: + latex_body = "" + elif override == "long" or len(lines) > 1: + inner = "".join(lines) + latex_body = f"\\begin{{aligned}}\n{inner}\\end{{aligned}}" + else: + # short : une seule ligne, format compact + latex_body = "".join(lines).replace(" &=", " =").replace("\\\\\n", "") + + latex_str = f"{left}\n{latex_body}\n{right}" + + # 6. Affichage Jupyter si demandé + if jupyter_display: + try: + from IPython.display import display, Latex as IPyLatex + if display and IPyLatex: + display(IPyLatex(latex_str)) + except ImportError: + pass + + return (latex_str, result) + + return wrapper + return decorator + + +# --------------------------------------------------------------------------- +# Capture des variables locales sans innerscope +# --------------------------------------------------------------------------- + +def _exec_and_capture(func, args, kwargs, capture: dict) -> None: + """Exécute func dans un namespace étendu et capture les variables locales. + + Utilise exec() sur le corps de la fonction avec les variables de fermeture + et arguments déjà présents dans `capture`. Met à jour `capture` in-place. + """ + try: + raw = inspect.getsource(func) + source = textwrap.dedent(raw) + except (OSError, TypeError): + return + + tree = ast.parse(source) + + # On cherche le premier FunctionDef + func_def = None + for node in ast.walk(tree): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + func_def = node + break + if func_def is None: + return + + # Reconstruire le corps sans le 'return' final comme module exécutable + body_stmts = [s for s in func_def.body if not isinstance(s, ast.Return)] + if not body_stmts: + return + + # Créer un nouveau module AST avec ces instructions + new_module = ast.Module(body=body_stmts, type_ignores=[]) + ast.fix_missing_locations(new_module) + + # Injecter les fonctions math disponibles + globals de la fonction (scope module) + exec_globals = { + "sqrt": sqrt, "pi": pi, "sin": sin, "cos": cos, "tan": tan, + "radians": radians, "log": log, "log10": log10, "exp": exp, + "floor": floor, "ceil": ceil, "fabs": fabs, "abs": abs, + "min": min, "max": max, "round": round, + } + if func.__globals__: + exec_globals.update(func.__globals__) + exec_globals.update(capture) + + try: + exec(compile(new_module, "", "exec"), exec_globals, capture) + except Exception: + pass \ No newline at end of file diff --git a/ourocode/eurocode/ec1/exploitation.py b/ourocode/eurocode/ec1/exploitation.py index c582cdf..4be12ee 100644 --- a/ourocode/eurocode/ec1/exploitation.py +++ b/ourocode/eurocode/ec1/exploitation.py @@ -1,7 +1,7 @@ from math import * import forallpeople as si -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.core.projet import Projet @@ -56,7 +56,7 @@ def alpha_A(self, A: float): q_k = self.qk if self.categorie in ("A_plancher", "A_escalier", "A_balcons", "B", "C3", "D1", "F"): - @handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\[", right="\]") + @handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\\[", right="\\]") def val(): 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) q_k_alpha_A = q_k * alpha_A @@ -80,14 +80,14 @@ def alpha_n(self, n: int): """ if n > 2: if self.categorie in ("B", "F"): - @handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\[", right="\]") + @handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\\[", right="\\]") def val(): alpha_n = 0.7 + 0.8 / n #EN 1991-1-1 §6.2.2(2) et 6.3.1.2(11) return alpha_n return val() elif self.categorie in ("A_plancher", "A_escalier", "A_balcons"): - @handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\[", right="\]") + @handcalc(override="short", precision=2, jupyter_display=self.JUPYTER_DISPLAY, left="\\[", right="\\]") def val(): alpha_n = 0.5 + 1.36 / n #EN 1991-1-1 §6.2.2(2) et 6.3.1.2(11) return alpha_n diff --git a/ourocode/eurocode/ec1/neige.py b/ourocode/eurocode/ec1/neige.py index 0890040..1c1c293 100644 --- a/ourocode/eurocode/ec1/neige.py +++ b/ourocode/eurocode/ec1/neige.py @@ -1,11 +1,11 @@ -#! env\Scripts\python.exe +#! env\Scripts\python.exe # Encoding in UTF-8 by Anthony PARISOT from math import sin, radians import pandas as pd import warnings import forallpeople as si -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.core.batiment import Batiment diff --git a/ourocode/eurocode/ec1/vent.py b/ourocode/eurocode/ec1/vent.py index 8fe687d..dfbb18d 100644 --- a/ourocode/eurocode/ec1/vent.py +++ b/ourocode/eurocode/ec1/vent.py @@ -1,4 +1,4 @@ -#! env\Scripts\python.exe +#! env\Scripts\python.exe # Encoding in UTF-8 by Anthony PARISOT import os import sys @@ -9,7 +9,7 @@ import pandas as pd import forallpeople as si -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc # sys.path.append(os.path.join(os.getcwd(), "ourocode")) # from eurocode.A0_Projet import Batiment diff --git a/ourocode/eurocode/ec3/assemblage/platine.py b/ourocode/eurocode/ec3/assemblage/platine.py index 95a8dfd..6bccb03 100644 --- a/ourocode/eurocode/ec3/assemblage/platine.py +++ b/ourocode/eurocode/ec3/assemblage/platine.py @@ -6,7 +6,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec3.assemblage.tige import Tige from ourocode.eurocode.ec3.element_droit.plat import Plat diff --git a/ourocode/eurocode/ec3/assemblage/soudure.py b/ourocode/eurocode/ec3/assemblage/soudure.py index 386abfa..e791c1e 100644 --- a/ourocode/eurocode/ec3/assemblage/soudure.py +++ b/ourocode/eurocode/ec3/assemblage/soudure.py @@ -9,7 +9,7 @@ from matplotlib.patches import Rectangle, Circle import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec3.element_droit.plat import Plat diff --git a/ourocode/eurocode/ec3/assemblage/tige.py b/ourocode/eurocode/ec3/assemblage/tige.py index b7f5a4a..170730a 100644 --- a/ourocode/eurocode/ec3/assemblage/tige.py +++ b/ourocode/eurocode/ec3/assemblage/tige.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec3.element_droit.plat import Plat diff --git a/ourocode/eurocode/ec3/element_droit/cisaillement.py b/ourocode/eurocode/ec3/element_droit/cisaillement.py index de5ae84..91da76b 100644 --- a/ourocode/eurocode/ec3/element_droit/cisaillement.py +++ b/ourocode/eurocode/ec3/element_droit/cisaillement.py @@ -5,7 +5,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec3.element_droit.plat import Plat diff --git a/ourocode/eurocode/ec3/element_droit/compression.py b/ourocode/eurocode/ec3/element_droit/compression.py index 2f9ceab..597cee0 100644 --- a/ourocode/eurocode/ec3/element_droit/compression.py +++ b/ourocode/eurocode/ec3/element_droit/compression.py @@ -5,7 +5,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec3.element_droit.plat import Plat diff --git a/ourocode/eurocode/ec3/element_droit/flexion.py b/ourocode/eurocode/ec3/element_droit/flexion.py index 76377c8..3d5c076 100644 --- a/ourocode/eurocode/ec3/element_droit/flexion.py +++ b/ourocode/eurocode/ec3/element_droit/flexion.py @@ -5,7 +5,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec3.element_droit.plat import Plat from ourocode.eurocode.ec3.element_droit.cisaillement import Cisaillement diff --git a/ourocode/eurocode/ec3/element_droit/plat.py b/ourocode/eurocode/ec3/element_droit/plat.py index ec9c78b..fd08ba2 100644 --- a/ourocode/eurocode/ec3/element_droit/plat.py +++ b/ourocode/eurocode/ec3/element_droit/plat.py @@ -6,7 +6,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.core.projet import Projet diff --git a/ourocode/eurocode/ec3/element_droit/traction.py b/ourocode/eurocode/ec3/element_droit/traction.py index 3174079..82399e6 100644 --- a/ourocode/eurocode/ec3/element_droit/traction.py +++ b/ourocode/eurocode/ec3/element_droit/traction.py @@ -5,7 +5,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec3.element_droit.plat import Plat diff --git a/ourocode/eurocode/ec3/feu.py b/ourocode/eurocode/ec3/feu.py index 7f98a46..4ede482 100644 --- a/ourocode/eurocode/ec3/feu.py +++ b/ourocode/eurocode/ec3/feu.py @@ -1,4 +1,4 @@ -#! env\Scripts\python.exe +#! env\Scripts\python.exe # Encoding in UTF-8 by Anthony PARISOT import math as mt import pandas as pd @@ -6,7 +6,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc # sys.path.append(os.path.join(os.getcwd(), "ourocode")) # from eurocode.EC3_Element_droit import Plat diff --git a/ourocode/eurocode/ec5/assemblage/agrafe.py b/ourocode/eurocode/ec5/assemblage/agrafe.py index b4d1194..62f4d0b 100644 --- a/ourocode/eurocode/ec5/assemblage/agrafe.py +++ b/ourocode/eurocode/ec5/assemblage/agrafe.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.assemblage.pointe import Pointe diff --git a/ourocode/eurocode/ec5/assemblage/assemblage.py b/ourocode/eurocode/ec5/assemblage/assemblage.py index 3030b06..a5ecd55 100644 --- a/ourocode/eurocode/ec5/assemblage/assemblage.py +++ b/ourocode/eurocode/ec5/assemblage/assemblage.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.core.projet import Projet from ourocode.eurocode.ec5.element_droit.barre import Barre diff --git a/ourocode/eurocode/ec5/assemblage/boulon.py b/ourocode/eurocode/ec5/assemblage/boulon.py index 7e6e2de..8adf1a9 100644 --- a/ourocode/eurocode/ec5/assemblage/boulon.py +++ b/ourocode/eurocode/ec5/assemblage/boulon.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.assemblage.assemblage import Assemblage diff --git a/ourocode/eurocode/ec5/assemblage/broche.py b/ourocode/eurocode/ec5/assemblage/broche.py index 05eb271..f96413e 100644 --- a/ourocode/eurocode/ec5/assemblage/broche.py +++ b/ourocode/eurocode/ec5/assemblage/broche.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.assemblage.boulon import Boulon diff --git a/ourocode/eurocode/ec5/assemblage/embrevement.py b/ourocode/eurocode/ec5/assemblage/embrevement.py index facea81..79278f4 100644 --- a/ourocode/eurocode/ec5/assemblage/embrevement.py +++ b/ourocode/eurocode/ec5/assemblage/embrevement.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.assemblage.assemblage import Assemblage from ourocode.eurocode.ec5.element_droit.barre import Barre diff --git a/ourocode/eurocode/ec5/assemblage/pointe.py b/ourocode/eurocode/ec5/assemblage/pointe.py index 64013c6..0cf8c91 100644 --- a/ourocode/eurocode/ec5/assemblage/pointe.py +++ b/ourocode/eurocode/ec5/assemblage/pointe.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.assemblage.assemblage import Assemblage, interpolationLineaire diff --git a/ourocode/eurocode/ec5/assemblage/tirefond.py b/ourocode/eurocode/ec5/assemblage/tirefond.py index 64c70d6..e2a1187 100644 --- a/ourocode/eurocode/ec5/assemblage/tirefond.py +++ b/ourocode/eurocode/ec5/assemblage/tirefond.py @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.assemblage.pointe import Pointe from ourocode.eurocode.ec5.assemblage.boulon import Boulon diff --git a/ourocode/eurocode/ec5/cvt.py b/ourocode/eurocode/ec5/cvt.py index 10c29ea..d7526a9 100644 --- a/ourocode/eurocode/ec5/cvt.py +++ b/ourocode/eurocode/ec5/cvt.py @@ -1,4 +1,4 @@ -#! env\Scripts\python.exe +#! env\Scripts\python.exe # Encoding in UTF-8 by Anthony PARISOT from math import sqrt, radians, cos, sin, floor from copy import deepcopy @@ -7,7 +7,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.core.batiment import Batiment from ourocode.eurocode.ec5.assemblage import Assemblage diff --git a/ourocode/eurocode/ec5/element_droit/barre.py b/ourocode/eurocode/ec5/element_droit/barre.py index 8afcf76..2bbad5b 100644 --- a/ourocode/eurocode/ec5/element_droit/barre.py +++ b/ourocode/eurocode/ec5/element_droit/barre.py @@ -11,7 +11,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.core.projet import Projet diff --git a/ourocode/eurocode/ec5/element_droit/cisaillement.py b/ourocode/eurocode/ec5/element_droit/cisaillement.py index 923cec6..b41e8d2 100644 --- a/ourocode/eurocode/ec5/element_droit/cisaillement.py +++ b/ourocode/eurocode/ec5/element_droit/cisaillement.py @@ -10,7 +10,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.element_droit.barre import Barre diff --git a/ourocode/eurocode/ec5/element_droit/compression.py b/ourocode/eurocode/ec5/element_droit/compression.py index bd211e8..4f48592 100644 --- a/ourocode/eurocode/ec5/element_droit/compression.py +++ b/ourocode/eurocode/ec5/element_droit/compression.py @@ -10,7 +10,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.element_droit.barre import Barre diff --git a/ourocode/eurocode/ec5/element_droit/flexion.py b/ourocode/eurocode/ec5/element_droit/flexion.py index 5b8045c..27d0e3f 100644 --- a/ourocode/eurocode/ec5/element_droit/flexion.py +++ b/ourocode/eurocode/ec5/element_droit/flexion.py @@ -10,7 +10,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.element_droit.barre import Barre from ourocode.eurocode.ec5.element_droit.compression import Compression diff --git a/ourocode/eurocode/ec5/element_droit/traction.py b/ourocode/eurocode/ec5/element_droit/traction.py index 987fd25..a359b26 100644 --- a/ourocode/eurocode/ec5/element_droit/traction.py +++ b/ourocode/eurocode/ec5/element_droit/traction.py @@ -10,7 +10,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.element_droit.barre import Barre diff --git a/ourocode/eurocode/ec5/feu/cisaillement_feu.py b/ourocode/eurocode/ec5/feu/cisaillement_feu.py index 153abed..c2e17c5 100644 --- a/ourocode/eurocode/ec5/feu/cisaillement_feu.py +++ b/ourocode/eurocode/ec5/feu/cisaillement_feu.py @@ -9,7 +9,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.feu.feu import Feu from ourocode.eurocode.ec5.element_droit.cisaillement import Cisaillement @@ -58,4 +58,8 @@ def val(): value = val() self.taux_tau_rd["equ6.13"] = value[1][0] self.taux_tau_rd["equ6.60"] = value[1][1] + synthese = [ + ["Cisaillement bois", None, max(value[1][0], value[1][1])], + ] + self._add_synthese_taux_travail(synthese) return value diff --git a/ourocode/eurocode/ec5/feu/compression_feu.py b/ourocode/eurocode/ec5/feu/compression_feu.py index c4dd3fa..844e371 100644 --- a/ourocode/eurocode/ec5/feu/compression_feu.py +++ b/ourocode/eurocode/ec5/feu/compression_feu.py @@ -9,7 +9,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.feu.feu import Feu from ourocode.eurocode.ec5.element_droit.compression import Compression @@ -139,4 +139,9 @@ def val(): self.taux_c_0_rd["equ6.24"] = value[1][2] self.taux_c_0_rd["equ6.2"] = value[1][0] + max_taux = max([v for v in self.taux_c_0_rd.values()]) + synthese = [ + ["Compression bois", None, max_taux], + ] + self._add_synthese_taux_travail(synthese) return value diff --git a/ourocode/eurocode/ec5/feu/feu.py b/ourocode/eurocode/ec5/feu/feu.py index 1e105aa..991b7f3 100644 --- a/ourocode/eurocode/ec5/feu/feu.py +++ b/ourocode/eurocode/ec5/feu/feu.py @@ -9,7 +9,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.element_droit.barre import Barre diff --git a/ourocode/eurocode/ec5/feu/flexion_feu.py b/ourocode/eurocode/ec5/feu/flexion_feu.py index 9341108..231e61f 100644 --- a/ourocode/eurocode/ec5/feu/flexion_feu.py +++ b/ourocode/eurocode/ec5/feu/flexion_feu.py @@ -9,7 +9,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.feu.feu import Feu from ourocode.eurocode.ec5.element_droit.flexion import Flexion @@ -211,4 +211,9 @@ def tract(taux_6_11, taux_6_12): self.taux_m_rd["equ6.11"] = traction_val[1][0] self.taux_m_rd["equ6.12"] = traction_val[1][1] + max_taux = max([v for v in self.taux_m_rd.values()]) + synthese = [ + ["Flexion bois", None, max_taux], + ] + self._add_synthese_taux_travail(synthese) return (latex, self.taux_m_rd) diff --git a/ourocode/eurocode/ec5/feu/traction_feu.py b/ourocode/eurocode/ec5/feu/traction_feu.py index c1e70a7..acf834f 100644 --- a/ourocode/eurocode/ec5/feu/traction_feu.py +++ b/ourocode/eurocode/ec5/feu/traction_feu.py @@ -9,7 +9,7 @@ import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.ec5.feu.feu import Feu from ourocode.eurocode.ec5.element_droit.traction import Traction @@ -47,6 +47,9 @@ def val(): return taux_6_1 value = val() - self.taux_t_0_rd["equ6.1"] = value[1] + synthese = [ + ["Traction bois", None, self.taux_t_0_rd['equ6.1']], + ] + self._add_synthese_taux_travail(synthese) return value diff --git a/ourocode/eurocode/ec8/sismique.py b/ourocode/eurocode/ec8/sismique.py index f199400..e0128f0 100644 --- a/ourocode/eurocode/ec8/sismique.py +++ b/ourocode/eurocode/ec8/sismique.py @@ -1,4 +1,4 @@ -#! env\Scripts\python.exe +#! env\Scripts\python.exe # Encoding in UTF-8 by Anthony PARISOT import os import matplotlib.pyplot as plt @@ -7,7 +7,7 @@ import pandas as pd import forallpeople as si si.environment("structural") -from handcalcs.decorator import handcalc +from ourocode.eurocode.core.renderer import handcalc from ourocode.eurocode.core.batiment import Batiment class Sismique(Batiment): diff --git a/pyproject.toml b/pyproject.toml index b4dd702..b576f47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,21 +8,25 @@ include = ["ourocode*"] [project] name = "ourocode" -version = "1.11.3" +version = "2.0.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"}] license = "Apache-2.0" license-files = ["LICEN[CS]E"] dependencies = [ - "handcalcs", "forallpeople", "pandas", "Pillow", + "matplotlib", + "pyvista[all,trame]", + "PyNiteFEA", ] requires-python = ">=3.12" classifiers = [ "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Operating System :: OS Independent", ] @@ -33,10 +37,6 @@ Homepage = "https://github.com/AnthonyPrst/ourocode" gui = [ "PySide6", ] -mef = [ - "PyNiteFEA", - "pyvista[all,trame]", -] full = [ "ourocode[gui,mef]", ] diff --git a/tests/test_EC8_Sismique.py b/tests/test_EC8_Sismique.py index aa5c80e..827cd49 100644 --- a/tests/test_EC8_Sismique.py +++ b/tests/test_EC8_Sismique.py @@ -52,7 +52,7 @@ def sismique(self, batiment): def test_init(self, sismique): assert sismique.region_sismique == "Zone 4" - assert sismique.gamma_1 == 1 + assert sismique.gamma_I == 1 assert sismique.h_bat == 10 assert sismique.d_bat == 30 assert sismique.b_bat == 20