Blenderで紙作る_04(今回紙は全く関係ない)
コードのターン。
VS Codeでそのままだとつらかったのでこのあたり設定した。
(大文字⇔小文字切り替えショートカット&pythonコードスニペット)
https://www.atmarkit.co.jp/ait/articles/1808/24/news030.html
https://itpc.blog.fc2.com/blog-entry-229.html
目的↓
・いい感じにオブジェクトを床かなんかにばらばら置いた感じに配置したい。手作業で1つずつ置く以外の方法で。
試しにスザンヌでやってます。
Blender API 2.8x全然わかんないので、オブジェクトを配置する方法すらわかんなくてつらかったです。
APIのdocの見方もよくわかんない。しかたないのでstackexchangeで調べたサンプルコードを切り貼りする…。
頑張ったところ、ランダムに「base_obj」という名前のオブジェクトをコピーしていっぱい置くコードができた。
参考
https://blender.stackexchange.com/questions/145658/link-new-object-to-scene-with-python-in-2-8
https://blender.stackexchange.com/questions/130124/blender-2-80-delete-collection-or-clear-the-intial-scene-in-scripting-mode
あと「コリジョン設定して落下させてみる」がやりたいことです。
Blenderの操作ログみたいなのだとopsやらcontextやらから呼び出してるようなのですが、コード用の方法があるのかすぐにわかんないのがきついですね…慣れればいけるのでしょうか。
オブジェクト落下はちゃんと調べたらこんな感じに設定すべきだった。
・床のように落ちなくていいもの → リジッドボディのパッシブ
・落ちてほしいもの(硬い) → リジッドボディのアクティブ
コピーしたオブジェクトに1つ1つリジッドボディを追加していくコードはできたけど、オブジェクトの置き方が重なりを考慮せずランダムなので、重なってると物理演算スタート後大爆発する。
# リジッドボディを複製オブジェクトに1つ1つ追加
for rigid_obj in copied_collection.all_objects:
rigid_obj.select_set(True) # 画面上でやるのと同じ操作するしかないのか…
bpy.context.view_layer.objects.active = rigid_obj
bpy.ops.rigidbody.objects_add()
rigid_obj.select_set(False)
↓大爆発しようとしているところ
↓大爆発したあと
100個複製したのに3個しか床に残らない…
(床に埋まってるのはベースオブジェクトなのでカウント外)
どうしようかと思いますが…
考えつく対処としては以下のところ。
①衝突しない位置に最初から配置
②初期配置で重なってなければ爆発しないはずなので、Z軸方向で高高度に渡ってランダム配置
③爆発しても床から落ちなければいいので、コリジョン設定した箱内でシミュレーション
やりたい順で昇順なんですが、探したら①っぽい方法見つけたので解読。
https://blender.stackexchange.com/questions/95616/generate-x-cubes-at-random-locations-but-not-inside-each-other
↑のURLにsverchok使う方法も書いてあったので試してみましたが
オブジェクトの形状見てくれるわけではなさそうですね。
他の回答も似たような方法ぽいので結局どの方法でやるかですね…
ところでsverchokの存在完全に忘れてました。そんなものもありましたね。
そんな感じに距離で計算しようと思っていましたが、BVHTree使って衝突チェックする方法見つけました。
でも、matrix_worldの位置情報が原点から動かずチェックうまくいかなかった。
調べたのですが、シーンにリンクしてないと位置情報正確に出なさそうということがわかりました。
import bpy
# オブジェクトをコピーしてlocationを1.0, 2.0, 3.0に移動
copied_obj = bpy.context.object.copy()
copied_obj.name = f"copied"
copied_obj.location = (1.0, 2.0, 3.0)
print(f"base:{bpy.context.object.matrix_world}")
"""
base:<Matrix 4x4 (1.0000, 0.0000, 0.0000, 3.9849)
(0.0000, 1.0000, 0.0000, -4.5178)
(0.0000, 0.0000, 1.0000, 3.1945)
(0.0000, 0.0000, 0.0000, 1.0000)>
コピー元オブジェクトの位置の情報。3.9849, -4.5178, 3.1945
"""
print(f"unlink: {copied_obj.matrix_world}")
"""
unlink: <Matrix 4x4 (1.0000, 0.0000, 0.0000, 3.9849)
(0.0000, 1.0000, 0.0000, -4.5178)
(0.0000, 0.0000, 1.0000, 3.1945)
(0.0000, 0.0000, 0.0000, 1.0000)>
location変えても出る位置の情報変わらない。3.9849, -4.5178, 3.1945
"""
bpy.context.scene.collection.objects.link(copied_obj)
print(f"link: {copied_obj.matrix_world}")
"""
link: <Matrix 4x4 (1.0000, 0.0000, 0.0000, 1.0000)
(0.0000, 1.0000, 0.0000, 2.0000)
(0.0000, 0.0000, 1.0000, 3.0000)
(0.0000, 0.0000, 0.0000, 1.0000)>
linkするとlocationの情報に更新される。1.0, 2.0, 3.0
"""
link時になにか値の更新処理が入っているのかと思って調べたところ、更新用のコードがあるっぽい。でも作っただけのオブジェクトは更新しても何も変わらなかったのでコレクションにlink(シーンに配置?)するしかないみたいです。
https://bluebirdofoz.hatenablog.com/entry/2020/01/12/140000
↓すでに置いてあるオブジェクトだとちゃんと更新されるのに…
import bpy
obj = bpy.context.object
obj.location = (0.0, 0.0, 0.0)
bpy.context.view_layer.update()
print(f"not moved:{bpy.context.object.matrix_world}")
"""
not moved:<Matrix 4x4 (1.0000, 0.0000, 0.0000, 0.0000)
(0.0000, 1.0000, 0.0000, 0.0000)
(0.0000, 0.0000, 1.0000, 0.0000)
(0.0000, 0.0000, 0.0000, 1.0000)>
位置情報0.0, 0.0, 0.0
"""
obj.location = (1.0, 2.0, 3.0)
print(f"moved: {obj.matrix_world}")
"""
moved: <Matrix 4x4 (1.0000, 0.0000, 0.0000, 1.0000)
(0.0000, 1.0000, 0.0000, 2.0000)
(0.0000, 0.0000, 1.0000, 3.0000)
(0.0000, 0.0000, 0.0000, 1.0000)>
location変えるだけで更新反映される。位置情報1.0, 2.0, 3.0
"""
もうそろそろ手で1つずつ置いていったほうが早かったくらいの時間をかけている頃になっている気はします。
直しました。↓
from typing import Tuple
import bpy
import random
from bpy.types import Scene, Object
from mathutils import Vector
from mathutils.bvhtree import BVHTree
# 定数
BASE_OBJ_NAME = 'base_obj' # ベースオブジェクト
BASE_MESH_NAME = 'base_mesh' # ベースメッシュ
COPIES = 50 # 複製回数
PLACING_AREA_RANGE = 5 # コピーしたオブジェクトを配置する範囲
COLLECTION_OF_COPIED = 'copied' # コピーしたオブジェクトの格納用コレクション名
MAX_TRYIALS = 10 # 最大試行回数
def crear_collection(collection_name: str, scene: Scene):
"""
コレクション作る(既存の場合、クリア上書き)
"""
collection = bpy.data.collections.get(collection_name)
# すでに同名のコレクションが存在する場合は消す
if collection:
# コレクション内のオブジェクト消す 他から参照されてるやつは消さない
objs_in_collection = [o for o in collection.objects if o.users == 1]
while objs_in_collection:
bpy.data.objects.remove(objs_in_collection.pop())
# コレクション自体も消す
bpy.data.collections.remove(collection)
# コレクション作る
collection = bpy.data.collections.new(collection_name)
scene.collection.children.link(collection)
return collection
def get_random_location(max: float, z_min: float = 0.0):
"""
ランダム座標取得
"""
x_val = (random.random() - 0.5) * max * 2
y_val = (random.random() - 0.5) * max * 2
z_val = random.random() * max + z_min
return (x_val, y_val, z_val)
def is_overlap_two(obj1: Object, obj2: Object) -> bool:
"""
2オブジェクトが重なってないかチェックする
毎回BVHTree作ってるのはどう考えても効率悪いけど
死ぬほど遅くなければこのままでいい…
"""
# Get their world matrix
mat1 = obj1.matrix_world
mat2 = obj2.matrix_world
# Get the geometry in world coordinates
vert1 = [mat1 @ v.co for v in obj1.data.vertices]
poly1 = [p.vertices for p in obj1.data.polygons]
vert2 = [mat2 @ v.co for v in obj2.data.vertices]
poly2 = [p.vertices for p in obj2.data.polygons]
# Create the BVH trees
bvh1 = BVHTree.FromPolygons(vert1, poly1)
bvh2 = BVHTree.FromPolygons(vert2, poly2)
if bvh1.overlap(bvh2):
return True
else:
return False
def is_overlap_list(obj: Object, compare_objs: Tuple[Object, ...]) -> bool:
"""
あるオブジェクトと、タプル中のオブジェクトが重なってないかチェックする
"""
result = False
for compare_obj in compare_objs:
if obj.name == compare_obj.name:
continue
if is_overlap_two(obj, compare_obj):
result = True
break
return result
def get_corner_vectors(obj: Object):
"""
指定オブジェクトのバウンディングボックスのワールド座標取得
"""
bbox_corners = [obj.matrix_world @
Vector(corner) for corner in obj.bound_box]
return bbox_corners
def main():
"""
メイン処理
"""
# 既存の情報取得する
base_obj = bpy.data.objects.get(BASE_OBJ_NAME)
scene = bpy.context.scene
# 複製オブジェクト格納用コレクション準備
copied_collection = crear_collection(COLLECTION_OF_COPIED, scene)
# コピーしてランダムに配置
for i in range(0, COPIES):
copied_obj = base_obj.copy()
copied_obj.name = f"copied_{i:03d}"
trial = 1
while trial <= MAX_TRYIALS:
origin_candidate = get_random_location(PLACING_AREA_RANGE, 1.0)
copied_obj.location = origin_candidate
copied_collection.objects.link(copied_obj) # 絶対このあたり遅くなってる
bpy.context.view_layer.update() # 強制再描画…
if not is_overlap_list(copied_obj, copied_collection.all_objects):
break
copied_collection.objects.unlink(copied_obj)
trial += 1
if trial > MAX_TRYIALS:
print("だめだった 試行回数:" + str(trial - 1))
bpy.data.objects.remove(copied_obj)
# リジッドボディを複製オブジェクトに1つ1つ追加
for rigid_obj in copied_collection.all_objects:
rigid_obj.select_set(True) # 画面上でやるのと同じ操作なのが気に入らない…
bpy.context.view_layer.objects.active = rigid_obj
bpy.ops.rigidbody.objects_add()
rigid_obj.select_set(False)
if __name__ == "__main__":
main()
上のコードでいい感じに重ならなくオブジェクト配置できるようになったところで結局大爆発することがわかりました。何もかも間違ってたんですか?
悔しかったので違和感(複製したオブジェクトが名前被りのときと同じような名付けられ方されているのおかしくない?)から探ったところ、リジッドボディ設定したオブジェクトは「RigidBodyWorld」に追加され、実行ごとに消されず残っていることがわかりました。
「copied」と「RigidBodyWorld」の両方にオブジェクトが追加されて2ユーザーからの参照になっているから、clear_colleciton()の1ユーザーにしか使われてないオブジェクト消す部分が動作しない無駄コードになってたんですね。
で消すようにしたら爆発しなくなった。シーンコレクションからみえなくてもリジッドボディ適用されて座標上重なってれば大爆発するようです。ダークマターみたいですね。
スザンヌをいっぱい置けてよかったです。最終的なコードは、もし気が向いたらコード直すかもしれないのでgithubに置きました。
https://github.com/aoeth/learning/blob/main/blender2.8x/20210515_blender_placing_suzannes.py
他参考ページ(参考になりそうだったが結局読んでいないものも含む)
https://ensekitt.hatenablog.com/entry/2018/07/23/200000
https://blender.stackexchange.com/questions/8459/get-blender-x-y-z-and-bounding-box-with-script
https://blender.stackexchange.com/questions/149891/python-in-blender-2-8-testing-if-two-objects-overlap-in-the-xy-plane/150014
https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
http://masayuki-osaka.hatenablog.com/entry/2019/01/09/140235
この記事が気に入ったらサポートをしてみませんか?