Eliminación del archivo de historial de cálculos y ajustes en la configuración de la interfaz. Se mejora la gestión de resultados interactivos, incluyendo la actualización del panel de entrada al editar expresiones. Se optimizan los tags de salida para una mejor visualización de resultados y se implementa un nuevo sistema de redibujo para gráficos interactivos.

This commit is contained in:
Miguel 2025-06-06 16:32:25 +02:00
parent f0bdf1e419
commit 0488122229
6 changed files with 532 additions and 1480 deletions

View File

@ -1,30 +0,0 @@
y = 2*x + 3
x = z * 4
z = 8
y=?
z=?
x=?
# Instanciación via sympify
ip = IP4(120.11.255.2,30)
ip.Nodes()
ip.to_hex()
ip + 1
10.1.1.1
p=Dec(16#FF + 16#FF) / 18
p
t= 2#1010 + 16#f
t.to_base(8)
ip.bit_representation()
ip=ip+20
ip.bit_representation()
ip.mask()
ip.get_prefix_length()
a = b + 5
solve(x)

View File

@ -1,6 +1,6 @@
{
"window_geometry": "1020x700+356+1216",
"sash_pos_x": 347,
"window_geometry": "1020x700+387+1235",
"sash_pos_x": 355,
"symbolic_mode": true,
"show_numeric_approximation": true,
"keep_symbolic_fractions": true,

View File

@ -298,6 +298,8 @@ CARACTERÍSTICAS:
def setup_interactive_manager(self):
"""Configura el gestor de resultados interactivos"""
self.interactive_manager = InteractiveResultManager(self.root)
# Configurar callback para actualizar el panel de entrada cuando se edite una expresión
self.interactive_manager.set_update_callback(self._update_input_expression)
def create_menu(self):
"""Crea el menú de la aplicación"""
@ -372,25 +374,38 @@ CARACTERÍSTICAS:
self.output_text.bind("<MouseWheel>", _unified_mouse_wheel)
def setup_output_tags(self):
"""Configura tags para coloreado de salida"""
self.output_text.tag_configure("error", foreground="#ff6b6b", font=("Consolas", 11, "bold"))
self.output_text.tag_configure("result", foreground="#abdbe3")
self.output_text.tag_configure("symbolic", foreground="#82aaff")
self.output_text.tag_configure("numeric", foreground="#c3e88d")
self.output_text.tag_configure("equation", foreground="#c792ea")
self.output_text.tag_configure("info", foreground="#ffcb6b")
self.output_text.tag_configure("comment", foreground="#546e7a")
self.output_text.tag_configure("class_hint", foreground="#888888")
self.output_text.tag_configure("type_hint", foreground="#6a6a6a")
"""Configura tags de formato para el panel de salida"""
default_font = self._get_input_font()
# Tags para tipos especializados (genéricos para cualquier tipo)
self.output_text.tag_configure("custom_type", foreground="#f9a825")
self.output_text.tag_configure("hex", foreground="#f9a825")
self.output_text.tag_configure("bin", foreground="#4fc3f7")
self.output_text.tag_configure("ip", foreground="#fff176")
self.output_text.tag_configure("date", foreground="#ff8a80")
self.output_text.tag_configure("chr_type", foreground="#80cbc4")
self.output_text.tag_configure("helper", foreground="#ffd700", font=("Consolas", 11, "italic"))
# Crear una fuente específica para errores (bold)
error_font = tkFont.Font(family=default_font.cget("family"), size=default_font.cget("size"), weight="bold")
# Tag base
self.output_text.tag_configure("base", font=default_font, foreground="#d4d4d4")
# Tags específicos
# Sympy y tipos base
self.output_text.tag_configure("symbolic", foreground="#9cdcfe") # Azul claro (SymPy)
self.output_text.tag_configure("numeric", foreground="#b5cea8") # Verde (Números)
self.output_text.tag_configure("boolean", foreground="#569cd6") # Azul (Booleanos)
self.output_text.tag_configure("string", foreground="#ce9178") # Naranja (Strings)
# Tipos registrados dinámicamente (usar un color base)
self.output_text.tag_configure("custom_type", foreground="#4ec9b0") # Turquesa (Tipos Custom)
# Estado de la aplicación
self.output_text.tag_configure("error", foreground="#f44747", font=error_font) # Rojo
self.output_text.tag_configure("comment", foreground="#6a9955") # Verde Oliva (Comentarios)
self.output_text.tag_configure("assignment", foreground="#dcdcaa") # Amarillo (Asignaciones)
self.output_text.tag_configure("equation", foreground="#c586c0") # Púrpura (Ecuaciones)
self.output_text.tag_configure("plot", foreground="#569cd6", underline=True) # Azul con subrayado (Plots)
# Para el nuevo indicador de tipo algebraico
self.output_text.tag_configure("type_indicator", foreground="#808080") # Gris oscuro
# Configurar tags para tipos específicos si es necesario (ejemplo)
# self.output_text.tag_configure("IP4", foreground="#4ec9b0")
# self.output_text.tag_configure("IntBase", foreground="#4ec9b0")
def on_key_release(self, event=None):
"""Maneja eventos de teclado"""
@ -670,28 +685,99 @@ CARACTERÍSTICAS:
self._display_output(output_data)
def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]:
"""Procesa el resultado de evaluación para display - ADAPTADO AL MOTOR PURO"""
"""Procesa el resultado de evaluación para display, priorizando resultados interactivos."""
output_parts = []
indicator_text: Optional[str] = None
if result.result_type == "comment":
output_parts.append(("comment", result.output if result.output is not None else ""))
return output_parts
if not result.success:
# Error
ayuda = self.obtener_ayuda(result.input_line)
if ayuda:
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
if len(ayuda_linea) > 120:
# Manejo de errores
error_prefix = "Error: "
main_error_message = f"{error_prefix}{result.error_message}"
# Intentar obtener ayuda contextual para el error
ayuda_text = self.obtener_ayuda(result.input_line) # obtener_ayuda devuelve string o None
if ayuda_text:
ayuda_linea = ayuda_text.replace("\n", " ").replace("\r", " ").strip()
if len(ayuda_linea) > 120: # Acortar si es muy larga
ayuda_linea = ayuda_linea[:117] + "..."
output_parts.append(("helper", ayuda_linea))
# Mostrar primero el error principal, luego la sugerencia
output_parts.append(("error", main_error_message))
output_parts.append( ("\n", "\n") ) # Separador para la ayuda
output_parts.append(("helper", f"Sugerencia: {ayuda_linea}"))
else:
output_parts.append(("error", f"Error: {result.error_message}"))
elif result.result_type == "comment":
output_parts.append(("comment", result.input_line))
elif result.result_type == "equation":
output_parts.append(("equation", result.output))
elif result.result_type == "symbolic":
output_parts.append(("symbolic", result.output))
output_parts.append(("error", main_error_message))
# No se añade type_indicator para errores aquí, el mensaje de error es suficiente.
else:
# Resultado general
output_parts.append(("result", result.output))
# RESULTADO EXITOSO:
# 1. Intentar crear un tag interactivo
interactive_tag_info = self.interactive_manager.create_interactive_tag(
result.actual_result_object,
self.output_text
)
if interactive_tag_info:
tag_name, display_text = interactive_tag_info
output_parts.append((tag_name, display_text))
# Añadir también el indicador de tipo algebraico
if result.algebraic_type:
indicator_text = f"[{result.algebraic_type}]"
output_parts.append((" ", " "))
output_parts.append(("type_indicator", indicator_text))
else:
# 2. Si no es interactivo, usar la lógica de formato de texto anterior
main_output_tag = "base"
if result.is_assignment:
main_output_tag = "assignment"
indicator_text = "[=]"
elif result.is_equation:
main_output_tag = "equation"
indicator_text = "[eq]"
elif result.result_type == "plot":
main_output_tag = "plot"
# Este caso es un fallback si create_interactive_tag no lo manejó
else:
# Lógica para determinar el tag principal y el texto del indicador
if result.algebraic_type:
type_lower = result.algebraic_type.lower()
if type_lower in self.output_text.tag_names():
main_output_tag = type_lower
elif isinstance(result.actual_result_object, sympy.Basic):
main_output_tag = "symbolic"
elif type_lower in ["int", "float", "complex"] or isinstance(result.actual_result_object, (int, float)):
main_output_tag = "numeric"
elif type_lower == "bool" or isinstance(result.actual_result_object, bool):
main_output_tag = "boolean"
elif type_lower == "str" or isinstance(result.actual_result_object, str):
main_output_tag = "string"
elif result.actual_result_object is not None and \
not isinstance(result.actual_result_object, (sympy.Basic, int, float, bool, str, list, dict, tuple, type(None))):
main_output_tag = "custom_type"
else:
main_output_tag = "symbolic"
else:
main_output_tag = "symbolic"
if result.algebraic_type:
is_collection = any(kw in result.algebraic_type.lower() for kw in ["matrix", "list", "dict", "tuple", "vector", "array"])
is_custom_obj_tag = (main_output_tag == "custom_type")
is_non_trivial_sympy = isinstance(result.actual_result_object, sympy.Basic) and \
result.algebraic_type not in ["Symbol", "Expr", "Integer", "Float", "Rational", "BooleanTrue", "BooleanFalse"]
if is_collection or is_custom_obj_tag or is_non_trivial_sympy:
indicator_text = f"[{result.algebraic_type}]"
output_parts.append((main_output_tag, result.output if result.output is not None else ""))
if indicator_text:
output_parts.append((" ", " "))
output_parts.append(("type_indicator", indicator_text))
return output_parts
@ -1119,14 +1205,41 @@ programación y análisis numérico.
self.logger.error(f"Error guardando historial: {e}", exc_info=True)
def on_close(self):
"""Maneja cierre de la aplicación"""
self.save_history()
self._save_settings()
if self.interactive_manager:
self.interactive_manager.close_all_windows()
self.root.destroy()
"""Maneja cierre de la aplicación de forma completa"""
try:
# Guardar historial y configuraciones
self.save_history()
self._save_settings()
# Cerrar todas las ventanas interactivas
if self.interactive_manager:
self.interactive_manager.close_all_windows()
# Detener cualquier job pendiente
if self._debounce_job:
self.root.after_cancel(self._debounce_job)
# Cerrar autocompletado si está abierto
self._close_autocomplete_popup()
# Asegurar que matplotlib libere recursos
try:
import matplotlib.pyplot as plt
plt.close('all')
except:
pass
# Forzar la salida del mainloop
self.root.quit()
except Exception as e:
self.logger.error(f"Error durante el cierre: {e}")
finally:
# Destruir la ventana principal como último recurso
try:
self.root.destroy()
except:
pass
def show_help_window(self):
"""Muestra ventana de ayuda con archivo externo - NUEVO SISTEMA"""
@ -1441,6 +1554,40 @@ Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el
self._evaluate_and_update()
self._adjust_input_pane_width()
def _update_input_expression(self, original_expression, new_expression):
"""Actualiza el panel de entrada reemplazando la expresión original con la nueva"""
try:
# Obtener todo el contenido actual
current_content = self.input_text.get("1.0", tk.END).rstrip('\n')
# Buscar y reemplazar la expresión original
if original_expression in current_content:
updated_content = current_content.replace(original_expression, new_expression, 1)
# Actualizar el panel de entrada
self.input_text.delete("1.0", tk.END)
self.input_text.insert("1.0", updated_content)
# Evaluar automáticamente la nueva expresión
self._evaluate_and_update()
self.logger.info(f"Expresión actualizada: '{original_expression}' -> '{new_expression}'")
else:
# Si no se encuentra la expresión original, agregar la nueva al final
if current_content and not current_content.endswith('\n'):
current_content += '\n'
updated_content = current_content + new_expression
self.input_text.delete("1.0", tk.END)
self.input_text.insert("1.0", updated_content)
self.logger.info(f"Expresión agregada: '{new_expression}'")
except Exception as e:
self.logger.error(f"Error actualizando expresión: {e}")
# Fallback: simplemente insertar la nueva expresión
self.input_text.insert(tk.END, f"\n{new_expression}")
def main():
"""Función principal"""

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@ from type_registry import (
discover_and_register_types
)
from tl_bracket_parser import BracketParser
from tl_popup import PlotResult
@dataclass
@ -29,11 +30,14 @@ class EvaluationResult:
"""Resultado de evaluación simplificado"""
input_line: str
output: str
result_type: str
result_type: str # e.g., "symbolic", "numeric", "error", "comment", "plot"
success: bool
error_message: Optional[str] = None
is_equation: bool = False
is_solve_query: bool = False
algebraic_type: Optional[str] = None # Tipo del objeto Python resultante (e.g., "Matrix", "Integer")
actual_result_object: Any = None # El objeto Python real del resultado
is_assignment: bool = False # True si la línea fue una asignación
class PureAlgebraicEngine:
@ -47,6 +51,7 @@ class PureAlgebraicEngine:
self.unified_context = {} # Contexto unificado para sympify
self.bracket_parser = BracketParser()
self.tokenization_patterns = [] # Patrones de tokenización
self.last_result_object = None # Para la variable 'last'
# Cargar tipos personalizados PRIMERO
self._load_custom_types()
@ -93,18 +98,34 @@ class PureAlgebraicEngine:
# 2. TIPOS PERSONALIZADOS REGISTRADOS (CLAVE PARA INSTANCIACIÓN)
registered_types = get_registered_base_context()
# 3. FUNCIONES DE PLOTTING
try:
from sympy.plotting import plot, plot3d, plot_parametric, plot3d_parametric_line
plotting_functions = {
'plot': plot,
'plot3d': plot3d,
'plot_parametric': plot_parametric,
'plot3d_parametric_line': plot3d_parametric_line,
}
except Exception as e:
self.logger.warning(f"Error cargando funciones de plotting: {e}")
plotting_functions = {}
# 3. 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
original_expr = str(args[0]) if args else ""
return PlotResult("plot", args, kwargs, original_expr)
def plot3d_wrapper(*args, **kwargs):
# Intentar extraer la expresión original del primer argumento
original_expr = str(args[0]) if args else ""
return PlotResult("plot3d", args, kwargs, original_expr)
def plot_parametric_wrapper(*args, **kwargs):
# Intentar extraer la expresión original del primer argumento
original_expr = str(args[0]) if args else ""
return PlotResult("plot_parametric", args, kwargs, original_expr)
def plot3d_parametric_line_wrapper(*args, **kwargs):
# Intentar extraer la expresión original del primer argumento
original_expr = str(args[0]) if args else ""
return PlotResult("plot3d_parametric_line", args, kwargs, original_expr)
plotting_functions = {
'plot': plot_wrapper,
'plot3d': plot3d_wrapper,
'plot_parametric': plot_parametric_wrapper,
'plot3d_parametric_line': plot3d_parametric_line_wrapper,
}
# 4. COMBINAR TODO EN CONTEXTO UNIFICADO
self.unified_context = {
@ -173,16 +194,18 @@ class PureAlgebraicEngine:
return tokenized_line
def _get_complete_context(self) -> Dict[str, Any]:
"""Obtiene contexto completo incluyendo variables del usuario"""
"""Obtiene contexto completo incluyendo variables del usuario y 'last'"""
complete_context = self.unified_context.copy()
complete_context.update(self.symbol_table)
complete_context['last'] = self.last_result_object # Añadir 'last' al contexto
return complete_context
def evaluate_line(self, line: str) -> EvaluationResult:
"""Evalúa una línea de entrada usando sympify unificado"""
line = line.strip()
if not line or line.startswith('#'):
return EvaluationResult(line, "", "comment", True)
# Devolver la línea como output para que se muestre como comentario
return EvaluationResult(line, line, "comment", True)
try:
# 1. Aplicar tokenización dinámica
@ -222,134 +245,189 @@ class PureAlgebraicEngine:
return any(op in line for op in comparison_ops)
def _evaluate_solve_shortcut(self, line: str) -> EvaluationResult:
"""Evalúa atajos de resolución"""
"""Evalúa atajos de resolución y gestiona 'last'"""
try:
if line.startswith('solve('):
# Función solve() - manejar casos especiales primero
# Extraer el contenido dentro de solve()
import re
match = re.match(r'solve\(([^)]+)\)', line)
if match:
var_content = match.group(1).strip()
# Si es una variable simple, usar nuestra lógica mejorada
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_content):
# Crear símbolo directamente sin usar context para evitar sustitución
var_symbol = sp.Symbol(var_content)
solution_result = self._smart_solve(var_symbol)
output = str(solution_result)
numeric = self._get_numeric_approximation(solution_result)
if numeric and str(solution_result) != str(numeric):
solution_result_obj = self._smart_solve(var_symbol)
output = str(solution_result_obj)
numeric = self._get_numeric_approximation(solution_result_obj)
if numeric and str(solution_result_obj) != str(numeric):
output += f"{numeric}"
return EvaluationResult(line, output, "symbolic", True, is_solve_query=True)
current_algebraic_type = type(solution_result_obj).__name__
# Los resultados de solve() generalmente no son plots
self.last_result_object = solution_result_obj
return EvaluationResult(
input_line=line,
output=output,
result_type="symbolic",
success=True,
is_solve_query=True,
algebraic_type=current_algebraic_type,
actual_result_object=solution_result_obj
)
# Para casos más complejos, usar sympify
result = self._evaluate_expression(line)
result.is_solve_query = True
# Para casos más complejos de solve() o si el regex no coincide,
# usar _evaluate_expression que ya maneja 'last' y los tipos.
# _evaluate_expression se encargará de self.last_result_object.
result = self._evaluate_expression(line) # Llama a la versión ya modificada
result.is_solve_query = True # Mantener esta bandera
return result
# Si no es solve(), podría ser otro tipo de atajo (si se añaden más tarde)
# Por ahora, si no empieza con solve(, lo tratamos como una expresión normal.
return self._evaluate_expression(line)
except Exception as e:
error_msg = f"Error en resolución: {str(e)}"
return EvaluationResult(line, error_msg, "error", False, str(e))
error_msg = f"Error en atajo de resolución '{line}': {type(e).__name__}: {str(e)}"
self.logger.error(error_msg)
# No actualizar last_result_object en caso de error
return EvaluationResult(line, error_msg, "error", False, str(e), is_solve_query=True)
def _evaluate_assignment(self, line: str) -> EvaluationResult:
"""Evalúa una asignación manteniendo doble registro"""
"""Evalúa una asignación"""
try:
# Separar variable = expresión
var_name, expr_str = line.split('=', 1)
var_name, expression_str = line.split('=', 1)
var_name = var_name.strip()
expr_str = expr_str.strip()
expression_str = expression_str.strip()
# Validar nombre de variable
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_name):
error_msg = f"Error: Nombre de variable inválido '{var_name}'"
self.logger.error(error_msg)
return EvaluationResult(line, error_msg, "error", False, error_msg)
# Evaluar la expresión del lado derecho
context = self._get_complete_context()
result = sympify(expr_str, locals=context)
# Usar _get_complete_context para incluir 'last' y otras variables
eval_context = self._get_complete_context()
result_obj = sympify(expression_str, locals=eval_context)
# 1. ASIGNACIÓN DIRECTA (para uso inmediato)
self.symbol_table[var_name] = result
# Variable asignada correctamente
# Actualizar tabla de símbolos
self.symbol_table[var_name] = result_obj
self.variables.add(var_name)
# 2. ECUACIÓN IMPLÍCITA (para solve)
var_symbol = sp.Symbol(var_name)
equation = Eq(var_symbol, result)
self.equations.append(equation)
output = f"{var_name} = {result_obj}"
# Añadir símbolo a variables conocidas
self.variables.add(var_symbol)
# Output conciso - mostrar el valor asignado
output = str(result)
# Añadir aproximación numérica si es útil
numeric = self._get_numeric_approximation(result)
if numeric and str(result) != str(numeric):
output += f"{numeric}"
return EvaluationResult(line, output, "assignment", True)
# Devolver el resultado de la asignación
return EvaluationResult(
input_line=line,
output=output,
result_type="assignment", # o "symbolic" si queremos que se muestre como tal
success=True,
is_assignment=True, # Indicar que es una asignación
algebraic_type="=", # Marcador para la GUI
actual_result_object=result_obj # Guardar el objeto asignado
)
except Exception as e:
error_msg = f"Error en asignación: {str(e)}"
return EvaluationResult(line, error_msg, "error", False, str(e))
error_msg = f"Error asignando '{line}': {type(e).__name__}: {str(e)}"
self.logger.error(error_msg)
# No actualizar last_result_object en caso de error
return EvaluationResult(line, error_msg, "error", False, str(e), is_assignment=True)
def _evaluate_equation(self, line: str) -> EvaluationResult:
"""Evalúa una ecuación y la añade al sistema"""
"""Evalúa una ecuación"""
try:
# Separar left = right
left_str, right_str = line.split('=', 1)
left_str = left_str.strip()
right_str = right_str.strip()
# Intentar convertir a objeto Ecuación de sympy
# Usar _get_complete_context para incluir 'last' y otras variables
eval_context = self._get_complete_context()
equation_obj = sympify(line, locals=eval_context, parse_function=lambda s: Eq(*s.split('=',1)))
if not isinstance(equation_obj, sp.Equality):
# Si no se pudo parsear como Eq(LHS,RHS), tratar como expresión que contiene un igual (posible error o comparación)
# Esto podría ser una comparación booleana que sympify evalúa
if isinstance(equation_obj, sp.logic.boolalg.BooleanFunction) or isinstance(equation_obj, bool):
# Es una comparación, evaluarla como expresión normal
return self._evaluate_expression(line)
else:
raise ValueError("La expresión no es una ecuación válida.")
# USAR SYMPIFY UNIFICADO para ambos lados
context = self._get_complete_context()
left_expr = sympify(left_str, locals=context)
right_expr = sympify(right_str, locals=context)
self.equations.append(equation_obj)
# Registrar símbolos de la ecuación para posible autocompletado o análisis futuro
for free_symbol in equation_obj.free_symbols:
self.variables.add(str(free_symbol))
# Crear ecuación
equation = Eq(left_expr, right_expr)
# Añadir al sistema
self.equations.append(equation)
# Extraer variables
eq_vars = equation.free_symbols
self.variables.update(eq_vars)
# Output conciso
output = str(equation)
# Evaluación numérica si es posible
numeric = self._get_numeric_approximation(equation.rhs)
if numeric and str(equation.rhs) != str(numeric):
output += f"{numeric}"
return EvaluationResult(line, output, "equation", True, is_equation=True)
output = str(equation_obj)
# No actualizar last_result_object para ecuaciones
return EvaluationResult(
input_line=line,
output=output,
result_type="equation",
success=True,
is_equation=True,
algebraic_type="eq", # Marcador para la GUI
actual_result_object=equation_obj
)
except Exception as e:
error_msg = f"Error en ecuación: {str(e)}"
return EvaluationResult(line, error_msg, "error", False, str(e))
error_msg = f"Error en ecuación '{line}': {type(e).__name__}: {str(e)}"
self.logger.error(error_msg)
# No actualizar last_result_object en caso de error
return EvaluationResult(line, error_msg, "error", False, str(e), is_equation=True)
def _evaluate_expression(self, line: str) -> EvaluationResult:
"""Evalúa una expresión usando sympify unificado ÚNICAMENTE"""
"""Evalúa una expresión usando sympify unificado y gestiona 'last'"""
try:
# USAR SYMPIFY UNIFICADO - Un solo parser
context = self._get_complete_context()
result = sympify(line, locals=context)
# Usar _get_complete_context para incluir 'last' y otras variables
eval_context = self._get_complete_context()
result_obj = sympify(line, locals=eval_context)
# Nota: Las asignaciones ahora se manejan en _evaluate_assignment
# Si es un PlotResult, asegurar que tenga la línea original
if isinstance(result_obj, PlotResult) and not result_obj.original_expression:
result_obj.original_expression = line
# Simplificar si es expresión SymPy
if hasattr(result, 'simplify'):
result = simplify(result)
output = str(result_obj)
result_type_str = "symbolic" # Tipo por defecto
current_algebraic_type = type(result_obj).__name__
# Manejo especial para tipos de sympy que pueden necesitar aproximación numérica
if hasattr(result_obj, 'evalf') and not isinstance(result_obj, (sp.Matrix, sp.Basic, sp.Expr)):
# Evitar evalf en matrices directamente o en tipos ya específicos como Integer, Float
pass
output = str(result)
# Añadir aproximación numérica
numeric = self._get_numeric_approximation(result)
if numeric and str(result) != str(numeric):
output += f"{numeric}"
return EvaluationResult(line, output, "symbolic", True)
numeric_approximation = self._get_numeric_approximation(result_obj)
if numeric_approximation and output != numeric_approximation:
output += f"{numeric_approximation}"
# Considerar si esto cambia el result_type a "numeric_approx"
# Determinar si es un objeto de plotting (para no asignarlo a 'last')
is_plot_object = False
if isinstance(result_obj, PlotResult):
is_plot_object = True
result_type_str = "plot" # Marcar como plot para la GUI
# No es necesario cambiar current_algebraic_type, ya será "PlotResult"
# Actualizar last_result_object si no es error y no es plot
if not is_plot_object:
self.last_result_object = result_obj
else:
# Si es un plot, no queremos que 'last' lo referencie para evitar problemas
# si el plot se cierra o se maneja de forma especial.
# Podríamos incluso setear last_result_object a None o al valor previo.
# Por ahora, simplemente no lo actualizamos si es plot.
pass
return EvaluationResult(
input_line=line,
output=output,
result_type=result_type_str,
success=True,
algebraic_type=current_algebraic_type,
actual_result_object=result_obj
)
except Exception as e:
error_msg = f"Error: {str(e)}"
error_msg = f"Error evaluando '{line}': {type(e).__name__}: {str(e)}"
self.logger.error(error_msg)
# No actualizar last_result_object en caso de error
return EvaluationResult(line, error_msg, "error", False, str(e))
def _smart_solve(self, *args, **kwargs):
@ -588,7 +666,7 @@ class PureAlgebraicEngine:
self.equations.clear()
self.variables.clear()
self.symbol_table.clear()
self.logger.info("Contexto limpio")
def get_context_info(self) -> Dict[str, Any]:
"""Información del contexto actual"""

View File

@ -13,10 +13,11 @@ import numpy as np
class PlotResult:
"""Placeholder para resultados de plotting - DEFINICIÓN PRINCIPAL"""
def __init__(self, plot_type: str, args: tuple, kwargs: dict):
def __init__(self, plot_type: str, args: tuple, kwargs: dict, original_expression: str = None):
self.plot_type = plot_type
self.args = args
self.kwargs = kwargs
self.original_expression = original_expression or ""
def __str__(self):
return f"📊 Ver {self.plot_type.title()}"
@ -31,8 +32,13 @@ class InteractiveResultManager:
def __init__(self, parent_window: tk.Tk):
self.parent = parent_window
self.open_windows: Dict[str, Toplevel] = {}
self.update_input_callback = None # Callback para actualizar el panel de entrada
def create_interactive_tag(self, result: Any, text_widget: tk.Text, index: str) -> Optional[Tuple[str, str]]:
def set_update_callback(self, callback):
"""Establece el callback para actualizar el panel de entrada"""
self.update_input_callback = callback
def create_interactive_tag(self, result: Any, text_widget: tk.Text) -> Optional[Tuple[str, str]]:
"""
Crea un tag interactivo para un resultado si es necesario
@ -135,7 +141,7 @@ class InteractiveResultManager:
print(f"❌ Error abriendo ventana interactiva: {e}")
def _show_plot_window(self, plot_result: PlotResult, window_key: str):
"""Muestra ventana con plot matplotlib"""
"""Muestra ventana con plot matplotlib e interfaz de edición"""
# Asegurar que las dimensiones de la ventana principal estén actualizadas
self.parent.update_idletasks()
parent_x = self.parent.winfo_x()
@ -144,7 +150,7 @@ class InteractiveResultManager:
parent_height = self.parent.winfo_height()
# Definir dimensiones y posición para la ventana del plot
plot_window_width = 600 # Ancho deseado para la ventana del plot (puedes ajustarlo)
plot_window_width = 700 # Aumentado para dar espacio al campo de edición
plot_window_height = parent_height # Misma altura que la ventana principal
# Posicionar la ventana del plot a la derecha de la ventana principal
@ -163,8 +169,129 @@ class InteractiveResultManager:
)
self.open_windows[window_key] = window
# Frame principal para organizar la ventana
main_frame = tk.Frame(window, bg="#2b2b2b")
main_frame.pack(fill=tk.BOTH, expand=True)
# Frame superior para el campo de edición
edit_frame = tk.Frame(main_frame, bg="#2b2b2b")
edit_frame.pack(fill=tk.X, padx=10, pady=5)
# Label para el campo de edición
tk.Label(
edit_frame,
text="Expresión:",
bg="#2b2b2b",
fg="#d4d4d4",
font=("Consolas", 10)
).pack(side=tk.LEFT)
# Campo de entrada para editar la expresión
self.current_expression = tk.StringVar()
self.current_expression.set(plot_result.original_expression)
expression_entry = tk.Entry(
edit_frame,
textvariable=self.current_expression,
bg="#1e1e1e",
fg="#d4d4d4",
font=("Consolas", 11),
insertbackground="#ffffff"
)
expression_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(10, 5))
# Botón para redibujar
redraw_btn = tk.Button(
edit_frame,
text="Redibujar",
command=lambda: self._redraw_plot(plot_result, canvas_frame, expression_entry.get()),
bg="#4fc3f7",
fg="white",
font=("Consolas", 9),
relief=tk.FLAT
)
redraw_btn.pack(side=tk.RIGHT, padx=5)
# Frame para el canvas del plot
canvas_frame = tk.Frame(main_frame, bg="#2b2b2b")
canvas_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# Configurar el protocolo de cierre para guardar la expresión editada
def on_window_close():
edited_expression = expression_entry.get().strip()
original_expression = plot_result.original_expression.strip()
# Si la expresión cambió y tenemos un callback, actualizar el panel de entrada
if edited_expression != original_expression and self.update_input_callback:
self.update_input_callback(original_expression, edited_expression)
# Limpiar la ventana del registro
if window_key in self.open_windows:
del self.open_windows[window_key]
# Cerrar la ventana
window.destroy()
window.protocol("WM_DELETE_WINDOW", on_window_close)
# Crear el plot inicial
self._create_plot_in_frame(plot_result, canvas_frame)
# Hacer focus en el campo de entrada para edición inmediata
expression_entry.focus_set()
expression_entry.select_range(0, tk.END)
def _redraw_plot(self, plot_result: PlotResult, canvas_frame: tk.Frame, new_expression: str):
"""Redibuja el plot con una nueva expresión"""
try:
fig, ax = plt.subplots(figsize=(8, 6)) # Tamaño de la figura interna del plot
# Limpiar el frame actual
for widget in canvas_frame.winfo_children():
widget.destroy()
# Evaluar la nueva expresión
import sympy as sp
# Crear contexto básico para evaluación
eval_context = {
'sin': sp.sin, 'cos': sp.cos, 'tan': sp.tan,
'exp': sp.exp, 'log': sp.log, 'sqrt': sp.sqrt,
'pi': sp.pi, 'e': sp.E, 'x': sp.Symbol('x'), 'y': sp.Symbol('y'),
'z': sp.Symbol('z'), 't': sp.Symbol('t')
}
# Evaluar la expresión
new_expr = sp.sympify(new_expression, locals=eval_context)
# Crear nuevo PlotResult con la expresión actualizada
new_plot_result = PlotResult(
plot_result.plot_type,
(new_expr,) + plot_result.args[1:], # Mantener argumentos adicionales
plot_result.kwargs,
new_expression
)
# Actualizar la expresión original en el objeto
plot_result.original_expression = new_expression
# Redibujar
self._create_plot_in_frame(new_plot_result, canvas_frame)
except Exception as e:
# Mostrar error en el frame
error_label = tk.Label(
canvas_frame,
text=f"Error en expresión: {e}",
fg="#f44747",
bg="#2b2b2b",
font=("Consolas", 11),
wraplength=600
)
error_label.pack(pady=20)
def _create_plot_in_frame(self, plot_result: PlotResult, parent_frame: tk.Frame):
"""Crea el plot dentro del frame especificado"""
try:
fig, ax = plt.subplots(figsize=(8, 6))
if plot_result.plot_type == "plot":
self._create_2d_plot(fig, ax, plot_result.args, plot_result.kwargs)
@ -172,30 +299,28 @@ class InteractiveResultManager:
self._create_3d_plot(fig, plot_result.args, plot_result.kwargs)
# Embed en tkinter
canvas = FigureCanvasTkAgg(fig, window)
canvas = FigureCanvasTkAgg(fig, parent_frame)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Toolbar para interactividad
try:
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
toolbar = NavigationToolbar2Tk(canvas, window)
toolbar = NavigationToolbar2Tk(canvas, parent_frame)
toolbar.update()
except ImportError:
pass # Si no está disponible, continuar sin toolbar
except Exception as e:
error_label = tk.Label(
window,
parent_frame,
text=f"Error generando plot: {e}",
fg="red",
fg="#f44747",
bg="#2b2b2b",
font=("Consolas", 12)
)
error_label.pack(pady=20)
print(f"❌ Error en plot: {e}")
def _create_2d_plot(self, fig, ax, args, kwargs):
"""Crea plot 2D usando SymPy"""
if len(args) >= 1:
@ -518,11 +643,27 @@ class InteractiveResultManager:
return window
def close_all_windows(self):
"""Cierra todas las ventanas interactivas"""
for window in list(self.open_windows.values()):
"""Cierra todas las ventanas interactivas de forma segura"""
windows_to_close = list(self.open_windows.items())
for window_key, window in windows_to_close:
try:
if window.winfo_exists():
window.destroy()
if window and window.winfo_exists():
# Forzar el cierre del protocolo de cierre si existe
window.protocol("WM_DELETE_WINDOW", window.destroy)
window.quit() # Detener el mainloop de la ventana si lo tiene
window.destroy() # Destruir la ventana
except tk.TclError:
# La ventana ya fue destruida o no es válida
pass
except Exception as e:
print(f"Error cerrando ventana {window_key}: {e}")
# Limpiar el diccionario
self.open_windows.clear()
# Cerrar todas las figuras de matplotlib para liberar memoria
try:
plt.close('all')
except:
pass