File size: 14,998 Bytes
ce55d98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
import gradio as gr
import subprocess
import tempfile
import os
import shutil
from pathlib import Path
import time

# Default example code
DEFAULT_CODE = '''from manim import *

class SimpleAnimation(Scene):
    def construct(self):
        # Create a simple text animation
        title = Text("Welcome to Manimator!", font_size=48, color=BLUE)
        subtitle = Text("Mathematical Animation Renderer", font_size=24)
        subtitle.next_to(title, DOWN)
        
        # Create geometric shapes
        circle = Circle(radius=1, color=RED)
        square = Square(side_length=2, color=GREEN)
        triangle = Triangle(color=YELLOW)
        
        # Position shapes
        circle.shift(LEFT * 3)
        square.shift(RIGHT * 3)
        triangle.shift(DOWN * 2)
        
        # Animate!
        self.play(Write(title), run_time=2)
        self.wait(0.5)
        self.play(Write(subtitle), run_time=1.5)
        self.wait(1)
        
        # Show shapes
        self.play(
            Create(circle), 
            Create(square), 
            Create(triangle),
            run_time=2
        )
        self.wait(1)
        
        # Transform shapes
        self.play(
            circle.animate.set_color(PURPLE),
            square.animate.rotate(PI/4),
            triangle.animate.scale(1.5),
            run_time=2
        )
        self.wait(1)
        
        # Group and move
        everything = Group(title, subtitle, circle, square, triangle)
        self.play(everything.animate.shift(UP * 0.5), run_time=1.5)
        self.wait(2)
'''

EXAMPLE_CODES = {
    "Simple Animation": DEFAULT_CODE,
    
    "Moving Shapes": '''from manim import *

class MovingShapes(Scene):
    def construct(self):
        # Create shapes
        circle = Circle(color=BLUE)
        square = Square(color=RED).shift(RIGHT * 2)
        
        # Animations
        self.play(Create(circle))
        self.play(circle.animate.shift(RIGHT * 2))
        self.play(Transform(circle, square))
        self.play(square.animate.scale(2).rotate(PI/4))
        self.wait()
''',
    
    "Text Animation": '''from manim import *

class TextAnimation(Scene):
    def construct(self):
        # Create text
        text1 = Text("This is Manim", font_size=72)
        text2 = Text("Create Amazing Animations!", font_size=48, color=YELLOW)
        text2.next_to(text1, DOWN)
        
        # Animate
        self.play(Write(text1))
        self.wait()
        self.play(text1.animate.scale(0.7).shift(UP))
        self.play(FadeIn(text2, shift=UP))
        self.wait(2)
''',
    
    "Graph Example": '''from manim import *

class GraphExample(Scene):
    def construct(self):
        # Create axes
        axes = Axes(
            x_range=[-3, 3, 1],
            y_range=[-3, 3, 1],
            axis_config={"color": BLUE}
        )
        
        # Create graph
        graph = axes.plot(lambda x: x**2, color=RED)
        graph_label = axes.get_graph_label(graph, label='f(x) = x^2')
        
        # Animate
        self.play(Create(axes))
        self.play(Create(graph), Write(graph_label))
        self.wait(2)
''',
    
    "Dot Movement": '''from manim import *

class DotMovement(Scene):
    def construct(self):
        # Create dots
        dot = Dot(color=RED)
        
        # Create path
        path = VMobject()
        path.set_points_as_corners([
            LEFT * 2,
            UP * 2,
            RIGHT * 2,
            DOWN * 2,
            LEFT * 2
        ])
        path.set_color(BLUE)
        
        # Animate
        self.play(Create(path))
        self.play(MoveAlongPath(dot, path), run_time=4)
        self.wait()
''',

    "Math Formulas (Text)": '''from manim import *

class MathFormulas(Scene):
    def construct(self):
        # Math using Text() - works without LaTeX!
        title = Text("Famous Formulas", font_size=48, color=BLUE)
        title.to_edge(UP)
        
        # Einstein's equation
        einstein = Text("E = mc²", font_size=60, color=YELLOW)
        
        # Pythagorean theorem
        pythagoras = Text("a² + b² = c²", font_size=60, color=GREEN)
        pythagoras.next_to(einstein, DOWN, buff=1)
        
        # Euler's identity
        euler = Text("e^(iπ) + 1 = 0", font_size=60, color=RED)
        euler.next_to(pythagoras, DOWN, buff=1)
        
        # Animate
        self.play(Write(title))
        self.wait(0.5)
        self.play(Write(einstein), run_time=2)
        self.wait(1)
        self.play(Write(pythagoras), run_time=2)
        self.wait(1)
        self.play(Write(euler), run_time=2)
        self.wait(2)
''',

    "3D Shapes": '''from manim import *

class ThreeDShapes(Scene):
    def construct(self):
        # Note: 3D requires ThreeDScene for rotation
        # This shows basic 3D objects in 2D view
        
        title = Text("3D Objects", font_size=48)
        title.to_edge(UP)
        
        # Create 3D shapes (shown in 2D projection)
        sphere = Sphere(radius=1, color=BLUE).shift(LEFT * 3)
        cube = Cube(side_length=2, color=RED)
        torus = Torus(color=GREEN).shift(RIGHT * 3).scale(0.7)
        
        # Animate
        self.play(Write(title))
        self.wait(0.5)
        self.play(Create(sphere), Create(cube), Create(torus), run_time=3)
        self.wait(2)
''',
}

def extract_scene_class(code):
    """Extract the Scene class name from code"""
    lines = code.split('\n')
    for line in lines:
        line = line.strip()
        if line.startswith('class ') and '(Scene)' in line:
            class_name = line.split('class ')[1].split('(')[0].strip()
            return class_name
    return None

def render_manim(code, quality, progress=gr.Progress()):
    """Render Manim animation from code"""
    
    if not code.strip():
        return None, "❌ Error: Please enter some code!"
    
    # Create temp directory
    temp_dir = tempfile.mkdtemp(prefix="gradio_manim_")
    
    try:
        progress(0.1, desc="Saving code...")
        
        # Save code to file
        temp_file = os.path.join(temp_dir, "animation.py")
        with open(temp_file, 'w', encoding='utf-8') as f:
            f.write(code)
        
        # Extract scene class
        scene_class = extract_scene_class(code)
        if not scene_class:
            return None, "❌ Error: No Scene class found in code!\n\nMake sure your code has a class that inherits from Scene, like:\nclass MyAnimation(Scene):"
        
        progress(0.2, desc=f"Found scene: {scene_class}")
        
        # Prepare command
        quality_map = {
            "Low (Fast)": "l",
            "Medium": "m", 
            "High": "h",
            "Production": "p"
        }
        quality_flag = quality_map.get(quality, "l")
        
        output_dir = os.path.join(temp_dir, "media")
        
        # Use Python -m manim for cross-platform compatibility
        import sys
        cmd = [
            sys.executable, "-m", "manim", "render",
            temp_file, scene_class,
            "-q", quality_flag,
            "--media_dir", output_dir,
            "--disable_caching"
        ]
        
        progress(0.3, desc="Starting render...")
        
        print(f"Running command: {' '.join(cmd)}")
        
        # Run manim
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=300,  # 5 minute timeout
            cwd=temp_dir  # Set working directory
        )
        
        print(f"Cmd run complete. Return code: {result.returncode}")
        
        progress(0.8, desc="Processing output...")
        
        # Combine stdout and stderr for output
        output_text = result.stdout + "\n" + result.stderr
        
        if result.returncode != 0:
            error_msg = f"❌ Rendering failed!\n\n{output_text}"
            return None, error_msg
        
        progress(0.9, desc="Finding video file...")
        
        # Find the generated video
        video_file = None
        videos_dir = os.path.join(output_dir, "videos", "animation")
        
        if os.path.exists(videos_dir):
            for root, dirs, files in os.walk(videos_dir):
                for file in files:
                    if file.endswith('.mp4'):
                        video_file = os.path.join(root, file)
                        break
                if video_file:
                    break
        
        if not video_file or not os.path.exists(video_file):
            # Check for static image
            images_dir = os.path.join(output_dir, "images", "animation")
            if os.path.exists(images_dir):
                for root, dirs, files in os.walk(images_dir):
                    for file in files:
                        if file.endswith(('.png', '.jpg')):
                            return None, f"⚠️ Generated static image instead of video.\n\nYour scene has no animations!\nAdd self.play(...) commands to create animated videos.\n\nOutput:\n{output_text}"
            
            return None, f"❌ Could not find output file.\n\n{output_text}"
        
        progress(1.0, desc="Done!")
        
        # Success message
        file_size = os.path.getsize(video_file) / (1024 * 1024)  # MB
        success_msg = f"✅ Animation rendered successfully!\n\n📊 Details:\n• Scene: {scene_class}\n• Quality: {quality}\n• File size: {file_size:.2f} MB"
        
        return video_file, success_msg
        
    except subprocess.TimeoutExpired:
        return None, "❌ Error: Rendering took too long (>5 minutes). Try reducing animation length or quality."
    except Exception as e:
        return None, f"❌ Error: {str(e)}"
    finally:
        # Cleanup is handled by Gradio, but we keep temp dir for video access
        pass

def load_example(example_name):
    """Load example code"""
    return EXAMPLE_CODES.get(example_name, DEFAULT_CODE)

def clear_all():
    """Clear all fields"""
    return "", None, "Ready to render!"

# Create Gradio interface
with gr.Blocks(title="Manim Animation Editor", theme=gr.themes.Soft()) as demo:
    gr.Markdown("""
    # 🎬 Manim Animation Editor
    
    Create mathematical animations using Manim! Write your code, click render, and watch your animation come to life.
    
    **📚 Quick Tips:**
    - Use `self.play(...)` to create animations (required for videos!)
    - Use `self.wait()` to pause
    - Use `Text()` for formulas (works everywhere!)
    - ⚠️ `MathTex()` requires LaTeX (may not work on hosted versions)
    - Check the examples below to get started
    """)
    
    with gr.Row():
        with gr.Column(scale=2):
            # Code editor
            code_input = gr.Code(
                value=DEFAULT_CODE,
                language="python",
                label="📝 Manim Code",
                lines=25
            )
            
            with gr.Row():
                # Quality selector
                quality_input = gr.Radio(
                    choices=["Low (Fast)", "Medium", "High", "Production"],
                    value="Low (Fast)",
                    label="🎥 Quality",
                    info="Higher quality = longer render time"
                )
                
            with gr.Row():
                # Action buttons
                render_btn = gr.Button("▶️ Render Animation", variant="primary", size="lg")
                clear_btn = gr.Button("🗑️ Clear", size="lg")
            
            # Examples
            gr.Markdown("### 📚 Example Animations")
            example_dropdown = gr.Dropdown(
                choices=list(EXAMPLE_CODES.keys()),
                label="Load Example",
                value="Simple Animation"
            )
            load_example_btn = gr.Button("Load Example Code")
            
        with gr.Column(scale=2):
            # Video output
            video_output = gr.Video(
                label="🎬 Animation Output",
                height=400
            )
            
            # Status/output text
            output_text = gr.Textbox(
                label="📋 Status & Console Output",
                value="Ready to render! Write your code and click 'Render Animation'.",
                lines=15,
                max_lines=15
            )
    
    # Info section
    with gr.Accordion("ℹ️ Help & Documentation", open=False):
        gr.Markdown("""
        ### Getting Started
        
        **Basic Structure:**
        ```python
        from manim import *
        
        class MyAnimation(Scene):
            def construct(self):
                # Create objects
                text = Text("Hello!")
                
                # Animate them
                self.play(Write(text))
                self.wait()
        ```
        
        **Common Commands:**
        - `Write(obj)` - Write text or formula
        - `Create(obj)` - Draw shapes
        - `FadeIn(obj)` - Fade in object
        - `Transform(obj1, obj2)` - Transform one object to another
        - `obj.animate.move_to(...)` - Move object
        - `self.wait(seconds)` - Pause for seconds
        
        **Common Objects:**
        - `Text("...")` - Text
        - `Circle()` - Circle
        - `Square()` - Square
        - `Line(start, end)` - Line
        - `Dot()` - Dot/Point
        
        **Colors:**
        - `RED`, `BLUE`, `GREEN`, `YELLOW`, `PURPLE`, `ORANGE`, `WHITE`, `BLACK`
        
        **Positions:**
        - `UP`, `DOWN`, `LEFT`, `RIGHT`
        - `ORIGIN` - Center (0, 0, 0)
        
        ### Requirements
        - Manim Community must be installed: `pip install manim`
        - For math formulas (MathTex), LaTeX must be installed
        
        ### Troubleshooting
        - **"No Scene class found"** - Make sure you have `class YourName(Scene):`
        - **"Static image generated"** - Add `self.play(...)` commands for animations
        - **LaTeX errors** - Use `Text()` instead of `MathTex()` or install LaTeX
        """)
    
    # Event handlers
    render_btn.click(
        fn=render_manim,
        inputs=[code_input, quality_input],
        outputs=[video_output, output_text]
    )
    
    load_example_btn.click(
        fn=load_example,
        inputs=[example_dropdown],
        outputs=[code_input]
    )
    
    clear_btn.click(
        fn=clear_all,
        outputs=[code_input, video_output, output_text]
    )
    
    # Footer
    gr.Markdown("""
    ---
    💡 **Pro Tip:** Start with examples, then modify them to learn!
    
    📖 [Manim Documentation](https://docs.manim.community/) | 
    🎓 [Manim Tutorial](https://docs.manim.community/en/stable/tutorials.html)
    """)

# Launch the app
if __name__ == "__main__":
    print("🚀 Starting Manim Animation Editor...")
    print("📱 Access from mobile: Use the public URL (with share=True)")
    print("💻 Local access: http://localhost:7860")
    
    demo.launch(
        # share=False,  # Set to True for public URL
        # debug=True
    )