mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
commit
9bd4f6714f
37 changed files with 318 additions and 270 deletions
16
README.md
16
README.md
|
@ -21,10 +21,10 @@ If you want to hack on manimlib itself, clone this repository and in that direct
|
|||
|
||||
```sh
|
||||
# Install python requirements
|
||||
pip3 install -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Try it out
|
||||
python3 -m manim example_scenes.py OpeningManimExample
|
||||
python -m manim example_scenes.py OpeningManimExample
|
||||
```
|
||||
|
||||
### Directly (Windows)
|
||||
|
@ -34,8 +34,8 @@ python3 -m manim example_scenes.py OpeningManimExample
|
|||
```sh
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
cd manim
|
||||
pip3 install -r requirements.txt
|
||||
python3 manim.py example_scenes.py OpeningManimExample
|
||||
pip install -r requirements.txt
|
||||
python manim.py example_scenes.py OpeningManimExample
|
||||
```
|
||||
|
||||
|
||||
|
@ -50,14 +50,14 @@ After installing `virtualenv` and `virtualenvwrapper`
|
|||
```sh
|
||||
git clone https://github.com/3b1b/manim.git
|
||||
mkvirtualenv -a manim -r requirements.txt manim
|
||||
python3 -m manim example_scenes.py OpeningManimExample
|
||||
python -m manim example_scenes.py OpeningManimExample
|
||||
```
|
||||
|
||||
|
||||
## Using manim
|
||||
Try running the following:
|
||||
```sh
|
||||
python3 -m manim example_scenes.py OpeningManimExample
|
||||
python -m manim example_scenes.py OpeningManimExample
|
||||
```
|
||||
This should pop up a window playing a simple scene.
|
||||
|
||||
|
@ -65,11 +65,11 @@ Some useful flags include:
|
|||
* `-w` to write the scene to a file
|
||||
* `-o` to write the scene to a file and open the result
|
||||
* `-s` to skip to the end and just show the final frame.
|
||||
* `-so` will asve the final frame to an image and show it
|
||||
* `-so` will save the final frame to an image and show it
|
||||
* `-n <number>` to skip ahead to the `n`'th animation of a scene.
|
||||
* `-f` to make the playback window fullscreen
|
||||
|
||||
Take a look at custom_defaults.yml for further configuration. For example, there you can specify where videos should be output to, where manim should look for image files and sounds you want to read in, and other defaults regarding style and video quality. If you have a file name "custom_defaults.yml" in the same directory where you are calling manim, it will look to the configuration of that file instead of the one in manim itself.
|
||||
Take a look at custom_defaults.yml for further configuration. To add your customization, you can either edit this file, or add another file by the same name "custom_defaults.yml" to whatever directory you are running manim from. For example [this is the one](https://github.com/3b1b/videos/blob/master/custom_defaults.yml) for 3blue1brown videos. There you can specify where videos should be output to, where manim should look for image files and sounds you want to read in, and other defaults regarding style and video quality.
|
||||
|
||||
Look through [https://github.com/3b1b/videos](https://github.com/3b1b/videos) to see the code for previous 3b1b videos. Note, however, that developments are often made to the library without considering backwards compatibility with those old projects. To run an old project with a guarantee that it will work, you will have to go back to the commit which completed that project.
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ from manimlib.imports import *
|
|||
|
||||
class OpeningManimExample(Scene):
|
||||
def construct(self):
|
||||
title = TextMobject("This is some \\LaTeX")
|
||||
basel = TexMobject(
|
||||
title = TexText("This is some \\LaTeX")
|
||||
basel = Tex(
|
||||
"\\sum_{n=1}^\\infty "
|
||||
"\\frac{1}{n^2} = \\frac{\\pi^2}{6}"
|
||||
)
|
||||
|
@ -22,18 +22,18 @@ class OpeningManimExample(Scene):
|
|||
)
|
||||
self.wait()
|
||||
|
||||
transform_title = TextMobject("That was a transform")
|
||||
transform_title.to_corner(UP + LEFT)
|
||||
transform_title = Text("That was a transform")
|
||||
transform_title.to_corner(UL)
|
||||
self.play(
|
||||
Transform(title, transform_title),
|
||||
LaggedStartMap(FadeOut, basel, shift=DOWN),
|
||||
)
|
||||
self.wait()
|
||||
|
||||
fade_comment = TextMobject(
|
||||
fade_comment = Text(
|
||||
"""
|
||||
You probably don't want to overuse\\\\
|
||||
Transforms, though, a simple fade often\\\\
|
||||
You probably don't want to overuse
|
||||
Transforms, though, a simple fade often
|
||||
looks nicer.
|
||||
""",
|
||||
font_size=36,
|
||||
|
@ -48,7 +48,7 @@ class OpeningManimExample(Scene):
|
|||
self.wait(3)
|
||||
|
||||
grid = NumberPlane((-10, 10), (-5, 5))
|
||||
grid_title = TextMobject(
|
||||
grid_title = Text(
|
||||
"But manim is for illustrating math, not text",
|
||||
)
|
||||
grid_title.to_edge(UP)
|
||||
|
@ -65,9 +65,9 @@ class OpeningManimExample(Scene):
|
|||
|
||||
matrix = [[1, 1], [0, 1]]
|
||||
linear_transform_title = VGroup(
|
||||
TextMobject("This is what the matrix"),
|
||||
Text("This is what the matrix"),
|
||||
IntegerMatrix(matrix, include_background_rectangle=True),
|
||||
TextMobject("looks like")
|
||||
Text("looks like")
|
||||
)
|
||||
linear_transform_title.arrange(RIGHT)
|
||||
linear_transform_title.to_edge(UP)
|
||||
|
@ -79,7 +79,7 @@ class OpeningManimExample(Scene):
|
|||
self.play(grid.apply_matrix, matrix, run_time=3)
|
||||
self.wait()
|
||||
|
||||
grid_transform_title = TextMobject(
|
||||
grid_transform_title = Text(
|
||||
"And this is a nonlinear transformation"
|
||||
)
|
||||
grid_transform_title.set_stroke(BLACK, 5, background=True)
|
||||
|
@ -111,12 +111,12 @@ class TextExample(Scene):
|
|||
text = Text("Here is a text", font="Consolas", font_size=90)
|
||||
difference = Text(
|
||||
"""
|
||||
The most important difference between Text and TextMobject is that\n
|
||||
you can change the font more easily, but can't use the LaTeX gramma
|
||||
The most important difference between Text and TexText is that\n
|
||||
you can change the font more easily, but can't use the LaTeX grammar
|
||||
""",
|
||||
font="Arial", font_size=24,
|
||||
# t2c is a dict that you can choose color for different text
|
||||
t2c={"Text": BLUE, "TextMobject": BLUE, "LaTeX": ORANGE}
|
||||
t2c={"Text": BLUE, "TexText": BLUE, "LaTeX": ORANGE}
|
||||
)
|
||||
VGroup(text, difference).arrange(DOWN, buff=1)
|
||||
self.play(Write(text))
|
||||
|
@ -175,18 +175,18 @@ class TexTransformExample(Scene):
|
|||
lines = VGroup(
|
||||
# Surrounding substrings with double braces
|
||||
# will ensure that those parts are separated
|
||||
# out in the TexMobject. For example, here the
|
||||
# TexMobject will have 5 submobjects, corresponding
|
||||
# out in the Tex. For example, here the
|
||||
# Tex will have 5 submobjects, corresponding
|
||||
# to the strings [A^2, +, B^2, =, C^2]
|
||||
TexMobject("{{A^2}} + {{B^2}} = {{C^2}}"),
|
||||
TexMobject("{{A^2}} = {{C^2}} - {{B^2}}"),
|
||||
Tex("{{A^2}} + {{B^2}} = {{C^2}}"),
|
||||
Tex("{{A^2}} = {{C^2}} - {{B^2}}"),
|
||||
# Alternatively, you can pass in the keyword argument
|
||||
# isolate with a list of strings that should be out as
|
||||
# their own submobject. So both lines below are equivalent
|
||||
# to what you'd get by wrapping every instance of "B", "C"
|
||||
# "=", "(" and ")" with double braces
|
||||
TexMobject("{{A^2}} = (C + B)(C - B)", **kw),
|
||||
TexMobject("A = \\sqrt{(C + B)(C - B)}", **kw)
|
||||
Tex("{{A^2}} = (C + B)(C - B)", **kw),
|
||||
Tex("A = \\sqrt{(C + B)(C - B)}", **kw)
|
||||
)
|
||||
lines.arrange(DOWN, buff=LARGE_BUFF)
|
||||
for line in lines:
|
||||
|
@ -258,8 +258,8 @@ class TexTransformExample(Scene):
|
|||
# those of a target, regardless of the submobject hierarchy in
|
||||
# each one, according to whether those pieces have the same
|
||||
# shape (as best it can).
|
||||
source = TextMobject("the morse code")
|
||||
target = TextMobject("here come dots")
|
||||
source = TexText("the morse code")
|
||||
target = TexText("here come dots")
|
||||
|
||||
self.play(Write(source))
|
||||
self.wait()
|
||||
|
@ -343,7 +343,7 @@ class SurfaceExample(Scene):
|
|||
|
||||
# Set perspective
|
||||
frame = self.camera.frame
|
||||
frame.set_rotation(
|
||||
frame.set_euler_angles(
|
||||
theta=-30 * DEGREES,
|
||||
phi=70 * DEGREES,
|
||||
)
|
||||
|
@ -388,7 +388,7 @@ class SurfaceExample(Scene):
|
|||
self.play(light.move_to, 3 * IN, run_time=5)
|
||||
self.play(light.shift, 10 * OUT, run_time=5)
|
||||
|
||||
drag_text = Text("Try clicking and dragging while pressing d")
|
||||
drag_text = Text("Try moving the mouse while pressing d or s")
|
||||
drag_text.move_to(light_text)
|
||||
drag_text.fix_in_frame()
|
||||
|
||||
|
|
10
logo/logo.py
10
logo/logo.py
|
@ -60,10 +60,10 @@ class Thumbnail(GraphScene):
|
|||
triangle.scale(0.1)
|
||||
|
||||
#
|
||||
x_label_p1 = TexMobject("a")
|
||||
output_label_p1 = TexMobject("f(a)")
|
||||
x_label_p2 = TexMobject("b")
|
||||
output_label_p2 = TexMobject("f(b)")
|
||||
x_label_p1 = Tex("a")
|
||||
output_label_p1 = Tex("f(a)")
|
||||
x_label_p2 = Tex("b")
|
||||
output_label_p2 = Tex("f(b)")
|
||||
v_line_p1 = get_v_line(input_tracker_p1)
|
||||
v_line_p2 = get_v_line(input_tracker_p2)
|
||||
h_line_p1 = get_h_line(input_tracker_p1)
|
||||
|
@ -170,7 +170,7 @@ class Thumbnail(GraphScene):
|
|||
# adding manim
|
||||
picture = Group(*self.mobjects)
|
||||
picture.scale(0.6).to_edge(LEFT, buff=SMALL_BUFF)
|
||||
manim = TextMobject("Manim").set_height(1.5) \
|
||||
manim = TexText("Manim").set_height(1.5) \
|
||||
.next_to(picture, RIGHT) \
|
||||
.shift(DOWN * 0.7)
|
||||
self.add(manim)
|
||||
|
|
|
@ -8,7 +8,7 @@ from manimlib.mobject.mobject import Mobject
|
|||
from manimlib.mobject.mobject import Group
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
|
||||
|
||||
|
@ -122,7 +122,7 @@ class TransformMatchingShapes(TransformMatchingParts):
|
|||
|
||||
class TransformMatchingTex(TransformMatchingParts):
|
||||
CONFIG = {
|
||||
"mobject_type": TexMobject,
|
||||
"mobject_type": Tex,
|
||||
"group_type": VGroup,
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ from manimlib.constants import *
|
|||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.mobject import Point
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.bezier import interpolate
|
||||
from manimlib.utils.simple_functions import fdiv
|
||||
from manimlib.utils.simple_functions import clip
|
||||
from manimlib.utils.space_ops import angle_of_vector
|
||||
|
@ -29,38 +28,39 @@ class CameraFrame(Mobject):
|
|||
"focal_distance": 2,
|
||||
}
|
||||
|
||||
def init_data(self):
|
||||
super().init_data()
|
||||
self.data["euler_angles"] = np.array(self.euler_angles, dtype=float)
|
||||
self.refresh_rotation_matrix()
|
||||
|
||||
def init_points(self):
|
||||
self.set_points([ORIGIN, LEFT, RIGHT, DOWN, UP])
|
||||
self.set_width(self.frame_shape[0], stretch=True)
|
||||
self.set_height(self.frame_shape[1], stretch=True)
|
||||
self.move_to(self.center_point)
|
||||
self.euler_angles = np.array(self.euler_angles, dtype='float64')
|
||||
self.refresh_camera_rotation_matrix()
|
||||
|
||||
def to_default_state(self):
|
||||
self.center()
|
||||
self.set_height(FRAME_HEIGHT)
|
||||
self.set_width(FRAME_WIDTH)
|
||||
self.set_rotation(0, 0, 0)
|
||||
self.set_euler_angles(0, 0, 0)
|
||||
return self
|
||||
|
||||
def get_inverse_camera_position_matrix(self):
|
||||
mat = np.identity(4)
|
||||
# Shift so that origin of real space coincides with camera origin
|
||||
mat[:3, 3] = -self.get_center().T
|
||||
# Rotate based on camera orientation
|
||||
mat[:3, :4] = np.dot(self.inverse_camera_rotation_matrix, mat[:3, :4])
|
||||
return mat
|
||||
def get_euler_angles(self):
|
||||
return self.data["euler_angles"]
|
||||
|
||||
def refresh_camera_rotation_matrix(self):
|
||||
theta, phi, gamma = self.euler_angles
|
||||
def get_inverse_camera_rotation_matrix(self):
|
||||
return self.inverse_camera_rotation_matrix
|
||||
|
||||
def refresh_rotation_matrix(self):
|
||||
# Rotate based on camera orientation
|
||||
theta, phi, gamma = self.get_euler_angles()
|
||||
quat = quaternion_mult(
|
||||
quaternion_from_angle_axis(theta, OUT, axis_normalized=True),
|
||||
quaternion_from_angle_axis(phi, RIGHT, axis_normalized=True),
|
||||
quaternion_from_angle_axis(gamma, OUT, axis_normalized=True),
|
||||
)
|
||||
self.inverse_camera_rotation_matrix = rotation_matrix_transpose_from_quaternion(quat)
|
||||
return self
|
||||
|
||||
def rotate(self, angle, axis=OUT, **kwargs):
|
||||
curr_rot_T = self.get_inverse_camera_rotation_matrix()
|
||||
|
@ -74,38 +74,44 @@ class CameraFrame(Mobject):
|
|||
rotation_matrix_transpose(theta, OUT),
|
||||
)
|
||||
gamma = angle_of_vector(np.dot(partial_rot_T, new_rot_T.T)[:, 0])
|
||||
# TODO, write a function that converts quaternions to euler angles
|
||||
self.euler_angles[:] = theta, phi, gamma
|
||||
self.set_euler_angles(theta, phi, gamma)
|
||||
return self
|
||||
|
||||
def set_rotation(self, theta=None, phi=None, gamma=None):
|
||||
def set_euler_angles(self, theta=None, phi=None, gamma=None):
|
||||
if theta is not None:
|
||||
self.euler_angles[0] = theta
|
||||
self.data["euler_angles"][0] = theta
|
||||
if phi is not None:
|
||||
self.euler_angles[1] = phi
|
||||
self.data["euler_angles"][1] = phi
|
||||
if gamma is not None:
|
||||
self.euler_angles[2] = gamma
|
||||
self.refresh_camera_rotation_matrix()
|
||||
self.data["euler_angles"][2] = gamma
|
||||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
||||
def set_theta(self, theta):
|
||||
return self.set_rotation(theta=theta)
|
||||
return self.set_euler_angles(theta=theta)
|
||||
|
||||
def set_phi(self, phi):
|
||||
return self.set_rotation(phi=phi)
|
||||
return self.set_euler_angles(phi=phi)
|
||||
|
||||
def set_gamma(self, gamma):
|
||||
return self.set_rotation(gamma=gamma)
|
||||
return self.set_euler_angles(gamma=gamma)
|
||||
|
||||
def increment_theta(self, dtheta):
|
||||
return self.set_rotation(theta=self.euler_angles[0] + dtheta)
|
||||
self.data["euler_angles"][0] += dtheta
|
||||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
||||
def increment_phi(self, dphi):
|
||||
new_phi = clip(self.euler_angles[1] + dphi, 0, PI)
|
||||
return self.set_rotation(phi=new_phi)
|
||||
phi = self.data["euler_angles"][1]
|
||||
new_phi = clip(phi + dphi, 0, PI)
|
||||
self.data["euler_angles"][1] = new_phi
|
||||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
||||
def increment_gamma(self, dgamma):
|
||||
return self.set_rotation(theta=self.euler_angles[2] + dgamma)
|
||||
self.data["euler_angles"][2] += dgamma
|
||||
self.refresh_rotation_matrix()
|
||||
return self
|
||||
|
||||
def get_shape(self):
|
||||
return (self.get_width(), self.get_height())
|
||||
|
@ -125,11 +131,9 @@ class CameraFrame(Mobject):
|
|||
def get_focal_distance(self):
|
||||
return self.focal_distance * self.get_height()
|
||||
|
||||
def interpolate(self, frame1, frame2, alpha, path_func):
|
||||
self.euler_angles[:] = interpolate(frame1.euler_angles, frame2.euler_angles, alpha)
|
||||
self.refresh_camera_rotation_matrix()
|
||||
# TODO, can probably safely call super
|
||||
self.set_points(interpolate(frame1.get_points(), frame2.get_points(), alpha))
|
||||
def interpolate(self, *args, **kwargs):
|
||||
super().interpolate(*args, **kwargs)
|
||||
self.refresh_rotation_matrix()
|
||||
|
||||
|
||||
class Camera(object):
|
||||
|
@ -412,20 +416,24 @@ class Camera(object):
|
|||
pass
|
||||
|
||||
def refresh_perspective_uniforms(self):
|
||||
frame = self.frame
|
||||
pw, ph = self.get_pixel_shape()
|
||||
fw, fh = self.frame.get_shape()
|
||||
fw, fh = frame.get_shape()
|
||||
# TODO, this should probably be a mobject uniform, with
|
||||
# the camera taking care of the conversion factor
|
||||
anti_alias_width = self.anti_alias_width / (ph / fh)
|
||||
transform = self.frame.get_inverse_camera_position_matrix()
|
||||
light = self.light_source.get_location()
|
||||
transformed_light = np.dot(transform, [*light, 1])[:3]
|
||||
# Orient light
|
||||
rotation = frame.get_inverse_camera_rotation_matrix()
|
||||
light_pos = self.light_source.get_location()
|
||||
light_pos = np.dot(rotation, light_pos)
|
||||
|
||||
self.perspective_uniforms = {
|
||||
'to_screen_space': tuple(transform.T.flatten()),
|
||||
'frame_shape': self.frame.get_shape(),
|
||||
'focal_distance': self.frame.get_focal_distance(),
|
||||
'anti_alias_width': anti_alias_width,
|
||||
'light_source_position': tuple(transformed_light),
|
||||
"frame_shape": frame.get_shape(),
|
||||
"anti_alias_width": anti_alias_width,
|
||||
"camera_center": tuple(frame.get_center()),
|
||||
"camera_rotation": tuple(np.array(rotation).T.flatten()),
|
||||
"light_source_position": tuple(light_pos),
|
||||
"focal_distance": frame.get_focal_distance(),
|
||||
}
|
||||
|
||||
def init_textures(self):
|
||||
|
|
|
@ -7,6 +7,8 @@ import sys
|
|||
import yaml
|
||||
from screeninfo import get_monitors
|
||||
|
||||
from manimlib.utils.config_ops import merge_dicts_recursively
|
||||
|
||||
|
||||
def parse_cli():
|
||||
try:
|
||||
|
@ -147,14 +149,22 @@ def get_module(file_name):
|
|||
|
||||
|
||||
def get_custom_defaults():
|
||||
# See if there's a custom_defaults file in current directory,
|
||||
# otherwise fall back on the one in manimlib
|
||||
filename = "custom_defaults.yml"
|
||||
if not os.path.exists(filename):
|
||||
filename = os.path.join(get_manim_dir(), filename)
|
||||
|
||||
with open(filename, "r") as file:
|
||||
manim_defaults_file = os.path.join(get_manim_dir(), filename)
|
||||
with open(manim_defaults_file, "r") as file:
|
||||
custom_defaults = yaml.safe_load(file)
|
||||
|
||||
# See if there's a custom_defaults file in current directory,
|
||||
# and if so, it further updates the defaults based on it.
|
||||
if os.path.exists(filename):
|
||||
with open(filename, "r") as file:
|
||||
local_defaults = yaml.safe_load(file)
|
||||
if local_defaults:
|
||||
custom_defaults = merge_dicts_recursively(
|
||||
custom_defaults,
|
||||
local_defaults,
|
||||
)
|
||||
|
||||
return custom_defaults
|
||||
|
||||
|
||||
|
@ -198,31 +208,12 @@ def get_configuration(args):
|
|||
# Default to putting window in the upper right of screen,
|
||||
# but make it full screen if -f is passed in
|
||||
monitor = get_monitors()[0]
|
||||
if args.full_screen:
|
||||
window_width = monitor.width
|
||||
else:
|
||||
window_width = monitor.width / 2
|
||||
window_height = window_width * 9 / 16
|
||||
custom_position = custom_defaults["window_position"]
|
||||
if "," in custom_position:
|
||||
posx, posy = map(int, custom_position.split(","))
|
||||
else:
|
||||
if custom_position[1] == "L":
|
||||
posx = 0
|
||||
elif custom_position[1] == "O":
|
||||
posx = int((monitor.width - window_width) / 2)
|
||||
elif custom_position[1] == "R":
|
||||
posx = int(monitor.width - window_width)
|
||||
if custom_position[0] == "U":
|
||||
posy = 0
|
||||
elif custom_position[0] == "O":
|
||||
posy = int((monitor.height - window_height) / 2)
|
||||
elif custom_position[0] == "D":
|
||||
posy = int(monitor.height - window_height)
|
||||
window_position = (posx, posy)
|
||||
if not args.full_screen:
|
||||
window_width //= 2
|
||||
window_height = window_width * 9 // 16
|
||||
config["window_config"] = {
|
||||
"size": (window_width, window_height),
|
||||
"position": window_position,
|
||||
}
|
||||
|
||||
# Arguments related to skipping
|
||||
|
|
|
@ -6,7 +6,7 @@ from manimlib.mobject.functions import ParametricCurve
|
|||
from manimlib.mobject.geometry import Arrow
|
||||
from manimlib.mobject.geometry import Line
|
||||
from manimlib.mobject.number_line import NumberLine
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.utils.config_ops import merge_dicts_recursively
|
||||
from manimlib.utils.simple_functions import binary_search
|
||||
|
@ -69,7 +69,7 @@ class CoordinateSystem():
|
|||
)
|
||||
|
||||
def get_axis_label(self, label_tex, axis, edge, direction, buff=MED_SMALL_BUFF):
|
||||
label = TexMobject(label_tex)
|
||||
label = Tex(label_tex)
|
||||
label.next_to(
|
||||
axis.get_edge_center(edge), direction,
|
||||
buff=buff
|
||||
|
|
|
@ -5,8 +5,8 @@ from manimlib.constants import *
|
|||
from manimlib.mobject.numbers import DecimalNumber
|
||||
from manimlib.mobject.numbers import Integer
|
||||
from manimlib.mobject.shape_matchers import BackgroundRectangle
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TextMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
|
||||
|
@ -28,7 +28,7 @@ def matrix_to_tex_string(matrix):
|
|||
|
||||
|
||||
def matrix_to_mobject(matrix):
|
||||
return TexMobject(matrix_to_tex_string(matrix))
|
||||
return Tex(matrix_to_tex_string(matrix))
|
||||
|
||||
|
||||
def vector_coordinate_label(vector_mob, integer_labels=True,
|
||||
|
@ -61,7 +61,7 @@ class Matrix(VMobject):
|
|||
"bracket_v_buff": MED_SMALL_BUFF,
|
||||
"add_background_rectangles_to_entries": False,
|
||||
"include_background_rectangle": False,
|
||||
"element_to_mobject": TexMobject,
|
||||
"element_to_mobject": Tex,
|
||||
"element_to_mobject_config": {},
|
||||
"element_alignment_corner": DR,
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class Matrix(VMobject):
|
|||
|
||||
def add_brackets(self):
|
||||
height = self.matrix.shape[0]
|
||||
bracket_pair = TexMobject("".join([
|
||||
bracket_pair = Tex("".join([
|
||||
"\\left[",
|
||||
"\\begin{array}{c}",
|
||||
*height * ["\\quad \\\\"],
|
||||
|
@ -173,22 +173,22 @@ class MobjectMatrix(Matrix):
|
|||
|
||||
|
||||
def get_det_text(matrix, determinant=None, background_rect=False, initial_scale_factor=2):
|
||||
parens = TexMobject("(", ")")
|
||||
parens = Tex("(", ")")
|
||||
parens.scale(initial_scale_factor)
|
||||
parens.stretch_to_fit_height(matrix.get_height())
|
||||
l_paren, r_paren = parens.split()
|
||||
l_paren.next_to(matrix, LEFT, buff=0.1)
|
||||
r_paren.next_to(matrix, RIGHT, buff=0.1)
|
||||
det = TextMobject("det")
|
||||
det = TexText("det")
|
||||
det.scale(initial_scale_factor)
|
||||
det.next_to(l_paren, LEFT, buff=0.1)
|
||||
if background_rect:
|
||||
det.add_background_rectangle()
|
||||
det_text = VGroup(det, l_paren, r_paren)
|
||||
if determinant is not None:
|
||||
eq = TexMobject("=")
|
||||
eq = Tex("=")
|
||||
eq.next_to(r_paren, RIGHT, buff=0.1)
|
||||
result = TexMobject(str(determinant))
|
||||
result = Tex(str(determinant))
|
||||
result.next_to(eq, RIGHT, buff=0.2)
|
||||
det_text.add(eq, result)
|
||||
return det_text
|
||||
|
|
|
@ -1145,8 +1145,14 @@ class Mobject(object):
|
|||
def align_family(self, mobject):
|
||||
mob1 = self
|
||||
mob2 = mobject
|
||||
n1 = len(mob1.submobjects)
|
||||
n2 = len(mob2.submobjects)
|
||||
n1 = len(mob1)
|
||||
n2 = len(mob2)
|
||||
while n1 == 1 and n2 > 1:
|
||||
mob1.set_submobjects(mob1[0].submobjects)
|
||||
n1 = len(mob1)
|
||||
while n2 == 1 and n1 > 1:
|
||||
mob2.set_submobjects(mob2[0].submobjects)
|
||||
n2 = len(mob2)
|
||||
if n1 != n2:
|
||||
mob1.add_n_more_submobjects(max(0, n2 - n1))
|
||||
mob2.add_n_more_submobjects(max(0, n1 - n2))
|
||||
|
@ -1169,9 +1175,10 @@ class Mobject(object):
|
|||
curr = len(self.submobjects)
|
||||
if curr == 0:
|
||||
# If empty, simply add n point mobjects
|
||||
center = self.get_center()
|
||||
null_mob = self.copy()
|
||||
null_mob.set_points([self.get_center()])
|
||||
self.set_submobjects([
|
||||
self.copy().set_points([center])
|
||||
null_mob.copy()
|
||||
for k in range(n)
|
||||
])
|
||||
return self
|
||||
|
@ -1276,6 +1283,7 @@ class Mobject(object):
|
|||
for mob in self.get_family():
|
||||
func(mob)
|
||||
mob.refresh_shader_wrapper_id()
|
||||
return self
|
||||
return wrapper
|
||||
|
||||
@affects_shader_info_id
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from manimlib.constants import *
|
||||
from manimlib.mobject.svg.tex_mobject import SingleStringTexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import SingleStringTex
|
||||
from manimlib.mobject.svg.tex_mobject import tex_string_to_mob_map
|
||||
from manimlib.mobject.svg.tex_mobject import SCALE_FACTOR_PER_FONT_POINT
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
|
@ -89,7 +89,7 @@ class DecimalNumber(VMobject):
|
|||
return self.data["font_size"][0]
|
||||
|
||||
def string_to_mob(self, tex_string):
|
||||
# Could just call SingleStringTexMobject, and there is
|
||||
# Could just call SingleStringTex, and there is
|
||||
# some code repetition here by looking to the same cache,
|
||||
# but it keeps things from initializing a new object
|
||||
# more than is necessary
|
||||
|
@ -98,7 +98,7 @@ class DecimalNumber(VMobject):
|
|||
result.scale(self.get_font_size() * SCALE_FACTOR_PER_FONT_POINT)
|
||||
return result
|
||||
else:
|
||||
return SingleStringTexMobject(tex_string, font_size=self.get_font_size())
|
||||
return SingleStringTex(tex_string, font_size=self.get_font_size())
|
||||
|
||||
def get_formatter(self, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -3,8 +3,8 @@ from manimlib.mobject.geometry import Line
|
|||
from manimlib.mobject.geometry import Rectangle
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.svg.brace import Brace
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TextMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.utils.color import color_gradient
|
||||
from manimlib.utils.iterables import listify
|
||||
|
@ -26,7 +26,7 @@ class SampleSpace(Rectangle):
|
|||
|
||||
def add_title(self, title="Sample space", buff=MED_SMALL_BUFF):
|
||||
# TODO, should this really exist in SampleSpaceScene
|
||||
title_mob = TextMobject(title)
|
||||
title_mob = TexText(title)
|
||||
if title_mob.get_width() > self.get_width():
|
||||
title_mob.set_width(self.get_width())
|
||||
title_mob.next_to(self, UP, buff=buff)
|
||||
|
@ -97,7 +97,7 @@ class SampleSpace(Rectangle):
|
|||
if isinstance(label, Mobject):
|
||||
label_mob = label
|
||||
else:
|
||||
label_mob = TexMobject(label)
|
||||
label_mob = Tex(label)
|
||||
label_mob.scale(self.default_label_scale_val)
|
||||
label_mob.next_to(brace, direction, buff)
|
||||
|
||||
|
@ -188,7 +188,7 @@ class BarChart(VGroup):
|
|||
if self.label_y_axis:
|
||||
labels = VGroup()
|
||||
for tick, value in zip(ticks, values):
|
||||
label = TexMobject(str(np.round(value, 2)))
|
||||
label = Tex(str(np.round(value, 2)))
|
||||
label.set_height(self.y_axis_label_height)
|
||||
label.next_to(tick, LEFT, SMALL_BUFF)
|
||||
labels.add(label)
|
||||
|
@ -211,7 +211,7 @@ class BarChart(VGroup):
|
|||
|
||||
bar_labels = VGroup()
|
||||
for bar, name in zip(bars, self.bar_names):
|
||||
label = TexMobject(str(name))
|
||||
label = Tex(str(name))
|
||||
label.scale(self.bar_label_scale_val)
|
||||
label.next_to(bar, DOWN, SMALL_BUFF)
|
||||
bar_labels.add(label)
|
||||
|
|
|
@ -4,14 +4,14 @@ from manimlib.animation.composition import AnimationGroup
|
|||
from manimlib.constants import *
|
||||
from manimlib.animation.fading import FadeIn
|
||||
from manimlib.animation.growing import GrowFromCenter
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TextMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.space_ops import get_norm
|
||||
|
||||
|
||||
class Brace(TexMobject):
|
||||
class Brace(Tex):
|
||||
CONFIG = {
|
||||
"buff": 0.2,
|
||||
"width_multiplier": 2,
|
||||
|
@ -34,7 +34,7 @@ class Brace(TexMobject):
|
|||
self.min_num_quads, self.max_num_quads
|
||||
)
|
||||
tex_string = "\\underbrace{%s}" % (num_quads * "\\qquad")
|
||||
TexMobject.__init__(self, tex_string, **kwargs)
|
||||
Tex.__init__(self, tex_string, **kwargs)
|
||||
self.tip_point_index = np.argmin(self.get_all_points()[:, 1])
|
||||
self.stretch_to_fit_width(target_width)
|
||||
self.shift(left - self.get_corner(UP + LEFT) + self.buff * DOWN)
|
||||
|
@ -56,12 +56,12 @@ class Brace(TexMobject):
|
|||
return self
|
||||
|
||||
def get_text(self, *text, **kwargs):
|
||||
text_mob = TextMobject(*text)
|
||||
text_mob = TexText(*text)
|
||||
self.put_at_tip(text_mob, **kwargs)
|
||||
return text_mob
|
||||
|
||||
def get_tex(self, *tex, **kwargs):
|
||||
tex_mob = TexMobject(*tex)
|
||||
tex_mob = Tex(*tex)
|
||||
self.put_at_tip(tex_mob, **kwargs)
|
||||
return tex_mob
|
||||
|
||||
|
@ -78,7 +78,7 @@ class Brace(TexMobject):
|
|||
|
||||
class BraceLabel(VMobject):
|
||||
CONFIG = {
|
||||
"label_constructor": TexMobject,
|
||||
"label_constructor": Tex,
|
||||
"label_scale": 1,
|
||||
}
|
||||
|
||||
|
@ -135,5 +135,5 @@ class BraceLabel(VMobject):
|
|||
|
||||
class BraceText(BraceLabel):
|
||||
CONFIG = {
|
||||
"label_constructor": TextMobject
|
||||
"label_constructor": TexText
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ from manimlib.mobject.geometry import Rectangle
|
|||
from manimlib.mobject.geometry import Square
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TextMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.three_dimensions import Cube
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
|
@ -21,7 +21,7 @@ from manimlib.utils.space_ops import complex_to_R3
|
|||
from manimlib.utils.space_ops import rotate_vector
|
||||
|
||||
|
||||
class Checkmark(TextMobject):
|
||||
class Checkmark(TexText):
|
||||
CONFIG = {
|
||||
"color": GREEN
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class Checkmark(TextMobject):
|
|||
super().__init__("\\ding{51}")
|
||||
|
||||
|
||||
class Exmark(TextMobject):
|
||||
class Exmark(TexText):
|
||||
CONFIG = {
|
||||
"color": RED
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ class Speedometer(VMobject):
|
|||
for index, angle in enumerate(tick_angle_range):
|
||||
vect = rotate_vector(RIGHT, angle)
|
||||
tick = Line((1 - self.tick_length) * vect, vect)
|
||||
label = TexMobject(str(10 * index))
|
||||
label = Tex(str(10 * index))
|
||||
label.set_height(self.tick_length)
|
||||
label.shift((1 + self.tick_length) * vect)
|
||||
self.add(tick, label)
|
||||
|
@ -365,7 +365,7 @@ class Bubble(SVGMobject):
|
|||
return self.content
|
||||
|
||||
def write(self, *text):
|
||||
self.add_content(TextMobject(*text))
|
||||
self.add_content(TexText(*text))
|
||||
return self
|
||||
|
||||
def resize_to_content(self):
|
||||
|
|
|
@ -20,7 +20,7 @@ SCALE_FACTOR_PER_FONT_POINT = 0.001
|
|||
tex_string_to_mob_map = {}
|
||||
|
||||
|
||||
class SingleStringTexMobject(VMobject):
|
||||
class SingleStringTex(VMobject):
|
||||
CONFIG = {
|
||||
"fill_opacity": 1.0,
|
||||
"stroke_width": 0,
|
||||
|
@ -28,7 +28,7 @@ class SingleStringTexMobject(VMobject):
|
|||
"font_size": 48,
|
||||
"height": None,
|
||||
"organize_left_to_right": False,
|
||||
"alignment": "", # Could be "\\centering",
|
||||
"alignment": "\\centering",
|
||||
"math_mode": True,
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ class SingleStringTexMobject(VMobject):
|
|||
|
||||
def balance_braces(self, tex):
|
||||
"""
|
||||
Makes TexMobject resiliant to unmatched { at start
|
||||
Makes Tex resiliant to unmatched { at start
|
||||
"""
|
||||
num_lefts, num_rights = [tex.count(char) for char in "{}"]
|
||||
while num_rights > num_lefts:
|
||||
|
@ -150,7 +150,7 @@ class SingleStringTexMobject(VMobject):
|
|||
return self
|
||||
|
||||
|
||||
class TexMobject(SingleStringTexMobject):
|
||||
class Tex(SingleStringTex):
|
||||
CONFIG = {
|
||||
"arg_separator": " ",
|
||||
# Note, use of isolate is largely rendered
|
||||
|
@ -205,7 +205,7 @@ class TexMobject(SingleStringTexMobject):
|
|||
tex_string = tex_string.strip()
|
||||
if len(tex_string) == 0:
|
||||
continue
|
||||
sub_tex_mob = SingleStringTexMobject(tex_string, **config)
|
||||
sub_tex_mob = SingleStringTex(tex_string, **config)
|
||||
num_submobs = len(sub_tex_mob)
|
||||
if num_submobs == 0:
|
||||
continue
|
||||
|
@ -227,7 +227,7 @@ class TexMobject(SingleStringTexMobject):
|
|||
return tex1 == tex2
|
||||
|
||||
return VGroup(*filter(
|
||||
lambda m: isinstance(m, SingleStringTexMobject) and test(tex, m.get_tex()),
|
||||
lambda m: isinstance(m, SingleStringTex) and test(tex, m.get_tex()),
|
||||
self.submobjects
|
||||
))
|
||||
|
||||
|
@ -271,14 +271,14 @@ class TexMobject(SingleStringTexMobject):
|
|||
return self
|
||||
|
||||
|
||||
class TextMobject(TexMobject):
|
||||
class TexText(Tex):
|
||||
CONFIG = {
|
||||
"math_mode": False,
|
||||
"arg_separator": "",
|
||||
}
|
||||
|
||||
|
||||
class BulletedList(TextMobject):
|
||||
class BulletedList(TexText):
|
||||
CONFIG = {
|
||||
"buff": MED_LARGE_BUFF,
|
||||
"dot_scale_factor": 2,
|
||||
|
@ -287,9 +287,9 @@ class BulletedList(TextMobject):
|
|||
|
||||
def __init__(self, *items, **kwargs):
|
||||
line_separated_items = [s + "\\\\" for s in items]
|
||||
TextMobject.__init__(self, *line_separated_items, **kwargs)
|
||||
TexText.__init__(self, *line_separated_items, **kwargs)
|
||||
for part in self:
|
||||
dot = TexMobject("\\cdot").scale(self.dot_scale_factor)
|
||||
dot = Tex("\\cdot").scale(self.dot_scale_factor)
|
||||
dot.next_to(part[0], LEFT, SMALL_BUFF)
|
||||
part.add_to_back(dot)
|
||||
self.arrange(
|
||||
|
@ -313,7 +313,7 @@ class BulletedList(TextMobject):
|
|||
other_part.set_fill(opacity=opacity)
|
||||
|
||||
|
||||
class TexMobjectFromPresetString(TexMobject):
|
||||
class TexFromPresetString(Tex):
|
||||
CONFIG = {
|
||||
# To be filled by subclasses
|
||||
"tex": None,
|
||||
|
@ -322,11 +322,11 @@ class TexMobjectFromPresetString(TexMobject):
|
|||
|
||||
def __init__(self, **kwargs):
|
||||
digest_config(self, kwargs)
|
||||
TexMobject.__init__(self, self.tex, **kwargs)
|
||||
Tex.__init__(self, self.tex, **kwargs)
|
||||
self.set_color(self.color)
|
||||
|
||||
|
||||
class Title(TextMobject):
|
||||
class Title(TexText):
|
||||
CONFIG = {
|
||||
"scale_factor": 1,
|
||||
"include_underline": True,
|
||||
|
@ -337,7 +337,7 @@ class Title(TextMobject):
|
|||
}
|
||||
|
||||
def __init__(self, *text_parts, **kwargs):
|
||||
TextMobject.__init__(self, *text_parts, **kwargs)
|
||||
TexText.__init__(self, *text_parts, **kwargs)
|
||||
self.scale(self.scale_factor)
|
||||
self.to_edge(UP)
|
||||
if self.include_underline:
|
||||
|
|
|
@ -4,7 +4,7 @@ import copy
|
|||
import hashlib
|
||||
import cairo
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.geometry import Dot, Rectangle
|
||||
from manimlib.mobject.geometry import Dot
|
||||
from manimlib.mobject.svg.svg_mobject import SVGMobject
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.customization import get_customization
|
||||
|
|
|
@ -8,6 +8,8 @@ from manimlib.utils.iterables import resize_preserving_order
|
|||
|
||||
|
||||
DEFAULT_DOT_CLOUD_RADIUS = 0.05
|
||||
DEFAULT_GRID_HEIGHT = 6
|
||||
DEFAULT_BUFF_RATIO = 0.5
|
||||
|
||||
|
||||
class DotCloud(PMobject):
|
||||
|
@ -34,36 +36,57 @@ class DotCloud(PMobject):
|
|||
self.data["radii"] = np.zeros((1, 1))
|
||||
self.set_radii(self.radii)
|
||||
|
||||
def set_points_by_grid(self, n_rows, n_cols, height=None, width=None):
|
||||
# TODO, add buff/hbuff/vbuff args...
|
||||
new_points = np.zeros((n_rows * n_cols, 3))
|
||||
new_points[:, 0] = np.tile(range(n_cols), n_rows)
|
||||
new_points[:, 1] = np.repeat(range(n_rows), n_cols)
|
||||
new_points[:, 2] = 0
|
||||
self.set_points(new_points)
|
||||
def to_grid(self, n_rows, n_cols, n_layers=1,
|
||||
buff_ratio=None,
|
||||
h_buff_ratio=1.0,
|
||||
v_buff_ratio=1.0,
|
||||
d_buff_ratio=1.0,
|
||||
height=DEFAULT_GRID_HEIGHT,
|
||||
):
|
||||
n_points = n_rows * n_cols * n_layers
|
||||
points = np.repeat(range(n_points), 3, axis=0).reshape((n_points, 3))
|
||||
points[:, 0] = points[:, 0] % n_cols
|
||||
points[:, 1] = (points[:, 1] // n_cols) % n_rows
|
||||
points[:, 2] = points[:, 2] // (n_rows * n_cols)
|
||||
self.set_points(points.astype(float))
|
||||
|
||||
radius = self.data["radii"].max()
|
||||
if height is None:
|
||||
height = n_rows * 3 * radius
|
||||
if width is None:
|
||||
width = n_cols * 3 * radius
|
||||
if buff_ratio is not None:
|
||||
v_buff_ratio = buff_ratio
|
||||
h_buff_ratio = buff_ratio
|
||||
d_buff_ratio = buff_ratio
|
||||
|
||||
self.set_height(height, stretch=True)
|
||||
self.set_width(width, stretch=True)
|
||||
radius = self.get_radius()
|
||||
ns = [n_cols, n_rows, n_layers]
|
||||
brs = [h_buff_ratio, v_buff_ratio, d_buff_ratio]
|
||||
for n, br, dim in zip(ns, brs, range(3)):
|
||||
self.rescale_to_fit(2 * radius * (1 + br) * (n - 1), dim, stretch=True)
|
||||
if height is not None:
|
||||
self.set_height(height)
|
||||
self.center()
|
||||
|
||||
return self
|
||||
|
||||
def set_radii(self, radii):
|
||||
if not isinstance(radii, numbers.Number):
|
||||
radii = resize_preserving_order(radii, self.get_num_points())
|
||||
self.data["radii"][:, 0] = radii
|
||||
radii = resize_preserving_order(radii, len(self.data["radii"]))
|
||||
self.data["radii"][:] = radii
|
||||
return self
|
||||
|
||||
def get_radii(self):
|
||||
return self.data["radii"]
|
||||
|
||||
def get_radius(self):
|
||||
return self.get_radii().max()
|
||||
|
||||
def scale(self, scale_factor, scale_radii=True, **kwargs):
|
||||
super().scale(scale_factor, **kwargs)
|
||||
if scale_radii:
|
||||
self.data["radii"] *= scale_factor
|
||||
self.set_radii(scale_factor * self.get_radii())
|
||||
return self
|
||||
|
||||
def make_3d(self, gloss=0.5, shadow=0.2):
|
||||
self.set_gloss(gloss)
|
||||
self.set_shadow(shadow)
|
||||
self.apply_depth_test()
|
||||
return self
|
||||
|
||||
def get_shader_data(self):
|
||||
|
|
|
@ -816,10 +816,12 @@ class VMobject(Mobject):
|
|||
@triggers_refreshed_triangulation
|
||||
def set_points(self, points):
|
||||
super().set_points(points)
|
||||
return self
|
||||
|
||||
@triggers_refreshed_triangulation
|
||||
def set_data(self, data):
|
||||
super().set_data(data)
|
||||
return self
|
||||
|
||||
# TODO, how to be smart about tangents here?
|
||||
@triggers_refreshed_triangulation
|
||||
|
@ -832,6 +834,7 @@ class VMobject(Mobject):
|
|||
@triggers_refreshed_triangulation
|
||||
def flip(self, *args, **kwargs):
|
||||
super().flip(*args, **kwargs)
|
||||
return self
|
||||
|
||||
# For shaders
|
||||
def init_shader_data(self):
|
||||
|
|
|
@ -2,7 +2,7 @@ import numpy as np
|
|||
|
||||
from manimlib.animation.animation import Animation
|
||||
from manimlib.constants import *
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
||||
|
@ -66,7 +66,7 @@ class RearrangeEquation(Scene):
|
|||
"""
|
||||
num_start_terms = len(start_terms)
|
||||
all_mobs = np.array(
|
||||
TexMobject(start_terms).split() + TexMobject(end_terms).split())
|
||||
Tex(start_terms).split() + Tex(end_terms).split())
|
||||
all_terms = np.array(start_terms + end_terms)
|
||||
for term in set(all_terms):
|
||||
matches = all_terms == term
|
||||
|
@ -86,7 +86,7 @@ class FlipThroughSymbols(Animation):
|
|||
}
|
||||
|
||||
def __init__(self, tex_list, **kwargs):
|
||||
mobject = TexMobject(self.curr_tex).shift(start_center)
|
||||
mobject = Tex(self.curr_tex).shift(start_center)
|
||||
Animation.__init__(self, mobject, **kwargs)
|
||||
|
||||
def interpolate_mobject(self, alpha):
|
||||
|
@ -94,7 +94,7 @@ class FlipThroughSymbols(Animation):
|
|||
|
||||
if new_tex != self.curr_tex:
|
||||
self.curr_tex = new_tex
|
||||
self.mobject = TexMobject(new_tex).shift(self.start_center)
|
||||
self.mobject = Tex(new_tex).shift(self.start_center)
|
||||
if not all(self.start_center == self.end_center):
|
||||
self.mobject.center().shift(
|
||||
(1 - alpha) * self.start_center + alpha * self.end_center
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from manimlib.constants import *
|
||||
from manimlib.mobject.numbers import Integer
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject, VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
from manimlib.utils.simple_functions import choose
|
||||
|
@ -45,7 +45,7 @@ class CountingScene(Scene):
|
|||
self.add(*mobjects)
|
||||
for mob, num in zip(mobjects, it.count(1)):
|
||||
if display_numbers:
|
||||
num_mob = TexMobject(str(num))
|
||||
num_mob = Tex(str(num))
|
||||
num_mob.center().shift(num_offset)
|
||||
self.add(num_mob)
|
||||
if mode == "highlight":
|
||||
|
@ -74,7 +74,7 @@ class CountingScene(Scene):
|
|||
raise Warning("Unknown mode")
|
||||
frame_time = run_time / (len(regions))
|
||||
for region, count in zip(regions, it.count(1)):
|
||||
num_mob = TexMobject(str(count))
|
||||
num_mob = Tex(str(count))
|
||||
num_mob.center().shift(num_offset)
|
||||
self.add(num_mob)
|
||||
self.set_color_region(region)
|
||||
|
@ -113,7 +113,7 @@ class GeneralizedPascalsTriangle(VMobject):
|
|||
]
|
||||
for n, k in self.coords:
|
||||
center = self.coords_to_center(n, k)
|
||||
num_mob = self.submob_class(n, k) # TexMobject(str(num))
|
||||
num_mob = self.submob_class(n, k) # Tex(str(num))
|
||||
scale_factor = min(
|
||||
1,
|
||||
self.portion_to_fill * self.cell_height / num_mob.get_height(),
|
||||
|
@ -137,7 +137,7 @@ class GeneralizedPascalsTriangle(VMobject):
|
|||
def generate_n_choose_k_mobs(self):
|
||||
self.coords_to_n_choose_k = {}
|
||||
for n, k in self.coords:
|
||||
nck_mob = TexMobject(r"{%d \choose %d}" % (n, k))
|
||||
nck_mob = Tex(r"{%d \choose %d}" % (n, k))
|
||||
scale_factor = min(
|
||||
1,
|
||||
self.portion_to_fill * self.cell_height / nck_mob.get_height(),
|
||||
|
@ -161,7 +161,7 @@ class GeneralizedPascalsTriangle(VMobject):
|
|||
return self
|
||||
|
||||
def generate_sea_of_zeros(self):
|
||||
zero = TexMobject("0")
|
||||
zero = Tex("0")
|
||||
self.sea_of_zeros = []
|
||||
for n in range(self.nrows):
|
||||
for a in range((self.nrows - n) / 2 + 1):
|
||||
|
|
|
@ -6,7 +6,7 @@ from manimlib.constants import *
|
|||
from manimlib.mobject.geometry import Arrow
|
||||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Dot
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
@ -26,7 +26,7 @@ class CountingScene(Scene):
|
|||
self.dots = VGroup()
|
||||
self.number = 0
|
||||
self.max_place = 0
|
||||
self.number_mob = VGroup(TexMobject(str(self.number)))
|
||||
self.number_mob = VGroup(Tex(str(self.number)))
|
||||
self.number_mob.scale(self.num_scale_factor)
|
||||
self.number_mob.shift(self.num_start_location)
|
||||
|
||||
|
@ -159,7 +159,7 @@ class CountingScene(Scene):
|
|||
place = 0
|
||||
max_place = self.max_place
|
||||
while place < max_place:
|
||||
digit = TexMobject(str(self.get_place_num(num, place)))
|
||||
digit = Tex(str(self.get_place_num(num, place)))
|
||||
if place >= len(self.digit_place_colors):
|
||||
self.digit_place_colors += self.digit_place_colors
|
||||
digit.set_color(self.digit_place_colors[place])
|
||||
|
|
|
@ -8,7 +8,7 @@ from manimlib.constants import *
|
|||
from manimlib.mobject.geometry import Circle
|
||||
from manimlib.mobject.geometry import Line
|
||||
from manimlib.mobject.matrix import Matrix
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.scene.scene import Scene
|
||||
|
||||
|
@ -48,7 +48,7 @@ class NumericalMatrixMultiplication(Scene):
|
|||
for c in range(k)
|
||||
for prefix in ["" if c == 0 else "+"]
|
||||
]
|
||||
mob_matrix[a][b] = TexMobject(parts, next_to_buff=0.1)
|
||||
mob_matrix[a][b] = Tex(parts, next_to_buff=0.1)
|
||||
return Matrix(mob_matrix)
|
||||
|
||||
def add_lines(self, left, right):
|
||||
|
@ -80,7 +80,7 @@ class NumericalMatrixMultiplication(Scene):
|
|||
self.show_frame()
|
||||
|
||||
def organize_matrices(self, left, right, result):
|
||||
equals = TexMobject("=")
|
||||
equals = Tex("=")
|
||||
everything = VGroup(left, right, equals, result)
|
||||
everything.arrange()
|
||||
everything.set_width(FRAME_WIDTH - 1)
|
||||
|
|
|
@ -9,8 +9,8 @@ from manimlib.mobject.geometry import Line
|
|||
from manimlib.mobject.geometry import Rectangle
|
||||
from manimlib.mobject.geometry import RegularPolygon
|
||||
from manimlib.mobject.number_line import NumberLine
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TextMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VectorizedPoint
|
||||
from manimlib.scene.scene import Scene
|
||||
|
@ -84,7 +84,7 @@ class GraphScene(Scene):
|
|||
self.x_labeled_nums = [x for x in self.x_labeled_nums if x != 0]
|
||||
x_axis.add_numbers(*self.x_labeled_nums)
|
||||
if self.x_axis_label:
|
||||
x_label = TextMobject(self.x_axis_label)
|
||||
x_label = TexText(self.x_axis_label)
|
||||
x_label.next_to(
|
||||
x_axis.get_tick_marks(), UP + RIGHT,
|
||||
buff=SMALL_BUFF
|
||||
|
@ -118,7 +118,7 @@ class GraphScene(Scene):
|
|||
self.y_labeled_nums = [y for y in self.y_labeled_nums if y != 0]
|
||||
y_axis.add_numbers(*self.y_labeled_nums)
|
||||
if self.y_axis_label:
|
||||
y_label = TextMobject(self.y_axis_label)
|
||||
y_label = TexText(self.y_axis_label)
|
||||
y_label.next_to(
|
||||
y_axis.get_corner(UP + RIGHT), UP + RIGHT,
|
||||
buff=SMALL_BUFF
|
||||
|
@ -201,7 +201,7 @@ class GraphScene(Scene):
|
|||
buff=MED_SMALL_BUFF,
|
||||
color=None,
|
||||
):
|
||||
label = TexMobject(label)
|
||||
label = Tex(label)
|
||||
color = color or graph.get_color()
|
||||
label.set_color(color)
|
||||
if x_val is None:
|
||||
|
@ -395,11 +395,11 @@ class GraphScene(Scene):
|
|||
|
||||
labels = VGroup()
|
||||
if dx_label is not None:
|
||||
group.dx_label = TexMobject(dx_label)
|
||||
group.dx_label = Tex(dx_label)
|
||||
labels.add(group.dx_label)
|
||||
group.add(group.dx_label)
|
||||
if df_label is not None:
|
||||
group.df_label = TexMobject(df_label)
|
||||
group.df_label = Tex(df_label)
|
||||
labels.add(group.df_label)
|
||||
group.add(group.df_label)
|
||||
|
||||
|
@ -444,9 +444,9 @@ class GraphScene(Scene):
|
|||
triangle.set_fill(color, 1)
|
||||
triangle.set_stroke(width=0)
|
||||
if label is None:
|
||||
T_label = TexMobject(self.variable_point_label, fill_color=color)
|
||||
T_label = Tex(self.variable_point_label, fill_color=color)
|
||||
else:
|
||||
T_label = TexMobject(label, fill_color=color)
|
||||
T_label = Tex(label, fill_color=color)
|
||||
|
||||
T_label.next_to(triangle, DOWN)
|
||||
v_line = self.get_vertical_line_to_graph(
|
||||
|
|
|
@ -118,9 +118,13 @@ class Scene(object):
|
|||
shell = InteractiveShellEmbed()
|
||||
# Have the frame update after each command
|
||||
shell.events.register('post_run_cell', lambda *a, **kw: self.update_frame())
|
||||
# Stack depth of 2 means the shell will use
|
||||
# the namespace of the caller, not this method
|
||||
shell(stack_depth=2)
|
||||
# Use the locals of the caller as the local namespace
|
||||
# once embeded, and add a few custom shortcuts
|
||||
local_ns = inspect.currentframe().f_back.f_locals
|
||||
local_ns["touch"] = self.interact
|
||||
for term in ("play", "add", "remove", "clear"):
|
||||
local_ns[term] = getattr(self, term)
|
||||
shell(local_ns=local_ns, stack_depth=2)
|
||||
# End scene when exiting an embed.
|
||||
raise EndSceneEarlyException()
|
||||
|
||||
|
@ -510,13 +514,20 @@ class Scene(object):
|
|||
# Event handling
|
||||
def on_mouse_motion(self, point, d_point):
|
||||
self.mouse_point.move_to(point)
|
||||
|
||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
||||
self.mouse_drag_point.move_to(point)
|
||||
frame = self.camera.frame
|
||||
if self.window.is_key_pressed(ord("d")):
|
||||
frame.increment_theta(-d_point[0])
|
||||
frame.increment_phi(d_point[1])
|
||||
elif self.window.is_key_pressed(ord("s")):
|
||||
shift = -d_point
|
||||
shift[0] *= frame.get_width() / 2
|
||||
shift[1] *= frame.get_height() / 2
|
||||
transform = frame.get_inverse_camera_rotation_matrix()
|
||||
shift = np.dot(np.transpose(transform), shift)
|
||||
frame.shift(shift)
|
||||
|
||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
||||
self.mouse_drag_point.move_to(point)
|
||||
|
||||
def on_mouse_press(self, point, button, mods):
|
||||
pass
|
||||
|
@ -529,11 +540,10 @@ class Scene(object):
|
|||
if self.window.is_key_pressed(ord("z")):
|
||||
factor = 1 + np.arctan(10 * offset[1])
|
||||
frame.scale(factor, about_point=point)
|
||||
elif self.window.is_key_pressed(ord("s")):
|
||||
transform = frame.get_inverse_camera_position_matrix()
|
||||
shift = np.dot(transform[:3, :3].T, offset)
|
||||
shift *= 10 * frame.get_height()
|
||||
frame.shift(-shift)
|
||||
else:
|
||||
transform = frame.get_inverse_camera_rotation_matrix()
|
||||
shift = np.dot(np.transpose(transform), offset)
|
||||
frame.shift(-20.0 * shift)
|
||||
|
||||
def on_key_release(self, symbol, modifiers):
|
||||
pass
|
||||
|
|
|
@ -20,8 +20,8 @@ from manimlib.mobject.matrix import Matrix
|
|||
from manimlib.mobject.matrix import VECTOR_LABEL_SCALE_FACTOR
|
||||
from manimlib.mobject.matrix import vector_coordinate_label
|
||||
from manimlib.mobject.mobject import Mobject
|
||||
from manimlib.mobject.svg.tex_mobject import TexMobject
|
||||
from manimlib.mobject.svg.tex_mobject import TextMobject
|
||||
from manimlib.mobject.svg.tex_mobject import Tex
|
||||
from manimlib.mobject.svg.tex_mobject import TexText
|
||||
from manimlib.mobject.types.vectorized_mobject import VGroup
|
||||
from manimlib.mobject.types.vectorized_mobject import VMobject
|
||||
from manimlib.scene.scene import Scene
|
||||
|
@ -123,10 +123,10 @@ class VectorScene(Scene):
|
|||
rotate=False,
|
||||
color=None,
|
||||
label_scale_factor=VECTOR_LABEL_SCALE_FACTOR):
|
||||
if not isinstance(label, TexMobject):
|
||||
if not isinstance(label, Tex):
|
||||
if len(label) == 1:
|
||||
label = "\\vec{\\textbf{%s}}" % label
|
||||
label = TexMobject(label)
|
||||
label = Tex(label)
|
||||
if color is None:
|
||||
color = vector.get_color()
|
||||
label.set_color(color)
|
||||
|
@ -419,7 +419,7 @@ class LinearTransformationScene(VectorScene):
|
|||
|
||||
def add_title(self, title, scale_factor=1.5, animate=False):
|
||||
if not isinstance(title, Mobject):
|
||||
title = TextMobject(title).scale(scale_factor)
|
||||
title = TexText(title).scale(scale_factor)
|
||||
title.to_edge(UP)
|
||||
title.add_background_rectangle()
|
||||
if animate:
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
uniform float anti_alias_width;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float focal_distance;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
uniform sampler2D Texture;
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
uniform vec2 frame_shape;
|
||||
uniform float anti_alias_width;
|
||||
uniform vec3 camera_center;
|
||||
uniform mat3 camera_rotation;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float focal_distance;
|
|
@ -1,9 +1,8 @@
|
|||
// Assumes the following uniforms exist in the surrounding context:
|
||||
// uniform mat4 to_screen_space;
|
||||
// uniform vec3 camera_center;
|
||||
// uniform mat3 camera_rotation;
|
||||
|
||||
vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_point){
|
||||
// normal = get_unit_normal(point, du_point, dv_point);
|
||||
// return normalize((to_screen_space * vec4(normal, 0.0)).xyz);
|
||||
vec3 cp = cross(
|
||||
(du_point - point),
|
||||
(dv_point - point)
|
||||
|
@ -13,6 +12,5 @@ vec3 get_rotated_surface_unit_normal_vector(vec3 point, vec3 du_point, vec3 dv_p
|
|||
vec3 v2 = dv_point - point;
|
||||
cp = cross(cross(v2, point), v2);
|
||||
}
|
||||
// The zero is deliberate, as we only want to rotate and not shift
|
||||
return normalize((to_screen_space * vec4(cp, 0.0)).xyz);
|
||||
return normalize(rotate_point_into_frame(cp));
|
||||
}
|
|
@ -1,12 +1,19 @@
|
|||
// Assumes the following uniforms exist in the surrounding context:
|
||||
// uniform mat4 to_screen_space;
|
||||
// uniform float is_fixed_in_frame;
|
||||
// uniform vec3 camera_center;
|
||||
// uniform mat3 camera_rotation;
|
||||
|
||||
vec3 rotate_point_into_frame(vec3 point){
|
||||
if(bool(is_fixed_in_frame)){
|
||||
return point;
|
||||
}
|
||||
return camera_rotation * point;
|
||||
}
|
||||
|
||||
|
||||
vec3 position_point_into_frame(vec3 point){
|
||||
if(bool(is_fixed_in_frame)){
|
||||
return point;
|
||||
}else{
|
||||
// Simply apply the pre-computed to_screen_space matrix.
|
||||
return (to_screen_space * vec4(point, 1)).xyz;
|
||||
}
|
||||
return rotate_point_into_frame(point - camera_center);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec4 color;
|
||||
in float fill_all; // Either 0 or 1e
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in vec3 unit_normal;
|
||||
|
@ -18,7 +17,7 @@ out float v_vert_index;
|
|||
|
||||
void main(){
|
||||
bp = position_point_into_frame(point);
|
||||
v_global_unit_normal = normalize(to_screen_space * vec4(unit_normal, 0)).xyz;
|
||||
v_global_unit_normal = rotate_point_into_frame(unit_normal);
|
||||
v_color = color;
|
||||
v_vert_index = vert_index;
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform vec3 light_source_position;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec2 uv_coords;
|
||||
in vec2 uv_b2;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in vec3 prev_point;
|
||||
|
@ -28,7 +27,7 @@ void main(){
|
|||
bp = position_point_into_frame(point);
|
||||
prev_bp = position_point_into_frame(prev_point);
|
||||
next_bp = position_point_into_frame(next_point);
|
||||
v_global_unit_normal = normalize(to_screen_space * vec4(unit_normal, 0)).xyz;
|
||||
v_global_unit_normal = rotate_point_into_frame(unit_normal);
|
||||
|
||||
v_stroke_width = STROKE_WIDTH_CONVERSION * stroke_width;
|
||||
v_color = color;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
uniform float anti_alias_width;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float focal_distance;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float focal_distance;
|
||||
uniform vec3 light_source_position;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in vec3 du_point;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float focal_distance;
|
||||
uniform vec3 light_source_position;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in vec3 du_point;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
#version 330
|
||||
|
||||
uniform vec2 frame_shape;
|
||||
uniform float anti_alias_width;
|
||||
uniform mat4 to_screen_space;
|
||||
uniform float is_fixed_in_frame;
|
||||
uniform float focal_distance;
|
||||
#INSERT camera_uniform_declarations.glsl
|
||||
|
||||
in vec3 point;
|
||||
in float radius;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import moderngl_window as mglw
|
||||
from moderngl_window.context.pyglet.window import Window as PygletWindow
|
||||
from moderngl_window.timers.clock import Timer
|
||||
from screeninfo import get_monitors
|
||||
|
||||
from manimlib.utils.config_ops import digest_config
|
||||
from manimlib.utils.customization import get_customization
|
||||
|
||||
|
||||
class Window(PygletWindow):
|
||||
|
@ -18,16 +20,33 @@ class Window(PygletWindow):
|
|||
digest_config(self, kwargs)
|
||||
self.scene = scene
|
||||
self.title = str(scene)
|
||||
if "position" in kwargs:
|
||||
self.position = kwargs["position"]
|
||||
|
||||
self.pressed_keys = set()
|
||||
self.position = self.find_initial_position()
|
||||
|
||||
mglw.activate_context(window=self)
|
||||
self.timer = Timer()
|
||||
self.config = mglw.WindowConfig(ctx=self.ctx, wnd=self, timer=self.timer)
|
||||
self.timer.start()
|
||||
|
||||
def find_initial_position(self):
|
||||
custom_position = get_customization()["window_position"]
|
||||
monitor = get_monitors()[0]
|
||||
window_width, window_height = self.size
|
||||
# Position might be specified with a string of the form
|
||||
# x,y for integers x and y
|
||||
if "," in custom_position:
|
||||
return tuple(map(int, custom_position.split(",")))
|
||||
|
||||
# Alternatively, it might be specified with a string like
|
||||
# UR, OO, DL, etc. specifiying what corner it should go to
|
||||
char_to_n = {"L": 0, "U": 0, "O": 1, "R": 2, "D": 2}
|
||||
width_diff = monitor.width - window_width
|
||||
height_diff = monitor.height - window_height
|
||||
return (
|
||||
char_to_n[custom_position[1]] * width_diff // 2,
|
||||
char_to_n[custom_position[0]] * height_diff // 2,
|
||||
)
|
||||
|
||||
# Delegate event handling to scene
|
||||
def pixel_coords_to_space_coords(self, px, py, relative=False):
|
||||
return self.scene.camera.pixel_coords_to_space_coords(px, py, relative)
|
||||
|
|
Loading…
Add table
Reference in a new issue