bl_info = {
"name": "Edge-Plane Intersection (Multi)",
"author": "ChatGPT + You",
"version": (1, 1),
"blender": (2, 80, 0),
"location": "3Dビュー > 編集モード > メッシュ > Edge-Plane Intersection (Multi)",
"description": "複数エッジの頂点を面の交点にスナップ",
"category": "Mesh",
}
import bpy
import bmesh
from mathutils import geometry
def get_global_coords(obj, v):
return obj.matrix_world @ v.co
def get_local_coords(obj, global_co):
return obj.matrix_world.inverted() @ global_co
class MESH_OT_edge_plane_intersection_multi(bpy.types.Operator):
bl_idname = "mesh.edge_plane_intersection_multi"
bl_label = "Edge-Plane Intersection (Multi)"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
return (obj is not None and obj.type == 'MESH' and context.mode == 'EDIT_MESH')
def execute(self, context):
obj = bpy.context.edit_object
bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.verts.ensure_lookup_table()
selected_faces = [f for f in bm.faces if f.select]
selected_edges = [e for e in bm.select_history if isinstance(e, bmesh.types.BMEdge)]
if len(selected_faces) != 1:
self.report({'WARNING'}, "面を1枚明示的に選択してください")
return {'CANCELLED'}
if len(selected_edges) < 1:
self.report({'WARNING'}, "エッジを1本以上明示的に選択してください")
return {'CANCELLED'}
face = selected_faces[0]
plane_verts = face.verts[:3]
plane_co = get_global_coords(obj, plane_verts[0])
plane_no = face.normal @ obj.matrix_world.to_3x3().transposed()
moved_count = 0
for edge in selected_edges:
v1, v2 = edge.verts
p1 = get_global_coords(obj, v1)
p2 = get_global_coords(obj, v2)
intersect = geometry.intersect_line_plane(p1, p2, plane_co, plane_no)
if intersect is None:
continue
d1 = (intersect - p1).length
d2 = (intersect - p2).length
target_vert = v1 if d1 < d2 else v2
target_vert.co = get_local_coords(obj, intersect)
moved_count += 1
bmesh.update_edit_mesh(obj.data, loop_triangles=False, destructive=False)
self.report({'INFO'}, f"{moved_count}個の頂点を交点に移動しました")
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator(MESH_OT_edge_plane_intersection_multi.bl_idname)
def register():
bpy.utils.register_class(MESH_OT_edge_plane_intersection_multi)
bpy.types.VIEW3D_MT_edit_mesh.append(menu_func)
def unregister():
bpy.types.VIEW3D_MT_edit_mesh.remove(menu_func)
bpy.utils.unregister_class(MESH_OT_edge_plane_intersection_multi)
if __name__ == "__main__":
register()
✅ 導入方法
上記コードを edge_to_plane_intersection.py という名前で保存
Blender > 編集 > プリファレンス > アドオン
インストール ボタンでこの .py ファイルを読み込む
チェックを入れて有効化
✅ 使用方法(変わらず)
編集モードで:
面1枚を選択(順序が重要)
そのあと エッジを複数明示的に選択(Shift+右クリック)
メニューから実行:
メッシュ > Edge-Plane Intersection (Multi)
開発段階ではスクリプトのほうが触りやすい
こちらが伸ばせる線は一本だけ
スクリプト
import bpy
import bmesh
from mathutils import Vector, geometry
def get_global_coords(obj, v):
return obj.matrix_world @ v.co
def get_local_coords(obj, global_co):
return obj.matrix_world.inverted() @ global_co
def main():
obj = bpy.context.edit_object
if obj is None or obj.type != 'MESH':
print("メッシュオブジェクトを選択してください")
return
bm = bmesh.from_edit_mesh(obj.data)
bm.faces.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.verts.ensure_lookup_table()
# 面の選択(select状態)
selected_faces = [f for f in bm.faces if f.select]
print("\n[面選択状態]:", selected_faces)
# 明示的に選択されたエッジのみ(選択履歴から取得)
selected_edges = [e for e in bm.select_history if isinstance(e, bmesh.types.BMEdge)]
print("[エッジ選択状態]:", selected_edges)
if len(selected_faces) != 1:
print("⚠️ 面を1枚明示的に選択してください")
return
if len(selected_edges) != 1:
print("⚠️ エッジを1本明示的に選択してください")
return
face = selected_faces[0]
edge = selected_edges[0]
# エッジの2頂点
v1, v2 = edge.verts
p1 = get_global_coords(obj, v1)
p2 = get_global_coords(obj, v2)
# 面の最初の3頂点を使って平面作成
verts = face.verts[:3]
plane_co = get_global_coords(obj, verts[0])
plane_no = face.normal @ obj.matrix_world.to_3x3().transposed()
# 線分と平面の交点を求める
intersect = geometry.intersect_line_plane(p1, p2, plane_co, plane_no)
if intersect is None:
print("⚠️ エッジは面と平行で交点がありません")
return
# 交点に近い方の頂点を移動
d1 = (intersect - p1).length
d2 = (intersect - p2).length
target_vert = v1 if d1 < d2 else v2
target_vert.co = get_local_coords(obj, intersect)
bmesh.update_edit_mesh(obj.data, loop_triangles=False, destructive=False)
print("✅ 交点に近い頂点を移動しました")
main()
💾 スクリプトの保存方法
Blenderを開く
上部メニューの Scripting タブをクリック
左上の「+ New」をクリックして新しいテキストを作成
上のコードをコピペ
Text > Save As から intersection_script.py などの名前で保存
▶ 実行方法
編集モード(Edit Mode)で、面1枚と辺1本を選択
上記スクリプトを貼った状態で、右上の「▶ Run Script」ボタンをクリック
→ 交点が自動で作成されます!
スクリプトの実行結果(print)のメッセージとかはコンソールから立ち上げないとでてこない(ubuntu)
ようなのでスクリプトを触るときはblenderをコンソールから立ち上げる
多角形ポリゴンを基準面に選ぶとどうなるかは
✅ 結論から言うと:
歪んだ四角形ポリゴンでも、
Blender はそのポリゴンに対して平均法線ベクトルを定義する。
面の法線から導かれる**「代表平面」を使って、Ax + By + Cz + D = 0 の形で近似的な平面方程式**は得られます。
🧠 技術的に何が起きているか:
プランナー(平面)でない四角形の例:
頂点A, B, C, Dが1平面上にないとき、四角形は実際にはねじれた面(非プラナー面)になります。
Blenderではこのような面の法線は、通常こうして決まります:
面が四角形(N-gon)であっても、Blender内部で三角形に分割(triangulate)して平均する。
例えば、対角線で「三角形ABC」と「三角形ACD」に分け、
それぞれの法線を出して平均(normalize)して、
代表法線 n = normalize(n1 + n2) を取得。
それを「面の法線」として返してきます。
らしい
コメント
コメントを投稿