diff options
Diffstat (limited to 'uv_magic_uv/op/copy_paste_uv.py')
-rw-r--r-- | uv_magic_uv/op/copy_paste_uv.py | 337 |
1 files changed, 283 insertions, 54 deletions
diff --git a/uv_magic_uv/op/copy_paste_uv.py b/uv_magic_uv/op/copy_paste_uv.py index 23bc8343..f5ff883e 100644 --- a/uv_magic_uv/op/copy_paste_uv.py +++ b/uv_magic_uv/op/copy_paste_uv.py @@ -23,7 +23,6 @@ __status__ = "production" __version__ = "5.2" __date__ = "17 Nov 2018" - import bmesh import bpy.utils from bpy.props import ( @@ -33,26 +32,244 @@ from bpy.props import ( EnumProperty, ) -from ..impl import copy_paste_uv_impl as impl from .. import common from ..utils.bl_class_registry import BlClassRegistry from ..utils.property_class_registry import PropertyClassRegistry - -__all__ = [ - 'Properties', - 'MUV_OT_CopyPasteUV_CopyUV', - 'MUV_MT_CopyPasteUV_CopyUV', - 'MUV_OT_CopyPasteUV_PasteUV', - 'MUV_MT_CopyPasteUV_PasteUV', - 'MUV_OT_CopyPasteUV_SelSeqCopyUV', - 'MUV_MT_CopyPasteUV_SelSeqCopyUV', - 'MUV_OT_CopyPasteUV_SelSeqPasteUV', - 'MUV_MT_CopyPasteUV_SelSeqPasteUV', -] +from ..utils import compatibility as compat + + +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_map): + uv_layers = [] + if 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 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[uv_map]) + ops_obj.report( + {'INFO'}, "Copy UV coordinate (UV map:{})".format(uv_map)) + + return uv_layers + + +def get_paste_uv_layers(ops_obj, obj, bm, src_info, uv_map): + uv_layers = [] + if 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 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 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[uv_map]) + ops_obj.report( + {'INFO'}, "Paste UV coordinate (UV map:{})".format(uv_map)) + + return uv_layers + + +def get_src_face_info(ops_obj, bm, uv_layers, only_select=False): + src_info = {} + for layer in uv_layers: + face_info = [] + for face in bm.faces: + if not only_select or face.select: + info = { + "index": face.index, + "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: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + src_info[layer.name] = face_info + + return src_info + + +def get_dest_face_info(ops_obj, bm, uv_layers, src_info, strategy, + only_select=False): + dest_info = {} + for layer in uv_layers: + face_info = [] + for face in bm.faces: + if not only_select or face.select: + info = { + "index": face.index, + "uvs": [l[layer].uv.copy() for l in face.loops], + } + face_info.append(info) + if not face_info: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + key = list(src_info.keys())[0] + src_face_count = len(src_info[key]) + dest_face_count = len(face_info) + if strategy == 'N_N' and src_face_count != dest_face_count: + ops_obj.report( + {'WARNING'}, + "Number of selected faces is different from copied" + + "(src:{}, dest:{})" + .format(src_face_count, dest_face_count)) + return None + dest_info[layer.name] = face_info + + return dest_info + + +def _get_select_history_src_face_info(ops_obj, bm, uv_layers): + 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 = { + "index": hist.index, + "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: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + src_info[layer.name] = face_info + + return src_info + + +def _get_select_history_dest_face_info(ops_obj, bm, uv_layers, src_info, + strategy): + 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 = { + "index": hist.index, + "uvs": [l[layer].uv.copy() for l in hist.loops], + } + face_info.append(info) + if not face_info: + ops_obj.report({'WARNING'}, "No faces are selected") + return None + key = list(src_info.keys())[0] + src_face_count = len(src_info[key]) + dest_face_count = len(face_info) + if strategy == 'N_N' and src_face_count != dest_face_count: + ops_obj.report( + {'WARNING'}, + "Number of selected faces is different from copied" + + "(src:{}, dest:{})" + .format(src_face_count, dest_face_count)) + return None + dest_info[layer.name] = face_info + + return dest_info + + +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[dinfo["index"]].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 @PropertyClassRegistry() -class Properties: +class _Properties: idname = "copy_paste_uv" @classmethod @@ -103,6 +320,7 @@ class Properties: @BlClassRegistry() +@compat.make_annotations class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate @@ -113,14 +331,14 @@ class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator): bl_description = "Copy UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map: StringProperty(default="__default", 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 impl.is_valid_context(context) + return _is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv @@ -128,12 +346,12 @@ class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) + uv_layers = get_copy_uv_layers(self, bm, self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - src_info = impl.get_src_face_info(self, bm, uv_layers) + src_info = get_src_face_info(self, bm, uv_layers, True) if src_info is None: return {'CANCELLED'} props.src_info = src_info @@ -156,7 +374,7 @@ class MUV_MT_CopyPasteUV_CopyUV(bpy.types.Menu): @classmethod def poll(cls, context): - return impl.is_valid_context(context) + return _is_valid_context(context) def draw(self, context): layout = self.layout @@ -179,6 +397,7 @@ class MUV_MT_CopyPasteUV_CopyUV(bpy.types.Menu): @BlClassRegistry() +@compat.make_annotations class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate @@ -189,8 +408,8 @@ class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate" bl_options = {'REGISTER', 'UNDO'} - uv_map: StringProperty(default="__default", options={'HIDDEN'}) - strategy: EnumProperty( + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + strategy = EnumProperty( name="Strategy", description="Paste Strategy", items=[ @@ -199,18 +418,18 @@ class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): ], default="N_M" ) - flip_copied_uv: BoolProperty( + flip_copied_uv = BoolProperty( name="Flip Copied UV", description="Flip Copied UV...", default=False ) - rotate_copied_uv: IntProperty( + rotate_copied_uv = IntProperty( default=0, name="Rotate Copied UV", min=0, max=30 ) - copy_seams: BoolProperty( + copy_seams = BoolProperty( name="Seams", description="Copy Seams", default=True @@ -225,7 +444,7 @@ class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): props = sc.muv_props.copy_paste_uv if not props.src_info: return False - return impl.is_valid_context(context) + return _is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv @@ -236,21 +455,21 @@ class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, - self.uv_map) + uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - dest_info = impl.get_dest_face_info(self, bm, uv_layers, - props.src_info, self.strategy) + dest_info = get_dest_face_info(self, bm, uv_layers, + props.src_info, self.strategy, True) if dest_info is None: return {'CANCELLED'} # paste - ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, - self.strategy, self.flip_copied_uv, - self.rotate_copied_uv, self.copy_seams) + 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'} @@ -259,6 +478,10 @@ class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator): bmesh.update_edit_mesh(obj.data) + if compat.check_version(2, 80, 0) < 0: + if self.copy_seams is True: + obj.data.show_edge_seams = True + return {'FINISHED'} @@ -278,7 +501,7 @@ class MUV_MT_CopyPasteUV_PasteUV(bpy.types.Menu): props = sc.muv_props.copy_paste_uv if not props.src_info: return False - return impl.is_valid_context(context) + return _is_valid_context(context) def draw(self, context): sc = context.scene @@ -314,6 +537,7 @@ class MUV_MT_CopyPasteUV_PasteUV(bpy.types.Menu): @BlClassRegistry() +@compat.make_annotations class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator): """ Operation class: Copy UV coordinate by selection sequence @@ -324,14 +548,14 @@ class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator): bl_description = "Copy UV data by selection sequence" bl_options = {'REGISTER', 'UNDO'} - uv_map: StringProperty(default="__default", 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 impl.is_valid_context(context) + return _is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv_selseq @@ -339,12 +563,12 @@ class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = impl.get_copy_uv_layers(self, bm, self.uv_map) + uv_layers = get_copy_uv_layers(self, bm, self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - src_info = impl.get_select_history_src_face_info(self, bm, uv_layers) + src_info = _get_select_history_src_face_info(self, bm, uv_layers) if src_info is None: return {'CANCELLED'} props.src_info = src_info @@ -367,7 +591,7 @@ class MUV_MT_CopyPasteUV_SelSeqCopyUV(bpy.types.Menu): @classmethod def poll(cls, context): - return impl.is_valid_context(context) + return _is_valid_context(context) def draw(self, context): layout = self.layout @@ -390,6 +614,7 @@ class MUV_MT_CopyPasteUV_SelSeqCopyUV(bpy.types.Menu): @BlClassRegistry() +@compat.make_annotations class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): """ Operation class: Paste UV coordinate by selection sequence @@ -400,8 +625,8 @@ class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): bl_description = "Paste UV coordinate by selection sequence" bl_options = {'REGISTER', 'UNDO'} - uv_map: StringProperty(default="__default", options={'HIDDEN'}) - strategy: EnumProperty( + uv_map = StringProperty(default="__default", options={'HIDDEN'}) + strategy = EnumProperty( name="Strategy", description="Paste Strategy", items=[ @@ -410,18 +635,18 @@ class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): ], default="N_M" ) - flip_copied_uv: BoolProperty( + flip_copied_uv = BoolProperty( name="Flip Copied UV", description="Flip Copied UV...", default=False ) - rotate_copied_uv: IntProperty( + rotate_copied_uv = IntProperty( default=0, name="Rotate Copied UV", min=0, max=30 ) - copy_seams: BoolProperty( + copy_seams = BoolProperty( name="Seams", description="Copy Seams", default=True @@ -436,7 +661,7 @@ class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): props = sc.muv_props.copy_paste_uv_selseq if not props.src_info: return False - return impl.is_valid_context(context) + return _is_valid_context(context) def execute(self, context): props = context.scene.muv_props.copy_paste_uv_selseq @@ -447,22 +672,22 @@ class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): bm = common.create_bmesh(obj) # get UV layer - uv_layers = impl.get_paste_uv_layers(self, obj, bm, props.src_info, - self.uv_map) + uv_layers = get_paste_uv_layers(self, obj, bm, props.src_info, + self.uv_map) if not uv_layers: return {'CANCELLED'} # get selected face - dest_info = impl.get_select_history_dest_face_info(self, bm, uv_layers, - props.src_info, - self.strategy) + dest_info = _get_select_history_dest_face_info(self, bm, uv_layers, + props.src_info, + self.strategy) if dest_info is None: return {'CANCELLED'} # paste - ret = impl.paste_uv(self, bm, props.src_info, dest_info, uv_layers, - self.strategy, self.flip_copied_uv, - self.rotate_copied_uv, self.copy_seams) + 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'} @@ -471,6 +696,10 @@ class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator): bmesh.update_edit_mesh(obj.data) + if compat.check_version(2, 80, 0) < 0: + if self.copy_seams is True: + obj.data.show_edge_seams = True + return {'FINISHED'} @@ -490,7 +719,7 @@ class MUV_MT_CopyPasteUV_SelSeqPasteUV(bpy.types.Menu): props = sc.muv_props.copy_paste_uv_selseq if not props.src_uvs or not props.src_pin_uvs: return False - return impl.is_valid_context(context) + return _is_valid_context(context) def draw(self, context): sc = context.scene |