2023年8月31日木曜日

Blender x Python でキューブを使ってマインクラフト風壁紙を自動で作る

 画像ファイルを読み込み1ピクセルを1つのキューブでブロックのように積み上げて壁画のようにするスクリプトになります。キューブのサイズ半分だけずらしているので真上から見ても真横から見ても同じ画像です。

 処理スピード感は、1ラインにあたるキューブ50コの処理時間が最初の1ライン目は0.1秒ですが、10ライン目2.5秒、50ライン目は16秒、100ライン目は30秒という感じでどんどん遅くなります。5000ピクセル程の小さな画像でも30分ほどかかりました。ということでほどほどのサイズでやってみてください。動画、静止画、スクリプトをご紹介します。どうすると早くなるんでしょうか。




#blender 3.0.0で動作確認
import cv2
import bpy

for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)

im = cv2.imread('c:/tmp/picture.jpg')
height, width, channels = im.shape[:3]

for x in range(width):
    for y in range(height):
        b,g,r = im[y,x]
        bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(-(height-y) , x, height-y), scale=(0.5, 0.5, 0.5))
        mat_name = str( hex(r*0x10000+g*0x100+b))
        mat = bpy.data.materials.new(mat_name)
        mat.diffuse_color = (r/256, g/256, b/256, 1.0)
        bpy.context.object.data.materials.append(mat)
       

2023年8月30日水曜日

Blender x Python で作るシンプルなドミノ倒し

物理演算のスゴクわかりやすいサンプルに仕立ててみました。 キーワードでググるとたいていのものは見つけらますが、見慣れない1命令があるとワクワクしますね。resizeで簡単に形を変えられるとは知りませんでした。

スタスタスタと気持ちよく倒れる100コのドミノを再生してみてください。planeのサイズとforカウンタの数を変えればいくらでも大きくできますよ。


#blender 3.0.0で動作確認
import bpy

for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)

bpy.ops.mesh.primitive_plane_add(size=400,  align='WORLD', location=(0, 0, -5), scale=(1, 1, 1))
bpy.context.object.rotation_euler[2] = 0.78

bpy.ops.rigidbody.object_add()
bpy.context.object.rigid_body.type = 'PASSIVE'

for i in range (0,100):
    bpy.ops.mesh.primitive_cube_add(location=(-200+i*4, 0, 0), size=1.0 )
    bpy.ops.rigidbody.object_add()
    bpy.ops.transform.resize(value=(1, 4, 10), constraint_axis=(False, True, True))

    if i==0:
        bpy.context.object.rotation_euler[1] = 0.3
        
こちらのサイトを参考にさせて頂きました。

Blender x Python アルキメデスの螺旋でドミノを並べるスクリプト

 アルキメデスの螺旋(Archimedes' spiral)と呼ばれる方程式  によって表される曲線(うずまき)があります。線同士の間隔が等しいのが特徴です。この方程式を利用して渦巻でドミノを並べてみました。

 グラフにプロットするだけなら計算式そのままで良いのですが、ドミノということでポイントが2つあります。1つは、等間隔となる必要があり、1つドミノ(キューブメッシュ)を配置したら一定距離になるまで次のドミノを配置しないこと。直前のドミノのx1,y1と配置しようとする新ドミノの位置x2,y2の距離を測り、距離が一定値以上となったら配置するようにしています。ドミノの高さ以下の距離であれば大丈夫です。 プロットする位置は計算で導き出せるようなのですが、何をしてもうまくいかなかったので、細かくループを回してその都度距離を測定する方法にしました。2点目はドミノの角度です。グラフにするだけなら角度という概念は出てきませんが、ドミノが前のドミノと次のドミノの中間の角度になる必要がありますので、プロットするタイミングでZ軸の角度を調整します。

 動画を見て頂けるとわかるのですが、ドミノを並べる個数とその間隔、うずまきの密集具合も指定することができ、どの場合でもドミノがキレイに倒れます。

2000個までやってみました。10分ぐらいかかります。

なんか気持ちよくないですか?(アニメーションのスピードは10倍に設定してあります)

2023年8月28日月曜日

ブレンダーモデルをPythonスクリプトとしてエクスポートする

 ブログの趣旨的になんでもPythonで表現しないといけないのですが、メッシュを操作してコンソールを確認してメモする繰り返しがあまりテンションが上がりません。ということで思う存分メッシュを配置して動かして大きくして小さくしたところで、出来上がった状態をいきなりPythonスクリプトに変換するスクリプトを作成しました。

 ループでパラメタを確認して文字列に落とし、最後にファイルに保存するスクリプトです。メッシュは複数対応できるようになっていて、位置(location)、サイズ(scale)、回転(rotation_euler)については情報を収集しています。キューブ以外でも可能ですが、ポリゴンの数が桁違いなのでとても長いスクリプトになります。

 こちらのサイトを参考にさせて頂きました。複数メッシュの対応と位置、サイズ、回転の情報収集を付与しております。

How can I export my blender model as a python script


■■■Blenderの状態

■■■Pythonスクリプトを作成するスクリプト

#blender 3.0.0で動作確認
import bpy

s = "import bpy\n"

for ob in bpy.data.objects:
    if ob and ob.type == "MESH":
        data = ob.data

        faces = ",\n         ".join(f'{p.vertices[:]}' for p in data.polygons)
        verts = ",\n         ".join(f"{v.co[:]}"  for v in data.vertices)
        location = "".join(f"{ob.location[:]}")
        scale = "".join(f"{ob.scale[:]}")
        rotation_euler = "".join(f"{ob.rotation_euler[:]}")

        s = s + f"""

verts = ({verts})

faces = ({faces})

scene = bpy.context.scene
me = bpy.data.meshes.new("{data.name}")
me.from_pydata(verts, [], faces)
ob = bpy.data.objects.new("{data.name}", me)
ob.location = {location}
ob.scale = {scale}
ob.rotation_euler = {rotation_euler}
scene.collection.objects.link(ob)

"""

with open('c:/tmp/make_primitive.py', 'a') as f:
    print(s, file=f) 
■■■生成されたスクリプト
※Blenderを日本語化しているため最初からのキューブは"Cube"、追加すると"立方体"になるようです。  
import bpy

verts = ((1.0, 1.0, 1.0),
         (1.0, 1.0, -1.0),
         (1.0, -1.0, 1.0),
         (1.0, -1.0, -1.0),
         (-1.0, 1.0, 1.0),
         (-1.0, 1.0, -1.0),
         (-1.0, -1.0, 1.0),
         (-1.0, -1.0, -1.0))

faces = ((0, 4, 6, 2),
         (3, 2, 6, 7),
         (7, 6, 4, 5),
         (5, 1, 3, 7),
         (1, 0, 2, 3),
         (5, 4, 0, 1))

scene = bpy.context.scene
me = bpy.data.meshes.new("Cube")
me.from_pydata(verts, [], faces)
ob = bpy.data.objects.new("Cube", me)
ob.location = (0.0, 0.0, 0.0)
ob.scale = (1.0, 1.0, 1.0)
ob.rotation_euler = (0.0, 0.0, 0.0)
scene.collection.objects.link(ob)

verts = ((-1.0, -1.0, -1.0),
         (-1.0, -1.0, 1.0),
         (-1.0, 1.0, -1.0),
         (-1.0, 1.0, 1.0),
         (1.0, -1.0, -1.0),
         (1.0, -1.0, 1.0),
         (1.0, 1.0, -1.0),
         (1.0, 1.0, 1.0))

faces = ((0, 1, 3, 2),
         (2, 3, 7, 6),
         (6, 7, 5, 4),
         (4, 5, 1, 0),
         (2, 6, 4, 0),
         (7, 3, 1, 5))

scene = bpy.context.scene
me = bpy.data.meshes.new("立方体")
me.from_pydata(verts, [], faces)
ob = bpy.data.objects.new("立方体", me)
ob.location = (1.4182631969451904, 4.194612503051758, 0.7980197072029114)
ob.scale = (1.6070706844329834, 1.6070706844329834, 1.6070706844329834)
ob.rotation_euler = (-0.6515693664550781, 0.14909927546977997, -0.39666253328323364)
scene.collection.objects.link(ob)


2023年8月27日日曜日

Blender x Python 三角形のドミノ倒し

 一直線ばかりだったドミノを少し変化させていきたいと思います。倒すドミノを1枚づつ増やしていく配置としました。あまり面白味がないので、ドミノの配置に少し乱数を加え、マテリアルを2種類用意してランダムに割り当てました。
 そろそろ立体にしてみようかと思っているところです。

#blender 3.0.0
import bpy
import random

for item in bpy.data.meshes:
    bpy.data.meshes.remove(item)

domino_distance = 10
domino_count = 50
domino_width = 4

mat_b = bpy.data.materials.new("Blue")
mat_b.diffuse_color = (0, 0, 1, 1.0)

mat_w = bpy.data.materials.new("White")
mat_w.diffuse_color = (1, 1, 1, 1.0)

for x in range(1,domino_count):
    for y in range(x):
        x_location = (x - 1)*domino_distance + random.random()
        y_location = (y-x/2)*domino_width *3
        bpy.ops.mesh.primitive_cube_add(location=(x_location, y_location, 0), scale=(1,domino_width,10),rotation=(0, 0, 0.0) )
        bpy.ops.rigidbody.object_add()
        bpy.context.object.rigid_body.friction = 1

        if random.random() > 0.6:
            bpy.context.object.data.materials.append(mat_w)
        else:
            bpy.context.object.data.materials.append(mat_b)

    if x== 1:
        bpy.context.object.rotation_euler[1] = 0.3

s=domino_distance * domino_count*1.2
bpy.ops.mesh.primitive_plane_add(size=s,  location=(s/2 - 10, 0, -10 ), scale=(1, 1, 1))
bpy.ops.rigidbody.object_add()
bpy.context.object.rigid_body.type = 'PASSIVE'