Created
December 24, 2025 03:20
-
-
Save Elteoremadebeethoven/aa2a8a704ec009a58d78a4343e970d9b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| import sys | |
| import subprocess | |
| from manim import * | |
| from manim.opengl import * | |
| # pip install pyperclip | |
| import pyperclip | |
| # pip install antlr4-python3-runtime | |
| from sympy import pprint | |
| from sympy.parsing.latex import parse_latex | |
| # File used to pass data from the Manim subprocess back to the main script | |
| HISTORY_FILE = "temp_history.txt" | |
| # USE: | |
| # alias mm='manim --renderer=opengl --fullscreen -p' | |
| def print_latex_formula(latex_str, use_unicode=True): | |
| try: | |
| expr = parse_latex(latex_str) | |
| pprint(expr, use_unicode=use_unicode) | |
| except Exception as e: | |
| print(f"Error al parsear o imprimir la fórmula: {e}") | |
| class SelectorTwoClicks(Scene): | |
| def construct(self): | |
| # 1. Get formula from environment variable or use default | |
| formula_text = os.environ.get("USER_FORMULA", r"a^2 + b^2 = c^2") | |
| # --- 2. Object Configuration --- | |
| try: | |
| # [0] breaks the VGroup to access individual glyphs | |
| self.formula = MathTex(formula_text)[0].scale(2) | |
| except Exception as e: | |
| print(f"LaTeX Error: {e}") | |
| self.formula = Text("LaTeX Error", color=RED) | |
| self.add(self.formula) | |
| # --- 3. Selector Configuration --- | |
| self.selector = Rectangle(width=1, height=1, color=YELLOW) | |
| self.selector.set_fill(YELLOW, opacity=0.2) | |
| self.selector.set_stroke(YELLOW, width=2) | |
| self.selector.set_opacity(0) | |
| self.add(self.selector) | |
| # --- 4. Start Marker --- | |
| self.start_marker = Dot(color=GREEN, radius=0.1) | |
| self.start_marker.set_opacity(0) | |
| self.add(self.start_marker) | |
| # --- 5. Instruction Text --- | |
| self.instructions = Text( | |
| "Click 1: Start Corner | Click 2: End Corner", | |
| font_size=24 | |
| ).to_edge(UP) | |
| self.add(self.instructions) | |
| # --- 6. State Variables --- | |
| self.is_selecting = False | |
| self.start_point = ORIGIN | |
| self.selected_indices = [] | |
| self.session_history = [] # In-memory history for this scene | |
| # Interactive mode | |
| self.interactive_embed() | |
| def on_mouse_press(self, point, button, modifiers): | |
| super().on_mouse_press(point, button, modifiers) | |
| if not self.is_selecting: | |
| # FIRST CLICK | |
| self.start_point = point | |
| self.is_selecting = True | |
| self.start_marker.move_to(point) | |
| self.start_marker.set_opacity(1) | |
| self.selector.set_width(0.01, stretch=True) | |
| self.selector.set_height(0.01, stretch=True) | |
| self.selector.move_to(point) | |
| self.selector.set_opacity(0.3) | |
| self.selector.set_stroke(opacity=1) | |
| self.formula.set_fill(WHITE, opacity=1) # Reset colors | |
| else: | |
| # SECOND CLICK | |
| self.is_selecting = False | |
| self.process_selection() | |
| self.start_marker.set_opacity(0) | |
| self.instructions.become(Text( | |
| "Click 1: Start Corner | Click 2: End Corner", | |
| font_size=24 | |
| ).to_edge(UP)) | |
| def on_mouse_motion(self, point, d_point): | |
| if self.is_selecting: | |
| # Resize rectangle | |
| new_center = (self.start_point + point) / 2 | |
| width = abs(self.start_point[0] - point[0]) | |
| height = abs(self.start_point[1] - point[1]) | |
| self.selector.set_width(max(width, 0.01), stretch=True) | |
| self.selector.set_height(max(height, 0.01), stretch=True) | |
| self.selector.move_to(new_center) | |
| self.calculate_intersections() | |
| super().on_mouse_motion(point, d_point) | |
| def calculate_intersections(self): | |
| """ Real-time AABB collision detection to color glyphs """ | |
| r_min = self.selector.get_corner(DL) | |
| r_max = self.selector.get_corner(UR) | |
| self.selected_indices = [] | |
| for i, sub_mob in enumerate(self.formula): | |
| o_min = sub_mob.get_corner(DL) | |
| o_max = sub_mob.get_corner(UR) | |
| in_x = o_min[0] >= r_min[0] and o_max[0] <= r_max[0] | |
| in_y = o_min[1] >= r_min[1] and o_max[1] <= r_max[1] | |
| if in_x and in_y: | |
| sub_mob.set_fill(RED, opacity=1) | |
| self.selected_indices.append(i) | |
| else: | |
| sub_mob.set_fill(WHITE, opacity=0.5) | |
| def process_selection(self): | |
| """ Handles printing, copying to clipboard, and saving to history """ | |
| if not self.selected_indices: | |
| return | |
| data_str = ",".join(map(str, self.selected_indices)) | |
| print(f"\n[Selected Indices]: {data_str}\n") | |
| def on_key_press(self, symbol, modifiers): | |
| """ | |
| S: Force save/copy of current selection | |
| """ | |
| if symbol == ord('s') or symbol == ord('S'): | |
| data_str = ",".join(map(str, self.selected_indices)) | |
| self.session_history.append(data_str) | |
| print(f"\n[Copied]: {data_str}\n") | |
| copy_to_clipboard(data_str) | |
| try: | |
| with open(HISTORY_FILE, "a") as f: | |
| f.write(data_str + "\n") | |
| except Exception as e: | |
| print(f"Error writing history: {e}") | |
| super().on_key_press(symbol, modifiers) | |
| def copy_to_clipboard(text: str) -> bool: | |
| try: | |
| pyperclip.copy(text) | |
| return True | |
| except Exception as e: | |
| print(f"Clipboard Error: {e}") | |
| return False | |
| # --- MAIN CONTROLLER --- | |
| if __name__ == "__main__": | |
| print("--- F_SELECTOR INTERACTIVE (ENGLISH) ---") | |
| while True: | |
| print("\n" + "="*50) | |
| try: | |
| user_input = input("Enter LaTeX formula (or 'EOF' to exit):\n> ").strip() | |
| except (EOFError, KeyboardInterrupt): | |
| print("\nExiting...") | |
| break | |
| if user_input.upper() == "EOF": | |
| print("Program finished.") | |
| break | |
| if not user_input: | |
| continue | |
| # Clean up history file from previous run | |
| if os.path.exists(HISTORY_FILE): | |
| os.remove(HISTORY_FILE) | |
| # Set environment variable | |
| env_vars = os.environ.copy() | |
| env_vars["USER_FORMULA"] = user_input | |
| # Build Manim command | |
| cmd = [ | |
| "manim", | |
| "--renderer=opengl", | |
| "--fullscreen", | |
| "-p", | |
| sys.argv[0], | |
| "SelectorTwoClicks" | |
| ] | |
| print(f"Rendering: {user_input} ...") | |
| print_latex_formula(fr'{user_input}', use_unicode=True) | |
| try: | |
| # Run Manim and wait for it to close | |
| subprocess.run(cmd, env=env_vars, check=False) | |
| # --- ON WINDOW CLOSE: PRINT SUMMARY --- | |
| print("\n" + "-"*20 + " SESSION SUMMARY " + "-"*20) | |
| if os.path.exists(HISTORY_FILE): | |
| with open(HISTORY_FILE, "r") as f: | |
| history = f.readlines() | |
| if history: | |
| print(f"You copied {len(history)} sets of indices:") | |
| for idx, line in enumerate(history): | |
| print(f" {idx + 1}. [{line.strip()}]") | |
| else: | |
| print("Nothing copied this session.") | |
| # Cleanup | |
| os.remove(HISTORY_FILE) | |
| else: | |
| print("No selections made.") | |
| print("-" * 57) | |
| except Exception as e: | |
| print(f"Error running Manim: {e}") | |
| r""" | |
| # =================================== | |
| # Example | |
| # =================================== | |
| from manim import * | |
| def gi(self, *ind)->VGroup: | |
| return VGroup(self[i] for i in ind) | |
| SingleStringMathTex.gi = gi | |
| class Test(Scene): | |
| def construct(self): | |
| t = MathTex(r"\lim_{x\to\infty} \frac{\frac{2}{x} - \frac{5}{x^2}}{1-\frac{1}{x^2}}")\ | |
| .scale(3) | |
| t[0].gi(6,7,8,10,11,12,13,17,18,19,20)\ | |
| .set_color(RED) | |
| self.add(t) | |
| """ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment