Better alignment of sub_mbojects

This commit is contained in:
Grant Sanderson 2016-04-14 19:30:47 -07:00
parent a9f620e250
commit bd3783586a
8 changed files with 92 additions and 64 deletions

View file

@ -15,9 +15,9 @@ class Transform(Animation):
"path_func" : straight_path "path_func" : straight_path
} }
def __init__(self, mobject, ending_mobject, **kwargs): def __init__(self, mobject, ending_mobject, **kwargs):
mobject = instantiate(mobject) mobject =mobject
#Copy ending_mobject so as to not mess with caller #Copy ending_mobject so as to not mess with caller
ending_mobject = instantiate(ending_mobject).copy() ending_mobject = ending_mobject.copy()
digest_config(self, kwargs, locals()) digest_config(self, kwargs, locals())
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points() count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
if count2 == 0: if count2 == 0:

View file

@ -9,7 +9,7 @@ import progressbar
import aggdraw import aggdraw
from helpers import * from helpers import *
from mobject import PointCloudMobject, VectorizedMobject from mobject import PMobject, VMobject
class Camera(object): class Camera(object):
CONFIG = { CONFIG = {
@ -72,9 +72,9 @@ class Camera(object):
for mob in mobjects for mob in mobjects
]) ])
for mobject in mobjects: for mobject in mobjects:
if isinstance(mobject, VectorizedMobject): if isinstance(mobject, VMobject):
self.display_vectorized(mobject) self.display_vectorized(mobject)
elif isinstance(mobject, PointCloudMobject): elif isinstance(mobject, PMobject):
self.display_point_cloud( self.display_point_cloud(
mobject.points, mobject.rgbs, mobject.points, mobject.rgbs,
self.adjusted_thickness(mobject.stroke_width) self.adjusted_thickness(mobject.stroke_width)
@ -95,34 +95,34 @@ class Camera(object):
self.pixel_array[covered] = rgb self.pixel_array[covered] = rgb
def display_vectorized(self, vect_mobject): def display_vectorized(self, vmobject):
if vect_mobject.is_subpath: if vmobject.is_subpath:
#Subpath vectorized mobjects are taken care #Subpath vectorized mobjects are taken care
#of by their parent #of by their parent
return return
im = Image.fromarray(self.pixel_array, mode = "RGB") im = Image.fromarray(self.pixel_array, mode = "RGB")
canvas = aggdraw.Draw(im) canvas = aggdraw.Draw(im)
pen, fill = self.get_pen_and_fill(vect_mobject) pen, fill = self.get_pen_and_fill(vmobject)
pathstring = self.get_pathstring(vect_mobject) pathstring = self.get_pathstring(vmobject)
symbol = aggdraw.Symbol(pathstring) symbol = aggdraw.Symbol(pathstring)
canvas.symbol((0, 0), symbol, pen, fill) canvas.symbol((0, 0), symbol, pen, fill)
canvas.flush() canvas.flush()
self.pixel_array[:,:] = np.array(im) self.pixel_array[:,:] = np.array(im)
def get_pen_and_fill(self, vect_mobject): def get_pen_and_fill(self, vmobject):
pen = aggdraw.Pen( pen = aggdraw.Pen(
vect_mobject.get_stroke_color().get_hex_l(), vmobject.get_stroke_color().get_hex_l(),
vect_mobject.stroke_width vmobject.stroke_width
) )
fill = aggdraw.Brush( fill = aggdraw.Brush(
vect_mobject.get_fill_color().get_hex_l(), vmobject.get_fill_color().get_hex_l(),
opacity = int(255*vect_mobject.get_fill_opacity()) opacity = int(255*vmobject.get_fill_opacity())
) )
return (pen, fill) return (pen, fill)
def get_pathstring(self, vect_mobject): def get_pathstring(self, vmobject):
result = "" result = ""
for mob in [vect_mobject]+vect_mobject.subpath_mobjects: for mob in [vmobject]+vmobject.subpath_mobjects:
points = mob.points points = mob.points
if len(points) == 0: if len(points) == 0:
continue continue

View file

@ -5,5 +5,5 @@ __all__ = [
] ]
from mobject import Mobject from mobject import Mobject
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PointCloudMobject from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
from vectorized_mobject import VectorizedMobject from vectorized_mobject import VMobject

View file

@ -6,9 +6,9 @@ from random import random
from helpers import * from helpers import *
from mobject import Mobject from mobject import Mobject
from point_cloud_mobject import PointCloudMobject from point_cloud_mobject import PMobject
class ImageMobject(PointCloudMobject): class ImageMobject(PMobject):
""" """
Automatically filters out black pixels Automatically filters out black pixels
""" """

View file

@ -20,8 +20,8 @@ class Mobject(object):
"color" : WHITE, "color" : WHITE,
"stroke_width" : DEFAULT_POINT_THICKNESS, "stroke_width" : DEFAULT_POINT_THICKNESS,
"name" : None, "name" : None,
"display_mode" : "points", #TODO, REMOVE
"dim" : 3, "dim" : 3,
"target" : None
} }
def __init__(self, *sub_mobjects, **kwargs): def __init__(self, *sub_mobjects, **kwargs):
digest_config(self, kwargs) digest_config(self, kwargs)
@ -320,10 +320,7 @@ class Mobject(object):
### Getters ### ### Getters ###
def get_num_points(self, including_submobjects = False): def get_num_points(self):
if including_submobjects:
return self.reduce_across_dimension(len, sum, 0)
else:
return len(self.points) return len(self.points)
def get_critical_point(self, direction): def get_critical_point(self, direction):
@ -406,23 +403,13 @@ class Mobject(object):
## Alignment ## Alignment
def align_data(self, mobject): def align_data(self, mobject):
self.align_sub_mobjects(mobject)
self.align_points(mobject) self.align_points(mobject)
#Recurse #Recurse
diff = len(self.sub_mobjects) - len(mobject.sub_mobjects)
if diff != 0:
if diff < 0:
larger, smaller = mobject, self
elif diff > 0:
larger, smaller = self, mobject
for sub_mob in larger.sub_mobjects[-abs(diff):]:
point_mob = sub_mob.get_point_mobject(
smaller.get_center()
)
smaller.add(point_mob)
for m1, m2 in zip(self.sub_mobjects, mobject.sub_mobjects): for m1, m2 in zip(self.sub_mobjects, mobject.sub_mobjects):
m1.align_data(m2) m1.align_data(m2)
def get_point_mobject(self, center): def get_point_mobject(self, center = None):
""" """
The simplest mobject to be transformed to or from self. The simplest mobject to be transformed to or from self.
Should by a point of the appropriate type Should by a point of the appropriate type
@ -442,6 +429,40 @@ class Mobject(object):
def align_points_with_larger(self, larger_mobject): def align_points_with_larger(self, larger_mobject):
raise Exception("Not implemented") raise Exception("Not implemented")
def align_sub_mobjects(self, mobject):
#If one is empty, and the other is not,
#push it into its submobject list
self_has_points, mob_has_points = [
mob.get_num_points() > 0
for mob in self, mobject
]
if self_has_points and not mob_has_points:
self.push_self_into_sub_mobjects()
elif mob_has_points and not self_has_points:
mob.push_self_into_sub_mobjects()
self_count = len(self.sub_mobjects)
mob_count = len(mobject.sub_mobjects)
diff = abs(self_count-mob_count)
if self_count < mob_count:
self.add_n_more_sub_mobjects(diff)
elif mob_count < self_count:
mobject.add_n_more_sub_mobjects(diff)
return self
def push_self_into_sub_mobjects(self):
copy = self.copy()
copy.sub_mobjects = []
self.points = np.zeros((0, self.dim))
self.add(copy)
return self
def add_n_more_sub_mobjects(self, n):
if n > 0 and len(self.sub_mobjects) == 0:
self.add(self.copy())
for i in range(n):
self.add(self.sub_mobjects[i].copy())
return self
def interpolate(self, mobject1, mobject2, alpha, path_func): def interpolate(self, mobject1, mobject2, alpha, path_func):
""" """
Turns target_mobject into an interpolation between mobject1 Turns target_mobject into an interpolation between mobject1

View file

@ -1,7 +1,7 @@
from .mobject import Mobject from .mobject import Mobject
from helpers import * from helpers import *
class PointCloudMobject(Mobject): class PMobject(Mobject):
def init_colors(self): def init_colors(self):
self.rgbs = np.zeros((0, 3)) self.rgbs = np.zeros((0, 3))
return self return self
@ -112,14 +112,14 @@ class PointCloudMobject(Mobject):
# Alignment # Alignment
def align_points_with_larger(self, larger_mobject): def align_points_with_larger(self, larger_mobject):
assert(isinstance(larger_mobject, PointCloudMobject)) assert(isinstance(larger_mobject, PMobject))
self.apply_over_attr_arrays( self.apply_over_attr_arrays(
lambda a : streth_array_to_length( lambda a : streth_array_to_length(
a, larger_mobject.get_num_points() a, larger_mobject.get_num_points()
) )
) )
def get_point_mobject(self, center): def get_point_mobject(self, center = None):
if center is None: if center is None:
center = self.get_center() center = self.get_center()
return Point(center) return Point(center)
@ -141,7 +141,7 @@ class PointCloudMobject(Mobject):
#TODO, Make the two implementations bellow non-redundant #TODO, Make the two implementations bellow non-redundant
class Mobject1D(PointCloudMobject): class Mobject1D(PMobject):
CONFIG = { CONFIG = {
"density" : DEFAULT_POINT_DENSITY_1D, "density" : DEFAULT_POINT_DENSITY_1D,
} }
@ -164,7 +164,7 @@ class Mobject1D(PointCloudMobject):
] ]
self.add_points(points, color = color) self.add_points(points, color = color)
class Mobject2D(PointCloudMobject): class Mobject2D(PMobject):
CONFIG = { CONFIG = {
"density" : DEFAULT_POINT_DENSITY_2D, "density" : DEFAULT_POINT_DENSITY_2D,
} }
@ -175,11 +175,11 @@ class Mobject2D(PointCloudMobject):
class Point(PointCloudMobject): class Point(PMobject):
CONFIG = { CONFIG = {
"color" : BLACK, "color" : BLACK,
} }
def __init__(self, location = ORIGIN, **kwargs): def __init__(self, location = ORIGIN, **kwargs):
PointCloudMobject.__init__(self, **kwargs) PMobject.__init__(self, **kwargs)
self.add_points([location]) self.add_points([location])

View file

@ -1,11 +1,11 @@
from mobject import Mobject from mobject import Mobject
from point_cloud_mobject import PointCloudMobject from point_cloud_mobject import PMobject
from image_mobject import ImageMobject from image_mobject import ImageMobject
from helpers import * from helpers import *
#TODO, Cleanup and refactor this file. #TODO, Cleanup and refactor this file.
class TexMobject(PointCloudMobject): class TexMobject(PMobject):
CONFIG = { CONFIG = {
"template_tex_file" : TEMPLATE_TEX_FILE, "template_tex_file" : TEMPLATE_TEX_FILE,
"color" : WHITE, "color" : WHITE,

View file

@ -4,13 +4,14 @@ from .mobject import Mobject
from helpers import * from helpers import *
class VectorizedMobject(Mobject): class VMobject(Mobject):
CONFIG = { CONFIG = {
"fill_color" : BLACK, "fill_color" : BLACK,
"fill_opacity" : 0.0, "fill_opacity" : 0.0,
#Indicates that it will not be displayed, but #Indicates that it will not be displayed, but
#that it should count in parent mobject's path #that it should count in parent mobject's path
"is_subpath" : False, "is_subpath" : False,
"closed" : True,
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.subpath_mobjects = [] self.subpath_mobjects = []
@ -18,8 +19,8 @@ class VectorizedMobject(Mobject):
## Colors ## Colors
def init_colors(self): def init_colors(self):
self.set_stroke(color = self.color) self.set_stroke(self.color, self.stroke_width)
self.set_fill(color = self.fill_color) self.set_fill(self.fill_color, self.fill_opacity)
return self return self
def set_family_attr(self, attr, value): def set_family_attr(self, attr, value):
@ -102,7 +103,7 @@ class VectorizedMobject(Mobject):
return self return self
def set_points(self, points): def set_points(self, points):
self.points = points self.points = np.array(points)
return self return self
def set_anchor_points(self, points, mode = "smooth"): def set_anchor_points(self, points, mode = "smooth"):
@ -120,7 +121,7 @@ class VectorizedMobject(Mobject):
def change_mode(self, mode): def change_mode(self, mode):
anchors, h1, h2 = self.get_anchors_and_handles() anchors, h1, h2 = self.get_anchors_and_handles()
self.set_points(anchors, mode = mode) self.set_anchor_points(anchors, mode = mode)
return self return self
def make_smooth(self): def make_smooth(self):
@ -131,7 +132,7 @@ class VectorizedMobject(Mobject):
def add_subpath(self, points): def add_subpath(self, points):
""" """
A VectorizedMobject is meant to represnt A VMobject is meant to represnt
a single "path", in the svg sense of the word. a single "path", in the svg sense of the word.
However, one such path may really consit of separate However, one such path may really consit of separate
continuous components if there is a move_to command. continuous components if there is a move_to command.
@ -140,7 +141,7 @@ class VectorizedMobject(Mobject):
but will be tracked in a separate special list for when but will be tracked in a separate special list for when
it comes time to display. it comes time to display.
""" """
subpath_mobject = VectorizedMobject( subpath_mobject = VMobject(
is_subpath = True is_subpath = True
) )
subpath_mobject.set_points(points) subpath_mobject.set_points(points)
@ -176,7 +177,13 @@ class VectorizedMobject(Mobject):
## Alignment ## Alignment
def align_points_with_larger(self, larger_mobject): def align_points_with_larger(self, larger_mobject):
assert(isinstance(larger_mobject, VectorizedMobject)) assert(isinstance(larger_mobject, VMobject))
num_anchors = self.get_num_anchor_points()
if num_anchors <= 1:
point = self.points[0] if len(self.points) else np.zeros(3)
self.points = np.zeros(larger_mobject.points.shape)
self.points[:,:] = point
return self
points = np.array([self.points[0]]) points = np.array([self.points[0]])
target_len = larger_mobject.get_num_anchor_points()-1 target_len = larger_mobject.get_num_anchor_points()-1
num_curves = self.get_num_anchor_points()-1 num_curves = self.get_num_anchor_points()-1
@ -199,7 +206,7 @@ class VectorizedMobject(Mobject):
self.set_points(points) self.set_points(points)
return self return self
def get_point_mobject(self, center): def get_point_mobject(self, center = None):
if center is None: if center is None:
center = self.get_center() center = self.get_center()
return VectorizedPoint(center) return VectorizedPoint(center)
@ -219,7 +226,7 @@ class VectorizedMobject(Mobject):
)) ))
def become_partial(self, mobject, a, b): def become_partial(self, mobject, a, b):
assert(isinstance(mobject, VectorizedMobject)) assert(isinstance(mobject, VMobject))
#Partial curve includes three portions: #Partial curve includes three portions:
#-A middle section, which matches the curve exactly #-A middle section, which matches the curve exactly
#-A start, which is some ending portion of an inner cubic #-A start, which is some ending portion of an inner cubic
@ -246,18 +253,18 @@ class VectorizedMobject(Mobject):
return self return self
class VectorizedPoint(VectorizedMobject): class VectorizedPoint(VMobject):
CONFIG = { CONFIG = {
"color" : BLACK, "color" : BLACK,
} }
def __init__(self, location = ORIGIN, **kwargs): def __init__(self, location = ORIGIN, **kwargs):
VectorizedMobject.__init__(self, **kwargs) VMobject.__init__(self, **kwargs)
self.set_points([location]) self.set_points([location])
class VectorizedMobjectFromSVGPathstring(VectorizedMobject): class VMobjectFromSVGPathstring(VMobject):
def __init__(self, path_string, **kwargs): def __init__(self, path_string, **kwargs):
digest_locals(self) digest_locals(self)
VectorizedMobject.__init__(self, **kwargs) VMobject.__init__(self, **kwargs)
def get_path_commands(self): def get_path_commands(self):
return [ return [