Files
blender-vrm/transfer_vertex_order.py
2026-01-16 11:00:23 +08:00

745 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "传递顶点顺序",
"author": "Jose Conseco based on UV Transfer from MagicUV Nutti",
"version": (2, 4),
"blender": (4, 2, 0),
"location": "侧边栏 (N) -> 工具面板",
"description": "通过顶点位置或选定面传递顶点ID",
"warning": "",
"wiki_url": "",
"category": "Object",
"doc_url": "https://bartoszstyperek.gumroad.com/l/copy_verts_ids",
}
from collections import OrderedDict
import bpy
import bmesh
from bpy.props import BoolProperty
from mathutils import kdtree, Vector
class CopyIDs():
def __init__(self):
self.transuv = ID_DATA()
class ID_DATA():
face_vert_ids = []
face_edge_ids = []
faces_id = []
face_loop_ids = []
class VOT_PT_CopyVertIds(bpy.types.Panel):
bl_idname = "VOT_PT_copyvertids"
bl_label = "传递顶点顺序"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tools'
def draw(self, context):
layout = self.layout
if context.mode == 'OBJECT':
layout.label(text = '编辑模式下有更多选项')
layout.operator("object.vert_id_transfer_proximity")
layout.operator("object.vert_id_transfer_uv")
layout.operator("object.preview_vertex_order")
layout.label(text="选中 -> 活动")
elif context.mode == 'EDIT_MESH':
layout.separator()
layout.operator("object.copy_vert_id")
layout.operator("object.paste_vert_id")
layout.operator("object.copy_paste_vert_id")
layout.operator("object.select_not_transfered_verts")
layout.operator("object.preview_vertex_order")
layout.label(text="选中 -> 活动")
class VOT_OT_TransferVertId(bpy.types.Operator):
"""通过顶点位置传递顶点ID"""
bl_label = "使用位置传递ID"
bl_idname = "object.vert_id_transfer_proximity"
bl_description = "通过顶点位置传递顶点ID适用于形状完全相同的网格\n需要选择两个网格物体"
bl_options = {'REGISTER'}
delta: bpy.props.FloatProperty(name="误差", description="搜索距离", default=0.1, min=0, max=1, precision = 4)
def execute(self, context):
sourceObj = context.active_object
TargetObjs = [obj for obj in context.selected_objects if obj!=sourceObj and obj.type=='MESH']
if not TargetObjs:
self.report({'ERROR'}, '你需要选择两个网格物体(源物体和接收顶点顺序的目标物体)!取消操作')
return {'CANCELLED'}
bm = bmesh.new() # load mesh
bm.from_mesh(sourceObj.data)
src_obj_kd_verts = kdtree.KDTree(len(bm.verts))
for i, v in enumerate(bm.verts):
src_obj_kd_verts.insert(v.co, i)
src_obj_kd_verts.balance()
src_obj_kd_edges = kdtree.KDTree(len(bm.edges))
for i, edge in enumerate(bm.edges):
src_obj_kd_edges.insert((edge.verts[0].co+edge.verts[1].co)/2, i)
src_obj_kd_edges.balance()
src_obj_kd_faces = kdtree.KDTree(len(bm.faces))
for i, f in enumerate(bm.faces):
src_obj_kd_faces.insert(f.calc_center_median(), i)
src_obj_kd_faces.balance()
bm.free()
processedVertsIdDict = {}
processedEdgesIdDict = {}
processedFacesIdDict = {}
for target in TargetObjs:
copiedCount = 0
processedVertsIdDict.clear()
bm = bmesh.new() # load mesh
bm.from_mesh(target.data)
for vert in bm.verts:
co, index, dist = src_obj_kd_verts.find(vert.co)
if dist < self.delta: # delta
copiedCount += 1
vert.index = index
processedVertsIdDict[vert] = index
for edge in bm.edges:
co, index, dist = src_obj_kd_edges.find((edge.verts[0].co+edge.verts[1].co)/2)
if dist<self.delta: #delta
copiedCount+=1
edge.index = index
processedEdgesIdDict[edge] = index
for face in bm.faces:
co, index, dist = src_obj_kd_faces.find(face.calc_center_median())
if dist<self.delta: #delta
copiedCount+=1
face.index = index
processedFacesIdDict[face] = index
VOT_OT_PasteVertID.sortOtherVerts(processedVertsIdDict, processedEdgesIdDict, processedFacesIdDict, bm)
bm.verts.sort()
bm.edges.sort()
bm.faces.sort()
bm.to_mesh(target.data)
bm.free()
self.report({'INFO'}, '已粘贴 '+str(copiedCount)+' 个顶点ID')
return {"FINISHED"}
class VOT_OT_TransferVertIdByUV(bpy.types.Operator):
"""通过UV传递顶点ID"""
bl_label = "使用UV传递ID"
bl_idname = "object.vert_id_transfer_uv"
bl_description = "使用UV从选中物体传递顶点ID到活动物体适用于形状不同但UV相同的网格\n需要选择两个网格物体"
bl_options = {'REGISTER'}
delta: bpy.props.FloatProperty(name="误差", description="搜索距离", default=0.01, min=0, max=0.1, precision = 5)
@staticmethod
def find_face_uv_center(face: bmesh.types.BMFace, uv_layer):
uv_ctr = Vector((0.0, 0.0))
uv_cnt = 0
for loop in face.loops:
uv_ctr += loop[uv_layer].uv
uv_cnt += 1
# calculate winding value to better deal with multiple faces with the same UV center
# (the main thing this is for is mirrored meshes that have the same UVs for both sides)
winding_1: Vector = (face.loops[1][uv_layer].uv - face.loops[0][uv_layer].uv).to_3d()
winding_2: Vector = (face.loops[2][uv_layer].uv - face.loops[0][uv_layer].uv).to_3d()
# by not normalizing, it also serves to differentiate by face size centers that otherwise might match
# will only be a z value since the input vectors were 2d on xy
winding = winding_1.cross(winding_2)
return (uv_ctr / uv_cnt).to_3d() + winding
def execute(self, context):
sourceObj = context.active_object
TargetObjs = [obj for obj in context.selected_objects if obj!=sourceObj and obj.type=='MESH']
if not TargetObjs:
self.report({'ERROR'}, '你需要选择两个网格物体(源物体和接收顶点顺序的目标物体)!取消操作')
return {'CANCELLED'}
bm_src = bmesh.new() # load mesh
bm_src.from_mesh(sourceObj.data)
bm_src.faces.ensure_lookup_table()
src_obj_kd_faces = kdtree.KDTree(len(bm_src.faces))
for f in bm_src.faces:
src_obj_kd_faces.insert(self.find_face_uv_center(f, bm_src.loops.layers.uv.active), f.index)
src_obj_kd_faces.balance()
processedVertsIdDict = {}
processedEdgesIdDict = {}
processedFacesIdDict = {}
for target in TargetObjs:
processedVertsIdDict.clear()
bm = bmesh.new() # load mesh
bm.from_mesh(target.data)
for face in bm.faces:
co, index, dist = src_obj_kd_faces.find(self.find_face_uv_center(face, bm.loops.layers.uv.active))
if dist<self.delta: #delta
face.index = index
processedFacesIdDict[face] = index
for loop_src, loop_dst in zip(bm_src.faces[index].loops, face.loops):
# will be copied over to the actual data later (so as to only be done once per vert/edge)
processedEdgesIdDict[loop_dst.edge] = loop_src.edge.index
loop_dst.edge.index = loop_src.edge.index
processedVertsIdDict[loop_dst.vert] = loop_src.vert.index
loop_dst.vert.index = loop_src.vert.index
copiedCount = len(processedVertsIdDict)
copiedCount += len(processedEdgesIdDict)
copiedCount += len(processedFacesIdDict)
VOT_OT_PasteVertID.sortOtherVerts(processedVertsIdDict, processedEdgesIdDict, processedFacesIdDict, bm)
bm.verts.sort()
bm.edges.sort()
bm.faces.sort()
bm.to_mesh(target.data)
bm.free()
self.report({'INFO'}, '已粘贴 '+str(copiedCount)+' 个顶点ID')
bm_src.free()
return {"FINISHED"}
class VOT_OT_CopyVertID(bpy.types.Operator):
bl_idname = "object.copy_vert_id"
bl_label = "复制顶点ID"
bl_description = "通过拓扑复制顶点ID需要选择两个面\n网格形状可以不同,但拓扑必须相同"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
props = context.scene.copy_indices.transuv
active_obj = context.active_object
self.obj = active_obj
bm = bmesh.from_edit_mesh(active_obj.data)
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
color_layer = get_color_layer(bm)
props.face_loop_ids.clear()
props.face_vert_ids.clear()
props.face_edge_ids.clear()
props.faces_id.clear()
# get selected faces
active_face = bm.faces.active
sel_faces = [face for face in bm.faces if face.select]
if len(sel_faces) != 2:
self.report({'WARNING'}, "必须选择两个面")
return {'CANCELLED'}
if not active_face or active_face not in sel_faces:
self.report({'WARNING'}, "必须有一个活动面")
return {'CANCELLED'}
# parse all faces according to selection
active_face_nor = active_face.normal.copy()
all_sorted_faces = main_parse(self, sel_faces, active_face, active_face_nor)
if all_sorted_faces:
for face, face_data in all_sorted_faces.items():
loops = face_data[0]
verts = face_data[1]
edges = face_data[2]
props.face_loop_ids.append([loop.index for loop in loops])
props.face_vert_ids.append([vert.index for vert in verts])
props.face_edge_ids.append([e.index for e in edges])
props.faces_id.append(face.index)
for loop in loops:
loop[color_layer] = Vector((1,0,0,1))
bmesh.update_edit_mesh(active_obj.data)
activate_color_layer(active_obj)
self.report({"INFO"}, "顶点顺序已复制。")
return {'FINISHED'}
class VOT_OT_PasteVertID(bpy.types.Operator):
bl_idname = "object.paste_vert_id"
bl_label = "粘贴顶点ID"
bl_description = (
"通过拓扑粘贴顶点ID需要选择两个与源物体拓扑匹配的面\n"
"网格形状可以不同,但拓扑必须相同"
)
bl_options = {'REGISTER', 'UNDO'}
invert_normals: BoolProperty(name="反转法线", description="反转法线", default=False)
@staticmethod
def sortOtherVerts(processedVertsIdDict, preocessedEdgesIsDict, preocessedFaceIsDict, bm):
"""Prevet verts on other islands from being all shuffled"""
# dicts instead of lists - faster search 4x?
if len(bm.verts) == len(processedVertsIdDict) and len(bm.faces) == len(preocessedFaceIsDict):
return #all verts, and faces were processed - > no other Islands -> quit
def fix_islands(processed_items, bm_element): #face, verts, or edges
processedItems = {item: id for (item, id) in processed_items.items()} # dicts instead of lists
processedIDs = {id: 1 for (item, id) in processed_items.items()} # dicts instead of lists
notProcessedItemsIds = {ele.index: 1 for ele in bm_element if ele not in processedItems} # it will have duplicated ids from processedIDs that have to be
spareIDS = [i for i in range(len(bm_element)) if (i not in processedIDs and i not in notProcessedItemsIds)]
notProcessedElements = [item for item in bm_element if item not in processedItems]
for item in notProcessedElements:
if item.index in processedIDs: # if duplicated id found in not processed verts
item.index = spareIDS.pop(0) # what if list is empty??
fix_islands(processedVertsIdDict, bm.verts)
fix_islands(preocessedEdgesIsDict, bm.edges)
fix_islands(preocessedFaceIsDict, bm.faces)
def execute(self, context):
props = context.scene.copy_indices.transuv
active_obj = context.active_object
bm = bmesh.from_edit_mesh(active_obj.data)
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
color_layer = get_color_layer(bm)
# get selection history
all_sel_faces = [
e for e in bm.select_history
if isinstance(e, bmesh.types.BMFace) and e.select]
if len(all_sel_faces) % 2 != 0:
self.report({'WARNING'}, "必须选择两个面")
return {'CANCELLED'}
# parse selection history
loopID_dict = {}
vertID_dict = {}
edgeID_dict = {}
faceID_dict = {}
for i, _ in enumerate(all_sel_faces):
if (i == 0) or (i % 2 == 0):
continue
sel_faces = [all_sel_faces[i - 1], all_sel_faces[i]]
active_face = all_sel_faces[i]
# parse all faces according to selection history
active_face_nor = active_face.normal.copy()
if self.invert_normals:
active_face_nor.negate()
all_sorted_faces = main_parse(self, sel_faces, active_face, active_face_nor)
# ipdb.set_trace()
if all_sorted_faces:
# check amount of copied/pasted faces
if len(all_sorted_faces) != len(props.face_vert_ids):
self.report(
{'WARNING'},
"网格的面数量不同"
)
return {'FINISHED'}
for j,(face, face_data) in enumerate(all_sorted_faces.items()):
loop_ids_cache = props.face_loop_ids[j]
vert_ids_cache = props.face_vert_ids[j]
edge_ids_cache = props.face_edge_ids[j]
face_id_cache = props.faces_id[j]
# check amount of copied/pasted verts
if len(vert_ids_cache) != len(face_data[1]):
bpy.ops.mesh.select_all(action='DESELECT')
# select problematic face
list(all_sorted_faces.keys())[j].select = True
self.report(
{'WARNING'},
"面的顶点数量不同"
)
return {'FINISHED'}
for k, vert in enumerate(face_data[1]):
vert.index = vert_ids_cache[k] #index
vertID_dict[vert] = vert.index
for k, loop in enumerate(face_data[0]):
loop.index = loop_ids_cache[k] #index
loop[color_layer] = Vector((0,1,0,1))
loopID_dict[loop] = loop.index
face.index = face_id_cache
faceID_dict[face] = face_id_cache
for k, edge in enumerate(face_data[2]): #edges
edge.index = edge_ids_cache[k] # index
edgeID_dict[edge] = edge.index
self.sortOtherVerts(vertID_dict, edgeID_dict, faceID_dict, bm)
#? does not exist bm.loops.sort()
bm.verts.sort()
bm.edges.sort()
bm.faces.sort()
#! !!! for faces in bm.fa
bmesh.update_edit_mesh(active_obj.data)
activate_color_layer(active_obj)
self.report({"INFO"}, "顶点顺序已粘贴。")
return {'FINISHED'}
class VOT_OT_CopyPasteVertID(bpy.types.Operator):
bl_idname = "object.copy_paste_vert_id"
bl_label = "复制并粘贴顶点ID"
bl_description = (
"从选中物体复制顶点顺序到活动物体。"
"两个物体都应处于编辑模式并选择两个相似的面"
)
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
selected_objects = context.selected_objects
if len(selected_objects) != 2 or any(obj.mode != "EDIT" for obj in selected_objects):
cls.poll_message_set("必须选择两个物体,且都需要处于编辑模式")
return False
return True
def execute(self, context):
selected_objects = context.selected_objects
target_obj = context.active_object
source_obj = next(obj for obj in selected_objects if obj != target_obj)
with context.temp_override(active_object=source_obj):
bpy.ops.object.copy_vert_id()
with context.temp_override(active_object=target_obj):
bpy.ops.object.paste_vert_id()
return {"FINISHED"}
def main_parse(self, sel_faces, active_face, active_face_nor):
all_sorted_faces = OrderedDict() # This is the main stuff
used_verts = set()
used_edges = set()
faces_to_parse = []
# get shared edge of two faces
cross_edges = []
for edge in active_face.edges:
if edge in sel_faces[0].edges and edge in sel_faces[1].edges:
cross_edges.append(edge)
# parse two selected faces
if cross_edges and len(cross_edges) == 1:
shared_edge = cross_edges[0]
vert1 = None
vert2 = None
dot_n = active_face_nor.normalized()
edge_vec_1 = (shared_edge.verts[1].co - shared_edge.verts[0].co)
edge_vec_len = edge_vec_1.length
edge_vec_1 = edge_vec_1.normalized()
af_center = active_face.calc_center_median()
af_vec = shared_edge.verts[0].co + (edge_vec_1 * (edge_vec_len * 0.5))
af_vec = (af_vec - af_center).normalized()
if af_vec.cross(edge_vec_1).dot(dot_n) > 0:
vert1 = shared_edge.verts[0]
vert2 = shared_edge.verts[1]
else:
vert1 = shared_edge.verts[1]
vert2 = shared_edge.verts[0]
# get active face stuff and uvs
# ipdb.set_trace()
face_stuff = get_other_verts_edges(active_face, vert1, vert2, shared_edge)
all_sorted_faces[active_face] = face_stuff
used_verts.update(active_face.verts)
used_edges.update(active_face.edges)
# get first selected face stuff and uvs as they share shared_edge
second_face = sel_faces[0]
if second_face is active_face:
second_face = sel_faces[1]
face_stuff = get_other_verts_edges(second_face, vert1, vert2, shared_edge)
all_sorted_faces[second_face] = face_stuff
used_verts.update(second_face.verts)
used_edges.update(second_face.edges)
# first Grow
faces_to_parse.append(active_face)
faces_to_parse.append(second_face)
else:
self.report({'WARNING'}, "两个面应该共享一条边")
return None
# parse all faces
while True:
new_parsed_faces = []
if not faces_to_parse:
break
for face in faces_to_parse:
face_stuff = all_sorted_faces.get(face)
new_faces = parse_faces(face, face_stuff, used_verts, used_edges, all_sorted_faces)
if new_faces == 'CANCELLED':
self.report({'WARNING'}, "超过2个面共享边")
return None
new_parsed_faces += new_faces
faces_to_parse = new_parsed_faces
return all_sorted_faces
def parse_faces(check_face, face_stuff, used_verts, used_edges, all_sorted_faces):
"""recurse faces around the new_grow only"""
new_shared_faces = []
for sorted_edge in face_stuff[2]:
shared_faces = sorted_edge.link_faces
if shared_faces:
if len(shared_faces) > 2:
bpy.ops.mesh.select_all(action='DESELECT')
for face_sel in shared_faces:
face_sel.select = True
shared_faces = []
return 'CANCELLED'
clear_shared_faces = get_new_shared_faces(check_face, sorted_edge, shared_faces, all_sorted_faces.keys())
if clear_shared_faces:
shared_face = clear_shared_faces[0]
# get vertices of the edge
vert1 = sorted_edge.verts[0]
vert2 = sorted_edge.verts[1]
if face_stuff[1].index(vert1) > face_stuff[1].index(vert2):
vert1 = sorted_edge.verts[1]
vert2 = sorted_edge.verts[0]
new_face_stuff = get_other_verts_edges(shared_face, vert1, vert2, sorted_edge)
all_sorted_faces[shared_face] = new_face_stuff
used_verts.update(shared_face.verts)
used_edges.update(shared_face.edges)
new_shared_faces.append(shared_face)
return new_shared_faces
def get_new_shared_faces(orig_face, shared_edge, check_faces, used_faces):
shared_faces = []
for face in check_faces:
is_shared_edge = shared_edge in face.edges
not_used = face not in used_faces
not_orig = face is not orig_face
not_hide = face.hide is False
if is_shared_edge and not_used and not_orig and not_hide:
shared_faces.append(face)
return shared_faces
def get_other_verts_edges(face, vert1, vert2, first_edge):
face_edges = [first_edge]
face_verts = [vert1, vert2]
other_edges = [edge for edge in face.edges if edge not in face_edges]
face_loops = [] # ! move to vert processing above? To maintain order ids?
def add_vert_loop(ver): #add vert link_loops
for loop in ver.link_loops:
if loop.face == face:
face_loops.append(loop)
break
add_vert_loop(vert1) #add loops in same order as verts
add_vert_loop(vert2)
for _ in range(len(other_edges)):
found_edge = None
# get sorted verts and edges
for edge in other_edges:
if face_verts[-1] in edge.verts:
other_vert = edge.other_vert(face_verts[-1])
if other_vert not in face_verts:
face_verts.append(other_vert)
add_vert_loop(other_vert)
found_edge = edge
if found_edge not in face_edges:
face_edges.append(edge)
break
other_edges.remove(found_edge)
return [face_loops, face_verts, face_edges]
panels = (
VOT_PT_CopyVertIds,
)
def update_panel(self, context):
message = "Vertex Order: Updating Panel locations has failed"
try:
for panel in panels:
if "bl_rna" in panel.__dict__:
bpy.utils.unregister_class(panel)
for panel in panels:
panel.bl_category = context.preferences.addons[__name__].preferences.category
bpy.utils.register_class(panel)
except Exception as e:
print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
pass
class WertOrderPreferences(bpy.types.AddonPreferences):
# this must match the addon name, use '__package__'
# when defining this in a submodule of a python package.
bl_idname = __name__
category: bpy.props.StringProperty( name="标签类别", description="为面板类别选择一个名称", default="Tools", update=update_panel )
def draw(self, context):
layout = self.layout
row = layout.row()
col = row.column()
col.label(text="标签类别:")
col.prop(self, "category", text="")
def get_color_layer(bm):
color_layer = bm.loops.layers.color.get("VTX_TRANSFER", None)
if not color_layer:
color_layer = bm.loops.layers.color.new("VTX_TRANSFER")
return color_layer
def activate_color_layer(obj):
obj.data.vertex_colors["VTX_TRANSFER"].active = True
class VOT_OT_SelectNotTransfered(bpy.types.Operator):
bl_idname = "object.select_not_transfered_verts"
bl_label = "选择未传递的顶点"
bl_description = "选择所有尚未传递顶点顺序的面"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
active_obj = context.active_object
self.obj = active_obj
bm = bmesh.from_edit_mesh(active_obj.data)
color_layer = get_color_layer(bm)
something_left = False
for face in bm.faces:
for loop in face.loops:
if loop[color_layer].z > 0.01:
face.select = True
something_left = True
break
else:
face.select = False
bmesh.update_edit_mesh(active_obj.data)
activate_color_layer(active_obj)
if something_left:
self.report({"INFO"}, "已选择面")
else:
self.report({"INFO"}, "顶点顺序传递完成,没有可选择的内容")
return {"FINISHED"}
class VOT_OT_PreviewVertexOrder(bpy.types.Operator):
bl_idname = "object.preview_vertex_order"
bl_label = "预览顶点顺序"
bl_description = "通过从选中物体在活动物体上创建形态键来预览顶点顺序"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
if not len(context.selected_objects) == 2:
cls.poll_message_set("需要选择2个物体")
return False
return True
def execute(self, context):
selected_objects = context.selected_objects
target_obj = context.active_object
source_obj = next(obj for obj in selected_objects if obj != target_obj)
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode = "OBJECT")
shape_keys = target_obj.data.shape_keys
if not shape_keys:
target_obj.shape_key_add(name="Basis")
shape_keys = target_obj.data.shape_keys
key_blocks = shape_keys.key_blocks
# if we already have that shape key, then we remove it first
if "VTX_TRANSFER" in key_blocks:
previous_shape_key = key_blocks["VTX_TRANSFER"]
shape_key_index = key_blocks[:].index(previous_shape_key)
current_shape_key_index = target_obj.active_shape_key_index
target_obj.active_shape_key_index = shape_key_index
bpy.ops.object.shape_key_remove()
target_obj.active_shape_key_index = current_shape_key_index
bpy.ops.object.join_shapes()
shape_key = key_blocks[:][-1]
shape_key.name = 'VTX_TRANSFER'
shape_key.value = 1.0
return {"FINISHED"}
classes = (
WertOrderPreferences,
VOT_OT_TransferVertId,
VOT_OT_TransferVertIdByUV,
VOT_OT_CopyVertID,
VOT_OT_PasteVertID,
VOT_OT_SelectNotTransfered,
VOT_PT_CopyVertIds,
VOT_OT_CopyPasteVertID,
VOT_OT_PreviewVertexOrder
)
def register():
bpy.types.Scene.copy_indices = CopyIDs()
# bpy.ops.wm.addon_enable(module="space_view3d_copy_attributes")
from bpy.utils import register_class
for cls in classes:
register_class(cls)
update_panel(None, bpy.context)
def unregister():
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
del bpy.types.Scene.copy_indices
if __name__ == "__main__":
register()