diff options
Diffstat (limited to 'uv_magic_uv/op/copy_paste_uv.py')
-rw-r--r-- | uv_magic_uv/op/copy_paste_uv.py | 883 |
1 files changed, 464 insertions, 419 deletions
diff --git a/uv_magic_uv/op/copy_paste_uv.py b/uv_magic_uv/op/copy_paste_uv.py index ee89b5e9..cc1baa30 100644 --- a/uv_magic_uv/op/copy_paste_uv.py +++ b/uv_magic_uv/op/copy_paste_uv.py @@ -20,11 +20,9 @@ __author__ = "imdjs, Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "5.1" -__date__ = "24 Feb 2018" +__version__ = "5.2" +__date__ = "17 Nov 2018" -import math -from math import atan2, sin, cos import bpy import bmesh @@ -34,104 +32,293 @@ from bpy.props import ( IntProperty, EnumProperty, ) -from mathutils import Vector from .. import common -class MUV_CPUVCopyUV(bpy.types.Operator): +__all__ = [ + 'Properties', + 'OpeartorCopyUV', + 'MenuCopyUV', + 'OperatorPasteUV', + 'MenuPasteUV', + 'OperatorSelSeqCopyUV', + 'MenuSelSeqCopyUV', + 'OperatorSelSeqPasteUV', + 'MenuSelSeqPasteUV', +] + + +def is_valid_context(context): + obj = context.object + + # only edit mode is allowed to execute + if obj is None: + return False + if obj.type != 'MESH': + return False + if context.object.mode != 'EDIT': + return False + + # only 'VIEW_3D' space is allowed to execute + for space in context.area.spaces: + if space.type == 'VIEW_3D': + break + else: + return False + + return True + + +def get_copy_uv_layers(ops_obj, bm): + uv_layers = [] + if ops_obj.uv_map == "__default": + if not bm.loops.layers.uv: + ops_obj.report( + {'WARNING'}, "Object must have more than one UV map") + return None + uv_layers.append(bm.loops.layers.uv.verify()) + ops_obj.report({'INFO'}, "Copy UV coordinate") + elif ops_obj.uv_map == "__all": + for uv in bm.loops.layers.uv.keys(): + uv_layers.append(bm.loops.layers.uv[uv]) + ops_obj.report({'INFO'}, "Copy UV coordinate (UV map: ALL)") + else: + uv_layers.append(bm.loops.layers.uv[ops_obj.uv_map]) + ops_obj.report( + {'INFO'}, "Copy UV coordinate (UV map:{})".format(ops_obj.uv_map)) + + return uv_layers + + +def get_paste_uv_layers(ops_obj, obj, bm, src_info): + uv_layers = [] + if ops_obj.uv_map == "__default": + if not bm.loops.layers.uv: + ops_obj.report( + {'WARNING'}, "Object must have more than one UV map") + return None + uv_layers.append(bm.loops.layers.uv.verify()) + ops_obj.report({'INFO'}, "Paste UV coordinate") + elif ops_obj.uv_map == "__new": + new_uv_map = common.create_new_uv_map(obj) + if not new_uv_map: + ops_obj.report({'WARNING'}, + "Reached to the maximum number of UV map") + return None + uv_layers.append(bm.loops.layers.uv[new_uv_map.name]) + ops_obj.report( + {'INFO'}, "Paste UV coordinate (UV map:{})".format(new_uv_map)) + elif ops_obj.uv_map == "__all": + for src_layer in src_info.keys(): + if src_layer not in bm.loops.layers.uv.keys(): + new_uv_map = common.create_new_uv_map(obj, src_layer) + if not new_uv_map: + ops_obj.report({'WARNING'}, + "Reached to the maximum number of UV map") + return None + uv_layers.append(bm.loops.layers.uv[src_layer]) + ops_obj.report({'INFO'}, "Paste UV coordinate (UV map: ALL)") + else: + uv_layers.append(bm.loops.layers.uv[ops_obj.uv_map]) + ops_obj.report( + {'INFO'}, "Paste UV coordinate (UV map:{})".format(ops_obj.uv_map)) + + return uv_layers + + +def paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip, + rotate, copy_seams): + for slayer_name, dlayer in zip(src_info.keys(), uv_layers): + src_faces = src_info[slayer_name] + dest_faces = dest_info[dlayer.name] + + for idx, dinfo in enumerate(dest_faces): + sinfo = None + if strategy == 'N_N': + sinfo = src_faces[idx] + elif strategy == 'N_M': + sinfo = src_faces[idx % len(src_faces)] + + suv = sinfo["uvs"] + spuv = sinfo["pin_uvs"] + ss = sinfo["seams"] + if len(sinfo["uvs"]) != len(dinfo["uvs"]): + ops_obj.report({'WARNING'}, "Some faces are different size") + return -1 + + suvs_fr = [uv for uv in suv] + spuvs_fr = [pin_uv for pin_uv in spuv] + ss_fr = [s for s in ss] + + # flip UVs + if flip is True: + suvs_fr.reverse() + spuvs_fr.reverse() + ss_fr.reverse() + + # rotate UVs + for _ in range(rotate): + uv = suvs_fr.pop() + pin_uv = spuvs_fr.pop() + s = ss_fr.pop() + suvs_fr.insert(0, uv) + spuvs_fr.insert(0, pin_uv) + ss_fr.insert(0, s) + + # paste UVs + for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr, + spuvs_fr, ss_fr): + l[dlayer].uv = suv + l[dlayer].pin_uv = spuv + if copy_seams is True: + l.edge.seam = ss + + return 0 + + +class Properties: + @classmethod + def init_props(cls, scene): + class Props(): + src_info = None + + scene.muv_props.copy_paste_uv = Props() + scene.muv_props.copy_paste_uv_selseq = Props() + + scene.muv_copy_paste_uv_enabled = BoolProperty( + name="Copy/Paste UV Enabled", + description="Copy/Paste UV is enabled", + default=False + ) + scene.muv_copy_paste_uv_copy_seams = BoolProperty( + name="Seams", + description="Copy Seams", + default=True + ) + scene.muv_copy_paste_uv_mode = EnumProperty( + items=[ + ('DEFAULT', "Default", "Default Mode"), + ('SEL_SEQ', "Selection Sequence", "Selection Sequence Mode") + ], + name="Copy/Paste UV Mode", + description="Copy/Paste UV Mode", + default='DEFAULT' + ) + scene.muv_copy_paste_uv_strategy = EnumProperty( + name="Strategy", + description="Paste Strategy", + items=[ + ('N_N', 'N:N', 'Number of faces must be equal to source'), + ('N_M', 'N:M', 'Number of faces must not be equal to source') + ], + default='N_M' + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_props.copy_paste_uv + del scene.muv_props.copy_paste_uv_selseq + del scene.muv_copy_paste_uv_enabled + del scene.muv_copy_paste_uv_copy_seams + del scene.muv_copy_paste_uv_mode + del scene.muv_copy_paste_uv_strategy + + +class OpeartorCopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate """ - bl_idname = "uv.muv_cpuv_copy_uv" - bl_label = "Copy UV (Operation)" - bl_description = "Copy UV coordinate (Operation)" + bl_idname = "uv.muv_copy_paste_uv_operator_copy_uv" + bl_label = "Copy UV" + bl_description = "Copy UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(options={'HIDDEN'}) + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return is_valid_context(context) def execute(self, context): - props = context.scene.muv_props.cpuv - if self.uv_map == "": - self.report({'INFO'}, "Copy UV coordinate") - else: - self.report( - {'INFO'}, "Copy UV coordinate (UV map:%s)" % (self.uv_map)) + props = context.scene.muv_props.copy_paste_uv obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() + bm = common.create_bmesh(obj) # get UV layer - if self.uv_map == "": - if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - else: - uv_layer = bm.loops.layers.uv[self.uv_map] + uv_layers = get_copy_uv_layers(self, bm) + if not uv_layers: + return {'CANCELLED'} # get selected face - props.src_uvs = [] - props.src_pin_uvs = [] - props.src_seams = [] - for face in bm.faces: - if face.select: - uvs = [l[uv_layer].uv.copy() for l in face.loops] - pin_uvs = [l[uv_layer].pin_uv for l in face.loops] - seams = [l.edge.seam for l in face.loops] - props.src_uvs.append(uvs) - props.src_pin_uvs.append(pin_uvs) - props.src_seams.append(seams) - if not props.src_uvs or not props.src_pin_uvs: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs)) + props.src_info = {} + for layer in uv_layers: + face_info = [] + for face in bm.faces: + if face.select: + info = { + "uvs": [l[layer].uv.copy() for l in face.loops], + "pin_uvs": [l[layer].pin_uv for l in face.loops], + "seams": [l.edge.seam for l in face.loops], + } + face_info.append(info) + if not face_info: + self.report({'WARNING'}, "No faces are selected") + return {'CANCELLED'} + props.src_info[layer.name] = face_info + + face_count = len([f for f in bm.faces if f.select]) + self.report({'INFO'}, "{} face(s) are copied".format(face_count)) return {'FINISHED'} -class MUV_CPUVCopyUVMenu(bpy.types.Menu): +class MenuCopyUV(bpy.types.Menu): """ Menu class: Copy UV coordinate """ - bl_idname = "uv.muv_cpuv_copy_uv_menu" - bl_label = "Copy UV" - bl_description = "Copy UV coordinate" + bl_idname = "uv.muv_copy_paste_uv_menu_copy_uv" + bl_label = "Copy UV (Menu)" + bl_description = "Menu of Copy UV coordinate" + + @classmethod + def poll(cls, context): + return is_valid_context(context) def draw(self, context): layout = self.layout # create sub menu obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) + bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - layout.operator( - MUV_CPUVCopyUV.bl_idname, - text="[Default]", - icon="IMAGE_COL" - ).uv_map = "" + + ops = layout.operator(OpeartorCopyUV.bl_idname, text="[Default]") + ops.uv_map = "__default" + + ops = layout.operator(OpeartorCopyUV.bl_idname, text="[All]") + ops.uv_map = "__all" + for m in uv_maps: - layout.operator( - MUV_CPUVCopyUV.bl_idname, - text=m, - icon="IMAGE_COL" - ).uv_map = m + ops = layout.operator(OpeartorCopyUV.bl_idname, text=m) + ops.uv_map = m -class MUV_CPUVPasteUV(bpy.types.Operator): +class OperatorPasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate """ - bl_idname = "uv.muv_cpuv_paste_uv" - bl_label = "Paste UV (Operation)" - bl_description = "Paste UV coordinate (Operation)" + bl_idname = "uv.muv_copy_paste_uv_operator_paste_uv" + bl_label = "Paste UV" + bl_description = "Paste UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(options={'HIDDEN'}) + uv_map = StringProperty(default="__default", options={'HIDDEN'}) strategy = EnumProperty( name="Strategy", description="Paste Strategy", @@ -153,104 +340,69 @@ class MUV_CPUVPasteUV(bpy.types.Operator): max=30 ) copy_seams = BoolProperty( - name="Copy Seams", + name="Seams", description="Copy Seams", default=True ) + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + sc = context.scene + props = sc.muv_props.copy_paste_uv + if not props.src_info: + return False + return is_valid_context(context) + def execute(self, context): - props = context.scene.muv_props.cpuv - if not props.src_uvs or not props.src_pin_uvs: + props = context.scene.muv_props.copy_paste_uv + if not props.src_info: self.report({'WARNING'}, "Need copy UV at first") return {'CANCELLED'} - if self.uv_map == "": - self.report({'INFO'}, "Paste UV coordinate") - else: - self.report( - {'INFO'}, "Paste UV coordinate (UV map:%s)" % (self.uv_map)) obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() + bm = common.create_bmesh(obj) # get UV layer - if self.uv_map == "": - if not bm.loops.layers.uv: + uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info) + if not uv_layers: + return {'CANCELLED'} + + # get selected face + dest_face_count = 0 + dest_info = {} + for layer in uv_layers: + face_info = [] + for face in bm.faces: + if face.select: + info = { + "uvs": [l[layer].uv.copy() for l in face.loops], + } + face_info.append(info) + if not face_info: + self.report({'WARNING'}, "No faces are selected") + return {'CANCELLED'} + key = list(props.src_info.keys())[0] + src_face_count = len(props.src_info[key]) + dest_face_count = len(face_info) + if self.strategy == 'N_N' and src_face_count != dest_face_count: self.report( - {'WARNING'}, "Object must have more than one UV map") + {'WARNING'}, + "Number of selected faces is different from copied" + + "(src:{}, dest:{})" + .format(src_face_count, dest_face_count)) return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - else: - uv_layer = bm.loops.layers.uv[self.uv_map] + dest_info[layer.name] = face_info - # get selected face - dest_uvs = [] - dest_pin_uvs = [] - dest_seams = [] - dest_face_indices = [] - for face in bm.faces: - if face.select: - dest_face_indices.append(face.index) - uvs = [l[uv_layer].uv.copy() for l in face.loops] - pin_uvs = [l[uv_layer].pin_uv for l in face.loops] - seams = [l.edge.seam for l in face.loops] - dest_uvs.append(uvs) - dest_pin_uvs.append(pin_uvs) - dest_seams.append(seams) - if not dest_uvs or not dest_pin_uvs: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs): - self.report( - {'WARNING'}, - "Number of selected faces is different from copied" + - "(src:%d, dest:%d)" % - (len(props.src_uvs), len(dest_uvs))) + # paste + ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers, + self.strategy, self.flip_copied_uv, + self.rotate_copied_uv, self.copy_seams) + if ret: return {'CANCELLED'} - # paste - for i, idx in enumerate(dest_face_indices): - suv = None - spuv = None - ss = None - duv = None - if self.strategy == 'N_N': - suv = props.src_uvs[i] - spuv = props.src_pin_uvs[i] - ss = props.src_seams[i] - duv = dest_uvs[i] - elif self.strategy == 'N_M': - suv = props.src_uvs[i % len(props.src_uvs)] - spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)] - ss = props.src_seams[i % len(props.src_seams)] - duv = dest_uvs[i] - if len(suv) != len(duv): - self.report({'WARNING'}, "Some faces are different size") - return {'CANCELLED'} - suvs_fr = [uv for uv in suv] - spuvs_fr = [pin_uv for pin_uv in spuv] - ss_fr = [s for s in ss] - # flip UVs - if self.flip_copied_uv is True: - suvs_fr.reverse() - spuvs_fr.reverse() - ss_fr.reverse() - # rotate UVs - for _ in range(self.rotate_copied_uv): - uv = suvs_fr.pop() - pin_uv = spuvs_fr.pop() - s = ss_fr.pop() - suvs_fr.insert(0, uv) - spuvs_fr.insert(0, pin_uv) - ss_fr.insert(0, s) - # paste UVs - for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr, - spuvs_fr, ss_fr): - l[uv_layer].uv = suv - l[uv_layer].pin_uv = spuv - if self.copy_seams is True: - l.edge.seam = ss - self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs)) + self.report({'INFO'}, "{} face(s) are pasted".format(dest_face_count)) bmesh.update_edit_mesh(obj.data) if self.copy_seams is True: @@ -259,234 +411,146 @@ class MUV_CPUVPasteUV(bpy.types.Operator): return {'FINISHED'} -class MUV_CPUVPasteUVMenu(bpy.types.Menu): +class MenuPasteUV(bpy.types.Menu): """ Menu class: Paste UV coordinate """ - bl_idname = "uv.muv_cpuv_paste_uv_menu" - bl_label = "Paste UV" - bl_description = "Paste UV coordinate" + bl_idname = "uv.muv_copy_paste_uv_menu_paste_uv" + bl_label = "Paste UV (Menu)" + bl_description = "Menu of Paste UV coordinate" + + @classmethod + def poll(cls, context): + sc = context.scene + props = sc.muv_props.copy_paste_uv + if not props.src_info: + return False + return is_valid_context(context) def draw(self, context): sc = context.scene layout = self.layout # create sub menu obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) + bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text="[Default]") - ops.uv_map = "" - ops.copy_seams = sc.muv_cpuv_copy_seams - ops.strategy = sc.muv_cpuv_strategy - for m in uv_maps: - ops = layout.operator(MUV_CPUVPasteUV.bl_idname, text=m) - ops.uv_map = m - ops.copy_seams = sc.muv_cpuv_copy_seams - ops.strategy = sc.muv_cpuv_strategy - -class MUV_CPUVIECopyUV(bpy.types.Operator): - """ - Operation class: Copy UV coordinate on UV/Image Editor - """ - - bl_idname = "uv.muv_cpuv_ie_copy_uv" - bl_label = "Copy UV" - bl_description = "Copy UV coordinate (only selected in UV/Image Editor)" - bl_options = {'REGISTER', 'UNDO'} + ops = layout.operator(OperatorPasteUV.bl_idname, text="[Default]") + ops.uv_map = "__default" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy - @classmethod - def poll(cls, context): - return context.mode == 'EDIT_MESH' + ops = layout.operator(OperatorPasteUV.bl_idname, text="[New]") + ops.uv_map = "__new" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy - def execute(self, context): - props = context.scene.muv_props.cpuv - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - uv_layer = bm.loops.layers.uv.verify() - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() - - for face in bm.faces: - if not face.select: - continue - skip = False - for l in face.loops: - if not l[uv_layer].select: - skip = True - break - if skip: - continue - props.src_uvs.append([l[uv_layer].uv.copy() for l in face.loops]) + ops = layout.operator(OperatorPasteUV.bl_idname, text="[All]") + ops.uv_map = "__all" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy - return {'FINISHED'} + for m in uv_maps: + ops = layout.operator(OperatorPasteUV.bl_idname, text=m) + ops.uv_map = m + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy -class MUV_CPUVIEPasteUV(bpy.types.Operator): +class OperatorSelSeqCopyUV(bpy.types.Operator): """ - Operation class: Paste UV coordinate on UV/Image Editor + Operation class: Copy UV coordinate by selection sequence """ - bl_idname = "uv.muv_cpuv_ie_paste_uv" - bl_label = "Paste UV" - bl_description = "Paste UV coordinate (only selected in UV/Image Editor)" + bl_idname = "uv.muv_copy_paste_uv_operator_selseq_copy_uv" + bl_label = "Copy UV (Selection Sequence)" + bl_description = "Copy UV data by selection sequence" bl_options = {'REGISTER', 'UNDO'} + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + @classmethod def poll(cls, context): - return context.mode == 'EDIT_MESH' - - def execute(self, context): - props = context.scene.muv_props.cpuv - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - uv_layer = bm.loops.layers.uv.verify() - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() - - dest_uvs = [] - dest_face_indices = [] - for face in bm.faces: - if not face.select: - continue - skip = False - for l in face.loops: - if not l[uv_layer].select: - skip = True - break - if skip: - continue - dest_face_indices.append(face.index) - uvs = [l[uv_layer].uv.copy() for l in face.loops] - dest_uvs.append(uvs) - - for suvs, duvs in zip(props.src_uvs, dest_uvs): - src_diff = suvs[1] - suvs[0] - dest_diff = duvs[1] - duvs[0] - - src_base = suvs[0] - dest_base = duvs[0] - - src_rad = atan2(src_diff.y, src_diff.x) - dest_rad = atan2(dest_diff.y, dest_diff.x) - if src_rad < dest_rad: - radian = dest_rad - src_rad - elif src_rad > dest_rad: - radian = math.pi * 2 - (src_rad - dest_rad) - else: # src_rad == dest_rad - radian = 0.0 - - ratio = dest_diff.length / src_diff.length - break - - for suvs, fidx in zip(props.src_uvs, dest_face_indices): - for l, suv in zip(bm.faces[fidx].loops, suvs): - base = suv - src_base - radian_ref = atan2(base.y, base.x) - radian_fin = (radian + radian_ref) - length = base.length - turn = Vector((length * cos(radian_fin), - length * sin(radian_fin))) - target_uv = Vector((turn.x * ratio, turn.y * ratio)) + \ - dest_base - l[uv_layer].uv = target_uv - - bmesh.update_edit_mesh(obj.data) - - return {'FINISHED'} - - -class MUV_CPUVSelSeqCopyUV(bpy.types.Operator): - """ - Operation class: Copy UV coordinate by selection sequence - """ - - bl_idname = "uv.muv_cpuv_selseq_copy_uv" - bl_label = "Copy UV (Selection Sequence) (Operation)" - bl_description = "Copy UV data by selection sequence (Operation)" - bl_options = {'REGISTER', 'UNDO'} - - uv_map = StringProperty(options={'HIDDEN'}) + # we can not get area/space/region from console + if common.is_console_mode(): + return True + return is_valid_context(context) def execute(self, context): - props = context.scene.muv_props.cpuv_selseq - if self.uv_map == "": - self.report({'INFO'}, "Copy UV coordinate (selection sequence)") - else: - self.report( - {'INFO'}, - "Copy UV coordinate (selection sequence) (UV map:%s)" - % (self.uv_map)) + props = context.scene.muv_props.copy_paste_uv_selseq obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() + bm = common.create_bmesh(obj) # get UV layer - if self.uv_map == "": - if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - else: - uv_layer = bm.loops.layers.uv[self.uv_map] + uv_layers = get_copy_uv_layers(self, bm) + if not uv_layers: + return {'CANCELLED'} # get selected face - props.src_uvs = [] - props.src_pin_uvs = [] - props.src_seams = [] - for hist in bm.select_history: - if isinstance(hist, bmesh.types.BMFace) and hist.select: - uvs = [l[uv_layer].uv.copy() for l in hist.loops] - pin_uvs = [l[uv_layer].pin_uv for l in hist.loops] - seams = [l.edge.seam for l in hist.loops] - props.src_uvs.append(uvs) - props.src_pin_uvs.append(pin_uvs) - props.src_seams.append(seams) - if not props.src_uvs or not props.src_pin_uvs: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - self.report({'INFO'}, "%d face(s) are selected" % len(props.src_uvs)) + props.src_info = {} + for layer in uv_layers: + face_info = [] + for hist in bm.select_history: + if isinstance(hist, bmesh.types.BMFace) and hist.select: + info = { + "uvs": [l[layer].uv.copy() for l in hist.loops], + "pin_uvs": [l[layer].pin_uv for l in hist.loops], + "seams": [l.edge.seam for l in hist.loops], + } + face_info.append(info) + if not face_info: + self.report({'WARNING'}, "No faces are selected") + return {'CANCELLED'} + props.src_info[layer.name] = face_info + + face_count = len([f for f in bm.faces if f.select]) + self.report({'INFO'}, "{} face(s) are selected".format(face_count)) return {'FINISHED'} -class MUV_CPUVSelSeqCopyUVMenu(bpy.types.Menu): +class MenuSelSeqCopyUV(bpy.types.Menu): """ Menu class: Copy UV coordinate by selection sequence """ - bl_idname = "uv.muv_cpuv_selseq_copy_uv_menu" - bl_label = "Copy UV (Selection Sequence)" - bl_description = "Copy UV coordinate by selection sequence" + bl_idname = "uv.muv_copy_paste_uv_menu_selseq_copy_uv" + bl_label = "Copy UV (Selection Sequence) (Menu)" + bl_description = "Menu of Copy UV coordinate by selection sequence" + + @classmethod + def poll(cls, context): + return is_valid_context(context) def draw(self, context): layout = self.layout obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) + bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - layout.operator( - MUV_CPUVSelSeqCopyUV.bl_idname, - text="[Default]", icon="IMAGE_COL").uv_map = "" + + ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text="[Default]") + ops.uv_map = "__default" + + ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text="[All]") + ops.uv_map = "__all" + for m in uv_maps: - layout.operator( - MUV_CPUVSelSeqCopyUV.bl_idname, - text=m, icon="IMAGE_COL").uv_map = m + ops = layout.operator(OperatorSelSeqCopyUV.bl_idname, text=m) + ops.uv_map = m -class MUV_CPUVSelSeqPasteUV(bpy.types.Operator): +class OperatorSelSeqPasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate by selection sequence """ - bl_idname = "uv.muv_cpuv_selseq_paste_uv" - bl_label = "Paste UV (Selection Sequence) (Operation)" - bl_description = "Paste UV coordinate by selection sequence (Operation)" + bl_idname = "uv.muv_copy_paste_uv_operator_selseq_paste_uv" + bl_label = "Paste UV (Selection Sequence)" + bl_description = "Paste UV coordinate by selection sequence" bl_options = {'REGISTER', 'UNDO'} - uv_map = StringProperty(options={'HIDDEN'}) + uv_map = StringProperty(default="__default", options={'HIDDEN'}) strategy = EnumProperty( name="Strategy", description="Paste Strategy", @@ -508,108 +572,69 @@ class MUV_CPUVSelSeqPasteUV(bpy.types.Operator): max=30 ) copy_seams = BoolProperty( - name="Copy Seams", + name="Seams", description="Copy Seams", default=True ) + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + sc = context.scene + props = sc.muv_props.copy_paste_uv_selseq + if not props.src_info: + return False + return is_valid_context(context) + def execute(self, context): - props = context.scene.muv_props.cpuv_selseq - if not props.src_uvs or not props.src_pin_uvs: + props = context.scene.muv_props.copy_paste_uv_selseq + if not props.src_info: self.report({'WARNING'}, "Need copy UV at first") return {'CANCELLED'} - if self.uv_map == "": - self.report({'INFO'}, "Paste UV coordinate (selection sequence)") - else: - self.report( - {'INFO'}, - "Paste UV coordinate (selection sequence) (UV map:%s)" - % (self.uv_map)) - obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) - if common.check_version(2, 73, 0) >= 0: - bm.faces.ensure_lookup_table() + bm = common.create_bmesh(obj) # get UV layer - if self.uv_map == "": - if not bm.loops.layers.uv: - self.report( - {'WARNING'}, "Object must have more than one UV map") - return {'CANCELLED'} - uv_layer = bm.loops.layers.uv.verify() - else: - uv_layer = bm.loops.layers.uv[self.uv_map] + uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info) + if not uv_layers: + return {'CANCELLED'} # get selected face - dest_uvs = [] - dest_pin_uvs = [] - dest_seams = [] - dest_face_indices = [] - for hist in bm.select_history: - if isinstance(hist, bmesh.types.BMFace) and hist.select: - dest_face_indices.append(hist.index) - uvs = [l[uv_layer].uv.copy() for l in hist.loops] - pin_uvs = [l[uv_layer].pin_uv for l in hist.loops] - seams = [l.edge.seam for l in hist.loops] - dest_uvs.append(uvs) - dest_pin_uvs.append(pin_uvs) - dest_seams.append(seams) - if not dest_uvs or not dest_pin_uvs: - self.report({'WARNING'}, "No faces are selected") - return {'CANCELLED'} - if self.strategy == 'N_N' and len(props.src_uvs) != len(dest_uvs): - self.report( - {'WARNING'}, - "Number of selected faces is different from copied faces " + - "(src:%d, dest:%d)" - % (len(props.src_uvs), len(dest_uvs))) - return {'CANCELLED'} + dest_face_count = 0 + dest_info = {} + for layer in uv_layers: + face_info = [] + for hist in bm.select_history: + if isinstance(hist, bmesh.types.BMFace) and hist.select: + info = { + "uvs": [l[layer].uv.copy() for l in hist.loops], + } + face_info.append(info) + if not face_info: + self.report({'WARNING'}, "No faces are selected") + return {'CANCELLED'} + key = list(props.src_info.keys())[0] + src_face_count = len(props.src_info[key]) + dest_face_count = len(face_info) + if self.strategy == 'N_N' and src_face_count != dest_face_count: + self.report( + {'WARNING'}, + "Number of selected faces is different from copied" + + "(src:{}, dest:{})" + .format(src_face_count, dest_face_count)) + return {'CANCELLED'} + dest_info[layer.name] = face_info # paste - for i, idx in enumerate(dest_face_indices): - suv = None - spuv = None - ss = None - duv = None - if self.strategy == 'N_N': - suv = props.src_uvs[i] - spuv = props.src_pin_uvs[i] - ss = props.src_seams[i] - duv = dest_uvs[i] - elif self.strategy == 'N_M': - suv = props.src_uvs[i % len(props.src_uvs)] - spuv = props.src_pin_uvs[i % len(props.src_pin_uvs)] - ss = props.src_seams[i % len(props.src_seams)] - duv = dest_uvs[i] - if len(suv) != len(duv): - self.report({'WARNING'}, "Some faces are different size") - return {'CANCELLED'} - suvs_fr = [uv for uv in suv] - spuvs_fr = [pin_uv for pin_uv in spuv] - ss_fr = [s for s in ss] - # flip UVs - if self.flip_copied_uv is True: - suvs_fr.reverse() - spuvs_fr.reverse() - ss_fr.reverse() - # rotate UVs - for _ in range(self.rotate_copied_uv): - uv = suvs_fr.pop() - pin_uv = spuvs_fr.pop() - s = ss_fr.pop() - suvs_fr.insert(0, uv) - spuvs_fr.insert(0, pin_uv) - ss_fr.insert(0, s) - # paste UVs - for l, suv, spuv, ss in zip(bm.faces[idx].loops, suvs_fr, - spuvs_fr, ss_fr): - l[uv_layer].uv = suv - l[uv_layer].pin_uv = spuv - if self.copy_seams is True: - l.edge.seam = ss + ret = paste_uv(self, bm, props.src_info, dest_info, uv_layers, + self.strategy, self.flip_copied_uv, + self.rotate_copied_uv, self.copy_seams) + if ret: + return {'CANCELLED'} - self.report({'INFO'}, "%d face(s) are copied" % len(dest_uvs)) + self.report({'INFO'}, "{} face(s) are pasted".format(dest_face_count)) bmesh.update_edit_mesh(obj.data) if self.copy_seams is True: @@ -618,29 +643,49 @@ class MUV_CPUVSelSeqPasteUV(bpy.types.Operator): return {'FINISHED'} -class MUV_CPUVSelSeqPasteUVMenu(bpy.types.Menu): +class MenuSelSeqPasteUV(bpy.types.Menu): """ Menu class: Paste UV coordinate by selection sequence """ - bl_idname = "uv.muv_cpuv_selseq_paste_uv_menu" - bl_label = "Paste UV (Selection Sequence)" - bl_description = "Paste UV coordinate by selection sequence" + bl_idname = "uv.muv_copy_paste_uv_menu_selseq_paste_uv" + bl_label = "Paste UV (Selection Sequence) (Menu)" + bl_description = "Menu of Paste UV coordinate by selection sequence" + + @classmethod + def poll(cls, context): + sc = context.scene + props = sc.muv_props.copy_paste_uv_selseq + if not props.src_uvs or not props.src_pin_uvs: + return False + return is_valid_context(context) def draw(self, context): sc = context.scene layout = self.layout # create sub menu obj = context.active_object - bm = bmesh.from_edit_mesh(obj.data) + bm = common.create_bmesh(obj) uv_maps = bm.loops.layers.uv.keys() - ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname, + + ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text="[Default]") - ops.uv_map = "" - ops.copy_seams = sc.muv_cpuv_copy_seams - ops.strategy = sc.muv_cpuv_strategy + ops.uv_map = "__default" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text="[New]") + ops.uv_map = "__new" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + + ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text="[All]") + ops.uv_map = "__all" + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy + for m in uv_maps: - ops = layout.operator(MUV_CPUVSelSeqPasteUV.bl_idname, text=m) + ops = layout.operator(OperatorSelSeqPasteUV.bl_idname, text=m) ops.uv_map = m - ops.copy_seams = sc.muv_cpuv_copy_seams - ops.strategy = sc.muv_cpuv_strategy + ops.copy_seams = sc.muv_copy_paste_uv_copy_seams + ops.strategy = sc.muv_copy_paste_uv_strategy |