Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'magic_uv/op/copy_paste_uv_uvedit.py')
-rw-r--r--magic_uv/op/copy_paste_uv_uvedit.py237
1 files changed, 227 insertions, 10 deletions
diff --git a/magic_uv/op/copy_paste_uv_uvedit.py b/magic_uv/op/copy_paste_uv_uvedit.py
index 7055915f..733c30b3 100644
--- a/magic_uv/op/copy_paste_uv_uvedit.py
+++ b/magic_uv/op/copy_paste_uv_uvedit.py
@@ -4,8 +4,8 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.5"
-__date__ = "6 Mar 2021"
+__version__ = "6.6"
+__date__ = "22 Apr 2022"
import math
from math import atan2, sin, cos
@@ -13,13 +13,22 @@ from math import atan2, sin, cos
import bpy
import bmesh
from mathutils import Vector
+from bpy.props import BoolProperty
from .. import common
from ..utils.bl_class_registry import BlClassRegistry
from ..utils.property_class_registry import PropertyClassRegistry
+from ..utils.graph import graph_is_isomorphic
+from ..utils import compatibility as compat
def _is_valid_context(context):
+ # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
+ # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
+ # after the execution
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
+ return False
+
# Multiple objects editing mode is not supported in this feature.
objs = common.get_uv_editable_objects(context)
if len(objs) != 1:
@@ -29,12 +38,6 @@ def _is_valid_context(context):
if context.object.mode != 'EDIT':
return False
- # 'IMAGE_EDITOR' and 'VIEW_3D' space is allowed to execute.
- # If 'View_3D' space is not allowed, you can't find option in Tool-Shelf
- # after the execution
- if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
- return False
-
return True
@@ -44,14 +47,33 @@ class _Properties:
@classmethod
def init_props(cls, scene):
- class Props():
+ class CopyPastUVProps():
src_uvs = None
- scene.muv_props.copy_paste_uv_uvedit = Props()
+ class CopyPasteUVIslandProps():
+ # [
+ # {
+ # "bmesh": BMesh,
+ # "uv_layer": UV Layer,
+ # "island": UV Island,
+ # }
+ # ]
+ src_data = []
+ src_objects = []
+
+ scene.muv_props.copy_paste_uv_uvedit = CopyPastUVProps()
+ scene.muv_props.copy_paste_uv_island = CopyPasteUVIslandProps()
+
+ scene.muv_copy_paste_uv_uvedit_unique_target = BoolProperty(
+ name="Unique Target",
+ description="Paste to the target uniquely",
+ default=False
+ )
@classmethod
def del_props(cls, scene):
del scene.muv_props.copy_paste_uv_uvedit
+ del scene.muv_props.copy_paste_uv_island
@BlClassRegistry()
@@ -182,3 +204,198 @@ class MUV_OT_CopyPasteUVUVEdit_PasteUV(bpy.types.Operator):
bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
+
+
+# Return selected/all count.
+# If context.tool_settings.use_uv_select_sync is enabled:
+# Return selected/all face count.
+# If context.tool_settings.use_uv_select_sync is disabled:
+# Return selected/all loop count.
+def get_counts(context, island, uv_layer):
+ selected_count = 0
+ all_count = 0
+ if context.tool_settings.use_uv_select_sync:
+ for f in island["faces"]:
+ all_count += 1
+ if f["face"].select:
+ selected_count += 1
+ else:
+ for f in island["faces"]:
+ for l in f["face"].loops:
+ all_count += 1
+ if l[uv_layer].select:
+ selected_count += 1
+
+ return selected_count, all_count
+
+
+@BlClassRegistry()
+class MUV_OT_CopyPasteUVUVEdit_CopyUVIsland(bpy.types.Operator):
+ """
+ Operation class: Copy UV island on UV/Image Editor
+ """
+
+ bl_idname = "uv.muv_copy_paste_uv_uvedit_copy_uv_island"
+ bl_label = "Copy UV Island (UV/Image Editor)"
+ bl_description = "Copy UV island (only selected in UV/Image Editor)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @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):
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_island
+
+ props.src_data = []
+ props.src_objects = []
+ objs = common.get_uv_editable_objects(context)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ if context.tool_settings.use_uv_select_sync:
+ islands = common.get_island_info_from_bmesh(
+ bm, only_selected=False)
+ else:
+ islands = common.get_island_info_from_bmesh(
+ bm, only_selected=True)
+ for isl in islands:
+ # Check if all UVs belonging to the island is selected.
+ selected_count, all_count = get_counts(context, isl, uv_layer)
+ if selected_count == 0:
+ continue
+ if selected_count != all_count:
+ self.report(
+ {'WARNING'},
+ "All UVs belonging to the island must be selected")
+ return {'CANCELLED'}
+
+ data = {
+ "bmesh": bm,
+ "uv_layer": uv_layer,
+ "island": isl
+ }
+ props.src_data.append(data)
+ props.src_objects.append(obj)
+
+ return {'FINISHED'}
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_CopyPasteUVUVEdit_PasteUVIsland(bpy.types.Operator):
+ """
+ Operation class: Paste UV island on UV/Image Editor
+ """
+
+ bl_idname = "uv.muv_copy_paste_uv_uvedit_paste_uv_island"
+ bl_label = "Paste UV Island (UV/Image Editor)"
+ bl_description = "Paste UV island (only selected in UV/Image Editor)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ unique_target = BoolProperty(
+ name="Unique Target",
+ description="Paste to the target uniquely",
+ default=False
+ )
+
+ @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_island
+ if not props.src_data:
+ return False
+ return _is_valid_context(context)
+
+ def execute(self, context):
+ sc = context.scene
+ props = sc.muv_props.copy_paste_uv_island
+
+ src_data = props.src_data
+ src_objs = props.src_objects
+
+ bms_and_uv_layers = {}
+ for d in src_data:
+ bms_and_uv_layers[d["bmesh"]] = d["uv_layer"]
+ dst_data = []
+ for bm, uv_layer in bms_and_uv_layers.items():
+ if context.tool_settings.use_uv_select_sync:
+ islands = common.get_island_info_from_bmesh(
+ bm, only_selected=False)
+ else:
+ islands = common.get_island_info_from_bmesh(
+ bm, only_selected=True)
+ for isl in islands:
+ # Check if all UVs belonging to the island is selected.
+ selected_count, all_count = get_counts(context, isl, uv_layer)
+ if selected_count == 0:
+ continue
+ if selected_count != all_count:
+ self.report(
+ {'WARNING'},
+ "All UVs belonging to the island must be selected")
+ return {'CANCELLED'}
+
+ dst_data.append(
+ {
+ "bm": bm,
+ "uv_layer": uv_layer,
+ "island": isl,
+ }
+ )
+
+ used = []
+ for ddata in dst_data:
+ dst_loops = []
+ for f in ddata["island"]["faces"]:
+ for l in f["face"].loops:
+ dst_loops.append(l)
+ dst_uv_layer = ddata["uv_layer"]
+
+ # Find a suitable island.
+ for sdata in src_data:
+ if self.unique_target and sdata in used:
+ continue
+
+ src_loops = []
+ for f in sdata["island"]["faces"]:
+ for l in f["face"].loops:
+ src_loops.append(l)
+ src_uv_layer = sdata["uv_layer"]
+
+ # Create UV graph.
+ src_uv_graph = common.create_uv_graph(src_loops, src_uv_layer)
+ dst_uv_graph = common.create_uv_graph(dst_loops, dst_uv_layer)
+
+ # Check if the graph is isomorphic.
+ # If the graph is isomorphic, matching pair is returned.
+ result, pairs = graph_is_isomorphic(src_uv_graph, dst_uv_graph)
+ if result:
+ # Paste UV island.
+ for n1, n2 in pairs.items():
+ uv1 = n1.value["uv_vert"][src_uv_layer].uv
+ l2 = n2.value["loops"]
+ for l in l2:
+ l[dst_uv_layer].uv = uv1
+ used.append(sdata)
+ break
+ else:
+ self.report({'WARNING'}, "Island does not match")
+ return {'CANCELLED'}
+
+ for obj in src_objs:
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}