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",
|
"window_geometry": "1020x700+387+1235",
|
||||||
"sash_pos_x": 347,
|
"sash_pos_x": 355,
|
||||||
"symbolic_mode": true,
|
"symbolic_mode": true,
|
||||||
"show_numeric_approximation": true,
|
"show_numeric_approximation": true,
|
||||||
"keep_symbolic_fractions": true,
|
"keep_symbolic_fractions": true,
|
||||||
|
|
227
main_calc_app.py
227
main_calc_app.py
|
@ -298,6 +298,8 @@ CARACTERÍSTICAS:
|
||||||
def setup_interactive_manager(self):
|
def setup_interactive_manager(self):
|
||||||
"""Configura el gestor de resultados interactivos"""
|
"""Configura el gestor de resultados interactivos"""
|
||||||
self.interactive_manager = InteractiveResultManager(self.root)
|
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):
|
def create_menu(self):
|
||||||
"""Crea el menú de la aplicación"""
|
"""Crea el menú de la aplicación"""
|
||||||
|
@ -372,25 +374,38 @@ CARACTERÍSTICAS:
|
||||||
self.output_text.bind("<MouseWheel>", _unified_mouse_wheel)
|
self.output_text.bind("<MouseWheel>", _unified_mouse_wheel)
|
||||||
|
|
||||||
def setup_output_tags(self):
|
def setup_output_tags(self):
|
||||||
"""Configura tags para coloreado de salida"""
|
"""Configura tags de formato para el panel de salida"""
|
||||||
self.output_text.tag_configure("error", foreground="#ff6b6b", font=("Consolas", 11, "bold"))
|
default_font = self._get_input_font()
|
||||||
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")
|
|
||||||
|
|
||||||
# Tags para tipos especializados (genéricos para cualquier tipo)
|
# Crear una fuente específica para errores (bold)
|
||||||
self.output_text.tag_configure("custom_type", foreground="#f9a825")
|
error_font = tkFont.Font(family=default_font.cget("family"), size=default_font.cget("size"), weight="bold")
|
||||||
self.output_text.tag_configure("hex", foreground="#f9a825")
|
|
||||||
self.output_text.tag_configure("bin", foreground="#4fc3f7")
|
# Tag base
|
||||||
self.output_text.tag_configure("ip", foreground="#fff176")
|
self.output_text.tag_configure("base", font=default_font, foreground="#d4d4d4")
|
||||||
self.output_text.tag_configure("date", foreground="#ff8a80")
|
|
||||||
self.output_text.tag_configure("chr_type", foreground="#80cbc4")
|
# Tags específicos
|
||||||
self.output_text.tag_configure("helper", foreground="#ffd700", font=("Consolas", 11, "italic"))
|
# 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):
|
def on_key_release(self, event=None):
|
||||||
"""Maneja eventos de teclado"""
|
"""Maneja eventos de teclado"""
|
||||||
|
@ -670,28 +685,99 @@ CARACTERÍSTICAS:
|
||||||
self._display_output(output_data)
|
self._display_output(output_data)
|
||||||
|
|
||||||
def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]:
|
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 = []
|
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:
|
if not result.success:
|
||||||
# Error
|
# Manejo de errores
|
||||||
ayuda = self.obtener_ayuda(result.input_line)
|
error_prefix = "Error: "
|
||||||
if ayuda:
|
main_error_message = f"{error_prefix}{result.error_message}"
|
||||||
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
|
|
||||||
if len(ayuda_linea) > 120:
|
# 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] + "..."
|
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:
|
else:
|
||||||
output_parts.append(("error", f"Error: {result.error_message}"))
|
output_parts.append(("error", main_error_message))
|
||||||
elif result.result_type == "comment":
|
# No se añade type_indicator para errores aquí, el mensaje de error es suficiente.
|
||||||
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))
|
|
||||||
else:
|
else:
|
||||||
# Resultado general
|
# RESULTADO EXITOSO:
|
||||||
output_parts.append(("result", result.output))
|
# 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
|
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)
|
self.logger.error(f"Error guardando historial: {e}", exc_info=True)
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
"""Maneja cierre de la aplicación"""
|
"""Maneja cierre de la aplicación de forma completa"""
|
||||||
self.save_history()
|
try:
|
||||||
self._save_settings()
|
# Guardar historial y configuraciones
|
||||||
|
self.save_history()
|
||||||
|
self._save_settings()
|
||||||
|
|
||||||
if self.interactive_manager:
|
# Cerrar todas las ventanas interactivas
|
||||||
self.interactive_manager.close_all_windows()
|
if self.interactive_manager:
|
||||||
|
self.interactive_manager.close_all_windows()
|
||||||
|
|
||||||
self.root.destroy()
|
# 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):
|
def show_help_window(self):
|
||||||
"""Muestra ventana de ayuda con archivo externo - NUEVO SISTEMA"""
|
"""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._evaluate_and_update()
|
||||||
self._adjust_input_pane_width()
|
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():
|
def main():
|
||||||
"""Función principal"""
|
"""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
|
discover_and_register_types
|
||||||
)
|
)
|
||||||
from tl_bracket_parser import BracketParser
|
from tl_bracket_parser import BracketParser
|
||||||
|
from tl_popup import PlotResult
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -29,11 +30,14 @@ class EvaluationResult:
|
||||||
"""Resultado de evaluación simplificado"""
|
"""Resultado de evaluación simplificado"""
|
||||||
input_line: str
|
input_line: str
|
||||||
output: str
|
output: str
|
||||||
result_type: str
|
result_type: str # e.g., "symbolic", "numeric", "error", "comment", "plot"
|
||||||
success: bool
|
success: bool
|
||||||
error_message: Optional[str] = None
|
error_message: Optional[str] = None
|
||||||
is_equation: bool = False
|
is_equation: bool = False
|
||||||
is_solve_query: 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:
|
class PureAlgebraicEngine:
|
||||||
|
@ -47,6 +51,7 @@ class PureAlgebraicEngine:
|
||||||
self.unified_context = {} # Contexto unificado para sympify
|
self.unified_context = {} # Contexto unificado para sympify
|
||||||
self.bracket_parser = BracketParser()
|
self.bracket_parser = BracketParser()
|
||||||
self.tokenization_patterns = [] # Patrones de tokenización
|
self.tokenization_patterns = [] # Patrones de tokenización
|
||||||
|
self.last_result_object = None # Para la variable 'last'
|
||||||
|
|
||||||
# Cargar tipos personalizados PRIMERO
|
# Cargar tipos personalizados PRIMERO
|
||||||
self._load_custom_types()
|
self._load_custom_types()
|
||||||
|
@ -93,18 +98,34 @@ class PureAlgebraicEngine:
|
||||||
# 2. TIPOS PERSONALIZADOS REGISTRADOS (CLAVE PARA INSTANCIACIÓN)
|
# 2. TIPOS PERSONALIZADOS REGISTRADOS (CLAVE PARA INSTANCIACIÓN)
|
||||||
registered_types = get_registered_base_context()
|
registered_types = get_registered_base_context()
|
||||||
|
|
||||||
# 3. FUNCIONES DE PLOTTING
|
# 3. FUNCIONES DE PLOTTING (WRAPPED)
|
||||||
try:
|
# Wrappers para capturar llamadas de plot y devolver un objeto PlotResult
|
||||||
from sympy.plotting import plot, plot3d, plot_parametric, plot3d_parametric_line
|
def plot_wrapper(*args, **kwargs):
|
||||||
plotting_functions = {
|
# Intentar extraer la expresión original del primer argumento
|
||||||
'plot': plot,
|
original_expr = str(args[0]) if args else ""
|
||||||
'plot3d': plot3d,
|
return PlotResult("plot", args, kwargs, original_expr)
|
||||||
'plot_parametric': plot_parametric,
|
|
||||||
'plot3d_parametric_line': plot3d_parametric_line,
|
def plot3d_wrapper(*args, **kwargs):
|
||||||
}
|
# Intentar extraer la expresión original del primer argumento
|
||||||
except Exception as e:
|
original_expr = str(args[0]) if args else ""
|
||||||
self.logger.warning(f"Error cargando funciones de plotting: {e}")
|
return PlotResult("plot3d", args, kwargs, original_expr)
|
||||||
plotting_functions = {}
|
|
||||||
|
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
|
# 4. COMBINAR TODO EN CONTEXTO UNIFICADO
|
||||||
self.unified_context = {
|
self.unified_context = {
|
||||||
|
@ -173,16 +194,18 @@ class PureAlgebraicEngine:
|
||||||
return tokenized_line
|
return tokenized_line
|
||||||
|
|
||||||
def _get_complete_context(self) -> Dict[str, Any]:
|
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 = self.unified_context.copy()
|
||||||
complete_context.update(self.symbol_table)
|
complete_context.update(self.symbol_table)
|
||||||
|
complete_context['last'] = self.last_result_object # Añadir 'last' al contexto
|
||||||
return complete_context
|
return complete_context
|
||||||
|
|
||||||
def evaluate_line(self, line: str) -> EvaluationResult:
|
def evaluate_line(self, line: str) -> EvaluationResult:
|
||||||
"""Evalúa una línea de entrada usando sympify unificado"""
|
"""Evalúa una línea de entrada usando sympify unificado"""
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or line.startswith('#'):
|
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:
|
try:
|
||||||
# 1. Aplicar tokenización dinámica
|
# 1. Aplicar tokenización dinámica
|
||||||
|
@ -222,134 +245,189 @@ class PureAlgebraicEngine:
|
||||||
return any(op in line for op in comparison_ops)
|
return any(op in line for op in comparison_ops)
|
||||||
|
|
||||||
def _evaluate_solve_shortcut(self, line: str) -> EvaluationResult:
|
def _evaluate_solve_shortcut(self, line: str) -> EvaluationResult:
|
||||||
"""Evalúa atajos de resolución"""
|
"""Evalúa atajos de resolución y gestiona 'last'"""
|
||||||
try:
|
try:
|
||||||
if line.startswith('solve('):
|
if line.startswith('solve('):
|
||||||
# Función solve() - manejar casos especiales primero
|
|
||||||
# Extraer el contenido dentro de solve()
|
|
||||||
import re
|
import re
|
||||||
match = re.match(r'solve\(([^)]+)\)', line)
|
match = re.match(r'solve\(([^)]+)\)', line)
|
||||||
if match:
|
if match:
|
||||||
var_content = match.group(1).strip()
|
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):
|
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)
|
var_symbol = sp.Symbol(var_content)
|
||||||
solution_result = self._smart_solve(var_symbol)
|
solution_result_obj = self._smart_solve(var_symbol)
|
||||||
output = str(solution_result)
|
|
||||||
numeric = self._get_numeric_approximation(solution_result)
|
|
||||||
if numeric and str(solution_result) != str(numeric):
|
|
||||||
output += f" ≈ {numeric}"
|
|
||||||
return EvaluationResult(line, output, "symbolic", True, is_solve_query=True)
|
|
||||||
|
|
||||||
# Para casos más complejos, usar sympify
|
output = str(solution_result_obj)
|
||||||
result = self._evaluate_expression(line)
|
numeric = self._get_numeric_approximation(solution_result_obj)
|
||||||
result.is_solve_query = True
|
if numeric and str(solution_result_obj) != str(numeric):
|
||||||
|
output += f" ≈ {numeric}"
|
||||||
|
|
||||||
|
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 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
|
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:
|
except Exception as e:
|
||||||
error_msg = f"Error en resolución: {str(e)}"
|
error_msg = f"Error en atajo de resolución '{line}': {type(e).__name__}: {str(e)}"
|
||||||
return EvaluationResult(line, error_msg, "error", False, 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:
|
def _evaluate_assignment(self, line: str) -> EvaluationResult:
|
||||||
"""Evalúa una asignación manteniendo doble registro"""
|
"""Evalúa una asignación"""
|
||||||
try:
|
try:
|
||||||
# Separar variable = expresión
|
var_name, expression_str = line.split('=', 1)
|
||||||
var_name, expr_str = line.split('=', 1)
|
|
||||||
var_name = var_name.strip()
|
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
|
# Evaluar la expresión del lado derecho
|
||||||
context = self._get_complete_context()
|
# Usar _get_complete_context para incluir 'last' y otras variables
|
||||||
result = sympify(expr_str, locals=context)
|
eval_context = self._get_complete_context()
|
||||||
|
result_obj = sympify(expression_str, locals=eval_context)
|
||||||
|
|
||||||
# 1. ASIGNACIÓN DIRECTA (para uso inmediato)
|
# Actualizar tabla de símbolos
|
||||||
self.symbol_table[var_name] = result
|
self.symbol_table[var_name] = result_obj
|
||||||
# Variable asignada correctamente
|
self.variables.add(var_name)
|
||||||
|
|
||||||
# 2. ECUACIÓN IMPLÍCITA (para solve)
|
output = f"{var_name} = {result_obj}"
|
||||||
var_symbol = sp.Symbol(var_name)
|
|
||||||
equation = Eq(var_symbol, result)
|
|
||||||
self.equations.append(equation)
|
|
||||||
|
|
||||||
# Añadir símbolo a variables conocidas
|
# Devolver el resultado de la asignación
|
||||||
self.variables.add(var_symbol)
|
return EvaluationResult(
|
||||||
|
input_line=line,
|
||||||
# Output conciso - mostrar el valor asignado
|
output=output,
|
||||||
output = str(result)
|
result_type="assignment", # o "symbolic" si queremos que se muestre como tal
|
||||||
|
success=True,
|
||||||
# Añadir aproximación numérica si es útil
|
is_assignment=True, # Indicar que es una asignación
|
||||||
numeric = self._get_numeric_approximation(result)
|
algebraic_type="=", # Marcador para la GUI
|
||||||
if numeric and str(result) != str(numeric):
|
actual_result_object=result_obj # Guardar el objeto asignado
|
||||||
output += f" ≈ {numeric}"
|
)
|
||||||
|
|
||||||
return EvaluationResult(line, output, "assignment", True)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Error en asignación: {str(e)}"
|
error_msg = f"Error asignando '{line}': {type(e).__name__}: {str(e)}"
|
||||||
return EvaluationResult(line, error_msg, "error", False, 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:
|
def _evaluate_equation(self, line: str) -> EvaluationResult:
|
||||||
"""Evalúa una ecuación y la añade al sistema"""
|
"""Evalúa una ecuación"""
|
||||||
try:
|
try:
|
||||||
# Separar left = right
|
# Intentar convertir a objeto Ecuación de sympy
|
||||||
left_str, right_str = line.split('=', 1)
|
# Usar _get_complete_context para incluir 'last' y otras variables
|
||||||
left_str = left_str.strip()
|
eval_context = self._get_complete_context()
|
||||||
right_str = right_str.strip()
|
equation_obj = sympify(line, locals=eval_context, parse_function=lambda s: Eq(*s.split('=',1)))
|
||||||
|
|
||||||
# USAR SYMPIFY UNIFICADO para ambos lados
|
if not isinstance(equation_obj, sp.Equality):
|
||||||
context = self._get_complete_context()
|
# Si no se pudo parsear como Eq(LHS,RHS), tratar como expresión que contiene un igual (posible error o comparación)
|
||||||
left_expr = sympify(left_str, locals=context)
|
# Esto podría ser una comparación booleana que sympify evalúa
|
||||||
right_expr = sympify(right_str, locals=context)
|
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.")
|
||||||
|
|
||||||
# Crear ecuación
|
self.equations.append(equation_obj)
|
||||||
equation = Eq(left_expr, right_expr)
|
# 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))
|
||||||
|
|
||||||
# Añadir al sistema
|
output = str(equation_obj)
|
||||||
self.equations.append(equation)
|
# No actualizar last_result_object para ecuaciones
|
||||||
|
return EvaluationResult(
|
||||||
# Extraer variables
|
input_line=line,
|
||||||
eq_vars = equation.free_symbols
|
output=output,
|
||||||
self.variables.update(eq_vars)
|
result_type="equation",
|
||||||
|
success=True,
|
||||||
# Output conciso
|
is_equation=True,
|
||||||
output = str(equation)
|
algebraic_type="eq", # Marcador para la GUI
|
||||||
|
actual_result_object=equation_obj
|
||||||
# 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)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Error en ecuación: {str(e)}"
|
error_msg = f"Error en ecuación '{line}': {type(e).__name__}: {str(e)}"
|
||||||
return EvaluationResult(line, error_msg, "error", False, 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:
|
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:
|
try:
|
||||||
# USAR SYMPIFY UNIFICADO - Un solo parser
|
# Usar _get_complete_context para incluir 'last' y otras variables
|
||||||
context = self._get_complete_context()
|
eval_context = self._get_complete_context()
|
||||||
result = sympify(line, locals=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
|
output = str(result_obj)
|
||||||
if hasattr(result, 'simplify'):
|
result_type_str = "symbolic" # Tipo por defecto
|
||||||
result = simplify(result)
|
current_algebraic_type = type(result_obj).__name__
|
||||||
|
|
||||||
output = str(result)
|
# 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
|
||||||
|
|
||||||
# Añadir aproximación numérica
|
numeric_approximation = self._get_numeric_approximation(result_obj)
|
||||||
numeric = self._get_numeric_approximation(result)
|
if numeric_approximation and output != numeric_approximation:
|
||||||
if numeric and str(result) != str(numeric):
|
output += f" ≈ {numeric_approximation}"
|
||||||
output += f" ≈ {numeric}"
|
# Considerar si esto cambia el result_type a "numeric_approx"
|
||||||
|
|
||||||
return EvaluationResult(line, output, "symbolic", True)
|
# 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:
|
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))
|
return EvaluationResult(line, error_msg, "error", False, str(e))
|
||||||
|
|
||||||
def _smart_solve(self, *args, **kwargs):
|
def _smart_solve(self, *args, **kwargs):
|
||||||
|
@ -588,7 +666,7 @@ class PureAlgebraicEngine:
|
||||||
self.equations.clear()
|
self.equations.clear()
|
||||||
self.variables.clear()
|
self.variables.clear()
|
||||||
self.symbol_table.clear()
|
self.symbol_table.clear()
|
||||||
self.logger.info("Contexto limpio")
|
|
||||||
|
|
||||||
def get_context_info(self) -> Dict[str, Any]:
|
def get_context_info(self) -> Dict[str, Any]:
|
||||||
"""Información del contexto actual"""
|
"""Información del contexto actual"""
|
||||||
|
|
171
tl_popup.py
171
tl_popup.py
|
@ -13,10 +13,11 @@ import numpy as np
|
||||||
class PlotResult:
|
class PlotResult:
|
||||||
"""Placeholder para resultados de plotting - DEFINICIÓN PRINCIPAL"""
|
"""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.plot_type = plot_type
|
||||||
self.args = args
|
self.args = args
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
self.original_expression = original_expression or ""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"📊 Ver {self.plot_type.title()}"
|
return f"📊 Ver {self.plot_type.title()}"
|
||||||
|
@ -31,8 +32,13 @@ class InteractiveResultManager:
|
||||||
def __init__(self, parent_window: tk.Tk):
|
def __init__(self, parent_window: tk.Tk):
|
||||||
self.parent = parent_window
|
self.parent = parent_window
|
||||||
self.open_windows: Dict[str, Toplevel] = {}
|
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
|
Crea un tag interactivo para un resultado si es necesario
|
||||||
|
|
||||||
|
@ -135,7 +141,7 @@ class InteractiveResultManager:
|
||||||
print(f"❌ Error abriendo ventana interactiva: {e}")
|
print(f"❌ Error abriendo ventana interactiva: {e}")
|
||||||
|
|
||||||
def _show_plot_window(self, plot_result: PlotResult, window_key: str):
|
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
|
# Asegurar que las dimensiones de la ventana principal estén actualizadas
|
||||||
self.parent.update_idletasks()
|
self.parent.update_idletasks()
|
||||||
parent_x = self.parent.winfo_x()
|
parent_x = self.parent.winfo_x()
|
||||||
|
@ -144,7 +150,7 @@ class InteractiveResultManager:
|
||||||
parent_height = self.parent.winfo_height()
|
parent_height = self.parent.winfo_height()
|
||||||
|
|
||||||
# Definir dimensiones y posición para la ventana del plot
|
# 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
|
plot_window_height = parent_height # Misma altura que la ventana principal
|
||||||
|
|
||||||
# Posicionar la ventana del plot a la derecha de 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
|
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:
|
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":
|
if plot_result.plot_type == "plot":
|
||||||
self._create_2d_plot(fig, ax, plot_result.args, plot_result.kwargs)
|
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)
|
self._create_3d_plot(fig, plot_result.args, plot_result.kwargs)
|
||||||
|
|
||||||
# Embed en tkinter
|
# Embed en tkinter
|
||||||
canvas = FigureCanvasTkAgg(fig, window)
|
canvas = FigureCanvasTkAgg(fig, parent_frame)
|
||||||
canvas.draw()
|
canvas.draw()
|
||||||
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
# Toolbar para interactividad
|
# Toolbar para interactividad
|
||||||
try:
|
try:
|
||||||
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
|
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
|
||||||
toolbar = NavigationToolbar2Tk(canvas, window)
|
toolbar = NavigationToolbar2Tk(canvas, parent_frame)
|
||||||
toolbar.update()
|
toolbar.update()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass # Si no está disponible, continuar sin toolbar
|
pass # Si no está disponible, continuar sin toolbar
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_label = tk.Label(
|
error_label = tk.Label(
|
||||||
window,
|
parent_frame,
|
||||||
text=f"Error generando plot: {e}",
|
text=f"Error generando plot: {e}",
|
||||||
fg="red",
|
fg="#f44747",
|
||||||
bg="#2b2b2b",
|
bg="#2b2b2b",
|
||||||
font=("Consolas", 12)
|
font=("Consolas", 12)
|
||||||
)
|
)
|
||||||
error_label.pack(pady=20)
|
error_label.pack(pady=20)
|
||||||
|
|
||||||
print(f"❌ Error en plot: {e}")
|
|
||||||
|
|
||||||
def _create_2d_plot(self, fig, ax, args, kwargs):
|
def _create_2d_plot(self, fig, ax, args, kwargs):
|
||||||
"""Crea plot 2D usando SymPy"""
|
"""Crea plot 2D usando SymPy"""
|
||||||
if len(args) >= 1:
|
if len(args) >= 1:
|
||||||
|
@ -518,11 +643,27 @@ class InteractiveResultManager:
|
||||||
return window
|
return window
|
||||||
|
|
||||||
def close_all_windows(self):
|
def close_all_windows(self):
|
||||||
"""Cierra todas las ventanas interactivas"""
|
"""Cierra todas las ventanas interactivas de forma segura"""
|
||||||
for window in list(self.open_windows.values()):
|
windows_to_close = list(self.open_windows.items())
|
||||||
|
|
||||||
|
for window_key, window in windows_to_close:
|
||||||
try:
|
try:
|
||||||
if window.winfo_exists():
|
if window and window.winfo_exists():
|
||||||
window.destroy()
|
# 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:
|
except tk.TclError:
|
||||||
|
# La ventana ya fue destruida o no es válida
|
||||||
pass
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error cerrando ventana {window_key}: {e}")
|
||||||
|
|
||||||
|
# Limpiar el diccionario
|
||||||
self.open_windows.clear()
|
self.open_windows.clear()
|
||||||
|
|
||||||
|
# Cerrar todas las figuras de matplotlib para liberar memoria
|
||||||
|
try:
|
||||||
|
plt.close('all')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
Loading…
Reference in New Issue