BlenderのPythonスクリプトで最初に覚えるべき内容をまとめたページは、このリンクにあります。
| 編集状態で、編集を継続 | オブジェクト状態で編集を終結 |
|---|---|
import bmesh obj=bpy.data.objects['立方体'] bpy.ops.object.mode_set(mode='EDIT')# 下記の編集オブジェクト取得のため編集モードにする bm = bmesh.from_edit_mesh(obj.data) # 編集モード中のメッシュデータをBMeshに変換 ここで、bmを使った編集を行います。 bmesh.update_edit_mesh(obj.data) # BMeshを更新(編集モードで、bmは引き続き編集可能) |
import bmesh obj=bpy.data.objects['立方体'] bm = bmesh.new()# BMeshを作成 bm.from_mesh(obj.data) # オブジェクトのメッシュデータをBMeshに変換します。 ここで、bmを使った編集を行います。 (編集モードであれば、bmesh.update_edit_mesh(me) 随時更新可能) bm.to_mesh(me) # 最終的なステップの反映 bm.free()# BMeshを破棄 |
for face:BMFace in bm.faces: # 全ての面の走査
for edge:BMEdge in face.edges: # 全ての辺の走査
for vert:BMVert in edge.verts: # 全ての辺の走査
vert の頂点処理
指定の辺の中に、指定位置に、頂点(bmesh.types.BMVert)を生成する。
import bmesh
bpy.ops.mesh.primitive_cube_add(size=2, location=(-3, 0, -3), scale=(1, 1, 1)) # 立方体の生成
obj = bpy.context.active_object # 直前でアクセスしたオブジェクト
bpy.ops.object.mode_set(mode='EDIT')# 下記の編集オブジェクト取得のため編集モードにする
bpy.ops.mesh.bisect(
plane_co=(0.0, 0.0, -3), # カット平面の基準点
plane_no=(0.0, 0.0, 1.0), # カット平面の法線ベクトル
)
bm = bmesh.from_edit_mesh(obj.data) # BMeshのコピーを取得
for e in bm.edges: e.select = False # 頂点を全て非選択
bm.edges.ensure_lookup_table()
edge=bm.edges[19] # 頂点(BMEdge)取得
edge.select = True # 必要ないが、視覚化して処理対象を明確にしています。
# 辺を指定した0.5の割合で分割し、新しい頂点を作成し、作成された辺と頂点のタプルを返します。
new_vert = bmesh.utils.edge_split(edge, edge.verts[0], 0.5)
bmesh.update_edit_mesh(obj.data) # BMeshを更新
bm.free()
import sys
sys.path.append('D:\\work')
import beditsub
obj=bpy.data.objects['立方体']
# ここで、GUI操作により、処理対象の辺だけを選択する。
beditsub.get_selected_edges_indices(obj) # 上記で選択した辺の添え字が、[19]と分かる
# ここで、、GUI操作により、Alt+A で非選択にする。
beditsub.select_edges_by_indices(obj, [19]) # [19]の添え字の辺を選択状態にして、対象の辺かを確認
BMeshのbm.verts.newメソッドで頂点を辺の位置に生成しています。import bpy import bmesh from mathutils import Vector import bmesh bpy.ops.mesh.primitive_cube_add(size=2, location=(-3, 0, -3), scale=(1, 1, 1)) # 立方体の生成 bpy.ops.object.mode_set(mode='EDIT')# 下記の編集オブジェクト取得のため編集モードにする obj = bpy.context.active_object # 直前でアクセスしたオブジェクト bm = bmesh.from_edit_mesh(obj.data)# BMeshの生成とメッシュデータの取り込み bm.edges.ensure_lookup_table() edge = bm.edges[0]# 操作対象となる辺を決めて、変数に参照 (最初の辺を例に使用) v1, v2 = edge.verts # 辺の両端の頂点 direction = v2.co - v1.co # 辺の方向ベクトルを計算 new_position = v1.co + direction * 0.3 # 新しい頂点の位置 (例: X方向に 0.3 の位置) new_vert = bm.verts.new(new_position) # 頂点生成 # bm.edges.new((v1, new_vert)) # v1とnew_vertで辺生成 bmesh.update_edit_mesh(obj.data) # BMeshを更新
import bpy
import bmesh
def is_point_on_edge(point, edge): # edgeの位置が、edgeの辺上に存在するかを調べる関数
v1, v2 = edge.verts# エッジの始点と終点
edge_dir = (v2.co - v1.co).normalized()
to_point = (point.co - v1.co)
projected = edge_dir.dot(to_point) * edge_dir
return (to_point - projected).length < 1e-6
mesh = bpy.data.meshes.new("TestMesh")# メッシュオブジェクトを作成
obj = bpy.data.objects.new("TestMesh", mesh)
bpy.context.collection.objects.link(obj)
bm = bmesh.new()# BMesh のセットアップ
v1 = bm.verts.new((0, 0, 0)) # 開始頂点作成
v2 = bm.verts.new((2, 0, 0)) # 終了頂点作成
edge = bm.edges.new((v1, v2)) # エッジを作成
print("分割前のエッジ数:", len(bm.edges))# 分割前のエッジ数を確認
split_vert = bm.verts.new((1, 0, 0)) # エッジの中間に新しい頂点を作成
result = bmesh.ops.split_edges(bm, edges=[edge], verts=[split_vert])# エッジを分割
print("分割で使う頂点が辺の上に存在するか?:", is_point_on_edge(split_vert, edge))
print("split_edges の結果:", result)# 分割結果を確認
print("分割後のエッジ数:", len(bm.edges))
bm.to_mesh(mesh)# メッシュに反映
bm.free()
分割前のエッジ数: 1
分割で使う頂点が辺の上に存在するか?: True
split_edges の結果: {'edges': [<BMEdge(0x000001E2F83D0550), index=0, verts=(0x000001E2A4B76510/0, 0x000001E2A4B76548/1)>]}
分割後のエッジ数: 1
正しく分割出来た場合は2になるはず!!(1のままで分割できていない。)
import bpy
import bmesh
mesh = bpy.data.meshes.new("TestMesh")# メッシュオブジェクトを作成
obj = bpy.data.objects.new("TestMesh", mesh)
bpy.context.collection.objects.link(obj)
bm = bmesh.new()# BMesh のセットアップ
v1 = bm.verts.new((0, 0, 0)) # 開始頂点作成
v2 = bm.verts.new((2, 0, 0)) # 終了頂点作成
edge = bm.edges.new((v1, v2)) # エッジを作成
print("分割前のエッジ数:", len(bm.edges))# 分割前のエッジ数を確認
result = bmesh.ops.bisect_edges(bm, edges=[edge], cuts=1)# エッジ上に新しい頂点を自動生成して分割
print("戻り値:", result)
print("split_edges の結果:", result)# 分割結果を確認
print("分割後のエッジ数:", len(bm.edges))
def get_new_verts_from_bisect(result): # bisect_edges の結果から新しい頂点を取得"
return [v for v in result['geom_split'] if isinstance(v, bmesh.types.BMVert)]
split_vert=get_new_verts_from_bisect(result)
print("生成された頂点:", split_vert[0] )
bm.to_mesh(mesh)# メッシュに反映
bm.free()
分割前のエッジ数: 1
戻り値: {'geom_split': [<BMVert(0x000001E2A4B62F80), index=2>, <BMEdge(0x000001E2F83D0F50), index=0, verts=(0x000001E2A4B62F80/2, 0x000001E2A4B62F48/1)>, <BMEdge(0x000001E2F83D0FA0), index=1, verts=(0x000001E2A4B62F10/0, 0x000001E2A4B62F80/2)>]}
分割後のエッジ数: 2
生成された頂点: <BMVert dead at 0x000001E2A356FF60>
上記で分かるように、自動的に分割され、分割で生成された頂点(BMVert)を取得するために、別途でget_new_verts_from_bisectの関数を作っており、
分割で生成された頂点を、位置変更などの操作を行う場合、余計な手間が必要で効率が悪いと想像できます。
import sys
sys.path.append('D:\\work')
from importlib import reload
import bpy
import bmesh
from mathutils import Vector
from mathutils.geometry import intersect_point_line
def create_vertex_on_nearest_edge(obj:bpy.types.Object, target_point:Vector)->Vector:
rtnV:Vector=None # 戻り値
bm = bmesh.new() # bmeshのセットアップ
bm.from_mesh(obj.data)
closest_edge = None # 最も近い辺とその上の最近接位置を見つける
closest_point_on_edge = None # 最も近い辺上の位置(Vector)
min_distance = float('inf')
for edge in bm.edges:
v1, v2 = edge.verts[0].co, edge.verts[1].co # 辺の2頂点
point_on_edge, factor = intersect_point_line(target_point, v1, v2)# 最近接位置のVectorを取得
if factor < 0.0:# 点が辺の範囲内にあるか確認
point_on_edge = v1
elif factor > 1.0:
point_on_edge = v2
distance = (target_point - point_on_edge).length
#print(f' distance :{distance }, {edge} closest_point_on_edge:{closest_point_on_edge}')
if distance < min_distance:
min_distance = distance # 最短距離の更新
closest_edge = edge # 最も近い辺の更新
closest_point_on_edge = point_on_edge # 最も近い辺上の位置(Vector)
#
#
if closest_edge and closest_point_on_edge:# 最近接辺上に頂点を生成
new_vert = bmesh.utils.edge_split(closest_edge, closest_edge.verts[0], 0.5) # 辺を分割
new_vert[1].co=closest_point_on_edge # 分割で生成した頂点の移動
rtnV=new_vert[1].co
print(f'create point:{rtnV}')
#
bpy.ops.object.mode_set(mode='OBJECT')
bm.to_mesh(obj.data)# bmeshをメッシュに反映
bm.free()
return rtnV;
for obj in bpy.data.objects:# 'Camera' と 'Light' 以外を全て削除する
if obj.name=='Camera' or obj.name=='Light': continue
bpy.data.objects.remove( obj )
bpy.ops.mesh.primitive_grid_add(x_subdivisions=1, y_subdivisions=2, size=5, location=(0, 0, 0), rotation=(3.14/2,0,0))
obj=bpy.context.active_object # 直前で生成したオブジェクト
target_point = Vector((1.0, 0.1, 0.1))
point=create_vertex_on_nearest_edge(obj, target_point)
print(f'生成位置:{point}')
bpy.ops.object.mode_set(mode='EDIT') # エディットモードへ
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT') # 頂点モード、他の指定:'FACE' 'EDGE'
bpy.ops.mesh.select_all(action='SELECT') # 全選択へ
生成位置:<Vector (1.0000, 0.0000, 0.0000)>
Blenderのスクリプトで、2つの頂点(BMVert)を結ぶ辺の生成import bmesh obj=bpy.data.objects['立方体'] bpy.ops.object.mode_set(mode='EDIT')# 下記の編集オブジェクト取得のため編集モードにする bm = bmesh.from_edit_mesh(obj.data) # BMeshのコピーを取得 for v in bm.verts: v.select = False # 頂点を全て非選択 bm.verts.ensure_lookup_table() # 頂点のインデックスと実際の頂点データと対応関係を保証状態へ v1=bm.verts[1] # 頂点(BMVert)取得 (辺生成用の頂点の一つ) v4=bm.verts[4] # 頂点(BMVert)取得 (辺生成用でもう一方の頂点) bm.edges.new((v1, v4)) # v1とv4の頂点を結んだ辺の生成 v1.select = True # 必要ないが、視覚化して処理対象を明確にしています。 v4.select = True bmesh.update_edit_mesh(obj.data) # BMeshを更新 bm.free()
Blenderのスクリプトで、面を分割する例です。これによって辺も生成れることになる。import bmesh obj=bpy.data.objects['立方体'] bpy.ops.object.mode_set(mode='EDIT')# 下記の編集オブジェクト取得のため編集モードにする bm = bmesh.from_edit_mesh(obj.data) # BMeshのコピーを取得 for v in bm.verts: v.select = False # 頂点を全て非選択 bm.faces.ensure_lookup_table() # 頂点のインデックスと実際の頂点データと対応関係を保証状態へ bm.verts.ensure_lookup_table() # 頂点のインデックスと実際の頂点データと対応関係を保証状態へ face=bm.faces[3] # 下記頂点を持つ面を指定する必要があります。 v1=bm.verts[1] # 頂点(BMVert)取得 (辺生成用の頂点の一つ) v4=bm.verts[4] # 頂点(BMVert)取得 (辺生成用でもう一方の頂点) bmesh.utils.face_split(face, v1, v4) # 面を、2つの頂点で結ぶ線で分割 v1.select = True # 必要ないが、視覚化して処理対象を明確にしています。 v4.select = True bmesh.update_edit_mesh(obj.data) # BMeshを更新 bm.free()
import bpy
import bmesh
# 面を分割するコードです。面を選択後、オブジェクトモードに変更して実行します。
obj=bpy.context.view_layer.objects.active # 一つの面を選択た状態のオブジェクトを取得
bpy.ops.object.mode_set(mode='EDIT')# 編集モードに変更
bpy.context.view_layer.update()# データ更新を明示的に実行
bm=bmesh.from_edit_mesh(obj.data)# BMeshにアクセス
edges=[edge for edge in bm.edges if edge.select]# 選択した辺群を取得
print(f'edges:{edges} len(edges):{len(edges)}')
try:
result=bmesh.ops.subdivide_edges(bm, edges=edges, use_grid_fill=True, cuts=3)# 辺群で細分化
bmesh.update_edit_mesh(obj.data) # BMeshを更新
except Exception as e:
print(f"エラーが発生しました: {e}")
以下ではbmesh.ops.subdivide_edgesメソッドの利用例として、次のsplit_nearest_faceメソッドを定義して利用しています。
import bpy
import bmesh
from mathutils import Vector
def split_nearest_face(obj, location, subdivisions=1):
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')# 編集モードに変更
bm = bmesh.from_edit_mesh(obj.data)# BMeshにアクセス
#
target_location = Vector(location)# 指定位置の近傍の面
nearest_face = None # 指定位置の近傍の面
min_distance = float('inf') # floatの最大値
for face in bm.faces: # BMFaceの繰り返し
closest_point = face.calc_center_median() # 面の中央となるVectorの取得
distance = (closest_point - target_location).length # 面との距離
if distance < min_distance :
min_distance = distance # 最小距離を更新
nearest_face = face # BMFace
#
#
#print(f'---------{nearest_face}')
edges=[edge for edge in nearest_face.edges] # 面から辺
for edge in bm.edges: edge.select=True
#print(f'選択辺の数:{len(edges)}, 選択辺:{edges}')
result=bmesh.ops.subdivide_edges(bm, edges=edges, use_grid_fill=True, cuts=subdivisions)# 選択した辺を細分化
#new_vertices = result.get("geom_split", []) # 細分化によって新たに生成された要素
new_vertices = [elem for elem in result["geom_split"] if isinstance(elem, bmesh.types.BMVert)] # 新しく作られた頂点
#print(f'new_vertices:{new_vertices}')
bpy.ops.mesh.select_all(action='DESELECT') # 全てを非選択へ
for vert in new_vertices: vert.select=True
bmesh.update_edit_mesh(obj.data) # BMeshを更新
for obj in bpy.data.objects:# 'Camera' と 'Light' 以外を全て削除する
if obj.name=='Camera' or obj.name=='Light': continue
bpy.data.objects.remove( obj )
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 0), scale=(1, 1, 1)) # 立方体の生成
obj = bpy.context.object # アクティブなオブジェクトを取得
split_nearest_face(obj, location=(0, -2, 0), subdivisions=3)
上記において、bmesh.ops.subdivide_edgesの戻り値のresultの内容は以下の辞書情報です。
{
"geom_split": [<BMVert>, <BMVert>, <BMEdge>, <BMEdge>, ...], # 細分化によって新たに生成された要素のみ。
"geom": [<BMVert>, <BMEdge>, <BMFace>, ...], # 細分化の影響を受けたすべての要素(既存のエッジやフェイスも含む)
}
import bpy
import bmesh
from mathutils import Vector
def split_nearest_faceN(obj, location, subdivisions=1, n=1):
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')# 編集モードに変更
bm = bmesh.from_edit_mesh(obj.data)# BMeshにアクセス
target_location = Vector(location)# 指定位置のVector化
facesN=[] # 走査対象の面
while n > 0: # 指定位置近傍の面をn個を、
nearest_face = None # 指定位置の近傍の面をfacesNに記憶
min_distance = float('inf') # floatの最大値
for face in bm.faces: # BMFaceの繰り返し
if face in facesN: continue
closest_point = face.calc_center_median() # 面の中央となるVectorの取得
distance = (closest_point - target_location).length # 面との距離
if distance < min_distance :
min_distance = distance # 最小距離を更新
nearest_face = face # BMFace
#
#
if nearest_face != None: facesN.append(nearest_face)
n-=1
#
print(f'分割対象の面の数:{len(facesN)}')
bpy.ops.mesh.select_all(action='DESELECT') # EDITモードにおいて全体を非選択へ
for nearest_face in facesN: nearest_face.select=True
bpy.context.view_layer.update()# データ更新を明示的に実行
edges=[edge for edge in bm.edges if edge.select] # 細分化で使う面の辺群の取得
try:
result=bmesh.ops.subdivide_edges(bm, edges=edges, use_grid_fill=True, cuts=subdivisions)# 辺群で細分化
bmesh.update_edit_mesh(obj.data) # BMeshを更新
except Exception as e:
print(f"エラーが発生しました: {e}")
#
print(f'分割対象の辺の数:{len(edges)}')
new_vertices = [elem for elem in result["geom_split"]] # 新しく作られた頂点や辺
for e in new_vertices: e.select=True
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='VERT')
bmesh.update_edit_mesh(obj.data) # BMeshを更新
for obj in bpy.data.objects:# 'Camera' と 'Light' 以外を全て削除する
if obj.name=='Camera' or obj.name=='Light': continue
bpy.data.objects.remove( obj )
bpy.ops.mesh.primitive_grid_add(x_subdivisions=5, y_subdivisions=5, size=5, location=(0, 0, 0), rotation=(3.14/2,0,0))
obj=bpy.context.active_object # 直前で生成したオブジェクト
bpy.ops.object.select_all(action='DESELECT') # OBJECTモードにおいて全体を非選択へ
split_nearest_faceN(obj, location=[0, -0.1, 0], subdivisions=3, n=5)
location=[0.5, -0.1, 0]の位置に近い2つの面を2分割指定として、
右記は2つの面を分割対象にした場合、2つの面のエッジとして4つの辺×2として
8つの辺を第2引数のedges指定すると失敗します。(Blender4.2で確認)