Skip to content

Instantly share code, notes, and snippets.

@Elteoremadebeethoven
Created December 24, 2025 03:20
Show Gist options
  • Select an option

  • Save Elteoremadebeethoven/aa2a8a704ec009a58d78a4343e970d9b to your computer and use it in GitHub Desktop.

Select an option

Save Elteoremadebeethoven/aa2a8a704ec009a58d78a4343e970d9b to your computer and use it in GitHub Desktop.
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