Manim RubiksCube¶
This plugin for Manim Community provides an implementation of the classic Rubik’s Cube. This project is open source (available on GitHub).
Installation¶
This plugin is available on PyPi. Usage of this plugin assumes that Python and Manim are both correctly installed. Manim is listed as a dependency, but no version is specified. This is because the plugin will work with any version of Manim Community that does not have breaking changes to the plugin. Some releases of this plugin have been tested with certain versions of Manim. To see what versions of Manim are confirmed to be compatible with this plugin (to the best of my testing), see Releases. By no means is this exclusive. Most releases of this plugin will work, more or less, on all versions of Manim Community.
To install the RubiksCube plugin run:
pip install manim-rubikscube
To see what version of manim-rubikscube you are running:
manim-rubikscube
or
pip list
Importing¶
To use the RubiksCube, you can either:
Add
from manim_rubikscube import *
to your scriptFollow the Manim steps for using plugins
Once the RubiksCube is imported, you can use the RubiksCube as any other mobject.
Examples¶
Creating a RubiksCube¶
FadeInExample ¶
from manim import *
from manim_rubikscube import *
class FadeInExample(ThreeDScene):
def construct(self):
# After creating the RubiksCube, it may be necessary to scale it to
# comfortably see the cube in the camera's frame
cube = RubiksCube().scale(0.6)
# Setup where the camera looks
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
# At this point, you have created a RubiksCube object.
# All that's left is to add it to the scene.
# A RubiksCube acts as any other Mobject. It can be added with
# self.add() or any Manim creation animation
self.play(
FadeIn(cube)
)
# Rotate the camera around the RubiksCube for 8 seconds
self.begin_ambient_camera_rotation(rate=0.5)
self.wait(8)
Changing the colors of a RubiksCube¶
ColorExample ¶
from manim import *
from manim_rubikscube import *
class ColorExample(ThreeDScene):
def construct(self):
# Colors are passed in the order [Up, Right, Front, Down, Left, Back]
# Default is [WHITE, "#B90000", "#009B48", "#FFD500", "#FF5900", "#0045AD"]
cube = RubiksCube(colors=[WHITE, ORANGE, DARK_BLUE, YELLOW, PINK, "#00FF00"]).scale(0.6)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.begin_ambient_camera_rotation(rate=0.5)
self.wait(8)
Setting the state of a RubiksCube¶
When you have a RubiksCube in real life and want to replicate it in manim, the set_state()
method enables this functionality. Or, if you know the state of any cube without knowing what movements got it to that point, this method allows you to also replicate that.
StateExample ¶
from manim import *
from manim_rubikscube import *
class StateExample(ThreeDScene):
def construct(self):
cube = RubiksCube().scale(0.6)
# The set_state method takes in a String that tells the RubiksCube what color each Cubie
# should be. Imagine that you have a RubiksCube that is flattened to 2D as below:
# |************|
# |*U1**U2**U3*|
# |************|
# |*U4**U5**U6*|
# |************|
# |*U7**U8**U9*|
# |************|
# |************|************|************|************|
# |*L1**L2**L3*|*F1**F2**F3*|*R1**R2**R3*|*B1**B2**B3*|
# |************|************|************|************|
# |*L4**L5**L6*|*F4**F5**F6*|*R4**R5**R6*|*B4**B5**B6*|
# |************|************|************|************|
# |*L7**L8**L9*|*F7**F8**F9*|*R7**R8**R9*|*B7**B8**B9*|
# |************|************|************|************|
# |************|
# |*D1**D2**D3*|
# |************|
# |*D4**D5**D6*|
# |************|
# |*D7**D8**D9*|
# |************|
# In order to tell the set_state method what color the U1 cubie should be, you tell it
# which face's color that is.
# For example, if the R face of the Cube is pink and U1 is pink,
# the first letter in the string is R.
# Similarly, because the center of the U face (U5) does not change color,
# it will be the letter U in the state string
# (for the U face, that would mean the 5th letter in the string).
# Starting at the number 1 cubie and working to the number 9 cubie, the order
# of the state string is the U face, then R face, followed by F, D, L, B,
# in that order.
# So, the first 9 letters in the string below tell the RubiksCube what color each
# Cubie in the U face is. So on and so forth for the other sides.
# This method works for a cube of any dimensions, as long as a color is provided
# for each Cubie face.
cube.set_state("BBFBUBUDFDDUURDDURLLLDFRBFRLLFFDLUFBDUBBLFFUDLRRRBLURR")
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.begin_ambient_camera_rotation(rate=0.5)
self.wait(8)
Properties of a RubiksCube¶
Note: It is not necessary to pass any parameters to the RubiksCube. Doing so is entirely for additional functionality and stylistic tweaks.
To this point, we have seen that one property of a RubiksCube is a list of colors for the cube faces. There are currently two other parameters that can be passed.
Dimension¶
2-Dimensional RubiksCube¶
TwoDimensionalExample ¶
from manim import *
from manim_rubikscube import *
class TwoDimensionalExample(ThreeDScene):
def construct(self):
# The first parameter the RubiksCube takes is dimension.
# Alternatively, dim=2 can be passed. Default dim is 3
cube = RubiksCube(2).scale(0.6)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.begin_ambient_camera_rotation(rate=0.5)
self.wait(3)
An example of set_state()
on a non-3-dimensional cube:
TwoDimensionalStateExample ¶
from manim import *
from manim_rubikscube import *
class TwoDimensionalStateExample(ThreeDScene):
def construct(self):
cube = RubiksCube(2).scale(0.6)
# Notice how there are fewer characters in the string compared
# to the 3-dimensional cube because there are less cubie faces to display
cube.set_state("RUFBLLBDRDDBRUUDLFFBFRLU")
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.begin_ambient_camera_rotation(rate=0.5)
self.wait(3)
10-Dimensional RubiksCube¶
WARNING: While this plugin can create a RubiksCube with large dimensions, it takes a long time to render. In the future, OpenGL rendering will vastly improve this.
TenDimensionalExample ¶
from manim import *
from manim_rubikscube import *
class TenDimensionalExample(ThreeDScene):
def construct(self):
cube = RubiksCube(10).scale(0.2)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.add(cube)
Offset¶
A RubiksCube has three different offset values. Offsets can be useful for isolating faces or Cubies for further explanation or analysis.
The
x_offset
determines how close/far Cubies are from Front to BackThe
y_offset
determines how close/far Cubies are from Right to LeftThe
z_offset
determines how close/far Cubies are from Top to Bottom
The default value for all three offsets is 2.1
. Adjusting these offsets changes the “gap” between Cubies
Offsets of 3¶
ThreeOffsetExample ¶
from manim import *
from manim_rubikscube import *
class ThreeOffsetExample(ThreeDScene):
def construct(self):
# Passing in 3 for each offset
cube = RubiksCube(x_offset=3, y_offset=3, z_offset=3).scale(0.5)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.begin_ambient_camera_rotation(rate=0.5)
self.wait(3)
y_offset of 4¶
YOffsetExample ¶
from manim import *
from manim_rubikscube import *
class YOffsetExample(ThreeDScene):
def construct(self):
# Only setting the y_offset
cube = RubiksCube(y_offset=4).scale(0.6)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.begin_ambient_camera_rotation(rate=0.5)
self.wait(3)
Accessing Faces and Cubies¶
Accessing a Cubie¶
A cubie is each individual cube in a RubiksCube. For a 3x3x3 RubiksCube, there are 27 cubies. The cube’s cubies are stored in a numpy array called cubies
.
For a 3-dimensional RubiksCube, the cubies array is structured as follows:
Shape: (dim, dim, dim)
[
[
[Cubie, Cubie, Cubie],
[Cubie, Cubie, Cubie],
[Cubie, Cubie, Cubie]
],
[
[Cubie, Cubie, Cubie],
[Cubie, Cubie, Cubie],
[Cubie, Cubie, Cubie]
],
[
[Cubie, Cubie, Cubie],
[Cubie, Cubie, Cubie],
[Cubie, Cubie, Cubie]
]
]
Each “level” in the array represents a coordinate. Each of the first three arrays represents a different X value (0, 1, or 2). In each of those arrays, there are three more arrays, each representing a different Y value (0, 1, or 2). Finally, there are three Cubie objects. Each represents a different Z value. The size of this array directly corresponds to the dimension of the RubiksCube. This structure, along with numpy, allows for easy, convenient, and cheap accessing of cubies and faces.
For Reference: If facing the Rubik’s Cube, X goes Front to Back, Y goes Right to Left, Z goes Down to Up. Each coordinate starts at 0 and goes to (Dimension - 1)
So, to access the Cubie at coordinates X=0, Y=0, Z=0, cube.cubies[0, 0, 0]
will return it. This holds true no matter the dimension of the RubiksCube.
IndicateCubieExample ¶
from manim import *
from manim_rubikscube import *
class IndicateCubieExample(ThreeDScene):
def construct(self):
cube = RubiksCube().scale(0.6)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.wait()
# Retrieve the cubie at 0, 0, 0 and play the Indicate animation on it
self.play(
Indicate(cube.cubies[0, 0, 0])
)
self.wait()
Accessing a Face¶
The RubiksCube has a method called get_face()
that will return an array of Cubies. At its core, this just accesses Cubies like we did above.
Because the front face of the RubiksCube has an X value of 0 (regardless of the dimension of the cube), returning all Cubies with an X value of 0 will give you the front face. When cube.get_face("F")
is called, it is effectively returning cube.cubies[0, :, :]
. This is possible for all 6 faces of the RubiksCube, and it can also be used manually to return more than just one “slice” of a RubiksCube at a time. This is achievable with numpy indexing.
IndicateFaceExample ¶
from manim import *
from manim_rubikscube import *
class IndicateFaceExample(ThreeDScene):
def construct(self):
cube = RubiksCube().scale(0.6)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.wait()
# Because get_face() returns an array of Cubie objects, they must
# be added to a VGroup before an animation can be called on all
# of them simultaneously
self.play(
Indicate(VGroup(*cube.get_face("F")))
)
self.wait()
Accessing a Cubie Face¶
Just as the cube’s get_face()
method works, once you have accessed a Cubie object, you can call get_face(face)
. For example, calling cube.cubies[0, 0, 0].get_face("F")
will return the front face of that cubie as a Square()
mobject. If the get_face()
method returns a different square than you expected, it is likely a result of the RubiksCube’s or the camera’s orientation changing your perspective of direction in the scene.
Face Rotations¶
The recommended way to rotate a face of the RubiksCube is to use the CubeMove()
animation. I highly discourage trying to rotate the cube without using this pre-made animation. While possible, it’s not worth it.
CubeMove animation¶
CubeMoveExample ¶
from manim import *
from manim_rubikscube import *
class CubeMoveExample(ThreeDScene):
def construct(self):
cube = RubiksCube().scale(0.6)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
self.play(
FadeIn(cube)
)
self.wait()
# CubeMove() is the recommended way to animate a move. It functions very similiarly to
# Rotating(). It takes a RubiksCube object and the face to rotate. The possible faces
# are F, B, U, D, L, and R. To do an inverse move, it is proceeded by a single quote (').
# To do a double move, put a "2" after the face to move. All three variations are shown:
self.play(CubeMove(cube, "F"))
# If you think a move is too fast or too slow, run_time can be provided (in seconds).
self.play(CubeMove(cube, "U2"), run_time=2)
self.play(CubeMove(cube, "R'"))
self.wait()
Solving the Cube¶
This implementation of a RubiksCube also includes Kociemba’s algorithm, a brilliantly fast solving algorithm made by Herbert Kociemba. The RubiksCube object includes the method solve_by_kociemba()
. Given a state, it will return a list of moves to perform. Solving is only possible for 3-dimensional cubes. Solving any other size RubiksCube will require hardcoding of the moves to perform. Currently, solve_by_kociemba()
requires a state string to solve (like the one used in set_state()
). In the future, this will be replaced with using the state of the cube without having to manually input the state of the cube.
from manim import *
from manim_rubikscube import *
class SolveExample(ThreeDScene):
def construct(self):
cube = RubiksCube()
print(cube.solve_by_kociemba("BBFBUBUDFDDUURDDURLLLDFRBFRLLFFDLUFBDUBBLFFUDLRRRBLURR"))
Given the state of the Cube, it returned the necessary moves to execute to solve it. All moves returned by the method are able to be read by CubeMove()
.
solve_by_kociemba() returned:
['F2', 'B2', "R'", "B'", 'R2', "L'", 'D', "F'", 'U', 'B', 'U2', 'L', 'U2', "R'", 'D2', 'R', 'L', 'D2', 'F2', 'B2']
Putting it All Together¶
AllTogetherExample ¶
from manim import *
from manim_rubikscube import *
class AllTogetherExample(ThreeDScene):
def construct(self):
# Change the cube from default colors
cube = RubiksCube(colors=[WHITE, ORANGE, DARK_BLUE, YELLOW, PINK, "#00FF00"]).scale(0.6)
self.move_camera(phi=50*DEGREES, theta=160*DEGREES)
self.renderer.camera.frame_center = cube.get_center()
# Set the state of the cube
state = "BBFBUBUDFDDUURDDURLLLDFRBFRLLFFDLUFBDUBBLFFUDLRRRBLURR"
cube.set_state(state)
self.play(FadeIn(cube))
self.wait()
# Loop through results of the kociemba algorithm
for m in cube.solve_by_kociemba(state):
# Execute the move
self.play(CubeMove(cube, m), run_time=1.5)
# Show the final product
self.play(
Rotating(cube, radians=2*PI, run_time=2)
)
To do¶
Transform()
between RubiksCubes of different dimensionsRotate multiple slices (like rotating the two front faces of a 4x4x4)
Check solvability of cube
Execute string of cube moves
solve_by_kociemba()
on current state of cubeAllow for parameters to be passed from
RubiksCube
toCubie
for use by theSquare()
facesSwitch from using center-tracking to index-tracking for
adjust_indices()
Allow for coloring of inner faces of Cubies
Focus on cubies and faces
Clean the code
and much more!
Acknowledgments¶
XorUnison’s Honeycomb mobject is an upcoming feature in Manim Community. This RubiksCube plugin takes advantage of Honeycomb’s
transform_tile()
method and offset implementation. Until it is added to Manim Community, the best way to see Honeycomb is through his videos on Tiling, the 2D version of Honeycomb.
License¶
This plugin is licensed under the MIT license (see LICENSE file).