From 68bed8937aa3f22ad4b5eafe074a41a6407ff902 Mon Sep 17 00:00:00 2001 From: Miguel Date: Wed, 11 Jun 2025 10:19:14 +0200 Subject: [PATCH] =?UTF-8?q?Mejora=20del=20panel=20LaTeX=20y=20ajustes=20en?= =?UTF-8?q?=20la=20configuraci=C3=B3n=20de=20la=20interfaz.=20Se=20impleme?= =?UTF-8?q?nta=20un=20sistema=20para=20verificar=20la=20disponibilidad=20d?= =?UTF-8?q?e=20MathJax=20y=20se=20optimiza=20la=20gesti=C3=B3n=20de=20ecua?= =?UTF-8?q?ciones=20pendientes.=20Se=20actualizan=20estilos=20y=20se=20aju?= =?UTF-8?q?sta=20la=20geometr=C3=ADa=20de=20la=20ventana.=20Se=20a=C3=B1ad?= =?UTF-8?q?e=20un=20wrapper=20para=20la=20funci=C3=B3n=20'solve'=20que=20c?= =?UTF-8?q?onvierte=20cadenas=20a=20s=C3=ADmbolos=20autom=C3=A1ticamente,?= =?UTF-8?q?=20mejorando=20la=20resoluci=C3=B3n=20de=20ecuaciones.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hybrid_calc_history.txt | 16 ++- hybrid_calc_settings.json | 10 +- main_calc_app_pyside6.py | 198 +++++++++++++++++++++++++++++--------- main_evaluation_puro.py | 58 ++++++++++- test_final.py | 44 +++++++++ 5 files changed, 265 insertions(+), 61 deletions(-) create mode 100644 test_final.py diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index cf02f25..9efcc89 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -1,12 +1,20 @@ x=t**2+5/m -t=? - m=3 x=4 - t=? - +solve(t) +form=solve(t) + + +ip=IP4(10.1.1.1,20) +ip.BroadcastAddress() + +# Test LATEX +$$Brix = \frac{Brix_{syrup} \cdot \delta_{syrup} + (Brix_{water} \cdot \delta_{water} \cdot Rateo)}{\delta_{syrup} + \delta_{water} \cdot Rateo}$$ +$$Brix = \frac{Solid_Weight}{Total_Weight}$$ +$$\delta = \frac{W}{V} \Rightarrow W = \delta \cdot V$$ +$$Brix_{Bev} = \frac{Brix_{syr} + Brix_{H_2O} \cdot R_M}{R_M + 1}$$ diff --git a/hybrid_calc_settings.json b/hybrid_calc_settings.json index 7af5413..a750eb2 100644 --- a/hybrid_calc_settings.json +++ b/hybrid_calc_settings.json @@ -1,15 +1,15 @@ { "window_geometry": { - "x": 732, - "y": 205, - "width": 741, + "x": 332, + "y": 170, + "width": 1353, "height": 700 }, "debug_mode": false, "latex_panel_visible": true, "sash_pos_x": 450, "splitter_sizes": [ - 210, - 155 + 357, + 472 ] } \ No newline at end of file diff --git a/main_calc_app_pyside6.py b/main_calc_app_pyside6.py index 12199dc..41c9867 100644 --- a/main_calc_app_pyside6.py +++ b/main_calc_app_pyside6.py @@ -116,8 +116,16 @@ class LatexPanel(QWidget): super().__init__(parent) self.equations = [] self._webview_available = False + self._mathjax_ready = False + self._pending_equations = [] + self._parent_calculator = parent self._setup_ui() + # Timer para verificar si MathJax está listo + self._mathjax_check_timer = QTimer() + self._mathjax_check_timer.timeout.connect(self._check_mathjax_ready) + self._mathjax_check_timer.start(500) # Verificar cada 500ms + def _setup_ui(self): """Configura la UI del panel""" layout = QVBoxLayout(self) @@ -212,8 +220,16 @@ class LatexPanel(QWidget): processEscapes: true }, chtml: { - scale: 1.1, + scale: 0.9, minScale: 0.5 + }, + startup: { + ready: function () { + MathJax.startup.defaultReady(); + // Notificar que MathJax está listo + window.mathJaxReady = true; + console.log('MathJax completamente cargado'); + } } }; @@ -223,19 +239,18 @@ class LatexPanel(QWidget): color: #d4d4d4; font-family: 'Segoe UI', Arial, sans-serif; margin: 0; - padding: 20px; - line-height: 1.6; + padding: 8px; + line-height: 1.2; } .equation-block { background: rgba(45, 45, 48, 0.8); - border-left: 4px solid; - margin: 12px 0; - padding: 15px 20px; - border-radius: 8px; - transition: all 0.2s ease; + border-left: 3px solid; + margin: 4px 0; + padding: 6px 12px; + border-radius: 4px; + transition: all 0.1s ease; } .equation-block:hover { - transform: translateY(-1px); background: rgba(45, 45, 48, 0.9); } .comment { border-left-color: #6a9955; } @@ -243,35 +258,49 @@ class LatexPanel(QWidget): .equation { border-left-color: #c586c0; } .symbolic { border-left-color: #9cdcfe; } - .equation-type { - font-size: 11px; - font-weight: bold; - text-transform: uppercase; - margin-bottom: 8px; - opacity: 0.8; + .math-content { + margin: 2px 0; + font-size: 14px; + } + + .comment-text { + font-style: italic; + color: #6a9955; + font-size: 12px; + margin: 0; } - .comment .equation-type { color: #6a9955; } - .assignment .equation-type { color: #dcdcaa; } - .equation .equation-type { color: #c586c0; } - .symbolic .equation-type { color: #9cdcfe; } .info-message { text-align: center; - padding: 40px 20px; - color: #888; - font-style: italic; + padding: 20px; + color: #666; + font-size: 12px; + } + + /* Reducir espaciado de MathJax */ + .MathJax { + font-size: 0.9em !important; + } + + mjx-math { + margin: 1px 0 !important; + } + + mjx-container { + margin: 2px 0 !important; }
- 📐 Panel de Ecuaciones LaTeX
- Las ecuaciones aparecerán aquí automáticamente + Panel de Ecuaciones LaTeX
""" + def _check_mathjax_ready(self): + """Verifica si MathJax está listo y renderiza ecuaciones pendientes""" + if not self._webview_available: + return + + # Verificar si MathJax está listo + self.webview.page().runJavaScript( + "window.mathJaxReady || false;", + self._on_mathjax_ready_check + ) + + def _on_mathjax_ready_check(self, ready): + """Callback cuando se verifica el estado de MathJax""" + if ready and not self._mathjax_ready: + self._mathjax_ready = True + self._mathjax_check_timer.stop() + logging.debug("✅ MathJax listo, procesando ecuaciones pendientes") + + # Renderizar ecuaciones pendientes + for eq in self._pending_equations: + self._add_equation_to_webview(eq['type'], eq['content']) + self._pending_equations.clear() + + # Trigger initial render si hay un calculador padre + if self._parent_calculator and hasattr(self._parent_calculator, '_trigger_initial_latex_render'): + self._parent_calculator._trigger_initial_latex_render() + + def _add_equation_to_webview(self, eq_type: str, content: str): + """Añade una ecuación directamente al webview""" + if self._webview_available: + escaped_content = content.replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"') + js_code = f"addEquation('{eq_type}', '{escaped_content}');" + self.webview.page().runJavaScript(js_code) + def add_equation(self, eq_type: str, content: str): """Añade una ecuación al panel""" self.equations.append({'type': eq_type, 'content': content}) if self._webview_available: - # Escapar contenido para JavaScript - escaped_content = content.replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"') - js_code = f"addEquation('{eq_type}', '{escaped_content}');" - self.webview.page().runJavaScript(js_code) + if self._mathjax_ready: + # MathJax está listo, renderizar inmediatamente + self._add_equation_to_webview(eq_type, content) + else: + # MathJax no está listo, guardar para después + self._pending_equations.append({'type': eq_type, 'content': content}) else: # Actualizar HTML en text browser self._update_text_browser() @@ -331,6 +402,7 @@ class LatexPanel(QWidget): def clear_equations(self): """Limpia todas las ecuaciones""" self.equations.clear() + self._pending_equations.clear() if self._webview_available: self.webview.page().runJavaScript("clearEquations();") @@ -341,28 +413,49 @@ class LatexPanel(QWidget): """Actualiza el contenido del text browser (fallback)""" html_parts = [""" """] for eq in self.equations: eq_type = eq['type'] content = eq['content'] - - type_label = eq_type.capitalize() css_class = eq_type if eq_type == 'comment': - html_parts.append(f'
{type_label}
{content}
') + html_parts.append(f'
{content}
') else: # Para ecuaciones matemáticas, mostrar en formato de código - html_parts.append(f'
{type_label}
{content}
') + html_parts.append(f'
{content}
') self.text_browser.setHtml(''.join(html_parts)) @@ -586,7 +679,7 @@ class HybridCalculatorPySide6(QMainWindow): main_layout.addWidget(self.latex_button) # Panel LaTeX (inicialmente oculto) - self.latex_panel = LatexPanel() + self.latex_panel = LatexPanel(self) self.latex_panel.setMinimumWidth(300) self.latex_panel.setMaximumWidth(500) @@ -890,6 +983,17 @@ class HybridCalculatorPySide6(QMainWindow): except Exception as e: self._show_error(f"Error durante evaluación: {e}") + def _trigger_initial_latex_render(self): + """Activa el renderizado inicial del panel LaTeX cuando MathJax está listo""" + try: + # Solo hacer evaluación inicial si hay contenido en el input + input_content = self.input_text.toPlainText() + if input_content.strip(): + logging.debug("🎯 Activando renderizado inicial de LaTeX") + self._evaluate_and_update() + except Exception as e: + logging.error(f"Error en renderizado inicial de LaTeX: {e}") + def _evaluate_lines(self, lines: List[str]): """Evalúa múltiples líneas de código""" output_data = [] diff --git a/main_evaluation_puro.py b/main_evaluation_puro.py index 70a7c9f..aae730e 100644 --- a/main_evaluation_puro.py +++ b/main_evaluation_puro.py @@ -80,7 +80,6 @@ class PureAlgebraicEngine: 'sqrt': sp.sqrt, 'abs': sp.Abs, 'pi': sp.pi, 'e': sp.E, 'I': sp.I, 'oo': sp.oo, 'inf': sp.oo, - 'solve': self._smart_solve, 'Eq': sp.Eq, 'simplify': sp.simplify, 'expand': sp.expand, 'factor': sp.factor, 'diff': sp.diff, 'integrate': sp.integrate, @@ -98,7 +97,55 @@ class PureAlgebraicEngine: # 2. TIPOS PERSONALIZADOS REGISTRADOS (CLAVE PARA INSTANCIACIÓN) registered_types = get_registered_base_context() - # 3. FUNCIONES DE PLOTTING (WRAPPED) + # 3. FUNCIÓN SOLVE ESPECIAL que maneja cadenas como símbolos + def solve_wrapper(*args, **kwargs): + """Wrapper para solve que convierte cadenas a símbolos automáticamente""" + processed_args = [] + for arg in args: + if isinstance(arg, str): + # Si es una cadena, convertir a símbolo + processed_args.append(sp.Symbol(arg)) + elif hasattr(arg, 'is_number') and arg.is_number: + # Si es un valor numérico, buscar el símbolo correspondiente + # en symbol_table y verificar contexto + for var_name, var_value in self.symbol_table.items(): + if var_value == arg: + # Encontramos una variable con este valor, usar el símbolo + processed_args.append(sp.Symbol(var_name)) + break + else: + # Si no se encuentra, intentar usar _smart_solve directo + # que puede manejar valores numéricos + processed_args.append(arg) + else: + processed_args.append(arg) + + result = self._smart_solve(*processed_args, **kwargs) + + # Si el resultado está vacío, intentar un enfoque alternativo + if hasattr(result, '__len__') and len(result) == 0: + # Caso especial: si solve() devuelve lista vacía, + # intentar resolver como si no hubiera valores asignados + alt_args = [] + for arg in args: + if hasattr(arg, 'is_number') and arg.is_number: + # Buscar la variable que tiene este valor + for var_name, var_value in self.symbol_table.items(): + if var_value == arg: + alt_args.append(sp.Symbol(var_name)) + break + else: + alt_args.append(arg) + else: + alt_args.append(arg) + + if alt_args != processed_args: + # Si hay diferencia, reintentar + return self._smart_solve(*alt_args, **kwargs) + + return result + + # 4. FUNCIONES DE PLOTTING (WRAPPED) # Wrappers para capturar llamadas de plot y devolver un objeto PlotResult def plot_wrapper(*args, **kwargs): # Intentar extraer la expresión original del primer argumento @@ -127,14 +174,15 @@ class PureAlgebraicEngine: 'plot3d_parametric_line': plot3d_parametric_line_wrapper, } - # 4. COMBINAR TODO EN CONTEXTO UNIFICADO + # 5. COMBINAR TODO EN CONTEXTO UNIFICADO self.unified_context = { **sympy_functions, **registered_types, # IP4, FourBytes, IntBase, etc. - **plotting_functions + **plotting_functions, + 'solve': solve_wrapper } - # 5. VERIFICAR CARGA DE TIPOS PRINCIPALES + # 6. VERIFICAR CARGA DE TIPOS PRINCIPALES required_classes = ['IP4', 'IP4Mask', 'FourBytes', 'IntBase', 'Hex', 'Bin', 'Dec', 'Chr', 'LaTeX'] missing_classes = [cls for cls in required_classes if cls not in self.unified_context] diff --git a/test_final.py b/test_final.py new file mode 100644 index 0000000..512e080 --- /dev/null +++ b/test_final.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +""" +Test final para verificar que el problema de solve(t) esté solucionado +""" +from main_evaluation_puro import PureAlgebraicEngine + +def test_solve_issue(): + print("=== TEST FINAL: PROBLEMA SOLVE(T) ===") + + engine = PureAlgebraicEngine() + + # Secuencia original del usuario + lines = [ + 'x=t**2+5/m', + 'm=3', + 'x=4', + 't=?', + 'solve(t)', + 'form=solve(t)' + ] + + print("Ejecutando secuencia del usuario:") + for i, line in enumerate(lines, 1): + result = engine.evaluate_line(line) + status = "✅" if result.success else "❌" + print(f"{i}. {line:<15} -> {result.output} {status}") + + # Verificar específicamente el caso problemático + if line == 'form=solve(t)': + if result.output == 'form = []': + print(" ❌ PROBLEMA PERSISTE: form está vacío") + return False + else: + print(" ✅ PROBLEMA SOLUCIONADO: form contiene resultado") + return True + + return True + +if __name__ == "__main__": + success = test_solve_issue() + if success: + print("\n🎉 PROBLEMA SOLUCIONADO EXITOSAMENTE") + else: + print("\n💥 PROBLEMA PERSISTE") \ No newline at end of file