Skip to content

Instantly share code, notes, and snippets.

@mio3io
Last active August 31, 2024 02:55
Show Gist options
  • Save mio3io/ba9c0cfc85a37500e0702eb1954d5037 to your computer and use it in GitHub Desktop.
Save mio3io/ba9c0cfc85a37500e0702eb1954d5037 to your computer and use it in GitHub Desktop.
選択した頂点のノーマルを対称化するBlenderアドオン(Menu > Mesh > Normal)
import bpy
import bmesh
from bpy.props import EnumProperty
from mathutils.kdtree import KDTree
bl_info = {
"name": "Mio3 Normal Symmetrize",
"version": (1, 0),
"blender": (4, 1, 0),
"location": "Mesh > Normal",
"description": "選択した頂点のノーマルのベクトルを対称化",
"category": "Mesh",
}
class MIO3_OT_normal_symmetrize(bpy.types.Operator):
bl_idname = "mesh.mio3_normal_symmetrize"
bl_label = "ベクトルを対称化"
bl_description = "選択した頂点のノーマルのベクトルを対称化"
bl_options = {"REGISTER", "UNDO"}
axis: EnumProperty(
name="Axis",
default="X",
items=[("X", "X", ""), ("Y", "Y", ""), ("Z", "Z", "")],
)
center_threshold = 0.00001
@classmethod
def poll(cls, context):
return context.object and context.object.mode == "EDIT"
def execute(self, context):
obj = context.active_object
obj.update_from_editmode()
if not obj.data.has_custom_normals:
return {"CANCELLED"}
bm = bmesh.from_edit_mesh(obj.data)
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
kd = KDTree(len(bm.verts))
for i, v in enumerate(bm.verts):
kd.insert(v.co, i)
kd.balance()
sharp_edges = {edge for edge in bm.edges if not edge.smooth}
normals = [list(l.normal) for l in obj.data.loops]
for v in bm.verts:
if v.select and self.is_not_center(v.co):
target_co = self.get_mirrored_co(v.co)
_, idx, dist = kd.find(target_co)
if dist < self.center_threshold:
sym_v = bm.verts[idx]
v_sharp_edges = any(e for e in v.link_edges if e in sharp_edges)
has_flat_face = any(not face.smooth for face in v.link_faces)
for v_face in v.link_faces:
sym_face = self.find_symmetric_face(v_face, sym_v.link_faces)
if sym_face:
if v_sharp_edges or has_flat_face:
for v_loop in v_face.loops:
if v_loop.vert == v:
sym_loop = next(loop for loop in sym_face.loops if loop.vert == sym_v)
mirror_normal = self.get_mirrored_normal(normals[sym_loop.index])
normals[v_loop.index] = tuple(mirror_normal)
else:
mirror_normal = self.get_mirrored_normal(normals[sym_v.link_loops[0].index])
for loop in v_face.loops:
if loop.vert == v:
normals[loop.index] = tuple(mirror_normal)
bmesh.update_edit_mesh(obj.data)
bpy.ops.object.mode_set(mode="OBJECT")
obj.data.normals_split_custom_set([tuple(n) for n in normals])
bpy.ops.object.mode_set(mode="EDIT")
return {"FINISHED"}
def is_not_center(self, co):
if self.axis == "X":
return abs(co.x) >= self.center_threshold
elif self.axis == "Y":
return abs(co.y) >= self.center_threshold
else:
return abs(co.z) >= self.center_threshold
def get_mirrored_co(self, co):
mirrored = co.copy()
if self.axis == "X":
mirrored.x = -mirrored.x
elif self.axis == "Y":
mirrored.y = -mirrored.y
else:
mirrored.z = -mirrored.z
return mirrored
def find_symmetric_face(self, face, sym_faces):
face_center = face.calc_center_median()
sym_face_center = self.get_mirrored_co(face_center)
return min(
sym_faces,
key=lambda f: (sym_face_center - f.calc_center_median()).length,
default=None,
)
def get_mirrored_normal(self, normal):
mirrored = list(normal)
if self.axis == "X":
mirrored[0] = -mirrored[0]
elif self.axis == "Y":
mirrored[1] = -mirrored[1]
else:
mirrored[2] = -mirrored[2]
return mirrored
def menu(self, context):
layout = self.layout
layout.separator()
self.layout.operator(MIO3_OT_normal_symmetrize.bl_idname)
def register():
bpy.utils.register_class(MIO3_OT_normal_symmetrize)
bpy.types.VIEW3D_MT_edit_mesh_normals.append(menu)
def unregister():
bpy.utils.unregister_class(MIO3_OT_normal_symmetrize)
bpy.types.VIEW3D_MT_edit_mesh_normals.remove(menu)
if __name__ == "__main__":
register()
@mio3io
Copy link
Author

mio3io commented May 28, 2024

【使用方法】

  • テキストファイルを保存
  • Blender→アドオン→保存したファイルをインストール

image

@mio3io
Copy link
Author

mio3io commented Jun 4, 2024

シャープエッジが入っている部分が崩れるようなので後日修正します

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment