mirror of
https://github.com/3b1b/manim.git
synced 2025-08-02 19:46:21 +08:00
Proper script wrapping implemented
This commit is contained in:
30
constants.py
30
constants.py
@ -1,15 +1,29 @@
|
||||
import os
|
||||
|
||||
PRODUCTION_QUALITY = True
|
||||
GENERALLY_BUFF_POINTS = PRODUCTION_QUALITY
|
||||
|
||||
DEFAULT_POINT_DENSITY_2D = 25 #if PRODUCTION_QUALITY else 20
|
||||
DEFAULT_POINT_DENSITY_1D = 150 #if PRODUCTION_QUALITY else 50
|
||||
GENERALLY_BUFF_POINTS = True
|
||||
|
||||
DEFAULT_HEIGHT = 1440 if PRODUCTION_QUALITY else 480
|
||||
DEFAULT_WIDTH = 2560 if PRODUCTION_QUALITY else 840
|
||||
PRODUCTION_QUALITY_DISPLAY_CONFIG = {
|
||||
"height" : 1440,
|
||||
"width" : 2560,
|
||||
"frame_duration" : 0.04,
|
||||
}
|
||||
|
||||
LOW_QUALITY_DISPLAY_CONFIG = {
|
||||
"height" : 480,
|
||||
"width" : 840,
|
||||
"frame_duration" : 0.15,
|
||||
}
|
||||
|
||||
|
||||
DEFAULT_POINT_DENSITY_2D = 25
|
||||
DEFAULT_POINT_DENSITY_1D = 150
|
||||
|
||||
#TODO, Make sure these are not needd
|
||||
DEFAULT_HEIGHT = PRODUCTION_QUALITY_DISPLAY_CONFIG["height"]
|
||||
DEFAULT_WIDTH = PRODUCTION_QUALITY_DISPLAY_CONFIG["width"]
|
||||
#All in seconds
|
||||
DEFAULT_FRAME_DURATION = 0.04 if PRODUCTION_QUALITY else 0.1
|
||||
DEFAULT_FRAME_DURATION = 0.04
|
||||
DEFAULT_ANIMATION_RUN_TIME = 3.0
|
||||
DEFAULT_TRANSFORM_RUN_TIME = 1.0
|
||||
DEFAULT_DITHER_TIME = 1.0
|
||||
@ -18,7 +32,7 @@ DEFAULT_DITHER_TIME = 1.0
|
||||
DEFAULT_NUM_STARS = 1000
|
||||
|
||||
SPACE_HEIGHT = 4.0
|
||||
SPACE_WIDTH = DEFAULT_WIDTH * SPACE_HEIGHT / DEFAULT_HEIGHT
|
||||
SPACE_WIDTH = DEFAULT_WIDTH * DEFAULT_HEIGHT / DEFAULT_WIDTH
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
IMAGE_DIR = os.path.join(THIS_DIR, "images")
|
||||
|
60
displayer.py
60
displayer.py
@ -80,7 +80,7 @@ def write_to_gif(scene, name):
|
||||
temppath = os.path.join(GIF_DIR, "Temp.gif")
|
||||
print "Writing " + name + "..."
|
||||
images = [Image.fromarray(frame) for frame in scene.frames]
|
||||
writeGif(temppath, images, scene.frame_duration)
|
||||
writeGif(temppath, images, scene.display_config["frame_duration"])
|
||||
print "Compressing..."
|
||||
os.system("gifsicle -O " + temppath + " > " + filepath)
|
||||
os.system("rm " + temppath)
|
||||
@ -95,7 +95,7 @@ def write_to_movie(scene, name):
|
||||
filedir = "/".join(filepath.split("/")[:-1])
|
||||
if not os.path.exists(filedir):
|
||||
os.makedirs(filedir)
|
||||
rate = int(1/scene.frame_duration)
|
||||
rate = int(1/scene.display_config["frame_duration"])
|
||||
|
||||
tmp_stem = os.path.join(TMP_IMAGE_DIR, name.replace("/", "_"))
|
||||
suffix = "-%04d.png"
|
||||
@ -114,7 +114,7 @@ def write_to_movie(scene, name):
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-vf",
|
||||
"fps=%d,format=yuv420p"%int(1/scene.frame_duration),
|
||||
"fps=%d,format=yuv420p"%rate,
|
||||
filepath + ".mp4"
|
||||
]
|
||||
os.system(" ".join(commands))
|
||||
@ -148,35 +148,35 @@ def write_to_movie(scene, name):
|
||||
# progress_bar.finish()
|
||||
|
||||
|
||||
class VideoSink(object):
|
||||
def __init__(self, size, filename="output", rate=10, byteorder="bgra") :
|
||||
self.size = size
|
||||
cmdstring = [
|
||||
'mencoder',
|
||||
'/dev/stdin',
|
||||
'-demuxer', 'rawvideo',
|
||||
'-rawvideo', 'w=%i:h=%i'%size[::-1]+":fps=%i:format=%s"%(rate,byteorder),
|
||||
'-o', filename+'.mp4',
|
||||
'-ovc', 'lavc',
|
||||
]
|
||||
self.p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False)
|
||||
# class VideoSink(object):
|
||||
# def __init__(self, size, filename="output", rate=10, byteorder="bgra") :
|
||||
# self.size = size
|
||||
# cmdstring = [
|
||||
# 'mencoder',
|
||||
# '/dev/stdin',
|
||||
# '-demuxer', 'rawvideo',
|
||||
# '-rawvideo', 'w=%i:h=%i'%size[::-1]+":fps=%i:format=%s"%(rate,byteorder),
|
||||
# '-o', filename+'.mp4',
|
||||
# '-ovc', 'lavc',
|
||||
# ]
|
||||
# self.p = subprocess.Popen(cmdstring, stdin=subprocess.PIPE, shell=False)
|
||||
|
||||
def run(self, image):
|
||||
"""
|
||||
Image comes in as HEIGHTxWIDTHx3 numpy array, order rgb
|
||||
"""
|
||||
assert image.shape == self.size + (3,)
|
||||
r, g, b = [image[:,:,i].astype('uint32') for i in range(3)]
|
||||
a = np.ones(image.shape[:2], dtype = 'uint32')
|
||||
#hacky
|
||||
image = sum([
|
||||
arr << 8**i
|
||||
for arr, i in zip(range(4), [a, r, g, b])
|
||||
])
|
||||
self.p.stdin.write(image.tostring())
|
||||
# def run(self, image):
|
||||
# """
|
||||
# Image comes in as HEIGHTxWIDTHx3 numpy array, order rgb
|
||||
# """
|
||||
# assert image.shape == self.size + (3,)
|
||||
# r, g, b = [image[:,:,i].astype('uint32') for i in range(3)]
|
||||
# a = np.ones(image.shape[:2], dtype = 'uint32')
|
||||
# #hacky
|
||||
# image = sum([
|
||||
# arr << 8**i
|
||||
# for arr, i in zip(range(4), [a, r, g, b])
|
||||
# ])
|
||||
# self.p.stdin.write(image.tostring())
|
||||
|
||||
def close(self):
|
||||
self.p.stdin.close()
|
||||
# def close(self):
|
||||
# self.p.stdin.close()
|
||||
|
||||
|
||||
|
||||
|
@ -28,6 +28,9 @@ def initials(name, sep_values = [" ", "_"]):
|
||||
s[0] for s in re.split("|".join(sep_values), name)
|
||||
])
|
||||
|
||||
def cammel_case_initials(name):
|
||||
return filter(lambda c : c.isupper(), name)
|
||||
|
||||
################################################
|
||||
|
||||
def drag_pixels(frames):
|
||||
|
@ -99,9 +99,13 @@ def tex_mobjects(expression, size = r"\HUGE"):
|
||||
images = tex_to_image(expression, size)
|
||||
if isinstance(images, list):
|
||||
#TODO, is checking listiness really the best here?
|
||||
return [ImageMobject(im).scale(2) for im in images]
|
||||
result = [ImageMobject(im).scale(2) for im in images]
|
||||
center = CompoundMobject(*result).get_center()
|
||||
for mob in result:
|
||||
mob.shift(-center)
|
||||
return result
|
||||
else:
|
||||
return ImageMobject(images).scale(2)
|
||||
return ImageMobject(images).center().scale(2)
|
||||
|
||||
|
||||
|
||||
|
@ -264,7 +264,7 @@ class Arrow(Mobject1D):
|
||||
[x, x, x] * self.direction + self.point
|
||||
for x in np.arange(-self.length, 0, self.epsilon)
|
||||
])
|
||||
tips_dir = np.array(-self.direction), np.array(-self.direction)
|
||||
tips_dir = [np.array(-self.direction), np.array(-self.direction)]
|
||||
for i, sgn in zip([0, 1], [-1, 1]):
|
||||
tips_dir[i] = rotate_vector(tips_dir[i], sgn * np.pi / 4, self.normal)
|
||||
self.add_points([
|
||||
|
1417
moser/main.py
1417
moser/main.py
File diff suppressed because it is too large
Load Diff
@ -7,12 +7,26 @@ from image_mobject import *
|
||||
from region import *
|
||||
from scene import Scene
|
||||
|
||||
RADIUS = SPACE_HEIGHT - 0.1
|
||||
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
|
||||
from graphs import *
|
||||
|
||||
RADIUS = SPACE_HEIGHT - 0.1
|
||||
CIRCLE_DENSITY = DEFAULT_POINT_DENSITY_1D*RADIUS
|
||||
MOVIE_PREFIX = "moser/"
|
||||
RADIANS = np.arange(0, 6, 6.0/7)
|
||||
N_PASCAL_ROWS = 7
|
||||
BIG_N_PASCAL_ROWS = 11
|
||||
|
||||
############################################
|
||||
|
||||
class CircleScene(Scene):
|
||||
args_list = [
|
||||
(RADIANS[:x],)
|
||||
for x in range(1, len(RADIANS)+1)
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(*args):
|
||||
return str(len(args[0])) #Length of radians
|
||||
|
||||
def __init__(self, radians, *args, **kwargs):
|
||||
Scene.__init__(self, *args, **kwargs)
|
||||
self.radius = RADIUS
|
||||
@ -29,6 +43,7 @@ class CircleScene(Scene):
|
||||
).shift((-SPACE_WIDTH+1, SPACE_HEIGHT-1.5, 0))
|
||||
self.add(self.circle, self.n_equals, *self.dots + self.lines)
|
||||
|
||||
|
||||
def generate_intersection_dots(self):
|
||||
"""
|
||||
Generates and adds attributes intersection_points and
|
||||
@ -95,8 +110,15 @@ class CircleScene(Scene):
|
||||
self.add(*self.circle_pieces)
|
||||
|
||||
class GraphScene(Scene):
|
||||
#Note, the placement of vertices in this is pretty hard coded, be
|
||||
#warned if you want to change it.
|
||||
args_list = [
|
||||
(CUBE_GRAPH,),
|
||||
(SAMPLE_GRAPH,),
|
||||
(OCTOHEDRON_GRAPH,),
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(*args):
|
||||
return args[0]["name"]
|
||||
|
||||
def __init__(self, graph, *args, **kwargs):
|
||||
Scene.__init__(self, *args, **kwargs)
|
||||
#See CUBE_GRAPH above for format of graph
|
||||
@ -123,7 +145,15 @@ class GraphScene(Scene):
|
||||
regions[-1].complement()#Outer region painted outwardly...
|
||||
self.regions = regions
|
||||
|
||||
class PascalsTriangleScene(Scene):
|
||||
class PascalsTriangleScene(Scene):
|
||||
args_list = [
|
||||
(N_PASCAL_ROWS,),
|
||||
(BIG_N_PASCAL_ROWS,),
|
||||
]
|
||||
@staticmethod
|
||||
def args_to_string(*args):
|
||||
return str(args[0])
|
||||
|
||||
def __init__(self, nrows, *args, **kwargs):
|
||||
Scene.__init__(self, *args, **kwargs)
|
||||
diagram_height = 2*SPACE_HEIGHT - 1
|
||||
@ -167,7 +197,6 @@ class PascalsTriangleScene(Scene):
|
||||
self.portion_to_fill= portion_to_fill
|
||||
self.coords_to_mobs = coords_to_mobs
|
||||
|
||||
|
||||
def generate_n_choose_k_mobs(self):
|
||||
self.coords_to_n_choose_k = {}
|
||||
for n, k in self.coords:
|
||||
@ -187,6 +216,9 @@ class PascalsTriangleScene(Scene):
|
||||
|
||||
##################################################
|
||||
|
||||
def int_list_to_string(int_list):
|
||||
return "-".join(map(str, int_list))
|
||||
|
||||
def choose(n, r):
|
||||
if n < r: return 0
|
||||
if r == 0: return 1
|
||||
|
30
scene.py
30
scene.py
@ -20,14 +20,12 @@ DEFAULT_COUNT_NUM_OFFSET = (SPACE_WIDTH - 1, SPACE_HEIGHT - 1, 0)
|
||||
DEFAULT_COUNT_RUN_TIME = 5.0
|
||||
|
||||
class Scene(object):
|
||||
def __init__(self,
|
||||
name = None,
|
||||
frame_duration = DEFAULT_FRAME_DURATION,
|
||||
def __init__(self,
|
||||
display_config = PRODUCTION_QUALITY_DISPLAY_CONFIG,
|
||||
background = None,
|
||||
height = DEFAULT_HEIGHT,
|
||||
width = DEFAULT_WIDTH,
|
||||
start_dither_time = DEFAULT_DITHER_TIME):
|
||||
self.frame_duration = frame_duration
|
||||
self.display_config = display_config
|
||||
self.frame_duration = display_config["frame_duration"]
|
||||
self.frames = []
|
||||
self.mobjects = []
|
||||
if background:
|
||||
@ -35,16 +33,15 @@ class Scene(object):
|
||||
#TODO, Error checking?
|
||||
else:
|
||||
self.original_background = np.zeros(
|
||||
(height, width, 3),
|
||||
(display_config["height"], display_config["width"], 3),
|
||||
dtype = 'uint8'
|
||||
)
|
||||
self.background = self.original_background
|
||||
self.shape = self.background.shape[:2]
|
||||
#TODO, space shape
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return self.name or "Babadinook" #TODO
|
||||
return self.__class__.__name__
|
||||
|
||||
def set_name(self, name):
|
||||
self.name = name
|
||||
@ -178,17 +175,28 @@ class Scene(object):
|
||||
def dither(self, duration = DEFAULT_DITHER_TIME):
|
||||
self.frames += [self.get_frame()]*int(duration / self.frame_duration)
|
||||
|
||||
def write_to_gif(self, name = None, end_dither_time = DEFAULT_DITHER_TIME):
|
||||
def write_to_gif(self, name = None,
|
||||
end_dither_time = DEFAULT_DITHER_TIME):
|
||||
self.dither(end_dither_time)
|
||||
disp.write_to_gif(self, name or str(self))
|
||||
|
||||
def write_to_movie(self, name = None, end_dither_time = DEFAULT_DITHER_TIME):
|
||||
def write_to_movie(self, name = None,
|
||||
end_dither_time = DEFAULT_DITHER_TIME):
|
||||
self.dither(end_dither_time)
|
||||
disp.write_to_movie(self, name or str(self))
|
||||
|
||||
def show(self):
|
||||
Image.fromarray(self.get_frame()).show()
|
||||
|
||||
# To list possible args that subclasses have
|
||||
# Elements should always be a tuple
|
||||
args_list = []
|
||||
|
||||
# For subclasses to turn args in the above
|
||||
# list into stings which can be appended to the name
|
||||
@staticmethod
|
||||
def args_to_string(*args):
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
|
@ -2,72 +2,100 @@ import sys
|
||||
import getopt
|
||||
import imp
|
||||
import itertools as it
|
||||
from helpers import initials, to_cammel_case
|
||||
from helpers import cammel_case_initials
|
||||
|
||||
def print_help_message():
|
||||
print '<script name> -f <function name or initials>'
|
||||
from constants import *
|
||||
|
||||
def get_scene_and_name(function_string, args_string,
|
||||
function_tuples):
|
||||
possible_func_args = []
|
||||
for func, args_list, args_to_string in function_tuples:
|
||||
if function_string in ("", func.__name__, initials(func.__name__)):
|
||||
for args in args_list:
|
||||
if not isinstance(args, tuple):
|
||||
args = (args,)
|
||||
if not args_to_string:
|
||||
args_to_string = lambda x : ""
|
||||
this_args_string = args_to_string(args)
|
||||
if args_string in ("", this_args_string):
|
||||
possible_func_args.append(
|
||||
(func, args, this_args_string)
|
||||
)
|
||||
if len(possible_func_args) == 0:
|
||||
print "No function_string arg_string pair \
|
||||
matching \"%s\" and \"%s\""%(function_string, args_string)
|
||||
HELP_MESSAGE = """
|
||||
<script name> [-s <scene name or initials> -a <arg_string>]
|
||||
"""
|
||||
SCENE_NOT_FOUND_MESSAGE = """
|
||||
No scene name or ititials \"%s\" and arg string \"%s\"
|
||||
"""
|
||||
MULTIPE_SCENES_FOUND_MESSAGE = """
|
||||
Multiple Scene/arg pairs satisfying this
|
||||
description, choose from the following list:
|
||||
"""
|
||||
CHOOSE_NUMBER_MESSAGE = "Choose a number from above: "
|
||||
INVALID_NUMBER_MESSAGE = "Invalid number!"
|
||||
|
||||
def find_scene_class_and_args(scene_string, args_extension,
|
||||
scene_classes):
|
||||
possible_results = []
|
||||
for SceneClass in scene_classes:
|
||||
possible_names = map(str.lower, (
|
||||
"",
|
||||
SceneClass.__name__,
|
||||
cammel_case_initials(SceneClass.__name__)
|
||||
))
|
||||
if scene_string.lower() in possible_names:
|
||||
if len(SceneClass.args_list) == 0:
|
||||
possible_results.append((SceneClass, ()))
|
||||
for args in SceneClass.args_list:
|
||||
assert(isinstance(args, tuple))
|
||||
this_args_extension = SceneClass.args_to_string(*args)
|
||||
if args_extension.lower() in ("", this_args_extension.lower()):
|
||||
possible_results.append((SceneClass, args))
|
||||
num_matches = len(possible_results)
|
||||
if num_matches == 0:
|
||||
print SCENE_NOT_FOUND_MESSAGE%(scene_string, args_extension)
|
||||
sys.exit(0)
|
||||
elif len(possible_func_args) == 1:
|
||||
elif num_matches == 1:
|
||||
index = 0
|
||||
|
||||
else:
|
||||
print "Multiple functions/arg pairs satisfying this " + \
|
||||
"description, choose from the following list:"
|
||||
print MULTIPE_SCENES_FOUND_MESSAGE
|
||||
count = 0
|
||||
for func, args, args_string in possible_func_args:
|
||||
print "%d: %s%s"%(
|
||||
for SceneClass, args in possible_results:
|
||||
print "%d: %s%s"%(
|
||||
count,
|
||||
to_cammel_case(func.__name__),
|
||||
args_string
|
||||
SceneClass.__name__,
|
||||
SceneClass.args_to_string(*args),
|
||||
)
|
||||
count += 1
|
||||
index = int(raw_input("Choose a number from above: "))
|
||||
function, args, args_string = possible_func_args[index]
|
||||
scene_name = to_cammel_case(function.__name__) + args_string
|
||||
print "Writing %s..."%scene_name
|
||||
scene = function(*args)
|
||||
return scene, scene_name
|
||||
try:
|
||||
index = input(CHOOSE_NUMBER_MESSAGE)
|
||||
assert(type(index) == int)
|
||||
assert(index in range(num_matches))
|
||||
except:
|
||||
print INVALID_NUMBER_MESSAGE
|
||||
sys.exit(0)
|
||||
return possible_results[index]
|
||||
|
||||
|
||||
def create_scene(sys_argv, function_tuples, movie_prefix = ""):
|
||||
def command_line_create_scene(sys_argv, scene_classes, movie_prefix = ""):
|
||||
try:
|
||||
opts, args = getopt.getopt(sys_argv, "ho:vf:v")#TODO, Learn about this
|
||||
opts, args = getopt.getopt(sys_argv, "ho:s:a:l")
|
||||
except getopt.GetoptError as err:
|
||||
print str(err)
|
||||
sys.exit(2)
|
||||
function_string = ""
|
||||
scene_string = ""
|
||||
args_extension = ""
|
||||
display_config = PRODUCTION_QUALITY_DISPLAY_CONFIG
|
||||
for opt, arg in opts:
|
||||
if opt == '-h':
|
||||
print_help_message()
|
||||
print HELP_MESSAGE
|
||||
return
|
||||
elif opt == '-f':
|
||||
function_string = arg
|
||||
elif opt == "-a":
|
||||
elif opt == '-s':
|
||||
scene_string = arg
|
||||
elif opt == '-a':
|
||||
args_extension = arg
|
||||
scene, name = get_scene_and_name(
|
||||
function_string,
|
||||
elif opt == '-l':
|
||||
display_config = LOW_QUALITY_DISPLAY_CONFIG
|
||||
SceneClass, args = find_scene_class_and_args(
|
||||
scene_string,
|
||||
args_extension,
|
||||
function_tuples
|
||||
scene_classes
|
||||
)
|
||||
print name
|
||||
name = SceneClass.__name__ + SceneClass.args_to_string(*args)
|
||||
print "Writing %s..."%name
|
||||
scene = SceneClass(*args, display_config = display_config)
|
||||
scene.write_to_movie(movie_prefix + name)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user