space

class SpaceScene(gravity=(0, -9.81), **kwargs)[source]

Bases: ZoomedScene

A rotational spring connection is created between the two rigid bodies. When the actual relative angle deviates from the target angle, the spring torque pulls it back; the damping torque dampens the oscillation.

Parameters

a_mob

The first Mobject to be connected. Typically acts as the pivot point or one of the bodies under physical influence.

b_mob

The second Mobject to be connected. It is linked to a_mob via a physical constraint such as a spring or hinge.

Examples

Example: SpaceSceneExample

from manim import *

import random

from manim import *
from manim_pymunk import *
from pathlib import Path
import svgelements as se

class Car(VGroup):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        width, height, wheel_radius = 2.5, 1.5, 1
        self.body = Rectangle(width=width, height=height, color=BLUE, fill_opacity=0.8)

        # 锚点获取
        p_B_body = self.body.get_corner(DL)
        p_C_body = self.body.get_corner(DR)

        # 车轮创建
        self.back_wheel = Circle(radius=wheel_radius, color=WHITE, fill_opacity=1)
        self.back_wheel.move_to(p_B_body + LEFT * 1.5 + DOWN * 0.5)

        self.front_wheel = Circle(radius=wheel_radius, color=WHITE, fill_opacity=1)
        self.front_wheel.move_to(p_C_body + RIGHT * 1.5 + DOWN * 0.5)

        # --- 新增:直角三角形铲斗 ABC ---
        # B点位置:前轮中心右侧 0.5
        pos_B = (
            self.front_wheel.get_center() + (wheel_radius + 0.3) * RIGHT + DOWN * 0.5
        )
        # C点位置:B点右侧 0.8 (水平)
        pos_C = pos_B + RIGHT * 1.8
        # A点位置:B点上方 0.6 (垂直)
        pos_A = pos_B + UP * 1

        self.shovel = Polygon(pos_A, pos_B, pos_C, color=ORANGE, fill_opacity=0.9)
        print(self.shovel.get_vertices())
        self.add(self.body, self.back_wheel, self.front_wheel, self.shovel)

    def add_constraints(self, static_body):
        # 1. 基础点位获取
        # 统一获取中心点,用于计算相对偏移
        body_center = self.body.get_center()
        # 1. 计算相对偏移(局部坐标)
        # 这样无论 move_to 到哪,结果都是固定的偏移量
        loc_A = self.body.get_corner(UL) - body_center
        loc_B = self.body.get_corner(DL) - body_center
        loc_C = self.body.get_corner(DR) - body_center
        loc_D = self.body.get_corner(UR) - body_center

        # 铲斗顶点坐标(用于物理锚点参考)
        # 注意:Manim-Pymunk 内部需要相对于各自 Mobject 中心的相对坐标
        s_A = self.shovel.get_vertices()[0]
        s_B = self.shovel.get_vertices()[1]
        print(self.shovel.get_vertices())

        # 2. 原有的车轮旋转销钉 (A-后轮, D-前轮)
        pivots = [
            VPinJoint(
                self.body,
                self.back_wheel,
                anchor_a_local=loc_A,
                anchor_b_local=ORIGIN,
                connect_line_class=Line,
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.front_wheel,
                anchor_a_local=loc_D,
                anchor_b_local=ORIGIN,
                connect_line_class=Line,
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
        ]

        shovel_locks = [
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_C,
                anchor_b_local=s_A - self.shovel.get_center(),
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_D,
                anchor_b_local=s_A - self.shovel.get_center(),
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_C,
                anchor_b_local=s_B - self.shovel.get_center(),
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_D,
                anchor_b_local=s_B - self.shovel.get_center(),
                # connect_line_class=Line,
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
        ]

        rest_dist = np.linalg.norm(
            self.back_wheel.get_center() - self.front_wheel.get_center()
        )
        horizontal_spring = VDampedSpring(
            self.back_wheel,
            self.front_wheel,
            rest_length=rest_dist,
            stiffness=500,
            damping=30,
            connect_line_config={"turns": 18, "color": RED, "stroke_width": 4},
        )

        suspensions = [
            VDampedSpring(
                self.body,
                self.back_wheel,
                anchor_a_local=loc_B,
                rest_length=np.linalg.norm(
                    self.body.get_corner(DL) - self.back_wheel.get_center()
                ),
                stiffness=400,
                damping=15,
                connect_line_config={"turns": 8, "color": RED, "stroke_width": 4},
            ),
            VDampedSpring(
                self.body,
                self.front_wheel,
                anchor_a_local=loc_C,
                rest_length=np.linalg.norm(
                    self.body.get_corner(DR) - self.front_wheel.get_center()
                ),
                stiffness=400,
                damping=15,
                connect_line_config={"turns": 8, "color": RED, "stroke_width": 4},
            ),
        ]

        # 5. 马达
        motors = [
            VSimpleMotor(self.body, self.back_wheel, rate=15, max_torque=1000),
            VSimpleMotor(self.body, self.front_wheel, rate=15, max_torque=1000),
        ]

        # 6. 旋转限制 (修正版)
        # 注意:我们需要限制的是 body 相对于“世界(静态空间)”的角度
        # 而不是相对于会旋转的轮子
        rotary_limits = [
            VRotaryLimitJoint(
                self.body,
                static_body,  # 关键:连接到静态背景
                min_angle=-60 * DEGREES,
                max_angle=60 * DEGREES,
            )
        ]
        return [
            *rotary_limits,
            *suspensions,
            *pivots,
            *shovel_locks,
            horizontal_spring,
            *motors,
        ]


class SpaceSceneExample(SpaceScene):
    def construct(self):
        floor = Line(start=LEFT * 2, end=RIGHT * 10, stroke_width=12, color=RED)
        floor.to_edge(DOWN, buff=0.1)

        slope = VMobjectFromSVGPath(
            se.Path(
                "M0 11C0 7.3 0 3.7 0 0 27.3 0 54.7 0 82 0 78 0 77 1 72 3 68 4 68 3 64 2 60 4 57 2 51 1 43 2 40 4 38 6 34 11 31 6 26 6 20 5 15 7 11 10 8 11 4 11 0 11"
            )
        )
        slope.set_stroke(color=WHITE, width=10).set_fill(
            color=BLACK, opacity=0.8
        ).next_to(floor, UP, buff=0).scale(4)

        stone_group = VGroup()

        slope_anchors = slope.get_anchors()
        stone_radius = 0.4
        for i in range(0, len(slope_anchors)):
            dot = Dot(slope_anchors[i], color=RED, radius=stone_radius)
            dot.shift(UP * stone_radius)
            stone_group.add(dot)

        rock_group = VGroup()

        x_min = slope.get_left()[0]
        x_max = slope.get_right()[0]
        y_min = slope.get_top()[1] + 10
        y_max = slope.get_top()[1] + 15

        rock_template = Line(start=ORIGIN, end=RIGHT * 0.5, stroke_width=40, color=RED)

        for _ in range(100):
            rand_x = random.uniform(x_min, x_max)
            rand_y = random.uniform(y_min, y_max)
            new_rock = rock_template.copy()
            new_rock.move_to([rand_x, rand_y, 0])
            new_rock.rotate(random.uniform(0, PI * 0.25))
            rock_group.add(new_rock)

        car = Car().shift(LEFT * 2).move_to(slope.get_start() + RIGHT * 5 + UP * 3)

        self.add_static_body(floor, slope)
        self.add_dynamic_body(*stone_group, *rock_group)
        self.add_dynamic_body(car.shovel, density=0.2)
        self.add_dynamic_body(car.body, density=5)
        self.add_dynamic_body(car.back_wheel, car.front_wheel, density=2, friction=0.8)
        self.add_shapes_filter(
            car.body, car.back_wheel, car.front_wheel, car.shovel, group=123
        )
        self.add_constraints(*car.add_constraints(static_body=slope))

        self.add(self.camera.frame)
        self.camera.frame.move_to(car)
        self.camera.frame.scale(2)
        self.camera.frame.add_updater(lambda m: m.move_to(car))

        # self.draw_debug_img(xlim=(-200, 200), ylim=(-1, 50))
        self.wait(3)
import random

from manim import *
from manim_pymunk import *
from pathlib import Path
import svgelements as se

class Car(VGroup):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        width, height, wheel_radius = 2.5, 1.5, 1
        self.body = Rectangle(width=width, height=height, color=BLUE, fill_opacity=0.8)

        # 锚点获取
        p_B_body = self.body.get_corner(DL)
        p_C_body = self.body.get_corner(DR)

        # 车轮创建
        self.back_wheel = Circle(radius=wheel_radius, color=WHITE, fill_opacity=1)
        self.back_wheel.move_to(p_B_body + LEFT * 1.5 + DOWN * 0.5)

        self.front_wheel = Circle(radius=wheel_radius, color=WHITE, fill_opacity=1)
        self.front_wheel.move_to(p_C_body + RIGHT * 1.5 + DOWN * 0.5)

        # --- 新增:直角三角形铲斗 ABC ---
        # B点位置:前轮中心右侧 0.5
        pos_B = (
            self.front_wheel.get_center() + (wheel_radius + 0.3) * RIGHT + DOWN * 0.5
        )
        # C点位置:B点右侧 0.8 (水平)
        pos_C = pos_B + RIGHT * 1.8
        # A点位置:B点上方 0.6 (垂直)
        pos_A = pos_B + UP * 1

        self.shovel = Polygon(pos_A, pos_B, pos_C, color=ORANGE, fill_opacity=0.9)
        print(self.shovel.get_vertices())
        self.add(self.body, self.back_wheel, self.front_wheel, self.shovel)

    def add_constraints(self, static_body):
        # 1. 基础点位获取
        # 统一获取中心点,用于计算相对偏移
        body_center = self.body.get_center()
        # 1. 计算相对偏移(局部坐标)
        # 这样无论 move_to 到哪,结果都是固定的偏移量
        loc_A = self.body.get_corner(UL) - body_center
        loc_B = self.body.get_corner(DL) - body_center
        loc_C = self.body.get_corner(DR) - body_center
        loc_D = self.body.get_corner(UR) - body_center

        # 铲斗顶点坐标(用于物理锚点参考)
        # 注意:Manim-Pymunk 内部需要相对于各自 Mobject 中心的相对坐标
        s_A = self.shovel.get_vertices()[0]
        s_B = self.shovel.get_vertices()[1]
        print(self.shovel.get_vertices())

        # 2. 原有的车轮旋转销钉 (A-后轮, D-前轮)
        pivots = [
            VPinJoint(
                self.body,
                self.back_wheel,
                anchor_a_local=loc_A,
                anchor_b_local=ORIGIN,
                connect_line_class=Line,
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.front_wheel,
                anchor_a_local=loc_D,
                anchor_b_local=ORIGIN,
                connect_line_class=Line,
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
        ]

        shovel_locks = [
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_C,
                anchor_b_local=s_A - self.shovel.get_center(),
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_D,
                anchor_b_local=s_A - self.shovel.get_center(),
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_C,
                anchor_b_local=s_B - self.shovel.get_center(),
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
            VPinJoint(
                self.body,
                self.shovel,
                anchor_a_local=loc_D,
                anchor_b_local=s_B - self.shovel.get_center(),
                # connect_line_class=Line,
                anchor_a_appearance=VMobject(),
                anchor_b_appearance=VMobject(),
            ),
        ]

        rest_dist = np.linalg.norm(
            self.back_wheel.get_center() - self.front_wheel.get_center()
        )
        horizontal_spring = VDampedSpring(
            self.back_wheel,
            self.front_wheel,
            rest_length=rest_dist,
            stiffness=500,
            damping=30,
            connect_line_config={"turns": 18, "color": RED, "stroke_width": 4},
        )

        suspensions = [
            VDampedSpring(
                self.body,
                self.back_wheel,
                anchor_a_local=loc_B,
                rest_length=np.linalg.norm(
                    self.body.get_corner(DL) - self.back_wheel.get_center()
                ),
                stiffness=400,
                damping=15,
                connect_line_config={"turns": 8, "color": RED, "stroke_width": 4},
            ),
            VDampedSpring(
                self.body,
                self.front_wheel,
                anchor_a_local=loc_C,
                rest_length=np.linalg.norm(
                    self.body.get_corner(DR) - self.front_wheel.get_center()
                ),
                stiffness=400,
                damping=15,
                connect_line_config={"turns": 8, "color": RED, "stroke_width": 4},
            ),
        ]

        # 5. 马达
        motors = [
            VSimpleMotor(self.body, self.back_wheel, rate=15, max_torque=1000),
            VSimpleMotor(self.body, self.front_wheel, rate=15, max_torque=1000),
        ]

        # 6. 旋转限制 (修正版)
        # 注意:我们需要限制的是 body 相对于“世界(静态空间)”的角度
        # 而不是相对于会旋转的轮子
        rotary_limits = [
            VRotaryLimitJoint(
                self.body,
                static_body,  # 关键:连接到静态背景
                min_angle=-60 * DEGREES,
                max_angle=60 * DEGREES,
            )
        ]
        return [
            *rotary_limits,
            *suspensions,
            *pivots,
            *shovel_locks,
            horizontal_spring,
            *motors,
        ]


class SpaceSceneExample(SpaceScene):
    def construct(self):
        floor = Line(start=LEFT * 2, end=RIGHT * 10, stroke_width=12, color=RED)
        floor.to_edge(DOWN, buff=0.1)

        slope = VMobjectFromSVGPath(
            se.Path(
                "M0 11C0 7.3 0 3.7 0 0 27.3 0 54.7 0 82 0 78 0 77 1 72 3 68 4 68 3 64 2 60 4 57 2 51 1 43 2 40 4 38 6 34 11 31 6 26 6 20 5 15 7 11 10 8 11 4 11 0 11"
            )
        )
        slope.set_stroke(color=WHITE, width=10).set_fill(
            color=BLACK, opacity=0.8
        ).next_to(floor, UP, buff=0).scale(4)

        stone_group = VGroup()

        slope_anchors = slope.get_anchors()
        stone_radius = 0.4
        for i in range(0, len(slope_anchors)):
            dot = Dot(slope_anchors[i], color=RED, radius=stone_radius)
            dot.shift(UP * stone_radius)
            stone_group.add(dot)

        rock_group = VGroup()

        x_min = slope.get_left()[0]
        x_max = slope.get_right()[0]
        y_min = slope.get_top()[1] + 10
        y_max = slope.get_top()[1] + 15

        rock_template = Line(start=ORIGIN, end=RIGHT * 0.5, stroke_width=40, color=RED)

        for _ in range(100):
            rand_x = random.uniform(x_min, x_max)
            rand_y = random.uniform(y_min, y_max)
            new_rock = rock_template.copy()
            new_rock.move_to([rand_x, rand_y, 0])
            new_rock.rotate(random.uniform(0, PI * 0.25))
            rock_group.add(new_rock)

        car = Car().shift(LEFT * 2).move_to(slope.get_start() + RIGHT * 5 + UP * 3)

        self.add_static_body(floor, slope)
        self.add_dynamic_body(*stone_group, *rock_group)
        self.add_dynamic_body(car.shovel, density=0.2)
        self.add_dynamic_body(car.body, density=5)
        self.add_dynamic_body(car.back_wheel, car.front_wheel, density=2, friction=0.8)
        self.add_shapes_filter(
            car.body, car.back_wheel, car.front_wheel, car.shovel, group=123
        )
        self.add_constraints(*car.add_constraints(static_body=slope))

        self.add(self.camera.frame)
        self.camera.frame.move_to(car)
        self.camera.frame.scale(2)
        self.camera.frame.add_updater(lambda m: m.move_to(car))

        # self.draw_debug_img(xlim=(-200, 200), ylim=(-1, 50))
        self.wait(3)

active_body(*mobs)[source]

Activates the physical bodies of the given Mobjects if they are sleeping. In physics simulations, bodies that have come to rest are often put to ‘sleep’ to save computation. This method forces those bodies back into an active state.

Parameters

mobs

The Mobjects whose associated physical bodies should be activated. This includes all sub-mobjects within the family tree of each provided Mobject.

Parameters:

mobs (Mobject)

Return type:

None

add_constraints(*mobs)[source]

Adds constraint Mobjects to the scene and installs them into the physical space. This method ensures that the constraints (such as springs, joints, or motors) are both visually rendered in Manim and physically simulated in Pymunk.

Parameters

mobs

The VConstraint objects to be added. Each must implement an install method to link with the physical space.

Parameters:

mobs (VConstraint)

add_dynamic_body(*mobs, family_members=False, is_solid=True, elasticity=0.8, friction=0.8, density=1.0, sensor=False, surface_velocity=(0.0, 0.0), center_of_gravity=(0.0, 0.0), velocity=(0.0, 0.0), angular_velocity=0.0)[source]

Adds Mobjects to the physical space as static bodies. Static bodies do not move under the influence of gravity or collisions and are typically used for environment boundaries like floors and walls.

Parameters

mobs

The Mobjects to be treated as static physical objects.

family_members

If True, all sub-mobjects (children) will also be added to the physical space.

is_solid

Determines if the body is solid. If False, it might be treated as a hollow boundary or wireframe depending on the implementation.

elasticity

The elasticity (restitution) of the shape. A value of 0.0 means no bounce, while 1.0 represents a perfectly elastic collision.

friction

The friction coefficient. Determines how much the object resists sliding along surfaces.

density

The density of the object. For static bodies, this is primarily used to calculate mass if the body is ever converted to dynamic.

sensor

If True, the shape will detect collisions but will not produce a physical collision response (objects will pass through it).

surface_velocity

The surface velocity of the shape. Useful for creating conveyor belt effects.

center_of_gravity

The center of gravity relative to the Mobject’s center.

velocity

The initial linear velocity of the body. Though static, this can affect how objects bounce off it.

angular_velocity

The initial angular velocity of the body.

Parameters:
  • elasticity (float)

  • friction (float)

  • density (float)

  • sensor (bool)

  • surface_velocity (Tuple[float, float])

  • center_of_gravity (Tuple[float, float])

  • velocity (Tuple[float, float])

  • angular_velocity (float)

add_kinematic_body(*mobs, family_members=False, is_solid=True, elasticity=0.8, friction=0.8, density=1.0, sensor=False, surface_velocity=(0.0, 0.0), center_of_gravity=(0.0, 0.0), velocity=(0.0, 0.0), angular_velocity=0.0)[source]

Adds Mobjects to the physical space as static bodies. Static bodies do not move under the influence of gravity or collisions and are typically used for environment boundaries like floors and walls.

Parameters

mobs

The Mobjects to be treated as static physical objects.

family_members

If True, all sub-mobjects (children) will also be added to the physical space.

is_solid

Determines if the body is solid. If False, it might be treated as a hollow boundary or wireframe depending on the implementation.

elasticity

The elasticity (restitution) of the shape. A value of 0.0 means no bounce, while 1.0 represents a perfectly elastic collision.

friction

The friction coefficient. Determines how much the object resists sliding along surfaces.

density

The density of the object. For static bodies, this is primarily used to calculate mass if the body is ever converted to dynamic.

sensor

If True, the shape will detect collisions but will not produce a physical collision response (objects will pass through it).

surface_velocity

The surface velocity of the shape. Useful for creating conveyor belt effects.

center_of_gravity

The center of gravity relative to the Mobject’s center.

velocity

The initial linear velocity of the body. Though static, this can affect how objects bounce off it.

angular_velocity

The initial angular velocity of the body.

Parameters:
  • elasticity (float)

  • friction (float)

  • density (float)

  • sensor (bool)

  • surface_velocity (Tuple[float, float])

  • center_of_gravity (Tuple[float, float])

  • velocity (Tuple[float, float])

  • angular_velocity (float)

add_shapes_filter(*mobs, group=0, categories=4294967295, mask=4294967295)[source]

Sets the collision filter for the shapes associated with the given Mobjects. This determines which shapes can collide with each other based on groups, categories, and masks.

Parameters

mobs

The Mobjects whose physical shapes will have the filter applied.

group

A group ID. Shapes in the same non-zero group do not collide. Useful for creating multi-part objects where internal parts ignore each other.

categories

A bitmask of the categories this shape belongs to. Default is all categories (0xFFFFFFFF).

mask

A bitmask of the categories this shape can collide with. Default is all categories (0xFFFFFFFF).

Parameters:
  • group (int)

  • categories (int)

  • mask (int)

add_static_body(*mobs, family_members=False, is_solid=True, elasticity=0.8, friction=0.8, density=1.0, sensor=False, surface_velocity=(0.0, 0.0), center_of_gravity=(0.0, 0.0), velocity=(0.0, 0.0), angular_velocity=0.0)[source]

Adds Mobjects to the physical space as static bodies. Static bodies do not move under the influence of gravity or collisions and are typically used for environment boundaries like floors and walls.

Parameters

mobs

The Mobjects to be treated as static physical objects.

family_members

If True, all sub-mobjects (children) will also be added to the physical space.

is_solid

Determines if the body is solid. If False, it might be treated as a hollow boundary or wireframe depending on the implementation.

elasticity

The elasticity (restitution) of the shape. A value of 0.0 means no bounce, while 1.0 represents a perfectly elastic collision.

friction

The friction coefficient. Determines how much the object resists sliding along surfaces.

density

The density of the object. For static bodies, this is primarily used to calculate mass if the body is ever converted to dynamic.

sensor

If True, the shape will detect collisions but will not produce a physical collision response (objects will pass through it).

surface_velocity

The surface velocity of the shape. Useful for creating conveyor belt effects.

center_of_gravity

The center of gravity relative to the Mobject’s center.

velocity

The initial linear velocity of the body. Though static, this can affect how objects bounce off it.

angular_velocity

The initial angular velocity of the body.

Parameters:
  • elasticity (float)

  • friction (float)

  • density (float)

  • sensor (bool)

  • surface_velocity (Tuple[float, float])

  • center_of_gravity (Tuple[float, float])

  • velocity (Tuple[float, float])

  • angular_velocity (float)

apply_force_at_local_point(*mobs, force, point=(0, 0, 0))[source]
Parameters:
  • mobs (Mobject)

  • force (Tuple[float, float, float])

  • point (Tuple[float, float, float])

apply_force_at_world_point(*mobs, force, point=(0, 0, 0))[source]
Parameters:
  • mobs (Mobject)

  • force (Tuple[float, float, float])

  • point (Tuple[float, float, float])

apply_impulse_at_local_point(*mobs, impulse, point=(0, 0, 0))[source]
Parameters:
  • mobs (Mobject)

  • impulse (Tuple[float, float, float])

  • point (Tuple[float, float, float])

Return type:

None

apply_impulse_at_world_point(*mobs, impulse, point=(0, 0, 0))[source]
Parameters:
  • mobs (Mobject)

  • impulse (Tuple[float, float, float])

  • point (Tuple[float, float, float])

Return type:

None

draw_debug_img(option=None, xlim=(-8, 8), ylim=(-5, 5))[source]

Pops up a Matplotlib window to render a debug view of the physical space. This is an essential diagnostic tool used to verify if collision shapes, constraints, and pivots are correctly aligned when they are not behaving as expected in the Manim render.

Note

This method will block the execution of the program until the pop-up window is manually closed.

Parameters

option

Pymunk debug draw options (e.g., pymunk.SpaceDebugDrawOptions). Determines what physical elements (shapes, constraints, collision points) are visible.

xlim

The display range for the X-axis in the plot.

ylim

The display range for the Y-axis in the plot.

Parameters:

option (int)

Return type:

None

static get_body(mob)[source]

Extracts the bound Pymunk Body object from a Manim Mobject.

This method retrieves the physical body associated with the Mobject, allowing for direct manipulation of physical properties like mass or velocity.

Parameters

mob

The target Mobject to extract the body from.

Returns

pymunk.Body | None

The bound physical body.

Raises

RuntimeError

If the Mobject has not been added to the physical space yet.

Parameters:

mob (Mobject)

Return type:

Body | None

get_line_query(mob, start, end, stroke_width)[source]
Parameters:
  • mob (Mobject)

  • start (Tuple[float, float, float])

  • end (Tuple[float, float, float])

  • stroke_width (float)

Return type:

list

get_point_query_info(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

list

get_shapea_shapeb_info(shape_a, shape_b)[source]
Parameters:
  • shape_a (Shape)

  • shape_b (Shape)

Return type:

list

static get_shapes(mob)[source]

Retrieves the list of Pymunk Shape objects associated with a Mobject.

Shapes define the collision boundaries of a body. One Mobject may consist of multiple physical shapes.

Parameters

mob

The Mobject whose physical shapes are to be retrieved.

Returns

list[pymunk.Shape] | None

A list of Pymunk shapes defining the collision boundaries.

Raises

RuntimeError

If the Mobject has not been added to the physical space yet.

Parameters:

mob (Mobject)

Return type:

list[Shape] | None

get_velocity_at_local_point(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

Tuple[float, float, float]

local_to_world(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

set_collision_detection_handler(collision_type_a, collision_type_b, begin=None, pre_solve=None, post_solve=None, separate=None, data=None)[source]
Parameters:
  • collision_type_a (int)

  • collision_type_b (int)

  • begin (Callable[[Arbiter, Space, Dict], bool])

  • pre_solve (Callable[[Arbiter, Space, Dict], bool])

  • post_solve (Callable[[Arbiter, Space, Dict], None])

  • separate (Callable[[Arbiter, Space, Dict], None])

  • data (Dict[Any, Any])

set_collision_type(*mobs, collision_type=4)[source]
Parameters:
  • mobs (Mobject)

  • collision_type (int)

set_position_func(*mobs, callback=None)[source]
Parameters:
  • mobs (Mobject)

  • callback (Callable[[Body, float], None])

set_velocity_func(*mobs, callback=None)[source]
Parameters:
  • mobs (Mobject)

  • callback (Callable[[Body, tuple[float, float], float, float], None])

set_wildcard_collision_handler(collision_type_a, begin=None, pre_solve=None, post_solve=None, separate=None, data=None)[source]
Parameters:
  • collision_type_a (int)

  • begin (Callable[[Arbiter, Space, Dict], bool])

  • pre_solve (Callable[[Arbiter, Space, Dict], bool])

  • post_solve (Callable[[Arbiter, Space, Dict], None])

  • separate (Callable[[Arbiter, Space, Dict], None])

  • data (Dict[Any, Any])

setup()[source]

Instance initialization configuration. Automatically add physical space to the scene and start the physics state updater.

sleep_body(*mobs)[source]

Forces the physical bodies of the given Mobjects into a sleeping state. Sleeping bodies are removed from the physics simulation update loop until they are touched by another active body or manually activated, which helps reduce CPU usage.

Parameters

mobs

The Mobjects whose associated physical bodies should be put to sleep. This iterates through all sub-mobjects within the family tree of each provided Mobject.

Parameters:

mobs (Mobject)

Return type:

None

velocity_at_world_point(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

Tuple[float, float, float]

world_to_local(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Parameters:

gravity (Tuple[float, float])

class VSpace(gravity=(0, -9.81), sub_step=8, **kwargs)[source]

Bases: Mobject

Pymunk physical space management is generally not used by users. This object has already been created in SpaceScene.

The Manim visualization manager for a Pymunk physical space.

The VSpace class encapsulates the Pymunk Space object, managing rigid bodies, shapes, and constraints within the simulation. It utilizes Manim’s updater mechanism to synchronize the physical simulation results with Mobject visual states in real-time.

Parameters

gravity

The gravity acceleration vector $(g_x, g_y)$. Defaults to $(0, -9.81)$.

sub_step
The number of sub-steps per frame for physical simulation. Increasing

this value improves numerical stability and collision accuracy.

Defaults to 8.

Examples

Example: VSpaceExample

from manim import *

import random
from manim_pymunk import *

class VSpaceExample(SpaceScene):
    def construct(self):
        COLLISION_TYPE = 123
        # 1. 地板
        floor = Line(start=LEFT * 5, end=RIGHT * 5, stroke_width=12, color=BLUE)
        floor.to_edge(DOWN, buff=0.1)
        self.add_static_body(floor)

        # 2. 生成石头
        stone_num = 15
        stones = [
            Dot(color=BLUE).move_to(
                random.uniform(1, 3) * UP + random.uniform(-2, 2) * RIGHT
            )
            for _ in range(stone_num)
        ]
        self.add_dynamic_body(*stones)

        self.set_collision_type(floor, *stones, collision_type=COLLISION_TYPE)

        def post_solve_callback(arbiter, space, data):
            # 测试获取碰撞瞬间的冲量长度
            if arbiter.total_impulse.length > 0.2:
                print(f"Impact Strength: {arbiter.total_impulse.length:.2f}")
            return True

        self.set_collision_detection_handler(
            collision_type_a=COLLISION_TYPE,
            collision_type_b=COLLISION_TYPE,
            post_solve=post_solve_callback,
        )

        self.apply_impulse_at_local_point(*stones, impulse=(0, 0.1, 0))

        self.apply_force_at_world_point(stones[0], force=(0.1, 0, 0))

        start_pt = (0, 2, 0)
        end_pt = (0, -7, 0)
        self.add(Line(start_pt, end_pt, color=RED))

        self.wait(1.5)

        results = self.get_line_query(floor, start_pt, end_pt, stroke_width=0.1)

        if results:
            hit_point = results[0][2]  # 获取碰撞点坐标
            print(f"Floor detected at: {hit_point}")
            # 在探测到的位置画一个临时的红圈验证
            self.add(Dot(hit_point, color=YELLOW, radius=0.1))

        final_vel = self.get_velocity_at_local_point(stones[-1])
        print(f"Last stone velocity: {final_vel}")
        self.wait(1)
import random
from manim_pymunk import *

class VSpaceExample(SpaceScene):
    def construct(self):
        COLLISION_TYPE = 123
        # 1. 地板
        floor = Line(start=LEFT * 5, end=RIGHT * 5, stroke_width=12, color=BLUE)
        floor.to_edge(DOWN, buff=0.1)
        self.add_static_body(floor)

        # 2. 生成石头
        stone_num = 15
        stones = [
            Dot(color=BLUE).move_to(
                random.uniform(1, 3) * UP + random.uniform(-2, 2) * RIGHT
            )
            for _ in range(stone_num)
        ]
        self.add_dynamic_body(*stones)

        self.set_collision_type(floor, *stones, collision_type=COLLISION_TYPE)

        def post_solve_callback(arbiter, space, data):
            # 测试获取碰撞瞬间的冲量长度
            if arbiter.total_impulse.length > 0.2:
                print(f"Impact Strength: {arbiter.total_impulse.length:.2f}")
            return True

        self.set_collision_detection_handler(
            collision_type_a=COLLISION_TYPE,
            collision_type_b=COLLISION_TYPE,
            post_solve=post_solve_callback,
        )

        self.apply_impulse_at_local_point(*stones, impulse=(0, 0.1, 0))

        self.apply_force_at_world_point(stones[0], force=(0.1, 0, 0))

        start_pt = (0, 2, 0)
        end_pt = (0, -7, 0)
        self.add(Line(start_pt, end_pt, color=RED))

        self.wait(1.5)

        results = self.get_line_query(floor, start_pt, end_pt, stroke_width=0.1)

        if results:
            hit_point = results[0][2]  # 获取碰撞点坐标
            print(f"Floor detected at: {hit_point}")
            # 在探测到的位置画一个临时的红圈验证
            self.add(Dot(hit_point, color=YELLOW, radius=0.1))

        final_vel = self.get_velocity_at_local_point(stones[-1])
        print(f"Last stone velocity: {final_vel}")
        self.wait(1)

animation_overrides = {}
static apply_force_at_local_point(mob, force, point=(0, 0, 0))[source]

Applies a force to a Mobject’s physical body at a point defined in local coordinates.

The force is applied relative to the body’s current orientation. If the point is not the center of gravity, it will also generate a torque, causing the object to rotate.

Parameters

mob

The Mobject whose physical body will receive the force.

force

The force vector $(f_x, f_y, f_z)$ to apply. Note that Pymunk operates in 2D, so the z-component is typically ignored.

point

The offset from the body’s center of gravity $(x, y, z)$ where the force is applied, in local coordinates.

Parameters:
  • mob (Mobject)

  • force (Tuple[float, float, float])

  • point (Tuple[float, float, float])

Return type:

None

static apply_force_at_world_point(mob, force, point=(0, 0, 0))[source]

Applies a force to a Mobject’s physical body at a point defined in world coordinates.

The force vector is applied at an absolute position in the scene. If the point does not coincide with the body’s center of gravity, it will generate torque and cause the body to rotate. This is useful for external influences that occur at specific scene locations.

Parameters

mob

The Mobject whose physical body will receive the force.

force

The force vector $(f_x, f_y, f_z)$ to apply. Note that Pymunk typically ignores the z-component.

point

The absolute position in the world (scene) coordinates where the force is applied. Defaults to the origin $(0, 0, 0)$.

Parameters:
  • mob (Mobject)

  • force (Tuple[float, float, float])

  • point (Tuple[float, float, float])

Return type:

None

static apply_impulse_at_local_point(mob, impulse, point=(0, 0, 0))[source]

Applies an instantaneous impulse to a Mobject’s physical body at a local point.

Impulses cause an immediate change in velocity (linear and angular) without requiring time to elapse, simulating effects like a sudden hit or explosion. The point is defined relative to the body’s current position and orientation.

Parameters

mob

The Mobject whose physical body will receive the impulse.

impulse

The impulse vector $(i_x, i_y, i_z)$ to apply. The z-component is typically ignored in 2D physics.

point

The offset from the body’s center of gravity $(x, y, z)$ where the impulse is applied, in local coordinates.

Parameters:
  • mob (Mobject)

  • impulse (Tuple[float, float, float])

  • point (Tuple[float, float, float])

Return type:

None

static apply_impulse_at_world_point(mob, impulse, point=(0, 0, 0))[source]

Applies an instantaneous impulse to a Mobject’s physical body at a world coordinate.

The impulse vector is applied at an absolute position in the scene. This causes an immediate change in the body’s linear and angular velocity. If the application point is offset from the body’s center of mass, the body will begin to rotate.

Parameters

mob

The Mobject whose physical body will receive the impulse.

impulse

The impulse vector $(i_x, i_y, i_z)$ to apply. The z-component is typically ignored in 2D physics.

point

The absolute position in world (scene) coordinates where the impulse is applied. Defaults to the origin $(0, 0, 0)$.

Parameters:
  • mob (Mobject)

  • impulse (tuple[float, float, float])

  • point (Tuple[float, float, float])

Return type:

None

static get_line_query(mob, start, end, stroke_width)[source]

Performs a segment query to detect intersections between a line and a Mobject’s shapes.

This method simulates a ‘laser beam’ or thick line segment traveling from ‘start’ to ‘end’. It identifies if and where this segment pierces the Mobject’s physical boundaries, considering the segment’s thickness.

Parameters

mob

The Mobject whose associated physical shapes will be checked for intersection.

start

The (x, y, z) starting point of the query segment.

end

The (x, y, z) ending point of the query segment.

stroke_width

The radius of the query segment. Effectively makes the ‘laser’ a thick cylinder/capsule for detection.

Returns

query_info_list

A list of tuples containing intersection data: - alpha: A float (0.0 to 1.0) representing the normalized distance

along the segment where the hit occurred.

  • normal: A 3D vector representing the surface normal at the impact point.

  • point: The exact 3D coordinate of the intersection point.

  • shape: The specific pymunk.Shape that was hit.

Parameters:
  • mob (Mobject)

  • start (Tuple[float, float, float])

  • end (Tuple[float, float, float])

  • stroke_width (float)

Return type:

list

static get_point_query_info(mob, point=(0, 0, 0))[source]

Performs a spatial query to find the relationship between a point and a Mobject’s shapes.

This method calculates how a specific point in space relates to the physical boundaries of a Mobject. It is essential for determining if a point is inside an object, how far it is from the surface, and the direction to the closest surface point.

Parameters

mob

The Mobject whose associated physical shapes will be queried.

point

A (x, y, z) coordinate representing the test location in the scene. Note: Only the (x, y) components are used for the 2D physics engine.

Returns

query_info_list

A list of tuples, where each tuple contains: - distance: The distance from the point to the shape (negative if inside). - gradient: A 3D vector representing the direction of the distance gradient. - point: The closest point on the shape’s surface to the query point. - shape: The specific pymunk.Shape object that was queried.

Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

list

static get_shapea_shapeb_info(shape_a, shape_b)[source]

Retrieves detailed contact information between two specific physical shapes.

This method performs a low-level collision query to find the ‘contact manifold’ between two shapes. It calculates the collision normal and the set of points where the two shapes are touching or overlapping.

Parameters

shape_a

The first pymunk.Shape to check for collision.

shape_b

The second pymunk.Shape to check for collision.

Returns

contact_data

A list where the first element is the collision normal, followed by tuples of contact point details: - normal: A 3D vector representing the direction required to resolve

the collision (from shape_a to shape_b).

  • point_a: The coordinate on the surface of shape_a involved in the contact.

  • point_b: The coordinate on the surface of shape_b involved in the contact.

  • distance: The penetration depth (negative if overlapping, positive if separated within the collision margin).

Parameters:
  • shape_a (Shape)

  • shape_b (Shape)

Return type:

list

init_updater()[source]
static local_to_world(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

Tuple[float, float, float]

remove_body_shapes_constraints(*items)[source]

Removes physical bodies, shapes, or constraints from the physical space.

This method handles the unregistration of Pymunk objects. It is crucial for maintaining simulation performance and preventing memory leaks or unexpected physical interactions after a Mobject has been removed from the scene.

Parameters

items

The Pymunk objects (Body, Shape, or Constraint) to be removed from the simulation space.

Parameters:

items (Body | Shape | Constraint)

Return type:

None

set_body_and_shapes(mob, body_type, is_solid, elasticity, friction, density, sensor, surface_velocity, center_of_gravity, velocity, angular_velocity)[source]

Sets up both the physical body and its collision shapes for a Mobject.

This method acts as a high-level initializer that configures the motion properties (velocity, gravity center) and the physical material properties (friction, elasticity) simultaneously, effectively binding a complete physical identity to a visual Mobject.

Parameters

mob

The Mobject to be initialized with physical properties.

body_type

The Pymunk body type (Dynamic, Static, or Kinematic).

is_solid

Whether the shapes are treated as solid objects or hollow boundaries.

elasticity

The coefficient of restitution. Controls how much energy is preserved after a collision.

friction

The friction coefficient. Controls how much the object resists sliding.

density

The density used to calculate mass and moment of inertia based on shape area.

sensor

If True, the shapes will trigger collision callbacks but won’t cause physical bounces.

surface_velocity

A constant velocity applied to the surface of the shape (e.g., for conveyor belts).

center_of_gravity

The center of mass relative to the Mobject’s center $(x, y)$.

velocity

The initial linear velocity vector $(v_x, v_y)$.

angular_velocity

The initial angular velocity in radians per second.

Parameters:
  • mob (Mobject)

  • body_type (int)

  • is_solid (bool)

  • elasticity (float)

  • friction (float)

  • density (float)

  • sensor (bool)

  • surface_velocity (Tuple[float, float])

  • center_of_gravity (Tuple[float, float])

  • velocity (Tuple[float, float])

  • angular_velocity (float)

Return type:

None

static set_position_func(mob, callback=None)[source]

Assigns a custom position update callback to a Mobject’s physical body.

By default, Pymunk updates a body’s position based on its velocity. This method allows you to override that behavior with custom logic. The callback is executed during every physical simulation step.

Parameters

mob

The Mobject whose physical body’s position update logic will be customized.

callback

A function with the signature def callback(body: pymunk.Body, dt: float). If None, the default Pymunk position update logic is restored.

Parameters:
  • mob (Mobject)

  • callback (Callable[[Body, float], None])

static set_velocity_func(mob, callback=None)[source]

Assigns a custom velocity update callback to a Mobject’s physical body.

This method overrides how Pymunk calculates velocity in each step. It is commonly used to implement specialized physical effects such as custom air resistance (drag), planetary gravity, or specific damping behaviors that differ from the global space settings.

Parameters

mob

The Mobject whose physical body’s velocity logic will be customized.

callback

A function with the signature: def callback(body: Body, gravity: Tuple[float, float], damping: float, dt: float) If None, the default Pymunk velocity update logic is restored.

Parameters:
  • mob (Mobject)

  • callback (Callable[[Body, tuple[float, float], float, float], None])

updaters: list[Updater]
static velocity_at_local_point(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

Tuple[float, float, float]

static velocity_at_world_point(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

Tuple[float, float, float]

static world_to_local(mob, point=(0, 0, 0))[source]
Parameters:
  • mob (Mobject)

  • point (Tuple[float, float, float])

Return type:

Tuple[float, float, float]

Parameters:
  • gravity (Tuple[float, float])

  • sub_step (int)

Modules

SpaceScene([gravity])

A rotational spring connection is created between the two rigid bodies.

VSpace([gravity, sub_step])

Pymunk physical space management is generally not used by users.