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:
parent
f0bdf1e419
commit
0488122229
|
@ -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)
|
|
@ -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,
|
||||
|
|
233
main_calc_app.py
233
main_calc_app.py
|
@ -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
|
@ -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"""
|
||||
|
|
173
tl_popup.py
173
tl_popup.py
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue