Web Top Page

BlenderのPythonスクリプトで最初に覚えるべき内容をまとめたページは、このリンクにあります。

BlenderのPythonスクリプト

BMeshを使う

obj=bpy.data.objects['立方体'] で得られるオブジェクトのtypeが 'bpy.types.Object'型 です。
mesh = obj.data # オブジェクトのメッシュデータ('bpy.types.Mesh'型)です。
この('bpy.types.Mesh'型)でも基本的な操作が可能ですが、 次に示すbmeshを使うと、高度な編集操作が可能になります。
編集状態で、編集を継続オブジェクト状態で編集を終結
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を破棄

上記のbmを使えば全ての頂点の操作は次のイメージで操作できます。
for face:BMFace in bm.faces: # 全ての面の走査
    for edge:BMEdge in face.edges: # 全ての辺の走査
       for vert:BMVert in edge.verts: # 全ての辺の走査
           vert の頂点処理

以下で、頂点生成と辺生成を実験しています。

頂点の生成

指定の辺の中に、指定位置に、頂点(bmesh.types.BMVert)を生成する。
この頂点生成で、辺を分割します。( 辺メニューの分割 )
(bmesh.utils.edge_split関数で行っています。
これは指定した割合 (0.0 - 1.0)の位置に頂点を作ります)
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()

上記で作成した頂点は辺に含まれます。その辺は立方体を切断したので、立方体に含まれた辺です。 よって、作成した頂点を移動すると、面を変更することになります。 なお辺の指定で、19の添え字を使っていますが、この値は、別途に次の操作で選んでいます。
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メソッドで頂点を辺の位置に生成しています。
(この方法では、前述のedge_splitと違って、辺に含まれる生成でなく、
独立した頂点の生成です。)
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を更新



bmesh.ops.split_edgesを使った頂点分割ができなかったので、その挙動のメモです。
エラーがなく、出来ない理由が判明できませんでした。
(メッシュオブジェクトとして、頂点2つを生成して、それで辺を生成後、分割用の頂点を生成し、 それで、bmesh.ops.split_edgesにより分割しようしているが、bmesh.ops.split_edgesだけ希望の通りに出来ませんでした。
なお、mathutils.Vectorの座標が、辺の上に存在するを調べる関数(is_point_on_edge)を定義してい検証に使っています。)
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のままで分割できていない。)



bmesh.ops.bisect_edgesを使った頂点分割は可能でしたので、その挙動のメモです。
(前述のbmesh.ops.split_edgesと違い、分割位置が指定できない代わりにcutの引数で、複数に分割することができます。
メッシュオブジェクトとして、頂点2つを生成して、それで辺を生成後、その辺をbmesh.ops.bisect_edges関数で 自動的に分割して頂点を生成しています。
なお、生成した頂点のリストを返す関数(get_new_verts_from_bisect)も定義して検証に使っています。 )
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の関数を作っており、 分割で生成された頂点を、位置変更などの操作を行う場合、余計な手間が必要で効率が悪いと想像できます。


mathutils.Vectorの座標に最も近い辺(BMEdge)上の位置に、 頂点(BMVert)を生成するコード
create_vertex_on_nearest_edge関数を作って利用する例です。
第1引数(bpy_types.Object)のオブジェクトの中の全て辺で、
第2引数(mathutils.Vector)の座標に最も近い辺を探して、その辺を分割するように頂点(BMVert)を生成する関数です。
なお戻り値は、分割のために生成した頂点の位置(mathutils.Vector)です。
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)>

グリッドを生成し、その中央の水平の辺の近辺の位置を第2引数にしてcreate_vertex_on_nearest_edgeを実行しています。
そのグリッドを第1引数して、そのオブジェクトの中の辺で最も第2引数に近い位置で辺を分割しています。



辺の生成


Blenderのスクリプトで、2つの頂点(BMVert)を結ぶ辺の生成
BMeshのedges.newメソッドで辺を生成しています。
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のスクリプトで、面を分割する例です。これによって辺も生成れることになる。
bmesh.utils.face_splitメソッドで、面を2つの頂点で結ぶ辺の生成で分割しています。
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()



面を細分化するbmesh.ops.subdivide_edgesメソッドの利用
bmesh.ops.subdivide_edgesは、指定されたエッジを分割し、新しい頂点やエッジを追加します。次のように使います。
bmesh.ops.subdivide_edges(bm, edges=[], cuts=1, use_grid_fill=False, use_single_edge=False)
  1. bm: 編集モードで取得して指定するBMeshオブジェクトです
  2. edges: 分割したいエッジのリストで、bm.edgesから取得できます。
  3. cuts: 各エッジをいくつのセグメントに分割するかを指定します
  4. use_grid_fill: グリッド状に分割するかどうかの指定です
  5. use_single_edge: 各エッジを独立に分割する場合に使用します
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メソッドを定義して利用しています。
split_nearest_faceは、第1引数のobjの中で、第2引数のリスト[x,y,z]で指定する位置に最も近い面を、 第3引数の分割数で細分するメソッドです。
以下では、カメラとライト以外を削除後に立方体を生成しています
そして、立方体をオブジェクトで、(0, -2, 0)の位置に近い面を、縦辺と横辺でそれぞれ3分割しています。
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>, ...], # 細分化の影響を受けたすべての要素(既存のエッジやフェイスも含む)
}



面を細分化するbmesh.ops.subdivide_edgesメソッドの利用2
bmesh.ops.subdivide_edgesメソッドの利用例として、次のsplit_nearest_faceNメソッドを定義して利用しています。
split_nearest_faceNは、第1引数のobjの中で、第2引数のリスト[x,y,z]で指定する位置に最も近いN個の面を、 第3引数の分割数で細分するメソッドです。
なおN個を指定パラメタが、第4引数の分割対象面の個数です。
以下では、カメラとライト以外を削除後に5×5のグリッドを生成しています
そして、location=(0, -2, 0)の位置に近い5つの面を3分割指定として、
split_nearest_faceN(obj, [0, -0.1, 0], subdivisions=3, n=5)と実行しています。
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分割指定として、
split_nearest_faceN(obj, location=[0.5, -0.1, 0], subdivisions=1, n=2)
を5×5のグリッドに施すと、右のようになります。

なお、bmesh.ops.subdivide_edgesの細分化の際にエッジを正しく指定しないとエラーになる場合があります。
例えば、分割面対象面が2並んでいる状態において、 bmesh.ops.subdivide_edgesの第2引数のedgesで余計なエッジを指定すると失敗します。
右記は2つの面を分割対象にした場合、2つの面のエッジとして4つの辺×2として 8つの辺を第2引数のedges指定すると失敗します。(Blender4.2で確認)
この場合、2つの面で共有する辺を除いて7つの辺を第2引数のedges指定することで正しく動作しました。
上記のsplit_nearest_faceNでは、その取得方法として、分割対象の面をfacesNのリストに記憶した後、 一旦その面だけを選択状態しています。
そしてこの状態で選択状態の辺だけを抽出することで、複数面で共有する辺を除いています。
右上の2つの面であれば7つの辺(bmesh.ops.subdivide_edgesの第2引数で使う辺リスト)が取得できます。