Last active
August 31, 2024 02:55
-
-
Save mio3io/ba9c0cfc85a37500e0702eb1954d5037 to your computer and use it in GitHub Desktop.
選択した頂点のノーマルを対称化するBlenderアドオン(Menu > Mesh > Normal)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
シャープエッジが入っている部分が崩れるようなので後日修正します
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
【使用方法】