mirror of
https://github.com/3b1b/manim.git
synced 2025-07-30 13:34:19 +08:00
Better alignment of sub_mbojects
This commit is contained in:
@ -15,9 +15,9 @@ class Transform(Animation):
|
||||
"path_func" : straight_path
|
||||
}
|
||||
def __init__(self, mobject, ending_mobject, **kwargs):
|
||||
mobject = instantiate(mobject)
|
||||
mobject =mobject
|
||||
#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())
|
||||
count1, count2 = mobject.get_num_points(), ending_mobject.get_num_points()
|
||||
if count2 == 0:
|
||||
|
28
camera.py
28
camera.py
@ -9,7 +9,7 @@ import progressbar
|
||||
import aggdraw
|
||||
|
||||
from helpers import *
|
||||
from mobject import PointCloudMobject, VectorizedMobject
|
||||
from mobject import PMobject, VMobject
|
||||
|
||||
class Camera(object):
|
||||
CONFIG = {
|
||||
@ -72,9 +72,9 @@ class Camera(object):
|
||||
for mob in mobjects
|
||||
])
|
||||
for mobject in mobjects:
|
||||
if isinstance(mobject, VectorizedMobject):
|
||||
if isinstance(mobject, VMobject):
|
||||
self.display_vectorized(mobject)
|
||||
elif isinstance(mobject, PointCloudMobject):
|
||||
elif isinstance(mobject, PMobject):
|
||||
self.display_point_cloud(
|
||||
mobject.points, mobject.rgbs,
|
||||
self.adjusted_thickness(mobject.stroke_width)
|
||||
@ -95,34 +95,34 @@ class Camera(object):
|
||||
self.pixel_array[covered] = rgb
|
||||
|
||||
|
||||
def display_vectorized(self, vect_mobject):
|
||||
if vect_mobject.is_subpath:
|
||||
def display_vectorized(self, vmobject):
|
||||
if vmobject.is_subpath:
|
||||
#Subpath vectorized mobjects are taken care
|
||||
#of by their parent
|
||||
return
|
||||
im = Image.fromarray(self.pixel_array, mode = "RGB")
|
||||
canvas = aggdraw.Draw(im)
|
||||
pen, fill = self.get_pen_and_fill(vect_mobject)
|
||||
pathstring = self.get_pathstring(vect_mobject)
|
||||
pen, fill = self.get_pen_and_fill(vmobject)
|
||||
pathstring = self.get_pathstring(vmobject)
|
||||
symbol = aggdraw.Symbol(pathstring)
|
||||
canvas.symbol((0, 0), symbol, pen, fill)
|
||||
canvas.flush()
|
||||
self.pixel_array[:,:] = np.array(im)
|
||||
|
||||
def get_pen_and_fill(self, vect_mobject):
|
||||
def get_pen_and_fill(self, vmobject):
|
||||
pen = aggdraw.Pen(
|
||||
vect_mobject.get_stroke_color().get_hex_l(),
|
||||
vect_mobject.stroke_width
|
||||
vmobject.get_stroke_color().get_hex_l(),
|
||||
vmobject.stroke_width
|
||||
)
|
||||
fill = aggdraw.Brush(
|
||||
vect_mobject.get_fill_color().get_hex_l(),
|
||||
opacity = int(255*vect_mobject.get_fill_opacity())
|
||||
vmobject.get_fill_color().get_hex_l(),
|
||||
opacity = int(255*vmobject.get_fill_opacity())
|
||||
)
|
||||
return (pen, fill)
|
||||
|
||||
def get_pathstring(self, vect_mobject):
|
||||
def get_pathstring(self, vmobject):
|
||||
result = ""
|
||||
for mob in [vect_mobject]+vect_mobject.subpath_mobjects:
|
||||
for mob in [vmobject]+vmobject.subpath_mobjects:
|
||||
points = mob.points
|
||||
if len(points) == 0:
|
||||
continue
|
||||
|
@ -5,5 +5,5 @@ __all__ = [
|
||||
]
|
||||
|
||||
from mobject import Mobject
|
||||
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PointCloudMobject
|
||||
from vectorized_mobject import VectorizedMobject
|
||||
from point_cloud_mobject import Point, Mobject1D, Mobject2D, PMobject
|
||||
from vectorized_mobject import VMobject
|
@ -6,9 +6,9 @@ from random import random
|
||||
|
||||
from helpers import *
|
||||
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
|
||||
"""
|
||||
|
@ -17,11 +17,11 @@ class Mobject(object):
|
||||
"""
|
||||
#Number of numbers used to describe a point (3 for pos, 3 for normal vector)
|
||||
CONFIG = {
|
||||
"color" : WHITE,
|
||||
"color" : WHITE,
|
||||
"stroke_width" : DEFAULT_POINT_THICKNESS,
|
||||
"name" : None,
|
||||
"display_mode" : "points", #TODO, REMOVE
|
||||
"dim" : 3,
|
||||
"name" : None,
|
||||
"dim" : 3,
|
||||
"target" : None
|
||||
}
|
||||
def __init__(self, *sub_mobjects, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
@ -320,11 +320,8 @@ class Mobject(object):
|
||||
|
||||
### Getters ###
|
||||
|
||||
def get_num_points(self, including_submobjects = False):
|
||||
if including_submobjects:
|
||||
return self.reduce_across_dimension(len, sum, 0)
|
||||
else:
|
||||
return len(self.points)
|
||||
def get_num_points(self):
|
||||
return len(self.points)
|
||||
|
||||
def get_critical_point(self, direction):
|
||||
result = np.zeros(self.dim)
|
||||
@ -406,23 +403,13 @@ class Mobject(object):
|
||||
|
||||
## Alignment
|
||||
def align_data(self, mobject):
|
||||
self.align_sub_mobjects(mobject)
|
||||
self.align_points(mobject)
|
||||
#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):
|
||||
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.
|
||||
Should by a point of the appropriate type
|
||||
@ -442,6 +429,40 @@ class Mobject(object):
|
||||
def align_points_with_larger(self, larger_mobject):
|
||||
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):
|
||||
"""
|
||||
Turns target_mobject into an interpolation between mobject1
|
||||
|
@ -1,7 +1,7 @@
|
||||
from .mobject import Mobject
|
||||
from helpers import *
|
||||
|
||||
class PointCloudMobject(Mobject):
|
||||
class PMobject(Mobject):
|
||||
def init_colors(self):
|
||||
self.rgbs = np.zeros((0, 3))
|
||||
return self
|
||||
@ -112,14 +112,14 @@ class PointCloudMobject(Mobject):
|
||||
|
||||
# Alignment
|
||||
def align_points_with_larger(self, larger_mobject):
|
||||
assert(isinstance(larger_mobject, PointCloudMobject))
|
||||
assert(isinstance(larger_mobject, PMobject))
|
||||
self.apply_over_attr_arrays(
|
||||
lambda a : streth_array_to_length(
|
||||
a, larger_mobject.get_num_points()
|
||||
)
|
||||
)
|
||||
|
||||
def get_point_mobject(self, center):
|
||||
def get_point_mobject(self, center = None):
|
||||
if center is None:
|
||||
center = self.get_center()
|
||||
return Point(center)
|
||||
@ -141,7 +141,7 @@ class PointCloudMobject(Mobject):
|
||||
|
||||
|
||||
#TODO, Make the two implementations bellow non-redundant
|
||||
class Mobject1D(PointCloudMobject):
|
||||
class Mobject1D(PMobject):
|
||||
CONFIG = {
|
||||
"density" : DEFAULT_POINT_DENSITY_1D,
|
||||
}
|
||||
@ -164,7 +164,7 @@ class Mobject1D(PointCloudMobject):
|
||||
]
|
||||
self.add_points(points, color = color)
|
||||
|
||||
class Mobject2D(PointCloudMobject):
|
||||
class Mobject2D(PMobject):
|
||||
CONFIG = {
|
||||
"density" : DEFAULT_POINT_DENSITY_2D,
|
||||
}
|
||||
@ -175,11 +175,11 @@ class Mobject2D(PointCloudMobject):
|
||||
|
||||
|
||||
|
||||
class Point(PointCloudMobject):
|
||||
class Point(PMobject):
|
||||
CONFIG = {
|
||||
"color" : BLACK,
|
||||
}
|
||||
def __init__(self, location = ORIGIN, **kwargs):
|
||||
PointCloudMobject.__init__(self, **kwargs)
|
||||
PMobject.__init__(self, **kwargs)
|
||||
self.add_points([location])
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
from mobject import Mobject
|
||||
from point_cloud_mobject import PointCloudMobject
|
||||
from point_cloud_mobject import PMobject
|
||||
from image_mobject import ImageMobject
|
||||
from helpers import *
|
||||
|
||||
#TODO, Cleanup and refactor this file.
|
||||
|
||||
class TexMobject(PointCloudMobject):
|
||||
class TexMobject(PMobject):
|
||||
CONFIG = {
|
||||
"template_tex_file" : TEMPLATE_TEX_FILE,
|
||||
"color" : WHITE,
|
||||
|
@ -4,13 +4,14 @@ from .mobject import Mobject
|
||||
|
||||
from helpers import *
|
||||
|
||||
class VectorizedMobject(Mobject):
|
||||
class VMobject(Mobject):
|
||||
CONFIG = {
|
||||
"fill_color" : BLACK,
|
||||
"fill_opacity" : 0.0,
|
||||
#Indicates that it will not be displayed, but
|
||||
#that it should count in parent mobject's path
|
||||
"is_subpath" : False,
|
||||
"closed" : True,
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.subpath_mobjects = []
|
||||
@ -18,8 +19,8 @@ class VectorizedMobject(Mobject):
|
||||
|
||||
## Colors
|
||||
def init_colors(self):
|
||||
self.set_stroke(color = self.color)
|
||||
self.set_fill(color = self.fill_color)
|
||||
self.set_stroke(self.color, self.stroke_width)
|
||||
self.set_fill(self.fill_color, self.fill_opacity)
|
||||
return self
|
||||
|
||||
def set_family_attr(self, attr, value):
|
||||
@ -102,7 +103,7 @@ class VectorizedMobject(Mobject):
|
||||
return self
|
||||
|
||||
def set_points(self, points):
|
||||
self.points = points
|
||||
self.points = np.array(points)
|
||||
return self
|
||||
|
||||
def set_anchor_points(self, points, mode = "smooth"):
|
||||
@ -120,7 +121,7 @@ class VectorizedMobject(Mobject):
|
||||
|
||||
def change_mode(self, mode):
|
||||
anchors, h1, h2 = self.get_anchors_and_handles()
|
||||
self.set_points(anchors, mode = mode)
|
||||
self.set_anchor_points(anchors, mode = mode)
|
||||
return self
|
||||
|
||||
def make_smooth(self):
|
||||
@ -131,7 +132,7 @@ class VectorizedMobject(Mobject):
|
||||
|
||||
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.
|
||||
However, one such path may really consit of separate
|
||||
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
|
||||
it comes time to display.
|
||||
"""
|
||||
subpath_mobject = VectorizedMobject(
|
||||
subpath_mobject = VMobject(
|
||||
is_subpath = True
|
||||
)
|
||||
subpath_mobject.set_points(points)
|
||||
@ -176,7 +177,13 @@ class VectorizedMobject(Mobject):
|
||||
## Alignment
|
||||
|
||||
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]])
|
||||
target_len = larger_mobject.get_num_anchor_points()-1
|
||||
num_curves = self.get_num_anchor_points()-1
|
||||
@ -199,7 +206,7 @@ class VectorizedMobject(Mobject):
|
||||
self.set_points(points)
|
||||
return self
|
||||
|
||||
def get_point_mobject(self, center):
|
||||
def get_point_mobject(self, center = None):
|
||||
if center is None:
|
||||
center = self.get_center()
|
||||
return VectorizedPoint(center)
|
||||
@ -219,7 +226,7 @@ class VectorizedMobject(Mobject):
|
||||
))
|
||||
|
||||
def become_partial(self, mobject, a, b):
|
||||
assert(isinstance(mobject, VectorizedMobject))
|
||||
assert(isinstance(mobject, VMobject))
|
||||
#Partial curve includes three portions:
|
||||
#-A middle section, which matches the curve exactly
|
||||
#-A start, which is some ending portion of an inner cubic
|
||||
@ -246,18 +253,18 @@ class VectorizedMobject(Mobject):
|
||||
return self
|
||||
|
||||
|
||||
class VectorizedPoint(VectorizedMobject):
|
||||
class VectorizedPoint(VMobject):
|
||||
CONFIG = {
|
||||
"color" : BLACK,
|
||||
}
|
||||
def __init__(self, location = ORIGIN, **kwargs):
|
||||
VectorizedMobject.__init__(self, **kwargs)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
self.set_points([location])
|
||||
|
||||
class VectorizedMobjectFromSVGPathstring(VectorizedMobject):
|
||||
class VMobjectFromSVGPathstring(VMobject):
|
||||
def __init__(self, path_string, **kwargs):
|
||||
digest_locals(self)
|
||||
VectorizedMobject.__init__(self, **kwargs)
|
||||
VMobject.__init__(self, **kwargs)
|
||||
|
||||
def get_path_commands(self):
|
||||
return [
|
||||
|
Reference in New Issue
Block a user