1
0

Blenderでハノイの塔を動かす

Last updated at Posted at 2022-01-01

Blenderでハノイの塔を動かす

k本のハノイの塔の動かし方(Python版)」をBlenderで動かしてみました。

完成したアニメーション

hanoi.gif

やり方

  • Blenderを起動し、ワークスペースをScriptingにします。
  • 「新規」を押し、下記をコピペし、テキストメニューの「スクリプト実行」をします。
# Make an animation of Tower of Hanoi
import sys
from functools import lru_cache
from math import pi, sin
from typing import Any, Generator

import bpy


@lru_cache(maxsize=1024)
def nmove(m: int, n: int) -> float:
    """minimum number of moves

    :param m: number of disks
    :param n: number of rods
    :return: minimum number of moves
    """
    n = min(m + 1, n)
    if n == 2:
        return 1 if m == 1 else float("inf")
    elif n == 3:
        return 2 ** m - 1
    elif n == m + 1:
        return 2 * m - 1
    return min(nmove(i, n) * 2 + nmove(m - i, n - 1) for i in range(1, m))


def hanoi(m: int, pos: list[int]) -> Generator[tuple[int, int], None, None]:
    """Moves of Tower of Hanoi

    :param m: number of disks
    :param n: number of rods
    :return: from, to
    """
    if m == 1:
        yield pos[0], pos[-1]
        return
    n = len(pos)
    assert n > 2, "Too few len(pos)"
    mn = min((nmove(i, n) * 2 + nmove(m - i, n - 1), i) for i in range(1, m))[1]
    yield from hanoi(mn, [pos[0]] + pos[2:] + [pos[1]])
    yield from hanoi(m - mn, [pos[0]] + pos[2:-1] + [pos[-1]])
    yield from hanoi(mn, pos[1:-1] + [pos[0], pos[-1]])


def set_mat(material, color) -> None:
    """Set a material and smooth"""

    obj = bpy.context.selected_objects[0]
    bpy.ops.object.shade_smooth()
    mat = bpy.data.materials.get(material) or bpy.data.materials.new(name=material)
    mat.use_nodes = True
    mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = color
    obj.active_material = mat
    bpy.ops.object.modifier_add(type='BEVEL')
    mdf = obj.modifiers[0]
    mdf.width = 0.02
    mdf.segments = 3

def make_hanoi(m: int, n: int) -> None:
    """Make an animation of Tower of Hanoi

    :param m: number of disks
    :param n: number of rods
    """
    # Delete all objects.
    bpy.ops.object.select_all(action="SELECT")
    bpy.ops.object.delete(use_global=False)
    # Add rods
    for i in range(n):
        bpy.ops.mesh.primitive_cylinder_add(
            vertices=12, radius=0.01, depth=0.5, location=(0, i, 0.2)
        )
        set_mat("M_rod", (0.5, 0.2, 0.1, 1))
    colors = [
        (0, 0.8, 0.1, 1),
        (1, 0.1, 0.1, 1),
        (0.8, 0.8, 0, 1),
        (0.1, 0.2, 1, 1),
        (0.8, 0.2, 0, 1),
    ]
    towers: list[list[Any]] = [[] for _ in range(n)]  # disks of rods
    # Add disks
    for i in range(m):
        bpy.ops.mesh.primitive_cylinder_add(
            radius=0.1 * (m - i), depth=0.1, location=(0, 0, 0.1 * i)
        )
        towers[0] += bpy.context.selected_objects
        set_mat(f"M{i}", colors[i % len(colors)])
    # Set animation
    tm = 1
    for d in towers[0]:
        d.keyframe_insert(data_path="location", frame=tm)
    for fr, to in hanoi(m, list(range(n))):
        d = towers[fr].pop()
        frz = len(towers[fr]) * 0.1
        toz = len(towers[to]) * 0.1
        towers[to].append(d)
        for t in range(10):
            p = t / (10 - 1)
            y = (to - fr) * p + fr
            z = sin(pi * p) * 1 + (toz - frz) * p + frz
            d.location = [0, y, z]
            d.keyframe_insert(data_path="location", frame=tm + t + 1)
        tm += 10
    bpy.data.scenes[-1].frame_end = tm
    bpy.ops.screen.frame_jump(end=False)


if __name__ == "__main__":
    sys.argv = sys.argv[:1] + sys.argv[(sys.argv + ["--"]).index("--") + 1 :]
    m = int(sys.argv[1]) if len(sys.argv) > 1 else 3
    n = int(sys.argv[2]) if len(sys.argv) > 2 else 3
    make_hanoi(m, n)

参考:BlenderでPythonを実行する方法

ちなみに、上記Pythonプログラムをファイルに保存し、下記のように実行することもできます。

blender -P Pythonのファイル -- ディスク数 塔の数

Blenderをコマンドラインから使う方法については、「Blenderのコマンドサンプル」や「BlenderでPythonを実行する方法」も参考にしてください。

以上

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0