Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -26,6 +26,10 @@ import pandas as pd
|
|
26 |
import plotly.express as px
|
27 |
import markdown
|
28 |
import zipfile
|
|
|
|
|
|
|
|
|
29 |
|
30 |
# Set up enhanced logging
|
31 |
logging.basicConfig(
|
@@ -63,7 +67,6 @@ def ensure_packages():
|
|
63 |
'matplotlib': '3.5.0', # For Python script runner
|
64 |
'seaborn': '0.11.2', # For enhanced visualizations
|
65 |
'scipy': '1.7.3', # For scientific computations
|
66 |
-
'accelerate': '0.20.0',
|
67 |
}
|
68 |
|
69 |
with st.spinner("Checking required packages..."):
|
@@ -302,7 +305,7 @@ QUALITY_PRESETS = {
|
|
302 |
"720p": {"resolution": "720p", "fps": "30"},
|
303 |
"1080p": {"resolution": "1080p", "fps": "60"},
|
304 |
"4K": {"resolution": "2160p", "fps": "60"},
|
305 |
-
"8K": {"resolution": "4320p", "fps": "60"}
|
306 |
}
|
307 |
|
308 |
# Animation speeds
|
@@ -510,7 +513,7 @@ def generate_manim_video(python_code, format_type, quality_preset, animation_spe
|
|
510 |
"720p": "-qm", # Medium quality
|
511 |
"1080p": "-qh", # High quality
|
512 |
"4K": "-qk", # 4K quality
|
513 |
-
"8K": "-
|
514 |
}
|
515 |
quality_flag = quality_map.get(quality_preset, "-qm")
|
516 |
|
@@ -1170,7 +1173,7 @@ def create_timeline_editor(code):
|
|
1170 |
animation_steps = df.to_dict('records')
|
1171 |
new_code = generate_code_from_timeline(animation_steps, code)
|
1172 |
|
1173 |
-
st.success("Timeline updated!
|
1174 |
return new_code
|
1175 |
|
1176 |
# Visual keyframe editor
|
@@ -1467,32 +1470,6 @@ def export_to_educational_format(video_data, format_type, animation_title, expla
|
|
1467 |
logger.error(traceback.format_exc())
|
1468 |
return None, None
|
1469 |
|
1470 |
-
def process_multiple_images(uploaded_images):
|
1471 |
-
"""Process multiple uploaded images and return information about them"""
|
1472 |
-
processed_images = []
|
1473 |
-
|
1474 |
-
# Create a unique image directory if it doesn't exist
|
1475 |
-
image_dir = os.path.join(os.getcwd(), "manim_assets", "images")
|
1476 |
-
os.makedirs(image_dir, exist_ok=True)
|
1477 |
-
|
1478 |
-
for img in uploaded_images:
|
1479 |
-
# Generate a unique filename and save the image
|
1480 |
-
file_extension = img.name.split(".")[-1]
|
1481 |
-
unique_filename = f"image_{int(time.time())}_{len(processed_images)}.{file_extension}"
|
1482 |
-
image_path = os.path.join(image_dir, unique_filename)
|
1483 |
-
|
1484 |
-
with open(image_path, "wb") as f:
|
1485 |
-
f.write(img.getvalue())
|
1486 |
-
|
1487 |
-
# Store image info
|
1488 |
-
processed_images.append({
|
1489 |
-
"name": img.name,
|
1490 |
-
"path": image_path,
|
1491 |
-
"preview": img
|
1492 |
-
})
|
1493 |
-
|
1494 |
-
return processed_images
|
1495 |
-
|
1496 |
def main():
|
1497 |
# Initialize session state variables if they don't exist
|
1498 |
if 'init' not in st.session_state:
|
@@ -1511,13 +1488,12 @@ def main():
|
|
1511 |
st.session_state.custom_library_result = ""
|
1512 |
st.session_state.python_script = "import matplotlib.pyplot as plt\nimport numpy as np\n\n# Example: Create a simple plot\nx = np.linspace(0, 10, 100)\ny = np.sin(x)\n\nplt.figure(figsize=(10, 6))\nplt.plot(x, y, 'b-', label='sin(x)')\nplt.title('Sine Wave')\nplt.xlabel('x')\nplt.ylabel('sin(x)')\nplt.grid(True)\nplt.legend()\n"
|
1513 |
st.session_state.python_result = None
|
|
|
1514 |
st.session_state.settings = {
|
1515 |
"quality": "720p",
|
1516 |
"format_type": "mp4",
|
1517 |
"animation_speed": "Normal"
|
1518 |
}
|
1519 |
-
st.session_state.pending_changes = False # Track if there are changes that need refresh
|
1520 |
-
st.session_state.pending_temp_code = "" # Store code changes before refresh
|
1521 |
|
1522 |
# Page configuration with improved layout
|
1523 |
st.set_page_config(
|
@@ -1625,41 +1601,6 @@ def main():
|
|
1625 |
margin-top: 1rem;
|
1626 |
border-left: 4px solid #ef4444;
|
1627 |
}
|
1628 |
-
|
1629 |
-
.refresh-button {
|
1630 |
-
background-color: #4CAF50;
|
1631 |
-
color: white;
|
1632 |
-
padding: 10px 20px;
|
1633 |
-
border: none;
|
1634 |
-
border-radius: 5px;
|
1635 |
-
cursor: pointer;
|
1636 |
-
font-weight: bold;
|
1637 |
-
margin: 10px 0;
|
1638 |
-
}
|
1639 |
-
|
1640 |
-
.refresh-button:hover {
|
1641 |
-
background-color: #45a049;
|
1642 |
-
}
|
1643 |
-
|
1644 |
-
.image-grid {
|
1645 |
-
display: grid;
|
1646 |
-
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
1647 |
-
gap: 10px;
|
1648 |
-
margin-top: 10px;
|
1649 |
-
}
|
1650 |
-
|
1651 |
-
.image-item {
|
1652 |
-
border: 1px solid #ddd;
|
1653 |
-
border-radius: 5px;
|
1654 |
-
padding: 5px;
|
1655 |
-
text-align: center;
|
1656 |
-
}
|
1657 |
-
|
1658 |
-
.image-item img {
|
1659 |
-
max-width: 100%;
|
1660 |
-
height: auto;
|
1661 |
-
border-radius: 3px;
|
1662 |
-
}
|
1663 |
</style>
|
1664 |
""", unsafe_allow_html=True)
|
1665 |
|
@@ -1684,18 +1625,11 @@ def main():
|
|
1684 |
st.session_state.ai_models = init_ai_models()
|
1685 |
|
1686 |
# Create main tabs
|
1687 |
-
|
|
|
1688 |
|
1689 |
-
# Sidebar for rendering settings
|
1690 |
with st.sidebar:
|
1691 |
-
# Add refresh button at the top of the sidebar
|
1692 |
-
if st.button("🔄 Refresh App", key="refresh_button", help="Refresh the app to apply all changes", use_container_width=True):
|
1693 |
-
st.success("App refreshed!")
|
1694 |
-
if st.session_state.pending_changes:
|
1695 |
-
st.session_state.code = st.session_state.pending_temp_code
|
1696 |
-
st.session_state.temp_code = st.session_state.pending_temp_code
|
1697 |
-
st.session_state.pending_changes = False
|
1698 |
-
|
1699 |
# Rendering settings section
|
1700 |
st.markdown("## ⚙️ Rendering Settings")
|
1701 |
|
@@ -1775,36 +1709,31 @@ def main():
|
|
1775 |
if uploaded_file:
|
1776 |
code_content = uploaded_file.getvalue().decode("utf-8")
|
1777 |
if code_content.strip(): # Only update if file has content
|
1778 |
-
st.session_state.
|
1779 |
-
st.session_state.
|
1780 |
-
st.info("Code loaded from file. Click 'Refresh App' to apply changes.")
|
1781 |
|
1782 |
# Code editor
|
1783 |
if ACE_EDITOR_AVAILABLE:
|
1784 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
1785 |
-
temp_code = st_ace(
|
1786 |
value=current_code,
|
1787 |
language="python",
|
1788 |
theme="monokai",
|
1789 |
min_lines=20,
|
1790 |
key="ace_editor"
|
1791 |
)
|
1792 |
-
if temp_code != current_code:
|
1793 |
-
st.session_state.pending_temp_code = temp_code
|
1794 |
-
st.session_state.pending_changes = True
|
1795 |
-
st.info("Code changes detected. Click 'Refresh App' to apply changes.")
|
1796 |
else:
|
1797 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
1798 |
-
temp_code = st.text_area(
|
1799 |
"Manim Python Code",
|
1800 |
value=current_code,
|
1801 |
height=400,
|
1802 |
key="code_textarea"
|
1803 |
)
|
1804 |
-
|
1805 |
-
|
1806 |
-
|
1807 |
-
|
1808 |
|
1809 |
# Generate button (use a form to prevent page reloads)
|
1810 |
generate_btn = st.button("🚀 Generate Animation", use_container_width=True, key="generate_btn")
|
@@ -1825,9 +1754,8 @@ class MyScene(Scene):
|
|
1825 |
self.wait(2)
|
1826 |
"""
|
1827 |
st.session_state.code += default_scene
|
1828 |
-
st.session_state.
|
1829 |
-
st.
|
1830 |
-
st.warning("No scene class found. Added a default scene. Click 'Refresh App' to see changes.")
|
1831 |
|
1832 |
with st.spinner("Generating animation..."):
|
1833 |
video_data, status = generate_manim_video(
|
@@ -1915,7 +1843,7 @@ class MyScene(Scene):
|
|
1915 |
1. **Syntax Errors**: Check your Python code for any syntax issues
|
1916 |
2. **Missing Scene Class**: Ensure your code contains a scene class that extends Scene
|
1917 |
3. **High Resolution Issues**: Try a lower quality preset for complex animations
|
1918 |
-
4. **Memory Issues**: For 4K
|
1919 |
5. **Format Issues**: Some formats require specific Manim configurations
|
1920 |
6. **GIF Generation**: If GIF doesn't work, try MP4 and we'll convert it automatically
|
1921 |
|
@@ -1987,9 +1915,11 @@ class MyScene(Scene):
|
|
1987 |
col_ai1, col_ai2 = st.columns(2)
|
1988 |
with col_ai1:
|
1989 |
if st.button("Use This Code", key="use_gen_code"):
|
1990 |
-
st.session_state.
|
1991 |
-
st.session_state.
|
1992 |
-
|
|
|
|
|
1993 |
|
1994 |
with col_ai2:
|
1995 |
if st.button("Render Preview", key="render_preview"):
|
@@ -2032,9 +1962,8 @@ class MyScene(Scene):
|
|
2032 |
key="latex_input"
|
2033 |
)
|
2034 |
|
2035 |
-
# Update session state
|
2036 |
-
|
2037 |
-
st.session_state.latex_formula = latex_input
|
2038 |
|
2039 |
# Common LaTeX formulas library
|
2040 |
st.markdown("#### Formula Library")
|
@@ -2082,9 +2011,10 @@ class MyScene(Scene):
|
|
2082 |
with formula_tabs[i]:
|
2083 |
for formula in formulas:
|
2084 |
if st.button(formula["name"], key=f"latex_{formula['name']}"):
|
|
|
2085 |
st.session_state.latex_formula = formula["latex"]
|
2086 |
-
#
|
2087 |
-
st.
|
2088 |
|
2089 |
# LaTeX code snippet
|
2090 |
st.markdown("#### Manim Code Snippet")
|
@@ -2122,10 +2052,13 @@ self.wait(2)
|
|
2122 |
# If we didn't find content, append to the end with default indentation
|
2123 |
lines.append(" " + "\n ".join(manim_latex_code.strip().split("\n")))
|
2124 |
|
2125 |
-
|
2126 |
-
st.session_state.
|
2127 |
-
st.
|
2128 |
-
|
|
|
|
|
|
|
2129 |
else:
|
2130 |
st.warning("Could not find 'construct' method in your code. Please add a scene class first.")
|
2131 |
else:
|
@@ -2139,9 +2072,13 @@ class LatexScene(Scene):
|
|
2139 |
self.play(Write(formula))
|
2140 |
self.wait(2)
|
2141 |
"""
|
2142 |
-
st.session_state.
|
2143 |
-
st.session_state.
|
2144 |
-
st.success("Created new scene with your LaTeX formula
|
|
|
|
|
|
|
|
|
2145 |
|
2146 |
with col_latex2:
|
2147 |
# LaTeX preview
|
@@ -2183,79 +2120,102 @@ class LatexScene(Scene):
|
|
2183 |
st.markdown("#### 📸 Image Assets")
|
2184 |
st.markdown("Upload images to use in your animations:")
|
2185 |
|
2186 |
-
#
|
2187 |
-
uploaded_images = st.file_uploader(
|
|
|
|
|
|
|
|
|
|
|
2188 |
|
2189 |
if uploaded_images:
|
2190 |
-
#
|
2191 |
-
|
2192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2193 |
|
2194 |
-
|
|
|
|
|
|
|
2195 |
if "image_paths" not in st.session_state:
|
2196 |
st.session_state.image_paths = []
|
2197 |
|
2198 |
-
|
2199 |
-
|
2200 |
-
|
2201 |
-
|
|
|
|
|
|
|
2202 |
|
2203 |
-
|
2204 |
-
|
2205 |
-
|
2206 |
-
|
2207 |
-
|
2208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2209 |
|
2210 |
-
|
2211 |
-
|
2212 |
-
|
2213 |
-
|
2214 |
-
|
2215 |
-
|
2216 |
-
|
2217 |
-
image{i+1} = ImageMobject(r"{img_info['path']}")
|
2218 |
-
image{i+1}.scale(2) # Adjust size as needed
|
2219 |
-
self.play(FadeIn(image{i+1}))
|
2220 |
self.wait(1)
|
2221 |
"""
|
2222 |
-
|
2223 |
-
|
2224 |
-
all_images_code = "\n".join(image_code_samples)
|
2225 |
-
st.code(all_images_code, language="python")
|
2226 |
-
|
2227 |
-
if st.button("Insert All Images Code", key="insert_images_code_btn"):
|
2228 |
-
if not st.session_state.code:
|
2229 |
-
base_code = """from manim import *
|
2230 |
|
2231 |
class ImageScene(Scene):
|
2232 |
def construct(self):
|
2233 |
"""
|
2234 |
-
|
2235 |
-
|
2236 |
-
|
2237 |
-
|
2238 |
-
|
2239 |
-
|
2240 |
-
|
2241 |
-
|
|
|
|
|
|
|
|
|
2242 |
|
2243 |
# Display previously uploaded images
|
2244 |
if st.session_state.image_paths:
|
2245 |
-
st.
|
2246 |
-
|
2247 |
-
|
2248 |
-
|
2249 |
-
|
2250 |
-
|
2251 |
-
|
2252 |
-
|
2253 |
-
|
2254 |
-
|
2255 |
-
|
2256 |
-
|
2257 |
-
|
2258 |
-
|
|
|
2259 |
|
2260 |
with asset_col2:
|
2261 |
# Audio uploader section
|
@@ -2341,8 +2301,8 @@ class YourScene(Scene):
|
|
2341 |
|
2342 |
# If code was modified by the timeline editor, update the session state
|
2343 |
if updated_code != st.session_state.code:
|
2344 |
-
st.session_state.
|
2345 |
-
st.session_state.
|
2346 |
|
2347 |
# EDUCATIONAL EXPORT TAB
|
2348 |
with tabs[5]:
|
@@ -2626,11 +2586,13 @@ plt.legend()
|
|
2626 |
|
2627 |
# Python code editor
|
2628 |
if selected_example != "Select an example..." and selected_example in example_scripts:
|
2629 |
-
|
|
|
|
|
2630 |
|
2631 |
if ACE_EDITOR_AVAILABLE:
|
2632 |
python_code = st_ace(
|
2633 |
-
value=
|
2634 |
language="python",
|
2635 |
theme="monokai",
|
2636 |
min_lines=15,
|
@@ -2639,12 +2601,12 @@ plt.legend()
|
|
2639 |
else:
|
2640 |
python_code = st.text_area(
|
2641 |
"Python Code",
|
2642 |
-
value=
|
2643 |
height=400,
|
2644 |
key="python_textarea"
|
2645 |
)
|
2646 |
|
2647 |
-
#
|
2648 |
st.session_state.python_script = python_code
|
2649 |
|
2650 |
# Check for input() calls
|
@@ -2707,20 +2669,25 @@ self.wait(1)
|
|
2707 |
"""
|
2708 |
# Insert into editor code
|
2709 |
if st.session_state.code:
|
2710 |
-
|
2711 |
-
st.session_state.
|
2712 |
-
st.
|
2713 |
-
|
|
|
|
|
2714 |
else:
|
2715 |
basic_scene = f"""from manim import *
|
2716 |
|
2717 |
class PlotScene(Scene):
|
2718 |
def construct(self):
|
2719 |
-
{plot_code}
|
2720 |
"""
|
2721 |
-
st.session_state.
|
2722 |
-
st.session_state.
|
2723 |
-
st.success(f"Created new scene with Plot {i+1}!
|
|
|
|
|
|
|
2724 |
|
2725 |
# Provide option to save the script
|
2726 |
if st.button("📄 Save This Script", key="save_script_btn"):
|
@@ -2795,6 +2762,13 @@ class PlotScene(Scene):
|
|
2795 |
self.wait(2)
|
2796 |
```
|
2797 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2798 |
|
2799 |
if __name__ == "__main__":
|
2800 |
main()
|
|
|
26 |
import plotly.express as px
|
27 |
import markdown
|
28 |
import zipfile
|
29 |
+
import contextlib
|
30 |
+
import threading
|
31 |
+
import traceback
|
32 |
+
from io import StringIO, BytesIO
|
33 |
|
34 |
# Set up enhanced logging
|
35 |
logging.basicConfig(
|
|
|
67 |
'matplotlib': '3.5.0', # For Python script runner
|
68 |
'seaborn': '0.11.2', # For enhanced visualizations
|
69 |
'scipy': '1.7.3', # For scientific computations
|
|
|
70 |
}
|
71 |
|
72 |
with st.spinner("Checking required packages..."):
|
|
|
305 |
"720p": {"resolution": "720p", "fps": "30"},
|
306 |
"1080p": {"resolution": "1080p", "fps": "60"},
|
307 |
"4K": {"resolution": "2160p", "fps": "60"},
|
308 |
+
"8K": {"resolution": "4320p", "fps": "60"} # Added 8K option
|
309 |
}
|
310 |
|
311 |
# Animation speeds
|
|
|
513 |
"720p": "-qm", # Medium quality
|
514 |
"1080p": "-qh", # High quality
|
515 |
"4K": "-qk", # 4K quality
|
516 |
+
"8K": "-qp" # 8K quality (production quality)
|
517 |
}
|
518 |
quality_flag = quality_map.get(quality_preset, "-qm")
|
519 |
|
|
|
1173 |
animation_steps = df.to_dict('records')
|
1174 |
new_code = generate_code_from_timeline(animation_steps, code)
|
1175 |
|
1176 |
+
st.success("Timeline updated! Code has been regenerated.")
|
1177 |
return new_code
|
1178 |
|
1179 |
# Visual keyframe editor
|
|
|
1470 |
logger.error(traceback.format_exc())
|
1471 |
return None, None
|
1472 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1473 |
def main():
|
1474 |
# Initialize session state variables if they don't exist
|
1475 |
if 'init' not in st.session_state:
|
|
|
1488 |
st.session_state.custom_library_result = ""
|
1489 |
st.session_state.python_script = "import matplotlib.pyplot as plt\nimport numpy as np\n\n# Example: Create a simple plot\nx = np.linspace(0, 10, 100)\ny = np.sin(x)\n\nplt.figure(figsize=(10, 6))\nplt.plot(x, y, 'b-', label='sin(x)')\nplt.title('Sine Wave')\nplt.xlabel('x')\nplt.ylabel('sin(x)')\nplt.grid(True)\nplt.legend()\n"
|
1490 |
st.session_state.python_result = None
|
1491 |
+
st.session_state.active_tab = 0 # Track currently active tab
|
1492 |
st.session_state.settings = {
|
1493 |
"quality": "720p",
|
1494 |
"format_type": "mp4",
|
1495 |
"animation_speed": "Normal"
|
1496 |
}
|
|
|
|
|
1497 |
|
1498 |
# Page configuration with improved layout
|
1499 |
st.set_page_config(
|
|
|
1601 |
margin-top: 1rem;
|
1602 |
border-left: 4px solid #ef4444;
|
1603 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1604 |
</style>
|
1605 |
""", unsafe_allow_html=True)
|
1606 |
|
|
|
1625 |
st.session_state.ai_models = init_ai_models()
|
1626 |
|
1627 |
# Create main tabs
|
1628 |
+
tab_names = ["✨ Editor", "🤖 AI Assistant", "📚 LaTeX Formulas", "🎨 Assets", "🎞️ Timeline", "🎓 Educational Export", "🐍 Python Runner"]
|
1629 |
+
tabs = st.tabs(tab_names)
|
1630 |
|
1631 |
+
# Sidebar for rendering settings and custom libraries
|
1632 |
with st.sidebar:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1633 |
# Rendering settings section
|
1634 |
st.markdown("## ⚙️ Rendering Settings")
|
1635 |
|
|
|
1709 |
if uploaded_file:
|
1710 |
code_content = uploaded_file.getvalue().decode("utf-8")
|
1711 |
if code_content.strip(): # Only update if file has content
|
1712 |
+
st.session_state.code = code_content
|
1713 |
+
st.session_state.temp_code = code_content
|
|
|
1714 |
|
1715 |
# Code editor
|
1716 |
if ACE_EDITOR_AVAILABLE:
|
1717 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
1718 |
+
st.session_state.temp_code = st_ace(
|
1719 |
value=current_code,
|
1720 |
language="python",
|
1721 |
theme="monokai",
|
1722 |
min_lines=20,
|
1723 |
key="ace_editor"
|
1724 |
)
|
|
|
|
|
|
|
|
|
1725 |
else:
|
1726 |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else ""
|
1727 |
+
st.session_state.temp_code = st.text_area(
|
1728 |
"Manim Python Code",
|
1729 |
value=current_code,
|
1730 |
height=400,
|
1731 |
key="code_textarea"
|
1732 |
)
|
1733 |
+
|
1734 |
+
# Update code in session state if it changed
|
1735 |
+
if st.session_state.temp_code != st.session_state.code:
|
1736 |
+
st.session_state.code = st.session_state.temp_code
|
1737 |
|
1738 |
# Generate button (use a form to prevent page reloads)
|
1739 |
generate_btn = st.button("🚀 Generate Animation", use_container_width=True, key="generate_btn")
|
|
|
1754 |
self.wait(2)
|
1755 |
"""
|
1756 |
st.session_state.code += default_scene
|
1757 |
+
st.session_state.temp_code = st.session_state.code
|
1758 |
+
st.warning("No scene class found. Added a default scene.")
|
|
|
1759 |
|
1760 |
with st.spinner("Generating animation..."):
|
1761 |
video_data, status = generate_manim_video(
|
|
|
1843 |
1. **Syntax Errors**: Check your Python code for any syntax issues
|
1844 |
2. **Missing Scene Class**: Ensure your code contains a scene class that extends Scene
|
1845 |
3. **High Resolution Issues**: Try a lower quality preset for complex animations
|
1846 |
+
4. **Memory Issues**: For 4K animations, reduce complexity or try again
|
1847 |
5. **Format Issues**: Some formats require specific Manim configurations
|
1848 |
6. **GIF Generation**: If GIF doesn't work, try MP4 and we'll convert it automatically
|
1849 |
|
|
|
1915 |
col_ai1, col_ai2 = st.columns(2)
|
1916 |
with col_ai1:
|
1917 |
if st.button("Use This Code", key="use_gen_code"):
|
1918 |
+
st.session_state.code = st.session_state.generated_code
|
1919 |
+
st.session_state.temp_code = st.session_state.generated_code
|
1920 |
+
# Switch to Editor tab
|
1921 |
+
st.session_state.active_tab = 0
|
1922 |
+
st.rerun()
|
1923 |
|
1924 |
with col_ai2:
|
1925 |
if st.button("Render Preview", key="render_preview"):
|
|
|
1962 |
key="latex_input"
|
1963 |
)
|
1964 |
|
1965 |
+
# Update session state
|
1966 |
+
st.session_state.latex_formula = latex_input
|
|
|
1967 |
|
1968 |
# Common LaTeX formulas library
|
1969 |
st.markdown("#### Formula Library")
|
|
|
2011 |
with formula_tabs[i]:
|
2012 |
for formula in formulas:
|
2013 |
if st.button(formula["name"], key=f"latex_{formula['name']}"):
|
2014 |
+
# Insert formula into the text area
|
2015 |
st.session_state.latex_formula = formula["latex"]
|
2016 |
+
# Rerun to update the text area and preview
|
2017 |
+
st.rerun()
|
2018 |
|
2019 |
# LaTeX code snippet
|
2020 |
st.markdown("#### Manim Code Snippet")
|
|
|
2052 |
# If we didn't find content, append to the end with default indentation
|
2053 |
lines.append(" " + "\n ".join(manim_latex_code.strip().split("\n")))
|
2054 |
|
2055 |
+
st.session_state.code = "\n".join(lines)
|
2056 |
+
st.session_state.temp_code = st.session_state.code
|
2057 |
+
st.success("LaTeX formula inserted into the editor!")
|
2058 |
+
|
2059 |
+
# Switch to Editor tab
|
2060 |
+
st.session_state.active_tab = 0
|
2061 |
+
st.rerun()
|
2062 |
else:
|
2063 |
st.warning("Could not find 'construct' method in your code. Please add a scene class first.")
|
2064 |
else:
|
|
|
2072 |
self.play(Write(formula))
|
2073 |
self.wait(2)
|
2074 |
"""
|
2075 |
+
st.session_state.code = basic_scene
|
2076 |
+
st.session_state.temp_code = basic_scene
|
2077 |
+
st.success("Created new scene with your LaTeX formula!")
|
2078 |
+
|
2079 |
+
# Switch to Editor tab
|
2080 |
+
st.session_state.active_tab = 0
|
2081 |
+
st.rerun()
|
2082 |
|
2083 |
with col_latex2:
|
2084 |
# LaTeX preview
|
|
|
2120 |
st.markdown("#### 📸 Image Assets")
|
2121 |
st.markdown("Upload images to use in your animations:")
|
2122 |
|
2123 |
+
# Allow multiple image uploads
|
2124 |
+
uploaded_images = st.file_uploader(
|
2125 |
+
"Upload Images",
|
2126 |
+
type=["jpg", "png", "jpeg", "svg"],
|
2127 |
+
accept_multiple_files=True,
|
2128 |
+
key="image_uploader_tab"
|
2129 |
+
)
|
2130 |
|
2131 |
if uploaded_images:
|
2132 |
+
# Create a unique image directory if it doesn't exist
|
2133 |
+
image_dir = os.path.join(os.getcwd(), "manim_assets", "images")
|
2134 |
+
os.makedirs(image_dir, exist_ok=True)
|
2135 |
+
|
2136 |
+
# Process each uploaded image
|
2137 |
+
for uploaded_image in uploaded_images:
|
2138 |
+
# Generate a unique filename and save the image
|
2139 |
+
file_extension = uploaded_image.name.split(".")[-1]
|
2140 |
+
unique_filename = f"image_{int(time.time())}_{uuid.uuid4().hex[:8]}.{file_extension}"
|
2141 |
+
image_path = os.path.join(image_dir, unique_filename)
|
2142 |
|
2143 |
+
with open(image_path, "wb") as f:
|
2144 |
+
f.write(uploaded_image.getvalue())
|
2145 |
+
|
2146 |
+
# Store the path in session state
|
2147 |
if "image_paths" not in st.session_state:
|
2148 |
st.session_state.image_paths = []
|
2149 |
|
2150 |
+
# Check if this image was already added
|
2151 |
+
# Check if this image was already added
|
2152 |
+
image_already_added = False
|
2153 |
+
for img in st.session_state.image_paths:
|
2154 |
+
if img["name"] == uploaded_image.name:
|
2155 |
+
image_already_added = True
|
2156 |
+
break
|
2157 |
|
2158 |
+
if not image_already_added:
|
2159 |
+
st.session_state.image_paths.append({
|
2160 |
+
"name": uploaded_image.name,
|
2161 |
+
"path": image_path
|
2162 |
+
})
|
2163 |
+
|
2164 |
+
# Display uploaded images in a grid
|
2165 |
+
st.markdown("##### Uploaded Images:")
|
2166 |
+
image_cols = st.columns(3)
|
2167 |
+
|
2168 |
+
for i, img_info in enumerate(st.session_state.image_paths[-len(uploaded_images):]):
|
2169 |
+
with image_cols[i % 3]:
|
2170 |
+
try:
|
2171 |
+
img = Image.open(img_info["path"])
|
2172 |
+
st.image(img, caption=img_info["name"], width=150)
|
2173 |
|
2174 |
+
# Show code snippet for this specific image
|
2175 |
+
if st.button(f"Use {img_info['name']}", key=f"use_img_{i}"):
|
2176 |
+
image_code = f"""
|
2177 |
+
# Load and display image
|
2178 |
+
image = ImageMobject(r"{img_info['path']}")
|
2179 |
+
image.scale(2) # Adjust size as needed
|
2180 |
+
self.play(FadeIn(image))
|
|
|
|
|
|
|
2181 |
self.wait(1)
|
2182 |
"""
|
2183 |
+
if not st.session_state.code:
|
2184 |
+
base_code = """from manim import *
|
|
|
|
|
|
|
|
|
|
|
|
|
2185 |
|
2186 |
class ImageScene(Scene):
|
2187 |
def construct(self):
|
2188 |
"""
|
2189 |
+
st.session_state.code = base_code + "\n " + image_code.replace("\n", "\n ")
|
2190 |
+
else:
|
2191 |
+
st.session_state.code += "\n" + image_code
|
2192 |
+
|
2193 |
+
st.session_state.temp_code = st.session_state.code
|
2194 |
+
st.success(f"Added {img_info['name']} to your code!")
|
2195 |
+
|
2196 |
+
# Switch to Editor tab
|
2197 |
+
st.session_state.active_tab = 0
|
2198 |
+
st.rerun()
|
2199 |
+
except Exception as e:
|
2200 |
+
st.error(f"Error loading image {img_info['name']}: {e}")
|
2201 |
|
2202 |
# Display previously uploaded images
|
2203 |
if st.session_state.image_paths:
|
2204 |
+
with st.expander("Previously Uploaded Images"):
|
2205 |
+
# Group images by 3 in each row
|
2206 |
+
for i in range(0, len(st.session_state.image_paths), 3):
|
2207 |
+
prev_cols = st.columns(3)
|
2208 |
+
for j in range(3):
|
2209 |
+
if i+j < len(st.session_state.image_paths):
|
2210 |
+
img_info = st.session_state.image_paths[i+j]
|
2211 |
+
with prev_cols[j]:
|
2212 |
+
try:
|
2213 |
+
img = Image.open(img_info["path"])
|
2214 |
+
st.image(img, caption=img_info["name"], width=100)
|
2215 |
+
st.markdown(f"<div class='small-text'>Path: {img_info['path']}</div>", unsafe_allow_html=True)
|
2216 |
+
except:
|
2217 |
+
st.markdown(f"**{img_info['name']}**")
|
2218 |
+
st.markdown(f"<div class='small-text'>Path: {img_info['path']}</div>", unsafe_allow_html=True)
|
2219 |
|
2220 |
with asset_col2:
|
2221 |
# Audio uploader section
|
|
|
2301 |
|
2302 |
# If code was modified by the timeline editor, update the session state
|
2303 |
if updated_code != st.session_state.code:
|
2304 |
+
st.session_state.code = updated_code
|
2305 |
+
st.session_state.temp_code = updated_code
|
2306 |
|
2307 |
# EDUCATIONAL EXPORT TAB
|
2308 |
with tabs[5]:
|
|
|
2586 |
|
2587 |
# Python code editor
|
2588 |
if selected_example != "Select an example..." and selected_example in example_scripts:
|
2589 |
+
python_code = example_scripts[selected_example]
|
2590 |
+
else:
|
2591 |
+
python_code = st.session_state.python_script
|
2592 |
|
2593 |
if ACE_EDITOR_AVAILABLE:
|
2594 |
python_code = st_ace(
|
2595 |
+
value=python_code,
|
2596 |
language="python",
|
2597 |
theme="monokai",
|
2598 |
min_lines=15,
|
|
|
2601 |
else:
|
2602 |
python_code = st.text_area(
|
2603 |
"Python Code",
|
2604 |
+
value=python_code,
|
2605 |
height=400,
|
2606 |
key="python_textarea"
|
2607 |
)
|
2608 |
|
2609 |
+
# Store script in session state (without clearing existing code)
|
2610 |
st.session_state.python_script = python_code
|
2611 |
|
2612 |
# Check for input() calls
|
|
|
2669 |
"""
|
2670 |
# Insert into editor code
|
2671 |
if st.session_state.code:
|
2672 |
+
st.session_state.code += "\n" + plot_code
|
2673 |
+
st.session_state.temp_code = st.session_state.code
|
2674 |
+
st.success(f"Plot {i+1} added to your animation code!")
|
2675 |
+
# Switch to Editor tab
|
2676 |
+
st.session_state.active_tab = 0
|
2677 |
+
st.rerun()
|
2678 |
else:
|
2679 |
basic_scene = f"""from manim import *
|
2680 |
|
2681 |
class PlotScene(Scene):
|
2682 |
def construct(self):
|
2683 |
+
{plot_code}
|
2684 |
"""
|
2685 |
+
st.session_state.code = basic_scene
|
2686 |
+
st.session_state.temp_code = basic_scene
|
2687 |
+
st.success(f"Created new scene with Plot {i+1}!")
|
2688 |
+
# Switch to Editor tab
|
2689 |
+
st.session_state.active_tab = 0
|
2690 |
+
st.rerun()
|
2691 |
|
2692 |
# Provide option to save the script
|
2693 |
if st.button("📄 Save This Script", key="save_script_btn"):
|
|
|
2762 |
self.wait(2)
|
2763 |
```
|
2764 |
""")
|
2765 |
+
|
2766 |
+
# Restore active tab if specified
|
2767 |
+
if st.session_state.active_tab is not None and st.session_state.active_tab < len(tabs):
|
2768 |
+
for i, tab in enumerate(tabs):
|
2769 |
+
if i == st.session_state.active_tab:
|
2770 |
+
tab.active = True
|
2771 |
+
break
|
2772 |
|
2773 |
if __name__ == "__main__":
|
2774 |
main()
|