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:
authornutti <nutti.metro@gmail.com>2020-10-23 14:18:09 +0300
committernutti <nutti.metro@gmail.com>2020-10-23 14:18:09 +0300
commit40e34a8bbb30c6a4ddc73aa11e92c6bd2095eb80 (patch)
tree5c46bb7af6381407596cab713f21dede0aa65f27 /magic_uv
parentec3c96a37e2daddd5ca9f652c4b018b00a3acc53 (diff)
Magic UV: Release v6.4
* Support multiple objects editing mode * Add snap to point/edge features * Fix bugs
Diffstat (limited to 'magic_uv')
-rw-r--r--magic_uv/__init__.py10
-rw-r--r--magic_uv/common.py59
-rw-r--r--magic_uv/lib/__init__.py4
-rw-r--r--magic_uv/op/__init__.py4
-rw-r--r--magic_uv/op/align_uv.py744
-rw-r--r--magic_uv/op/align_uv_cursor.py107
-rw-r--r--magic_uv/op/clip_uv.py157
-rw-r--r--magic_uv/op/copy_paste_uv.py60
-rw-r--r--magic_uv/op/copy_paste_uv_object.py41
-rw-r--r--magic_uv/op/copy_paste_uv_uvedit.py28
-rw-r--r--magic_uv/op/flip_rotate_uv.py95
-rw-r--r--magic_uv/op/mirror_uv.py107
-rw-r--r--magic_uv/op/move_uv.py22
-rw-r--r--magic_uv/op/pack_uv.py75
-rw-r--r--magic_uv/op/preserve_uv_aspect.py358
-rw-r--r--magic_uv/op/select_uv.py48
-rw-r--r--magic_uv/op/smooth_uv.py52
-rw-r--r--magic_uv/op/texture_lock.py299
-rw-r--r--magic_uv/op/texture_projection.py141
-rw-r--r--magic_uv/op/texture_wrap.py28
-rw-r--r--magic_uv/op/transfer_uv.py38
-rw-r--r--magic_uv/op/unwrap_constraint.py81
-rw-r--r--magic_uv/op/uv_bounding_box.py74
-rw-r--r--magic_uv/op/uv_inspection.py292
-rw-r--r--magic_uv/op/uv_sculpt.py339
-rw-r--r--magic_uv/op/uvw.py45
-rw-r--r--magic_uv/op/world_scale_uv.py321
-rw-r--r--magic_uv/preferences.py4
-rw-r--r--magic_uv/properites.py4
-rw-r--r--magic_uv/ui/IMAGE_MT_uvs.py20
-rw-r--r--magic_uv/ui/VIEW3D_MT_object.py4
-rw-r--r--magic_uv/ui/VIEW3D_MT_uv_map.py4
-rw-r--r--magic_uv/ui/__init__.py4
-rw-r--r--magic_uv/ui/uvedit_copy_paste_uv.py4
-rw-r--r--magic_uv/ui/uvedit_editor_enhancement.py4
-rw-r--r--magic_uv/ui/uvedit_uv_manipulation.py65
-rw-r--r--magic_uv/ui/view3d_copy_paste_uv_editmode.py4
-rw-r--r--magic_uv/ui/view3d_copy_paste_uv_objectmode.py4
-rw-r--r--magic_uv/ui/view3d_uv_manipulation.py4
-rw-r--r--magic_uv/ui/view3d_uv_mapping.py4
-rw-r--r--magic_uv/updater.py4
-rw-r--r--magic_uv/utils/__init__.py4
-rw-r--r--magic_uv/utils/addon_updater.py4
-rw-r--r--magic_uv/utils/bl_class_registry.py4
-rw-r--r--magic_uv/utils/compatibility.py17
-rw-r--r--magic_uv/utils/property_class_registry.py4
46 files changed, 2315 insertions, 1480 deletions
diff --git a/magic_uv/__init__.py b/magic_uv/__init__.py
index 0df3d49f..e9c95f24 100644
--- a/magic_uv/__init__.py
+++ b/magic_uv/__init__.py
@@ -20,21 +20,23 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
bl_info = {
"name": "Magic UV",
"author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs"
"Keith (Wahooney) Boshoff, McBuff, MaxRobinot, "
- "Alexander Milovsky, Dusan Stevanovic",
- "version": (6, 3, 0),
+ "Alexander Milovsky, Dusan Stevanovic, MatthiasThDs",
+ "version": (6, 4, 0),
"blender": (2, 80, 0),
"location": "See Add-ons Preferences",
"description": "UV Toolset. See Add-ons Preferences for details",
"warning": "",
"support": "COMMUNITY",
+ "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
+ "uv/magic_uv.html",
"doc_url": "{BLENDER_MANUAL_URL}/addons/uv/magic_uv.html",
"tracker_url": "https://github.com/nutti/Magic-UV",
"category": "UV",
diff --git a/magic_uv/common.py b/magic_uv/common.py
index 11696667..3817486c 100644
--- a/magic_uv/common.py
+++ b/magic_uv/common.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from collections import defaultdict
from pprint import pprint
@@ -44,6 +44,14 @@ def is_console_mode():
return os.environ["MUV_CONSOLE_MODE"] == "true"
+def is_valid_space(context, allowed_spaces):
+ for area in context.screen.areas:
+ for space in area.spaces:
+ if space.type in allowed_spaces:
+ return True
+ return False
+
+
def is_debug_mode():
return __DEBUG_MODE
@@ -422,23 +430,30 @@ def find_texture_layer(bm):
return bm.faces.layers.tex.verify()
-def find_texture_nodes(obj):
+def find_texture_nodes_from_material(mtrl):
nodes = []
- for mat in obj.material_slots:
- if not mat.material:
+ if not mtrl.node_tree:
+ return nodes
+ for node in mtrl.node_tree.nodes:
+ tex_node_types = [
+ 'TEX_ENVIRONMENT',
+ 'TEX_IMAGE',
+ ]
+ if node.type not in tex_node_types:
continue
- if not mat.material.node_tree:
+ if not node.image:
continue
- for node in mat.material.node_tree.nodes:
- tex_node_types = [
- 'TEX_ENVIRONMENT',
- 'TEX_IMAGE',
- ]
- if node.type not in tex_node_types:
- continue
- if not node.image:
- continue
- nodes.append(node)
+ nodes.append(node)
+
+ return nodes
+
+
+def find_texture_nodes(obj):
+ nodes = []
+ for slot in obj.material_slots:
+ if not slot.material:
+ continue
+ nodes.extend(find_texture_nodes_from_material(slot.material))
return nodes
@@ -1166,6 +1181,18 @@ def __is_points_in_polygon(points, subject_points):
return True
+def get_uv_editable_objects(context):
+ if compat.check_version(2, 80, 0) < 0:
+ objs = [context.active_object]
+ else:
+ objs = [o for o in bpy.data.objects
+ if compat.get_object_select(o) and o.type == 'MESH']
+ objs.append(context.active_object)
+
+ objs = list(set(objs))
+ return objs
+
+
def get_overlapped_uv_info(bm_list, faces_list, uv_layer_list, mode):
# at first, check island overlapped
isl = []
diff --git a/magic_uv/lib/__init__.py b/magic_uv/lib/__init__.py
index 5e06552d..8bed4656 100644
--- a/magic_uv/lib/__init__.py
+++ b/magic_uv/lib/__init__.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
if "bpy" in locals():
import importlib
diff --git a/magic_uv/op/__init__.py b/magic_uv/op/__init__.py
index b7316192..459a37e0 100644
--- a/magic_uv/op/__init__.py
+++ b/magic_uv/op/__init__.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
if "bpy" in locals():
import importlib
diff --git a/magic_uv/op/align_uv.py b/magic_uv/op/align_uv.py
index 77afcc25..cb68bf25 100644
--- a/magic_uv/op/align_uv.py
+++ b/magic_uv/op/align_uv.py
@@ -20,14 +20,19 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import math
from math import atan2, tan, sin, cos
import bpy
-from bpy.props import EnumProperty, BoolProperty, FloatProperty
+from bpy.props import (
+ EnumProperty,
+ BoolProperty,
+ FloatProperty,
+ FloatVectorProperty,
+)
import bmesh
from mathutils import Vector
@@ -39,23 +44,18 @@ from .. import common
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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
# '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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -339,6 +339,66 @@ class _Properties:
default='MIDDLE'
)
+ scene.muv_align_uv_snap_method = EnumProperty(
+ name="Snap Method",
+ description="Snap method",
+ items=[
+ ('POINT', "Point", "Snap to point"),
+ ('EDGE', "Edge", "Snap to edge"),
+ ],
+ default='POINT'
+ )
+ scene.muv_align_uv_snap_point_group = EnumProperty(
+ name="Snap Group (Point)",
+ description="Group that snap (point) operation applies for",
+ items=[
+ ('VERT', "Vertex", "Vertex"),
+ ('FACE', "Face", "Face"),
+ ('UV_ISLAND', "UV Island", "UV Island"),
+ ],
+ default='FACE'
+ )
+ scene.muv_align_uv_snap_point_target = FloatVectorProperty(
+ name="Snap Target (Point)",
+ description="Target point where UV vertices snap to",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ )
+ scene.muv_align_uv_snap_edge_group = EnumProperty(
+ name="Snap Group (Edge)",
+ description="Group that snap (edge) operation applies for",
+ items=[
+ ('EDGE', "Edge", "Edge"),
+ ('FACE', "Face", "Face"),
+ ('UV_ISLAND', "UV Island", "UV Island"),
+ ],
+ default='FACE'
+ )
+ scene.muv_align_uv_snap_edge_target_1 = FloatVectorProperty(
+ name="Snap Target (Edge)",
+ description="Target edge where UV vertices snap to",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ )
+ scene.muv_align_uv_snap_edge_target_2 = FloatVectorProperty(
+ name="Snap Target (Edge)",
+ description="Target edge where UV vertices snap to",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ )
+
@classmethod
def del_props(cls, scene):
del scene.muv_align_uv_enabled
@@ -348,6 +408,12 @@ class _Properties:
del scene.muv_align_uv_horizontal
del scene.muv_align_uv_mesh_infl
del scene.muv_align_uv_location
+ del scene.muv_align_uv_snap_method
+ del scene.muv_align_uv_snap_point_group
+ del scene.muv_align_uv_snap_point_target
+ del scene.muv_align_uv_snap_edge_group
+ del scene.muv_align_uv_snap_edge_target_1
+ del scene.muv_align_uv_snap_edge_target_2
@BlClassRegistry()
@@ -378,77 +444,90 @@ class MUV_OT_AlignUV_Circle(bpy.types.Operator):
return _is_valid_context(context)
def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- uv_layer = bm.loops.layers.uv.verify()
-
- # loop_seqs[horizontal][vertical][loop]
- loop_seqs, error = common.get_loop_sequences(bm, uv_layer, True)
- if not loop_seqs:
- self.report({'WARNING'}, error)
- return {'CANCELLED'}
-
- # get circle and new UVs
- uvs = [hseq[0][0][uv_layer].uv.copy() for hseq in loop_seqs]
- c, r = _get_circle(uvs[0:3])
- new_uvs = _calc_v_on_circle(uvs, c, r)
-
- # check if center is identical
- center_is_identical = False
- center = loop_seqs[0][-1][0].vert
- if (len(loop_seqs[0][-1]) == 1) and loop_seqs[0][-1][0].vert == center:
- center_is_identical = True
-
- # check if topology is correct
- if center_is_identical:
- for hseq in loop_seqs[1:]:
- if len(hseq[-1]) != 1:
- self.report({'WARNING'}, "Last face must be triangle")
- return {'CANCELLED'}
- if hseq[-1][0].vert != center:
- self.report({'WARNING'}, "Center must be identical")
- return {'CANCELLED'}
- else:
- for hseq in loop_seqs[1:]:
- if len(hseq[-1]) == 1:
- self.report({'WARNING'}, "Last face must not be triangle")
- return {'CANCELLED'}
- if hseq[-1][0].vert == center:
- self.report({'WARNING'}, "Center must not be identical")
- return {'CANCELLED'}
-
- # align to circle
- if self.transmission:
- for hidx, hseq in enumerate(loop_seqs):
- for vidx, pair in enumerate(hseq):
- all_ = int((len(hseq) + 1) / 2)
- if center_is_identical:
- r = (all_ - int((vidx + 1) / 2)) / all_
- else:
- r = (1 + all_ - int((vidx + 1) / 2)) / all_
- pair[0][uv_layer].uv = c + (new_uvs[hidx] - c) * r
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = common.get_loop_sequences(bm, uv_layer, True)
+ if not loop_seqs:
+ self.report({'WARNING'},
+ "Object {}: {}".format(obj.name, error))
+ return {'CANCELLED'}
+
+ # get circle and new UVs
+ uvs = [hseq[0][0][uv_layer].uv.copy() for hseq in loop_seqs]
+ c, r = _get_circle(uvs[0:3])
+ new_uvs = _calc_v_on_circle(uvs, c, r)
+
+ # check if center is identical
+ center_is_identical = False
+ center = loop_seqs[0][-1][0].vert
+ if (len(loop_seqs[0][-1]) == 1) and \
+ loop_seqs[0][-1][0].vert == center:
+ center_is_identical = True
+
+ # check if topology is correct
+ if center_is_identical:
+ for hseq in loop_seqs[1:]:
+ if len(hseq[-1]) != 1:
+ self.report({'WARNING'},
+ "Object {}: Last face must be triangle"
+ .format(obj.name))
+ return {'CANCELLED'}
+ if hseq[-1][0].vert != center:
+ self.report({'WARNING'},
+ "Object {}: Center must be identical"
+ .format(obj.name))
+ return {'CANCELLED'}
+ else:
+ for hseq in loop_seqs[1:]:
+ if len(hseq[-1]) == 1:
+ self.report({'WARNING'},
+ "Object {}: Last face must not be triangle"
+ .format(obj.name))
+ return {'CANCELLED'}
+ if hseq[-1][0].vert == center:
+ self.report({'WARNING'},
+ "Object {}: Center must not be identical"
+ .format(obj.name))
+ return {'CANCELLED'}
+
+ # align to circle
+ if self.transmission:
+ for hidx, hseq in enumerate(loop_seqs):
+ for vidx, pair in enumerate(hseq):
+ all_ = int((len(hseq) + 1) / 2)
+ if center_is_identical:
+ r = (all_ - int((vidx + 1) / 2)) / all_
+ else:
+ r = (1 + all_ - int((vidx + 1) / 2)) / all_
+ pair[0][uv_layer].uv = c + (new_uvs[hidx] - c) * r
+ if self.select:
+ pair[0][uv_layer].select = True
+
+ if len(pair) < 2:
+ continue
+ # for quad polygon
+ next_hidx = (hidx + 1) % len(loop_seqs)
+ pair[1][uv_layer].uv = \
+ c + ((new_uvs[next_hidx]) - c) * r
+ if self.select:
+ pair[1][uv_layer].select = True
+ else:
+ for hidx, hseq in enumerate(loop_seqs):
+ pair = hseq[0]
+ pair[0][uv_layer].uv = new_uvs[hidx]
+ pair[1][uv_layer].uv = new_uvs[(hidx + 1) % len(loop_seqs)]
if self.select:
pair[0][uv_layer].select = True
-
- if len(pair) < 2:
- continue
- # for quad polygon
- next_hidx = (hidx + 1) % len(loop_seqs)
- pair[1][uv_layer].uv = c + ((new_uvs[next_hidx]) - c) * r
- if self.select:
pair[1][uv_layer].select = True
- else:
- for hidx, hseq in enumerate(loop_seqs):
- pair = hseq[0]
- pair[0][uv_layer].uv = new_uvs[hidx]
- pair[1][uv_layer].uv = new_uvs[(hidx + 1) % len(loop_seqs)]
- if self.select:
- pair[0][uv_layer].select = True
- pair[1][uv_layer].select = True
- bmesh.update_edit_mesh(obj.data)
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
@@ -585,22 +664,25 @@ class MUV_OT_AlignUV_Straighten(bpy.types.Operator):
self.__align_wo_transmission(loop_seqs, uv_layer)
def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- uv_layer = bm.loops.layers.uv.verify()
-
- # loop_seqs[horizontal][vertical][loop]
- loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
- if not loop_seqs:
- self.report({'WARNING'}, error)
- return {'CANCELLED'}
+ objs = common.get_uv_editable_objects(context)
- # align
- self.__align(loop_seqs, uv_layer)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
- bmesh.update_edit_mesh(obj.data)
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
+ if not loop_seqs:
+ self.report({'WARNING'},
+ "Object {}: {}".format(obj.name, error))
+ return {'CANCELLED'}
+
+ # align
+ self.__align(loop_seqs, uv_layer)
+
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
@@ -982,25 +1064,469 @@ class MUV_OT_AlignUV_Axis(bpy.types.Operator):
width, height)
def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- uv_layer = bm.loops.layers.uv.verify()
-
- # loop_seqs[horizontal][vertical][loop]
- loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
- if not loop_seqs:
- self.report({'WARNING'}, error)
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
+ if not loop_seqs:
+ self.report({'WARNING'},
+ "Object {}: {}".format(obj.name, error))
+ return {'CANCELLED'}
+
+ # get height and width
+ uv_max, uv_min = self.__get_uv_max_min(loop_seqs, uv_layer)
+ width = uv_max.x - uv_min.x
+ height = uv_max.y - uv_min.y
+
+ self.__align(loop_seqs, uv_layer, uv_min, width, height)
+
+ bmesh.update_edit_mesh(obj.data)
+
+ return {'FINISHED'}
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_AlignUV_SnapToPoint(bpy.types.Operator):
+
+ bl_idname = "uv.muv_align_uv_snap_to_point"
+ bl_label = "Align UV (Snap to Point)"
+ bl_description = "Align UV to the target point"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ group = EnumProperty(
+ name="Snap Group",
+ description="Group that snap operation applies for",
+ items=[
+ ('VERT', "Vertex", "Vertex"),
+ ('FACE', "Face", "Face"),
+ ('UV_ISLAND', "UV Island", "UV Island"),
+ ],
+ default='FACE'
+ )
+ target = FloatVectorProperty(
+ name="Snap Target",
+ description="Target where UV vertices snap to",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ )
+
+ def _get_snap_target_loops(self, context, bm, uv_layer):
+ target_loops = []
+
+ selected_faces = [f for f in bm.faces if f.select]
+
+ # Process snap operation.
+ for face in selected_faces:
+ for l in face.loops:
+ if context.tool_settings.use_uv_select_sync or \
+ l[uv_layer].select:
+ target_loops.append(l)
+
+ return target_loops
+
+ def _get_snap_target_faces(self, context, bm, uv_layer):
+ target_faces = []
+
+ selected_faces = [f for f in bm.faces if f.select]
+
+ for face in selected_faces:
+ for l in face.loops:
+ if not context.tool_settings.use_uv_select_sync and \
+ not l[uv_layer].select:
+ break
+ else:
+ target_faces.append(face)
+
+ return target_faces
+
+ def _get_snap_target_islands(self, context, bm, uv_layer):
+ target_islands = []
+
+ islands = common.get_island_info_from_bmesh(bm, only_selected=True)
+
+ for isl in islands:
+ some_verts_not_selected = False
+ for face in isl["faces"]:
+ for l in face["face"].loops:
+ if not context.tool_settings.use_uv_select_sync and \
+ not l[uv_layer].select:
+ some_verts_not_selected = True
+ break
+ if not some_verts_not_selected:
+ target_islands.append(isl)
+
+ return target_islands
+
+ def execute(self, context):
+ objs = common.get_uv_editable_objects(context)
+
+ group_to_reason = {
+ 'VERT': "Vertex",
+ 'FACE': "Face",
+ 'UV_ISLAND': "UV Island",
+ }
+ no_selection_reason = group_to_reason[self.group]
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if self.group == 'VERT':
+ target_loops = \
+ self._get_snap_target_loops(context, bm, uv_layer)
+
+ # Process snap operation.
+ for l in target_loops:
+ l[uv_layer].uv = self.target
+ no_selection_reason = None
+
+ elif self.group == 'FACE':
+ target_faces = \
+ self._get_snap_target_faces(context, bm, uv_layer)
+
+ for face in target_faces:
+ ave_uv = Vector((0.0, 0.0))
+ for l in face.loops:
+ ave_uv += l[uv_layer].uv
+ ave_uv /= len(face.loops)
+ diff = Vector(self.target) - ave_uv
+
+ # Process snap operation.
+ for l in face.loops:
+ l[uv_layer].uv += diff
+ no_selection_reason = None
+
+ elif self.group == 'UV_ISLAND':
+ target_islands = \
+ self._get_snap_target_islands(context, bm, uv_layer)
+
+ for isl in target_islands:
+ ave_uv = Vector((0.0, 0.0))
+ count = 0
+ for face in isl["faces"]:
+ for l in face["face"].loops:
+ ave_uv += l[uv_layer].uv
+ count += 1
+ if count != 0:
+ ave_uv /= count
+ diff = Vector(self.target) - ave_uv
+
+ # Process snap operation.
+ for face in isl["faces"]:
+ for l in face["face"].loops:
+ l[uv_layer].uv += diff
+ no_selection_reason = None
+
+ bmesh.update_edit_mesh(obj.data)
+
+ if no_selection_reason:
+ self.report(
+ {'WARNING'},
+ "Must select more than 1 {}.".format(no_selection_reason)
+ )
return {'CANCELLED'}
- # get height and width
- uv_max, uv_min = self.__get_uv_max_min(loop_seqs, uv_layer)
- width = uv_max.x - uv_min.x
- height = uv_max.y - uv_min.y
+ return {'FINISHED'}
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_AlignUV_Snap_SetPointTargetToCursor(bpy.types.Operator):
+
+ bl_idname = "uv.muv_align_uv_snap_set_point_target_to_cursor"
+ bl_label = "Set Point Target to Cursor"
+ bl_description = """Set point target to the cursor for
+ 'Align UV (Snap to Point)'"""
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ sc = context.scene
+
+ _, _, space = common.get_space('IMAGE_EDITOR', 'WINDOW',
+ 'IMAGE_EDITOR')
+ cursor_loc = space.cursor_location
+
+ sc.muv_align_uv_snap_point_target = cursor_loc
+
+ return {'FINISHED'}
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_AlignUV_Snap_SetPointTargetToVertexGroup(bpy.types.Operator):
+
+ bl_idname = "uv.muv_align_uv_snap_set_point_target_to_vertex_group"
+ bl_label = "Set Point Target to Vertex Group"
+ bl_description = """Set point target to the average of vertices for
+ 'Align UV (Snap to Point)'"""
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ sc = context.scene
+ objs = common.get_uv_editable_objects(context)
+
+ ave_uv = Vector((0.0, 0.0))
+ count = 0
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ selected_faces = [f for f in bm.faces if f.select]
+ for face in selected_faces:
+ for l in face.loops:
+ if context.tool_settings.use_uv_select_sync or \
+ l[uv_layer].select:
+ ave_uv += l[uv_layer].uv
+ count += 1
+ if count != 0:
+ ave_uv /= count
+
+ sc.muv_align_uv_snap_point_target = ave_uv
+
+ return {'FINISHED'}
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_AlignUV_SnapToEdge(bpy.types.Operator):
- self.__align(loop_seqs, uv_layer, uv_min, width, height)
+ bl_idname = "uv.muv_align_uv_snap_to_edge"
+ bl_label = "Align UV (Snap to Edge)"
+ bl_description = "Align UV to the target edge"
+ bl_options = {'REGISTER', 'UNDO'}
- bmesh.update_edit_mesh(obj.data)
+ group = EnumProperty(
+ name="Snap Group",
+ description="Group that snap operation applies for",
+ items=[
+ ('EDGE', "Edge", "Edge"),
+ ('FACE', "Face", "Face"),
+ ('UV_ISLAND', "UV Island", "UV Island"),
+ ],
+ default='FACE'
+ )
+ target_1 = FloatVectorProperty(
+ name="Snap Target 1",
+ description="Vertex 1 of the target edge",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ )
+ target_2 = FloatVectorProperty(
+ name="Snap Target 2",
+ description="Vertex 2 of the target edge",
+ size=2,
+ precision=4,
+ soft_min=-1.0,
+ soft_max=1.0,
+ step=1,
+ default=(0.000, 0.000),
+ )
+
+ def _calc_snap_move_amount(self, loops, uv_layer):
+ ave = (loops[0][uv_layer].uv + loops[1][uv_layer].uv) / 2
+ target = (Vector(self.target_1) + Vector(self.target_2)) / 2
+
+ return target - ave
+
+ def _get_snap_target_loop_pairs(self, bm, uv_layer):
+ target_loop_pairs = []
+
+ selected_edges = [e for e in bm.edges if e.select]
+
+ cand_loops = []
+ for edge in selected_edges:
+ for l in edge.link_loops:
+ if l[uv_layer].select:
+ cand_loops.append(l)
+
+ for l in cand_loops:
+ if l[uv_layer].select and l.link_loop_next[uv_layer].select:
+ d = {l, l.link_loop_next}
+ if d not in target_loop_pairs:
+ assert l.face == l.link_loop_next.face
+ target_loop_pairs.append(d)
+
+ return target_loop_pairs
+
+ def _find_target_island_from_face(self, islands, face):
+ for isl in islands:
+ for f in isl["faces"]:
+ if f["face"] == face:
+ return isl
+
+ return None
+
+ def execute(self, context):
+ objs = common.get_uv_editable_objects(context)
+
+ no_selection = True
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if self.group == 'EDGE':
+ target_loop_pairs = \
+ self._get_snap_target_loop_pairs(bm, uv_layer)
+
+ for pair in target_loop_pairs:
+ p = list(pair)
+ diff = self._calc_snap_move_amount(p, uv_layer)
+
+ # Process snap operation.
+ p[0][uv_layer].uv += diff
+ p[1][uv_layer].uv += diff
+
+ no_selection = False
+
+ elif self.group == 'FACE':
+ target_loop_pairs = \
+ self._get_snap_target_loop_pairs(bm, uv_layer)
+
+ face_processed = []
+ for pair in target_loop_pairs:
+ p = list(pair)
+ diff = self._calc_snap_move_amount(p, uv_layer)
+
+ # Process snap operation.
+ face = p[0].face
+ if face in face_processed:
+ self.report(
+ {'WARNING'},
+ "Must select only one edge per face. (Object: {})"
+ .format(obj.name)
+ )
+ return {'CANCELLED'}
+ face_processed.append(face)
+ for l in face.loops:
+ l[uv_layer].uv += diff
+ no_selection = False
+
+ elif self.group == 'UV_ISLAND':
+ target_loop_pairs = \
+ self._get_snap_target_loop_pairs(bm, uv_layer)
+
+ islands = common.get_island_info_from_bmesh(
+ bm, only_selected=False)
+
+ isl_processed = []
+ for pair in target_loop_pairs:
+ p = list(pair)
+ diff = self._calc_snap_move_amount(p, uv_layer)
+
+ # Find island to process.
+ face = p[0].face
+ target_isl = \
+ self._find_target_island_from_face(islands, face)
+ if target_isl is None:
+ self.report(
+ {'WARNING'},
+ "Failed to find island. (Object: {})"
+ .format(obj.name)
+ )
+ return {'CANCELLED'}
+ if target_isl in isl_processed:
+ self.report(
+ {'WARNING'},
+ """Must select only one edge per island.
+ (Object: {})"""
+ .format(obj.name)
+ )
+ return {'CANCELLED'}
+ isl_processed.append(target_isl)
+
+ # Process snap operation.
+ for f in target_isl["faces"]:
+ for l in f["face"].loops:
+ l[uv_layer].uv += diff
+ no_selection = False
+
+ bmesh.update_edit_mesh(obj.data)
+
+ if no_selection:
+ self.report({'WARNING'}, "Must select more than 1 Edge.")
+ return {'CANCELLED'}
+
+ return {'FINISHED'}
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_AlignUV_Snap_SetEdgeTargetToEdgeCenter(bpy.types.Operator):
+
+ bl_idname = "uv.muv_align_uv_snap_set_edge_target_to_edge_center"
+ bl_label = "Set Edge Target to Edge Center"
+ bl_description = """Set edge target to the center of edge for
+ 'Align UV (Snap to Edge)'"""
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def _get_target_loop_pairs(self, bm, uv_layer):
+ target_loop_pairs = []
+
+ selected_edges = [e for e in bm.edges if e.select]
+
+ cand_loops = []
+ for edge in selected_edges:
+ for l in edge.link_loops:
+ if l[uv_layer].select:
+ cand_loops.append(l)
+
+ for l in cand_loops:
+ if l[uv_layer].select and l.link_loop_next[uv_layer].select:
+ d = {l, l.link_loop_next}
+ if d not in target_loop_pairs:
+ assert l.face == l.link_loop_next.face
+ target_loop_pairs.append(d)
+
+ return target_loop_pairs
+
+ def execute(self, context):
+ sc = context.scene
+ objs = common.get_uv_editable_objects(context)
+
+ ave_uv_1 = Vector((0.0, 0.0))
+ ave_uv_2 = Vector((0.0, 0.0))
+ count = 0
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ target_loop_pairs = self._get_target_loop_pairs(bm, uv_layer)
+ for pair in target_loop_pairs:
+ p = list(pair)
+ uv_1 = p[0][uv_layer].uv
+ uv_2 = p[1][uv_layer].uv
+ ave_uv_1 += uv_1
+ ave_uv_2 += uv_2
+ count += 1
+
+ if count != 0:
+ ave_uv_1 /= count
+ ave_uv_2 /= count
+
+ sc.muv_align_uv_snap_edge_target_1 = ave_uv_1
+ sc.muv_align_uv_snap_edge_target_2 = ave_uv_2
return {'FINISHED'}
diff --git a/magic_uv/op/align_uv_cursor.py b/magic_uv/op/align_uv_cursor.py
index 884f645a..08c90db7 100644
--- a/magic_uv/op/align_uv_cursor.py
+++ b/magic_uv/op/align_uv_cursor.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from mathutils import Vector
@@ -38,10 +38,7 @@ 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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -180,47 +177,67 @@ class MUV_OT_AlignUVCursor(bpy.types.Operator):
else:
bd_size = [1.0, 1.0]
+ large_value = 1e7
if self.base == 'UV':
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if not bm.loops.layers.uv:
- return None
- uv_layer = bm.loops.layers.uv.verify()
-
- max_ = Vector((-10000000.0, -10000000.0))
- min_ = Vector((10000000.0, 10000000.0))
- for f in bm.faces:
- if not f.select:
- continue
- for l in f.loops:
- uv = l[uv_layer].uv
- max_.x = max(max_.x, uv.x)
- max_.y = max(max_.y, uv.y)
- min_.x = min(min_.x, uv.x)
- min_.y = min(min_.y, uv.y)
- center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0))
-
+ objs = common.get_uv_editable_objects(context)
+ no_selected_face = True
+ if objs:
+ max_ = Vector((-large_value, -large_value))
+ min_ = Vector((large_value, large_value))
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if not bm.loops.layers.uv:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ max_.x = max(max_.x, uv.x)
+ max_.y = max(max_.y, uv.y)
+ min_.x = min(min_.x, uv.x)
+ min_.y = min(min_.y, uv.y)
+ no_selected_face = False
+ if no_selected_face:
+ max_ = Vector((1.0, 1.0))
+ min_ = Vector((0.0, 0.0))
+ center = Vector((
+ (max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0
+ ))
+
+ # pylint: disable=R1702
elif self.base == 'UV_SEL':
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if not bm.loops.layers.uv:
- return None
- uv_layer = bm.loops.layers.uv.verify()
-
- max_ = Vector((-10000000.0, -10000000.0))
- min_ = Vector((10000000.0, 10000000.0))
- for f in bm.faces:
- if not f.select:
- continue
- for l in f.loops:
- if not l[uv_layer].select:
- continue
- uv = l[uv_layer].uv
- max_.x = max(max_.x, uv.x)
- max_.y = max(max_.y, uv.y)
- min_.x = min(min_.x, uv.x)
- min_.y = min(min_.y, uv.y)
- center = Vector(((max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0))
+ objs = common.get_uv_editable_objects(context)
+ no_selected_face = True
+ if objs:
+ max_ = Vector((-large_value, -large_value))
+ min_ = Vector((large_value, large_value))
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if not bm.loops.layers.uv:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for l in f.loops:
+ if not l[uv_layer].select:
+ continue
+ uv = l[uv_layer].uv
+ max_.x = max(max_.x, uv.x)
+ max_.y = max(max_.y, uv.y)
+ min_.x = min(min_.x, uv.x)
+ min_.y = min(min_.y, uv.y)
+ no_selected_face = False
+ if no_selected_face:
+ max_ = Vector((1.0, 1.0))
+ min_ = Vector((0.0, 0.0))
+ center = Vector((
+ (max_.x + min_.x) / 2.0, (max_.y + min_.y) / 2.0
+ ))
elif self.base == 'TEXTURE':
min_ = Vector((0.0, 0.0))
diff --git a/magic_uv/op/clip_uv.py b/magic_uv/op/clip_uv.py
index c6f006e2..990d35a6 100644
--- a/magic_uv/op/clip_uv.py
+++ b/magic_uv/op/clip_uv.py
@@ -20,8 +20,8 @@
__author__ = "Dusan Stevanovic, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import math
@@ -38,13 +38,18 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
+
+ # only edit mode is allowed to execute
+ 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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -156,72 +161,78 @@ class MUV_OT_ClipUV(bpy.types.Operator):
return _is_valid_context(context)
def execute(self, context):
- obj = context.active_object
- bm = common.create_bmesh(obj)
-
- 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()
-
- for face in bm.faces:
- if not face.select:
- continue
-
- selected_loops = [l for l in face.loops
- if l[uv_layer].select or
- context.scene.tool_settings.use_uv_select_sync]
- if not selected_loops:
- continue
-
- # average of UV coordinates on the face
- max_uv = Vector((-10000000.0, -10000000.0))
- min_uv = Vector((10000000.0, 10000000.0))
- for l in selected_loops:
- uv = l[uv_layer].uv
- max_uv.x = max(max_uv.x, uv.x)
- max_uv.y = max(max_uv.y, uv.y)
- min_uv.x = min(min_uv.x, uv.x)
- min_uv.y = min(min_uv.y, uv.y)
-
- # clip
- move_uv = Vector((0.0, 0.0))
- clip_size = Vector(self.clip_uv_range_max) - \
- Vector(self.clip_uv_range_min)
- if max_uv.x > self.clip_uv_range_max[0]:
- target_x = math.fmod(max_uv.x - self.clip_uv_range_min[0],
- clip_size.x)
- if target_x < 0.0:
- target_x += clip_size.x
- target_x += self.clip_uv_range_min[0]
- move_uv.x = target_x - max_uv.x
- if min_uv.x < self.clip_uv_range_min[0]:
- target_x = math.fmod(min_uv.x - self.clip_uv_range_min[0],
- clip_size.x)
- if target_x < 0.0:
- target_x += clip_size.x
- target_x += self.clip_uv_range_min[0]
- move_uv.x = target_x - min_uv.x
- if max_uv.y > self.clip_uv_range_max[1]:
- target_y = math.fmod(max_uv.y - self.clip_uv_range_min[1],
- clip_size.y)
- if target_y < 0.0:
- target_y += clip_size.y
- target_y += self.clip_uv_range_min[1]
- move_uv.y = target_y - max_uv.y
- if min_uv.y < self.clip_uv_range_min[1]:
- target_y = math.fmod(min_uv.y - self.clip_uv_range_min[1],
- clip_size.y)
- if target_y < 0.0:
- target_y += clip_size.y
- target_y += self.clip_uv_range_min[1]
- move_uv.y = target_y - min_uv.y
-
- # update UV
- for l in selected_loops:
- l[uv_layer].uv = l[uv_layer].uv + move_uv
-
- bmesh.update_edit_mesh(obj.data)
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = common.create_bmesh(obj)
+
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+
+ uv_layer = bm.loops.layers.uv.verify()
+
+ for face in bm.faces:
+ if not face.select:
+ continue
+
+ selected_loops = [
+ l for l in face.loops
+ if l[uv_layer].select or
+ context.scene.tool_settings.use_uv_select_sync
+ ]
+ if not selected_loops:
+ continue
+
+ # average of UV coordinates on the face
+ max_uv = Vector((-10000000.0, -10000000.0))
+ min_uv = Vector((10000000.0, 10000000.0))
+ for l in selected_loops:
+ uv = l[uv_layer].uv
+ max_uv.x = max(max_uv.x, uv.x)
+ max_uv.y = max(max_uv.y, uv.y)
+ min_uv.x = min(min_uv.x, uv.x)
+ min_uv.y = min(min_uv.y, uv.y)
+
+ # clip
+ move_uv = Vector((0.0, 0.0))
+ clip_size = Vector(self.clip_uv_range_max) - \
+ Vector(self.clip_uv_range_min)
+ if max_uv.x > self.clip_uv_range_max[0]:
+ target_x = math.fmod(max_uv.x - self.clip_uv_range_min[0],
+ clip_size.x)
+ if target_x < 0.0:
+ target_x += clip_size.x
+ target_x += self.clip_uv_range_min[0]
+ move_uv.x = target_x - max_uv.x
+ if min_uv.x < self.clip_uv_range_min[0]:
+ target_x = math.fmod(min_uv.x - self.clip_uv_range_min[0],
+ clip_size.x)
+ if target_x < 0.0:
+ target_x += clip_size.x
+ target_x += self.clip_uv_range_min[0]
+ move_uv.x = target_x - min_uv.x
+ if max_uv.y > self.clip_uv_range_max[1]:
+ target_y = math.fmod(max_uv.y - self.clip_uv_range_min[1],
+ clip_size.y)
+ if target_y < 0.0:
+ target_y += clip_size.y
+ target_y += self.clip_uv_range_min[1]
+ move_uv.y = target_y - max_uv.y
+ if min_uv.y < self.clip_uv_range_min[1]:
+ target_y = math.fmod(min_uv.y - self.clip_uv_range_min[1],
+ clip_size.y)
+ if target_y < 0.0:
+ target_y += clip_size.y
+ target_y += self.clip_uv_range_min[1]
+ move_uv.y = target_y - min_uv.y
+
+ # update UV
+ for l in selected_loops:
+ l[uv_layer].uv = l[uv_layer].uv + move_uv
+
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/copy_paste_uv.py b/magic_uv/op/copy_paste_uv.py
index ba754425..0410ee8d 100644
--- a/magic_uv/op/copy_paste_uv.py
+++ b/magic_uv/op/copy_paste_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>, Jace Priester"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bmesh
import bpy.utils
@@ -39,21 +39,17 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ # Multiple objects editing mode is not supported in this feature.
+ objs = common.get_uv_editable_objects(context)
+ if len(objs) != 1:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -343,7 +339,10 @@ class MUV_OT_CopyPasteUV_CopyUV(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.copy_paste_uv
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = common.create_bmesh(obj)
# get UV layer
@@ -379,8 +378,11 @@ class MUV_MT_CopyPasteUV_CopyUV(bpy.types.Menu):
def draw(self, context):
layout = self.layout
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
+
# create sub menu
- obj = context.active_object
bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
@@ -452,7 +454,10 @@ class MUV_OT_CopyPasteUV_PasteUV(bpy.types.Operator):
if not props.src_info:
self.report({'WARNING'}, "Need copy UV at first")
return {'CANCELLED'}
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = common.create_bmesh(obj)
# get UV layer
@@ -507,8 +512,11 @@ class MUV_MT_CopyPasteUV_PasteUV(bpy.types.Menu):
def draw(self, context):
sc = context.scene
layout = self.layout
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
+
# create sub menu
- obj = context.active_object
bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
@@ -560,7 +568,10 @@ class MUV_OT_CopyPasteUV_SelSeqCopyUV(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.copy_paste_uv_selseq
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = common.create_bmesh(obj)
# get UV layer
@@ -596,7 +607,10 @@ class MUV_MT_CopyPasteUV_SelSeqCopyUV(bpy.types.Menu):
def draw(self, context):
layout = self.layout
- obj = context.active_object
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
+
bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
@@ -669,7 +683,10 @@ class MUV_OT_CopyPasteUV_SelSeqPasteUV(bpy.types.Operator):
if not props.src_info:
self.report({'WARNING'}, "Need copy UV at first")
return {'CANCELLED'}
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = common.create_bmesh(obj)
# get UV layer
@@ -718,15 +735,18 @@ class MUV_MT_CopyPasteUV_SelSeqPasteUV(bpy.types.Menu):
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:
+ if not props.src_info:
return False
return _is_valid_context(context)
def draw(self, context):
sc = context.scene
layout = self.layout
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
+
# create sub menu
- obj = context.active_object
bm = common.create_bmesh(obj)
uv_maps = bm.loops.layers.uv.keys()
diff --git a/magic_uv/op/copy_paste_uv_object.py b/magic_uv/op/copy_paste_uv_object.py
index 1b812b82..2130ea38 100644
--- a/magic_uv/op/copy_paste_uv_object.py
+++ b/magic_uv/op/copy_paste_uv_object.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bmesh
import bpy
@@ -44,21 +44,17 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ # Multiple objects editing mode is not supported in this feature.
+ objs = common.get_uv_editable_objects(context)
+ if len(objs) != 1:
+ return False
# only object mode is allowed to execute
- if obj is None:
- return False
- if obj.type != 'MESH':
- return False
if context.object.mode != 'OBJECT':
return False
# only 'VIEW_3D' space is allowed to execute
- for space in context.area.spaces:
- if space.type == 'VIEW_3D':
- break
- else:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -121,7 +117,10 @@ class MUV_OT_CopyPasteUVObject_CopyUV(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.copy_paste_uv_object
bpy.ops.object.mode_set(mode='EDIT')
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = common.create_bmesh(obj)
# get UV layer
@@ -155,10 +154,14 @@ class MUV_MT_CopyPasteUVObject_CopyUV(bpy.types.Menu):
def poll(cls, context):
return _is_valid_context(context)
- def draw(self, _):
+ def draw(self, context):
layout = self.layout
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
+
# create sub menu
- uv_maps = compat.get_object_uv_layers(bpy.context.active_object).keys()
+ uv_maps = compat.get_object_uv_layers(obj).keys()
ops = layout.operator(MUV_OT_CopyPasteUVObject_CopyUV.bl_idname,
text="[Default]")
@@ -211,11 +214,10 @@ class MUV_OT_CopyPasteUVObject_PasteUV(bpy.types.Operator):
self.report({'WARNING'}, "Need copy UV at first")
return {'CANCELLED'}
- for o in bpy.data.objects:
+ objs = common.get_uv_editable_objects(context)
+ for o in objs:
if not compat.object_has_uv_layers(o):
continue
- if not compat.get_object_select(o):
- continue
bpy.ops.object.mode_set(mode='OBJECT')
compat.set_active_object(o)
@@ -277,11 +279,10 @@ class MUV_MT_CopyPasteUVObject_PasteUV(bpy.types.Menu):
layout = self.layout
# create sub menu
uv_maps = []
- for obj in bpy.data.objects:
+ objs = common.get_uv_editable_objects(context)
+ for obj in objs:
if not compat.object_has_uv_layers(obj):
continue
- if not compat.get_object_select(obj):
- continue
uv_maps.extend(compat.get_object_uv_layers(obj).keys())
ops = layout.operator(MUV_OT_CopyPasteUVObject_PasteUV.bl_idname,
diff --git a/magic_uv/op/copy_paste_uv_uvedit.py b/magic_uv/op/copy_paste_uv_uvedit.py
index f12851dd..abfb69a8 100644
--- a/magic_uv/op/copy_paste_uv_uvedit.py
+++ b/magic_uv/op/copy_paste_uv_uvedit.py
@@ -20,8 +20,8 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import math
from math import atan2, sin, cos
@@ -36,23 +36,19 @@ from ..utils.property_class_registry import PropertyClassRegistry
def _is_valid_context(context):
- obj = context.object
+ # Multiple objects editing mode is not supported in this feature.
+ objs = common.get_uv_editable_objects(context)
+ if len(objs) != 1:
+ return False
# 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
# '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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -94,7 +90,10 @@ class MUV_OT_CopyPasteUVUVEdit_CopyUV(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.copy_paste_uv_uvedit
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = bmesh.from_edit_mesh(obj.data)
uv_layer = bm.loops.layers.uv.verify()
if common.check_version(2, 73, 0) >= 0:
@@ -140,7 +139,10 @@ class MUV_OT_CopyPasteUVUVEdit_PasteUV(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.copy_paste_uv_uvedit
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = bmesh.from_edit_mesh(obj.data)
uv_layer = bm.loops.layers.uv.verify()
if common.check_version(2, 73, 0) >= 0:
diff --git a/magic_uv/op/flip_rotate_uv.py b/magic_uv/op/flip_rotate_uv.py
index d0ac6a83..7e6dbdf9 100644
--- a/magic_uv/op/flip_rotate_uv.py
+++ b/magic_uv/op/flip_rotate_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
import bmesh
@@ -37,37 +37,31 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
-def _get_uv_layer(ops_obj, bm):
+def _get_uv_layer(bm):
# get UV layer
if not bm.loops.layers.uv:
- ops_obj.report({'WARNING'}, "Object must have more than one UV map")
return None
uv_layer = bm.loops.layers.uv.verify()
return uv_layer
-def _get_src_face_info(ops_obj, bm, uv_layers, only_select=False):
+def _get_src_face_info(bm, uv_layers, only_select=False):
src_info = {}
for layer in uv_layers:
face_info = []
@@ -81,14 +75,13 @@ def _get_src_face_info(ops_obj, bm, uv_layers, only_select=False):
}
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 _paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip,
+def _paste_uv(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]
@@ -105,7 +98,6 @@ def _paste_uv(ops_obj, bm, src_info, dest_info, uv_layers, strategy, flip,
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]
@@ -199,34 +191,47 @@ class MUV_OT_FlipRotateUV(bpy.types.Operator):
def execute(self, context):
self.report({'INFO'}, "Flip/Rotate 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()
-
- # get UV layer
- uv_layer = _get_uv_layer(self, bm)
- if not uv_layer:
- return {'CANCELLED'}
-
- # get selected face
- src_info = _get_src_face_info(self, bm, [uv_layer], True)
- if not src_info:
+ objs = common.get_uv_editable_objects(context)
+
+ face_count = 0
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ # get UV layer
+ uv_layer = _get_uv_layer(bm)
+ if not uv_layer:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+
+ # get selected face
+ src_info = _get_src_face_info(bm, [uv_layer], True)
+ if not src_info:
+ continue
+
+ # paste
+ ret = _paste_uv(bm, src_info, src_info, [uv_layer], 'N_N',
+ self.flip, self.rotate, self.seams)
+ if ret:
+ self.report({'WARNING'},
+ "Some Object {}'s faces are different size"
+ .format(obj.name))
+ return {'CANCELLED'}
+
+ bmesh.update_edit_mesh(obj.data)
+ if compat.check_version(2, 80, 0) < 0:
+ if self.seams is True:
+ obj.data.show_edge_seams = True
+
+ face_count += len(src_info[list(src_info.keys())[0]])
+
+ if face_count == 0:
+ self.report({'WARNING'}, "No faces are selected")
return {'CANCELLED'}
-
- face_count = len(src_info[list(src_info.keys())[0]])
- self.report({'INFO'}, "{} face(s) are selected".format(face_count))
-
- # paste
- ret = _paste_uv(self, bm, src_info, src_info, [uv_layer], 'N_N',
- self.flip, self.rotate, self.seams)
- if ret:
- return {'CANCELLED'}
-
- bmesh.update_edit_mesh(obj.data)
-
- if compat.check_version(2, 80, 0) < 0:
- if self.seams is True:
- obj.data.show_edge_seams = True
+ self.report({'INFO'},
+ "{} face(s) are fliped/rotated".format(face_count))
return {'FINISHED'}
diff --git a/magic_uv/op/mirror_uv.py b/magic_uv/op/mirror_uv.py
index dcbaad5e..a893ab83 100644
--- a/magic_uv/op/mirror_uv.py
+++ b/magic_uv/op/mirror_uv.py
@@ -20,8 +20,8 @@
__author__ = "Keith (Wahooney) Boshoff, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import (
@@ -39,21 +39,16 @@ from .. import common
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -168,48 +163,52 @@ class MUV_OT_MirrorUV(bpy.types.Operator):
return _is_valid_context(context)
def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
-
- error = self.error
- axis = self.axis
-
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- 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()
-
- faces = [f for f in bm.faces if f.select]
- for f_dst in faces:
- count = len(f_dst.verts)
- for f_src in bm.faces:
- # check if this is a candidate to do mirror UV
- if f_src.index == f_dst.index:
- continue
- if count != len(f_src.verts):
- continue
-
- # test if the vertices x values are the same sign
- dst = _get_face_center(f_dst)
- src = _get_face_center(f_src)
- if (dst.x > 0 and src.x > 0) or (dst.x < 0 and src.x < 0):
- continue
-
- # invert source axis
- if axis == 'X':
- src.x = -src.x
- elif axis == 'Y':
- src.y = -src.z
- elif axis == 'Z':
- src.z = -src.z
-
- # do mirror UV
- if _is_vector_similar(dst, src, error):
- _mirror_uvs(uv_layer, f_src, f_dst, self.axis, self.error)
-
- bmesh.update_edit_mesh(obj.data)
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+
+ error = self.error
+ axis = self.axis
+
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+ uv_layer = bm.loops.layers.uv.verify()
+
+ faces = [f for f in bm.faces if f.select]
+ for f_dst in faces:
+ count = len(f_dst.verts)
+ for f_src in bm.faces:
+ # check if this is a candidate to do mirror UV
+ if f_src.index == f_dst.index:
+ continue
+ if count != len(f_src.verts):
+ continue
+
+ # test if the vertices x values are the same sign
+ dst = _get_face_center(f_dst)
+ src = _get_face_center(f_src)
+ if (dst.x > 0 and src.x > 0) or (dst.x < 0 and src.x < 0):
+ continue
+
+ # invert source axis
+ if axis == 'X':
+ src.x = -src.x
+ elif axis == 'Y':
+ src.y = -src.z
+ elif axis == 'Z':
+ src.z = -src.z
+
+ # do mirror UV
+ if _is_vector_similar(dst, src, error):
+ _mirror_uvs(uv_layer, f_src, f_dst,
+ self.axis, self.error)
+
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/move_uv.py b/magic_uv/op/move_uv.py
index 19160a46..fb7c287d 100644
--- a/magic_uv/op/move_uv.py
+++ b/magic_uv/op/move_uv.py
@@ -20,8 +20,8 @@
__author__ = "kgeogeo, mem, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import BoolProperty
@@ -34,21 +34,17 @@ from ..utils.property_class_registry import PropertyClassRegistry
def _is_valid_context(context):
- obj = context.object
+ # Multiple objects editing mode is not supported in this feature.
+ objs = common.get_uv_editable_objects(context)
+ if len(objs) != 1:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -179,7 +175,9 @@ class MUV_OT_MoveUV(bpy.types.Operator):
context.window_manager.modal_handler_add(self)
- obj = context.active_object
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = bmesh.from_edit_mesh(obj.data)
active_uv = bm.loops.layers.uv.active
self.__topology_dict, self.__ini_uvs = self._find_uv(bm, active_uv)
diff --git a/magic_uv/op/pack_uv.py b/magic_uv/op/pack_uv.py
index 0d7ed966..75fc760c 100644
--- a/magic_uv/op/pack_uv.py
+++ b/magic_uv/op/pack_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from math import fabs
@@ -42,23 +42,18 @@ from .. import common
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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
# '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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -227,23 +222,37 @@ class MUV_OT_PackUV(bpy.types.Operator):
return _is_valid_context(context)
def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- 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()
-
- selected_faces = [f for f in bm.faces if f.select]
- island_info = common.get_island_info(obj)
+ objs = common.get_uv_editable_objects(context)
+
+ island_info = []
+ selected_faces = []
+ island_to_bm = {}
+ island_to_uv_layer = {}
+ bm_to_loop_lists = {}
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+ uv_layer = bm.loops.layers.uv.verify()
+
+ selected_faces.extend([f for f in bm.faces if f.select])
+ isl = common.get_island_info(obj)
+ for i, info in enumerate(isl):
+ id_ = i + len(island_info)
+ island_to_bm[id_] = bm
+ island_to_uv_layer[id_] = uv_layer
+ info["id"] = id_
+ island_info.extend(isl)
+ bm_to_loop_lists[bm] = [l for f in bm.faces for l in f.loops]
+
num_group = _group_island(island_info,
self.allowable_center_deviation,
self.allowable_size_deviation)
-
- loop_lists = [l for f in bm.faces for l in f.loops]
bpy.ops.mesh.select_all(action='DESELECT')
# pack UV
@@ -252,7 +261,8 @@ class MUV_OT_PackUV(bpy.types.Operator):
lambda i, idx=gidx: i['group'] == idx, island_info))
for f in group[0]['faces']:
f['face'].select = True
- bmesh.update_edit_mesh(obj.data)
+ for obj in objs:
+ bmesh.update_edit_mesh(obj.data)
bpy.ops.uv.select_all(action='SELECT')
bpy.ops.uv.pack_islands(rotate=self.rotate, margin=self.margin)
@@ -262,13 +272,19 @@ class MUV_OT_PackUV(bpy.types.Operator):
lambda i, idx=gidx: i['group'] == idx, island_info))
if len(group) <= 1:
continue
+ src_bm = island_to_bm[group[0]["id"]]
+ src_uv_layer = island_to_uv_layer[group[0]["id"]]
+ src_loop_lists = bm_to_loop_lists[src_bm]
for g in group[1:]:
+ dst_bm = island_to_bm[g["id"]]
+ dst_uv_layer = island_to_uv_layer[g["id"]]
+ dst_loop_lists = bm_to_loop_lists[dst_bm]
for (src_face, dest_face) in zip(
group[0]['sorted'], g['sorted']):
for (src_loop, dest_loop) in zip(
src_face['face'].loops, dest_face['face'].loops):
- loop_lists[dest_loop.index][uv_layer].uv = loop_lists[
- src_loop.index][uv_layer].uv
+ dst_loop_lists[dest_loop.index][dst_uv_layer].uv = \
+ src_loop_lists[src_loop.index][src_uv_layer].uv
# restore face/UV selection
bpy.ops.uv.select_all(action='DESELECT')
@@ -277,6 +293,7 @@ class MUV_OT_PackUV(bpy.types.Operator):
f.select = True
bpy.ops.uv.select_all(action='SELECT')
- bmesh.update_edit_mesh(obj.data)
+ for obj in objs:
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/preserve_uv_aspect.py b/magic_uv/op/preserve_uv_aspect.py
index 5b3e50cf..270bc7ec 100644
--- a/magic_uv/op/preserve_uv_aspect.py
+++ b/magic_uv/op/preserve_uv_aspect.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import StringProperty, EnumProperty, BoolProperty
@@ -35,21 +35,16 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -143,155 +138,204 @@ class MUV_OT_PreserveUVAspect(bpy.types.Operator):
# Note: the current system only works if the
# f[tex_layer].image doesn't return None
# which will happen in certain cases
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
-
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
-
- 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()
-
- sel_faces = [f for f in bm.faces if f.select]
- dest_img = bpy.data.images[self.dest_img_name]
-
- info = {}
-
- if compat.check_version(2, 80, 0) >= 0:
- tex_image = common.find_image(obj)
- for f in sel_faces:
- if tex_image not in info.keys():
- info[tex_image] = {}
- info[tex_image]['faces'] = []
- info[tex_image]['faces'].append(f)
- else:
- tex_layer = bm.faces.layers.tex.verify()
- for f in sel_faces:
- if not f[tex_layer].image in info.keys():
- info[f[tex_layer].image] = {}
- info[f[tex_layer].image]['faces'] = []
- info[f[tex_layer].image]['faces'].append(f)
-
- for img in info:
- if img is None:
- continue
-
- src_img = img
- ratio = Vector((
- dest_img.size[0] / src_img.size[0],
- dest_img.size[1] / src_img.size[1]))
-
- if self.origin == 'CENTER':
- origin = Vector((0.0, 0.0))
- num = 0
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin = origin + uv
- num = num + 1
- origin = origin / num
- elif self.origin == 'LEFT_TOP':
- origin = Vector((100000.0, -100000.0))
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = min(origin.x, uv.x)
- origin.y = max(origin.y, uv.y)
- elif self.origin == 'LEFT_CENTER':
- origin = Vector((100000.0, 0.0))
- num = 0
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = min(origin.x, uv.x)
- origin.y = origin.y + uv.y
- num = num + 1
- origin.y = origin.y / num
- elif self.origin == 'LEFT_BOTTOM':
- origin = Vector((100000.0, 100000.0))
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = min(origin.x, uv.x)
- origin.y = min(origin.y, uv.y)
- elif self.origin == 'CENTER_TOP':
- origin = Vector((0.0, -100000.0))
- num = 0
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = origin.x + uv.x
- origin.y = max(origin.y, uv.y)
- num = num + 1
- origin.x = origin.x / num
- elif self.origin == 'CENTER_BOTTOM':
- origin = Vector((0.0, 100000.0))
- num = 0
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = origin.x + uv.x
- origin.y = min(origin.y, uv.y)
- num = num + 1
- origin.x = origin.x / num
- elif self.origin == 'RIGHT_TOP':
- origin = Vector((-100000.0, -100000.0))
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = max(origin.x, uv.x)
- origin.y = max(origin.y, uv.y)
- elif self.origin == 'RIGHT_CENTER':
- origin = Vector((-100000.0, 0.0))
- num = 0
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = max(origin.x, uv.x)
- origin.y = origin.y + uv.y
- num = num + 1
- origin.y = origin.y / num
- elif self.origin == 'RIGHT_BOTTOM':
- origin = Vector((-100000.0, 100000.0))
- for f in info[img]['faces']:
- for l in f.loops:
- uv = l[uv_layer].uv
- origin.x = max(origin.x, uv.x)
- origin.y = min(origin.y, uv.y)
+ objs = common.get_uv_editable_objects(context)
+
+ obj_list = {} # { Material: Object }
+ for obj in objs:
+ if common.check_version(2, 80, 0) >= 0:
+ # If more than two selected objects shares same
+ # material, we need to calculate new UV coordinates
+ # before image on texture node is overwritten.
+ material_to_rewrite = []
+ for slot in obj.material_slots:
+ if not slot.material:
+ continue
+ nodes = common.find_texture_nodes_from_material(
+ slot.material)
+ if len(nodes) >= 2:
+ self.report(
+ {'WARNING'},
+ "Object {} must not have more than 2 "
+ "shader nodes with image texture"
+ .format(obj.name))
+ return {'CANCELLED'}
+ if not nodes:
+ continue
+ material_to_rewrite.append(slot.material)
+
+ if len(material_to_rewrite) >= 2:
+ self.report(
+ {'WARNING'},
+ "Object {} must not have more than 2 "
+ "materials with image texture"
+ .format(obj.name))
+ return {'CANCELLED'}
+ if len(material_to_rewrite) == 0:
+ self.report(
+ {'WARNING'},
+ "Object {} must not have more than 1 "
+ "material with image texture"
+ .format(obj.name))
+ return {'CANCELLED'}
+ if material_to_rewrite[0] not in obj_list.keys():
+ obj_list[material_to_rewrite[0]] = []
+ obj_list[material_to_rewrite[0]].append(obj)
else:
- self.report({'ERROR'}, "Unknown Operation")
- return {'CANCELLED'}
-
- info[img]['ratio'] = ratio
- info[img]['origin'] = origin
-
- for img in info:
- if img is None:
- continue
+ # If blender version is < (2, 79), multiple objects editing
+ # mode is not supported. So, we add dummy key to obj_list.
+ obj_list["Dummy"] = [obj]
+
+ # pylint: disable=R1702
+ for mtrl, o in obj_list.items():
+ for obj in o:
+ bm = bmesh.from_edit_mesh(obj.data)
+
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ 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()
+
+ sel_faces = [f for f in bm.faces if f.select]
+ dest_img = bpy.data.images[self.dest_img_name]
+
+ info = {}
+
+ if compat.check_version(2, 80, 0) >= 0:
+ tex_image = common.find_image(obj)
+ for f in sel_faces:
+ if tex_image not in info.keys():
+ info[tex_image] = {}
+ info[tex_image]['faces'] = []
+ info[tex_image]['faces'].append(f)
+ else:
+ tex_layer = bm.faces.layers.tex.verify()
+ for f in sel_faces:
+ if not f[tex_layer].image in info.keys():
+ info[f[tex_layer].image] = {}
+ info[f[tex_layer].image]['faces'] = []
+ info[f[tex_layer].image]['faces'].append(f)
+
+ for img in info:
+ if img is None:
+ continue
+
+ src_img = img
+ ratio = Vector((
+ dest_img.size[0] / src_img.size[0],
+ dest_img.size[1] / src_img.size[1]))
+
+ if self.origin == 'CENTER':
+ origin = Vector((0.0, 0.0))
+ num = 0
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin = origin + uv
+ num = num + 1
+ origin = origin / num
+ elif self.origin == 'LEFT_TOP':
+ origin = Vector((100000.0, -100000.0))
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = min(origin.x, uv.x)
+ origin.y = max(origin.y, uv.y)
+ elif self.origin == 'LEFT_CENTER':
+ origin = Vector((100000.0, 0.0))
+ num = 0
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = min(origin.x, uv.x)
+ origin.y = origin.y + uv.y
+ num = num + 1
+ origin.y = origin.y / num
+ elif self.origin == 'LEFT_BOTTOM':
+ origin = Vector((100000.0, 100000.0))
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = min(origin.x, uv.x)
+ origin.y = min(origin.y, uv.y)
+ elif self.origin == 'CENTER_TOP':
+ origin = Vector((0.0, -100000.0))
+ num = 0
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = origin.x + uv.x
+ origin.y = max(origin.y, uv.y)
+ num = num + 1
+ origin.x = origin.x / num
+ elif self.origin == 'CENTER_BOTTOM':
+ origin = Vector((0.0, 100000.0))
+ num = 0
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = origin.x + uv.x
+ origin.y = min(origin.y, uv.y)
+ num = num + 1
+ origin.x = origin.x / num
+ elif self.origin == 'RIGHT_TOP':
+ origin = Vector((-100000.0, -100000.0))
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = max(origin.x, uv.x)
+ origin.y = max(origin.y, uv.y)
+ elif self.origin == 'RIGHT_CENTER':
+ origin = Vector((-100000.0, 0.0))
+ num = 0
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = max(origin.x, uv.x)
+ origin.y = origin.y + uv.y
+ num = num + 1
+ origin.y = origin.y / num
+ elif self.origin == 'RIGHT_BOTTOM':
+ origin = Vector((-100000.0, 100000.0))
+ for f in info[img]['faces']:
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin.x = max(origin.x, uv.x)
+ origin.y = min(origin.y, uv.y)
+ else:
+ self.report({'ERROR'}, "Unknown Operation")
+ return {'CANCELLED'}
+
+ info[img]['ratio'] = ratio
+ info[img]['origin'] = origin
+
+ for img in info:
+ if img is None:
+ continue
+
+ for f in info[img]['faces']:
+ if compat.check_version(2, 80, 0) < 0:
+ tex_layer = bm.faces.layers.tex.verify()
+ f[tex_layer].image = dest_img
+ for l in f.loops:
+ uv = l[uv_layer].uv
+ origin = info[img]['origin']
+ ratio = info[img]['ratio']
+ diff = uv - origin
+ diff.x = diff.x / ratio.x
+ diff.y = diff.y / ratio.y
+ uv.x = origin.x + diff.x
+ uv.y = origin.y + diff.y
+ l[uv_layer].uv = uv
+
+ bmesh.update_edit_mesh(obj.data)
if compat.check_version(2, 80, 0) >= 0:
- nodes = common.find_texture_nodes(obj)
+ nodes = common.find_texture_nodes_from_material(mtrl)
nodes[0].image = dest_img
- for f in info[img]['faces']:
- if compat.check_version(2, 80, 0) < 0:
- tex_layer = bm.faces.layers.tex.verify()
- f[tex_layer].image = dest_img
- for l in f.loops:
- uv = l[uv_layer].uv
- origin = info[img]['origin']
- ratio = info[img]['ratio']
- diff = uv - origin
- diff.x = diff.x / ratio.x
- diff.y = diff.y / ratio.y
- uv.x = origin.x + diff.x
- uv.y = origin.y + diff.y
- l[uv_layer].uv = uv
-
- bmesh.update_edit_mesh(obj.data)
-
return {'FINISHED'}
diff --git a/magic_uv/op/select_uv.py b/magic_uv/op/select_uv.py
index d80b43a8..a405e66d 100644
--- a/magic_uv/op/select_uv.py
+++ b/magic_uv/op/select_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import BoolProperty
@@ -30,27 +30,21 @@ import bmesh
from .. import common
from ..utils.bl_class_registry import BlClassRegistry
from ..utils.property_class_registry import PropertyClassRegistry
-from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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
# '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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -92,18 +86,13 @@ class MUV_OT_SelectUV_SelectOverlapped(bpy.types.Operator):
return _is_valid_context(context)
def execute(self, context):
- objs = [o for o in bpy.data.objects if compat.get_object_select(o)]
+ objs = common.get_uv_editable_objects(context)
bm_list = []
uv_layer_list = []
faces_list = []
- for o in bpy.data.objects:
- if not compat.get_object_select(o):
- continue
- if o.type != 'MESH':
- continue
-
- bm = bmesh.from_edit_mesh(o.data)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
uv_layer = bm.loops.layers.uv.verify()
@@ -126,8 +115,8 @@ class MUV_OT_SelectUV_SelectOverlapped(bpy.types.Operator):
for l in info["subject_face"].loops:
l[info["subject_uv_layer"]].select = True
- for o in objs:
- bmesh.update_edit_mesh(o.data)
+ for obj in objs:
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
@@ -151,18 +140,13 @@ class MUV_OT_SelectUV_SelectFlipped(bpy.types.Operator):
return _is_valid_context(context)
def execute(self, context):
- objs = [o for o in bpy.data.objects if compat.get_object_select(o)]
+ objs = common.get_uv_editable_objects(context)
bm_list = []
uv_layer_list = []
faces_list = []
- for o in bpy.data.objects:
- if not compat.get_object_select(o):
- continue
- if o.type != 'MESH':
- continue
-
- bm = bmesh.from_edit_mesh(o.data)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
uv_layer = bm.loops.layers.uv.verify()
@@ -184,7 +168,7 @@ class MUV_OT_SelectUV_SelectFlipped(bpy.types.Operator):
for l in info["face"].loops:
l[info["uv_layer"]].select = True
- for o in objs:
- bmesh.update_edit_mesh(o.data)
+ for obj in objs:
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/smooth_uv.py b/magic_uv/op/smooth_uv.py
index 94e41367..9b721615 100644
--- a/magic_uv/op/smooth_uv.py
+++ b/magic_uv/op/smooth_uv.py
@@ -20,8 +20,8 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import BoolProperty, FloatProperty
@@ -34,23 +34,18 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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
# '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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -261,21 +256,24 @@ class MUV_OT_SmoothUV(bpy.types.Operator):
self.__smooth_wo_transmission(loop_seqs, uv_layer)
def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- uv_layer = bm.loops.layers.uv.verify()
-
- # loop_seqs[horizontal][vertical][loop]
- loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
- if not loop_seqs:
- self.report({'WARNING'}, error)
- return {'CANCELLED'}
-
- # smooth
- self.__smooth(loop_seqs, uv_layer)
-
- bmesh.update_edit_mesh(obj.data)
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # loop_seqs[horizontal][vertical][loop]
+ loop_seqs, error = common.get_loop_sequences(bm, uv_layer)
+ if not loop_seqs:
+ self.report({'WARNING'},
+ "Object {}: {}".format(obj.name, error))
+ return {'CANCELLED'}
+
+ # smooth
+ self.__smooth(loop_seqs, uv_layer)
+
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/texture_lock.py b/magic_uv/op/texture_lock.py
index ddcaf315..fb9ac4c7 100644
--- a/magic_uv/op/texture_lock.py
+++ b/magic_uv/op/texture_lock.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import math
from math import atan2, cos, sqrt, sin, fabs
@@ -188,21 +188,16 @@ def _calc_tri_vert(v0, v1, angle0, angle1):
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -215,7 +210,7 @@ class _Properties:
@classmethod
def init_props(cls, scene):
class Props():
- verts_orig = None
+ verts_orig = {} # { Object: verts_orig }
scene.muv_props.texture_lock = Props()
@@ -282,20 +277,26 @@ class MUV_OT_TextureLock_Lock(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.texture_lock
- obj = bpy.context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
-
- if not bm.loops.layers.uv:
- self.report({'WARNING'}, "Object must have more than one UV map")
- return {'CANCELLED'}
-
- props.verts_orig = [
- {"vidx": v.index, "vco": v.co.copy(), "moved": False}
- for v in bm.verts if v.select]
+ objs = common.get_uv_editable_objects(context)
+
+ props.verts_orig = {}
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+
+ props.verts_orig[obj] = [
+ {"vidx": v.index, "vco": v.co.copy(), "moved": False}
+ for v in bm.verts if v.select
+ ]
return {'FINISHED'}
@@ -335,53 +336,63 @@ class MUV_OT_TextureLock_Unlock(bpy.types.Operator):
def execute(self, context):
sc = context.scene
props = sc.muv_props.texture_lock
- obj = bpy.context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
-
- 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()
-
- verts = [v.index for v in bm.verts if v.select]
- verts_orig = props.verts_orig
-
- # move UV followed by vertex coordinate
- for vidx, v_orig in zip(verts, verts_orig):
- if vidx != v_orig["vidx"]:
- self.report({'ERROR'}, "Internal Error")
- return {"CANCELLED"}
+ objs = common.get_uv_editable_objects(context)
- v = bm.verts[vidx]
- link_loops = _get_link_loops(v)
-
- result = []
-
- for ll in link_loops:
- ini_geom = _get_ini_geom(ll, uv_layer, verts_orig, v_orig)
- target_uv = _get_target_uv(
- ll, uv_layer, verts_orig, v, ini_geom)
- result.append({"l": ll["l"], "uv": target_uv})
-
- # connect other face's UV
- if self.connect:
- ave = Vector((0.0, 0.0))
- for r in result:
- ave = ave + r["uv"]
- ave = ave / len(result)
- for r in result:
- r["l"][uv_layer].uv = ave
- else:
- for r in result:
- r["l"][uv_layer].uv = r["uv"]
- v_orig["moved"] = True
- bmesh.update_edit_mesh(obj.data)
+ if set(objs) != set(props.verts_orig.keys()):
+ self.report({'WARNING'},
+ "Object list does not match between Lock and Unlock")
+ return {'CANCELLED'}
- props.verts_orig = None
+ for obj in objs:
+ verts_orig = props.verts_orig[obj]
+
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+ uv_layer = bm.loops.layers.uv.verify()
+
+ verts = [v.index for v in bm.verts if v.select]
+
+ # move UV followed by vertex coordinate
+ for vidx, v_orig in zip(verts, verts_orig):
+ if vidx != v_orig["vidx"]:
+ self.report({'ERROR'}, "Internal Error")
+ return {"CANCELLED"}
+
+ v = bm.verts[vidx]
+ link_loops = _get_link_loops(v)
+
+ result = []
+
+ for ll in link_loops:
+ ini_geom = _get_ini_geom(ll, uv_layer, verts_orig, v_orig)
+ target_uv = _get_target_uv(
+ ll, uv_layer, verts_orig, v, ini_geom)
+ result.append({"l": ll["l"], "uv": target_uv})
+
+ # connect other face's UV
+ if self.connect:
+ ave = Vector((0.0, 0.0))
+ for r in result:
+ ave = ave + r["uv"]
+ ave = ave / len(result)
+ for r in result:
+ r["l"][uv_layer].uv = ave
+ else:
+ for r in result:
+ r["l"][uv_layer].uv = r["uv"]
+ v_orig["moved"] = True
+ bmesh.update_edit_mesh(obj.data)
+
+ props.verts_orig = {}
return {'FINISHED'}
@@ -423,84 +434,100 @@ class MUV_OT_TextureLock_Intr(bpy.types.Operator):
cls.__timer = None
def __init__(self):
- self.__intr_verts_orig = []
- self.__intr_verts = []
+ self.__intr_verts_orig = {} # { Object: verts_orig }
+ self.__intr_verts = {} # { Object: verts_orig }
def __sel_verts_changed(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ if obj not in self.__intr_verts:
+ return True
- prev = set(self.__intr_verts)
- now = {v.index for v in bm.verts if v.select}
+ prev = set(self.__intr_verts[obj])
+ now = {v.index for v in bm.verts if v.select}
- return prev != now
+ if prev != now:
+ return True
+
+ return False
def __reinit_verts(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
-
- self.__intr_verts_orig = [
- {"vidx": v.index, "vco": v.co.copy(), "moved": False}
- for v in bm.verts if v.select]
- self.__intr_verts = [v.index for v in bm.verts if v.select]
+ objs = common.get_uv_editable_objects(context)
+ self.__intr_verts_orig = {}
+ self.__intr_verts = {}
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ self.__intr_verts_orig[obj] = [
+ {"vidx": v.index, "vco": v.co.copy(), "moved": False}
+ for v in bm.verts if v.select]
+ self.__intr_verts[obj] = [v.index for v in bm.verts if v.select]
def __update_uv(self, context):
"""
Update UV when vertex coordinates are changed
"""
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
-
- if not bm.loops.layers.uv:
- self.report({'WARNING'}, "Object must have more than one UV map")
- return
- uv_layer = bm.loops.layers.uv.verify()
-
- verts = [v.index for v in bm.verts if v.select]
- verts_orig = self.__intr_verts_orig
-
- for vidx, v_orig in zip(verts, verts_orig):
- if vidx != v_orig["vidx"]:
- self.report({'ERROR'}, "Internal Error")
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.data))
return
+ uv_layer = bm.loops.layers.uv.verify()
+
+ verts = [v.index for v in bm.verts if v.select]
+ verts_orig = self.__intr_verts_orig[obj]
+
+ for vidx, v_orig in zip(verts, verts_orig):
+ if vidx != v_orig["vidx"]:
+ self.report({'ERROR'}, "Internal Error")
+ return
+
+ v = bm.verts[vidx]
+ link_loops = _get_link_loops(v)
+
+ result = []
+ for ll in link_loops:
+ ini_geom = _get_ini_geom(ll, uv_layer, verts_orig, v_orig)
+ target_uv = _get_target_uv(
+ ll, uv_layer, verts_orig, v, ini_geom)
+ result.append({"l": ll["l"], "uv": target_uv})
+
+ # UV connect option is always true, because it raises
+ # unexpected behavior
+ ave = Vector((0.0, 0.0))
+ for r in result:
+ ave = ave + r["uv"]
+ ave = ave / len(result)
+ for r in result:
+ r["l"][uv_layer].uv = ave
+ v_orig["moved"] = True
+ bmesh.update_edit_mesh(obj.data)
- v = bm.verts[vidx]
- link_loops = _get_link_loops(v)
-
- result = []
- for ll in link_loops:
- ini_geom = _get_ini_geom(ll, uv_layer, verts_orig, v_orig)
- target_uv = _get_target_uv(
- ll, uv_layer, verts_orig, v, ini_geom)
- result.append({"l": ll["l"], "uv": target_uv})
-
- # UV connect option is always true, because it raises
- # unexpected behavior
- ave = Vector((0.0, 0.0))
- for r in result:
- ave = ave + r["uv"]
- ave = ave / len(result)
- for r in result:
- r["l"][uv_layer].uv = ave
- v_orig["moved"] = True
- bmesh.update_edit_mesh(obj.data)
-
- common.redraw_all_areas()
- self.__intr_verts_orig = [
- {"vidx": v.index, "vco": v.co.copy(), "moved": False}
- for v in bm.verts if v.select]
+ common.redraw_all_areas()
+ self.__intr_verts_orig[obj] = [
+ {"vidx": v.index, "vco": v.co.copy(), "moved": False}
+ for v in bm.verts if v.select]
def modal(self, context, event):
if not _is_valid_context(context):
diff --git a/magic_uv/op/texture_projection.py b/magic_uv/op/texture_projection.py
index b754dd88..694fac0d 100644
--- a/magic_uv/op/texture_projection.py
+++ b/magic_uv/op/texture_projection.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from collections import namedtuple
@@ -124,21 +124,16 @@ def _region_to_canvas(rg_vec, canvas):
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -356,62 +351,76 @@ class MUV_OT_TextureProjection_Project(bpy.types.Operator):
_, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
# get faces to be texture projected
- obj = context.active_object
- world_mat = obj.matrix_world
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
-
- # get UV and texture layer
- if not bm.loops.layers.uv:
- if sc.muv_texture_projection_assign_uvmap:
- bm.loops.layers.uv.new()
- else:
- self.report({'WARNING'},
- "Object must have more than one UV map")
- return {'CANCELLED'}
-
- uv_layer = bm.loops.layers.uv.verify()
- if compat.check_version(2, 80, 0) < 0:
- tex_layer = bm.faces.layers.tex.verify()
-
- sel_faces = [f for f in bm.faces if f.select]
-
- # transform 3d space to screen region
- v_screen = [
- view3d_utils.location_3d_to_region_2d(
- region,
- space.region_3d,
- compat.matmul(world_mat, l.vert.co))
- for f in sel_faces for l in f.loops
- ]
-
- # transform screen region to canvas
- v_canvas = [
- _region_to_canvas(
- v,
- _get_canvas(bpy.context,
- sc.muv_texture_projection_tex_magnitude)
- ) for v in v_screen
- ]
-
- if compat.check_version(2, 80, 0) >= 0:
- # set texture
- nodes = common.find_texture_nodes(obj)
- nodes[0].image = \
- bpy.data.images[sc.muv_texture_projection_tex_image]
-
- # project texture to object
- i = 0
- for f in sel_faces:
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ world_mat = obj.matrix_world
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ # get UV and texture layer
+ if not bm.loops.layers.uv:
+ if sc.muv_texture_projection_assign_uvmap:
+ bm.loops.layers.uv.new()
+ else:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+
+ uv_layer = bm.loops.layers.uv.verify()
if compat.check_version(2, 80, 0) < 0:
- f[tex_layer].image = \
+ tex_layer = bm.faces.layers.tex.verify()
+
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # transform 3d space to screen region
+ v_screen = [
+ view3d_utils.location_3d_to_region_2d(
+ region,
+ space.region_3d,
+ compat.matmul(world_mat, l.vert.co))
+ for f in sel_faces for l in f.loops
+ ]
+
+ # transform screen region to canvas
+ v_canvas = [
+ _region_to_canvas(
+ v,
+ _get_canvas(bpy.context,
+ sc.muv_texture_projection_tex_magnitude)
+ ) for v in v_screen
+ ]
+
+ # assign image
+ if compat.check_version(2, 80, 0) >= 0:
+ node_tree = obj.active_material.node_tree
+ output_node = node_tree.nodes["Material Output"]
+
+ nodes = common.find_texture_nodes_from_material(
+ obj.active_material)
+ if len(nodes) >= 1:
+ tex_node = nodes[0]
+ else:
+ tex_node = node_tree.nodes.new(type="ShaderNodeTexImage")
+ tex_node.image = \
bpy.data.images[sc.muv_texture_projection_tex_image]
- for l in f.loops:
- l[uv_layer].uv = v_canvas[i].to_2d()
- i = i + 1
-
- common.redraw_all_areas()
- bmesh.update_edit_mesh(obj.data)
+ node_tree.links.new(
+ output_node.inputs["Surface"], tex_node.outputs["Color"])
+ else:
+ for f in sel_faces:
+ f[tex_layer].image = \
+ bpy.data.images[sc.muv_texture_projection_tex_image]
+
+ # project texture to object
+ i = 0
+ for f in sel_faces:
+ for l in f.loops:
+ l[uv_layer].uv = v_canvas[i].to_2d()
+ i = i + 1
+
+ common.redraw_all_areas()
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/texture_wrap.py b/magic_uv/op/texture_wrap.py
index 92512438..5fd0cfe6 100644
--- a/magic_uv/op/texture_wrap.py
+++ b/magic_uv/op/texture_wrap.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import (
@@ -35,21 +35,17 @@ from ..utils.property_class_registry import PropertyClassRegistry
def _is_valid_context(context):
- obj = context.object
+ # Multiple objects editing mode is not supported in this feature.
+ objs = common.get_uv_editable_objects(context)
+ if len(objs) != 1:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -111,7 +107,10 @@ class MUV_OT_TextureWrap_Refer(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.texture_wrap
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
@@ -156,7 +155,10 @@ class MUV_OT_TextureWrap_Set(bpy.types.Operator):
def execute(self, context):
sc = context.scene
props = sc.muv_props.texture_wrap
- obj = context.active_object
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
diff --git a/magic_uv/op/transfer_uv.py b/magic_uv/op/transfer_uv.py
index ce9639a7..bcf9fab9 100644
--- a/magic_uv/op/transfer_uv.py
+++ b/magic_uv/op/transfer_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from collections import OrderedDict
@@ -36,21 +36,17 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ # Multiple objects editing mode is not supported in this feature.
+ objs = common.get_uv_editable_objects(context)
+ if len(objs) != 1:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -377,8 +373,11 @@ class MUV_OT_TransferUV_CopyUV(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.transfer_uv
- active_obj = context.active_object
- bm = bmesh.from_edit_mesh(active_obj.data)
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
+ bm = bmesh.from_edit_mesh(obj.data)
if compat.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
@@ -391,7 +390,7 @@ class MUV_OT_TransferUV_CopyUV(bpy.types.Operator):
return {'CANCELLED'}
props.topology_copied = faces
- bmesh.update_edit_mesh(active_obj.data)
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
@@ -433,8 +432,11 @@ class MUV_OT_TransferUV_PasteUV(bpy.types.Operator):
def execute(self, context):
props = context.scene.muv_props.transfer_uv
- active_obj = context.active_object
- bm = bmesh.from_edit_mesh(active_obj.data)
+
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
+ bm = bmesh.from_edit_mesh(obj.data)
if compat.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
@@ -448,10 +450,10 @@ class MUV_OT_TransferUV_PasteUV(bpy.types.Operator):
if ret:
return {'CANCELLED'}
- bmesh.update_edit_mesh(active_obj.data)
+ bmesh.update_edit_mesh(obj.data)
if compat.check_version(2, 80, 0) < 0:
if self.copy_seams:
- active_obj.data.show_edge_seams = True
+ obj.data.show_edge_seams = True
return {'FINISHED'}
diff --git a/magic_uv/op/unwrap_constraint.py b/magic_uv/op/unwrap_constraint.py
index 3c23575a..dcaa79b4 100644
--- a/magic_uv/op/unwrap_constraint.py
+++ b/magic_uv/op/unwrap_constraint.py
@@ -18,8 +18,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import (
@@ -36,21 +36,16 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -146,23 +141,28 @@ class MUV_OT_UnwrapConstraint(bpy.types.Operator):
return _is_valid_context(context)
def execute(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
-
- # bpy.ops.uv.unwrap() makes one UV map at least
- 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()
-
- # get original UV coordinate
- faces = [f for f in bm.faces if f.select]
- uv_list = []
- for f in faces:
- uvs = [l[uv_layer].uv.copy() for l in f.loops]
- uv_list.append(uvs)
+ objs = common.get_uv_editable_objects(context)
+
+ uv_list = {} # { Object: uv_list }
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ # bpy.ops.uv.unwrap() makes one UV map at least
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # get original UV coordinate
+ faces = [f for f in bm.faces if f.select]
+ uv_list[obj] = []
+ for f in faces:
+ uvs = [l[uv_layer].uv.copy() for l in f.loops]
+ uv_list[obj].append(uvs)
# unwrap
bpy.ops.uv.unwrap(
@@ -173,14 +173,21 @@ class MUV_OT_UnwrapConstraint(bpy.types.Operator):
margin=self.margin)
# when U/V-Constraint is checked, revert original coordinate
- for f, uvs in zip(faces, uv_list):
- for l, uv in zip(f.loops, uvs):
- if self.u_const:
- l[uv_layer].uv.x = uv.x
- if self.v_const:
- l[uv_layer].uv.y = uv.y
-
- # update mesh
- bmesh.update_edit_mesh(obj.data)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+ faces = [f for f in bm.faces if f.select]
+
+ for f, uvs in zip(faces, uv_list[obj]):
+ for l, uv in zip(f.loops, uvs):
+ if self.u_const:
+ l[uv_layer].uv.x = uv.x
+ if self.v_const:
+ l[uv_layer].uv.y = uv.y
+
+ # update mesh
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/uv_bounding_box.py b/magic_uv/op/uv_bounding_box.py
index d4edac9c..fc3455b4 100644
--- a/magic_uv/op/uv_bounding_box.py
+++ b/magic_uv/op/uv_bounding_box.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from enum import IntEnum
import math
@@ -59,10 +59,7 @@ 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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -700,23 +697,35 @@ class MUV_OT_UVBoundingBox(bpy.types.Operator):
Get UV coordinate
"""
sc = context.scene
- obj = context.active_object
+ objs = common.get_uv_editable_objects(context)
uv_info = []
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- if not bm.loops.layers.uv:
- return None
- uv_layer = bm.loops.layers.uv.verify()
- for f in bm.faces:
- if not f.select:
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ if not bm.loops.layers.uv:
continue
- for i, l in enumerate(f.loops):
- if sc.muv_uv_bounding_box_boundary == 'UV_SEL':
- if l[uv_layer].select:
- uv_info.append((f.index, i, l[uv_layer].uv.copy()))
- elif sc.muv_uv_bounding_box_boundary == 'UV':
- uv_info.append((f.index, i, l[uv_layer].uv.copy()))
+ uv_layer = bm.loops.layers.uv.verify()
+ for f in bm.faces:
+ if not f.select:
+ continue
+ for i, l in enumerate(f.loops):
+ if sc.muv_uv_bounding_box_boundary == 'UV_SEL':
+ if l[uv_layer].select:
+ uv_info.append({
+ "bmesh": bm,
+ "fidx": f.index,
+ "lidx": i,
+ "uv": l[uv_layer].uv.copy()
+ })
+ elif sc.muv_uv_bounding_box_boundary == 'UV':
+ uv_info.append({
+ "bmesh": bm,
+ "fidx": f.index,
+ "lidx": i,
+ "uv": l[uv_layer].uv.copy()
+ })
if not uv_info:
return None
return uv_info
@@ -731,7 +740,7 @@ class MUV_OT_UVBoundingBox(bpy.types.Operator):
bottom = MAX_VALUE
for info in uv_info_ini:
- uv = info[2]
+ uv = info["uv"]
if uv.x < left:
left = uv.x
if uv.x > right:
@@ -762,22 +771,21 @@ class MUV_OT_UVBoundingBox(bpy.types.Operator):
"""
Update UV coordinate
"""
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.faces.ensure_lookup_table()
- if not bm.loops.layers.uv:
- return
- uv_layer = bm.loops.layers.uv.verify()
+
for info in uv_info_ini:
- fidx = info[0]
- lidx = info[1]
- uv = info[2]
+ bm = info["bmesh"]
+ uv_layer = bm.loops.layers.uv.verify()
+ fidx = info["fidx"]
+ lidx = info["lidx"]
+ uv = info["uv"]
v = mathutils.Vector((uv.x, uv.y, 0.0))
av = compat.matmul(trans_mat, v)
bm.faces[fidx].loops[lidx][uv_layer].uv = mathutils.Vector(
(av.x, av.y))
- bmesh.update_edit_mesh(obj.data)
+
+ objs = common.get_uv_editable_objects(context)
+ for obj in objs:
+ bmesh.update_edit_mesh(obj.data)
def __update_ctrl_point(self, ctrl_points_ini, trans_mat):
"""
diff --git a/magic_uv/op/uv_inspection.py b/magic_uv/op/uv_inspection.py
index 8aae181e..49525b98 100644
--- a/magic_uv/op/uv_inspection.py
+++ b/magic_uv/op/uv_inspection.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import random
from math import fabs
@@ -42,23 +42,18 @@ else:
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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
# '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
- for space in context.area.spaces:
- if (space.type == 'IMAGE_EDITOR') or (space.type == 'VIEW_3D'):
- break
- else:
+ if not common.is_valid_space(context, ['IMAGE_EDITOR', 'VIEW_3D']):
return False
return True
@@ -67,17 +62,13 @@ def _is_valid_context(context):
def _update_uvinsp_info(context):
sc = context.scene
props = sc.muv_props.uv_inspection
+ objs = common.get_uv_editable_objects(context)
bm_list = []
uv_layer_list = []
faces_list = []
- for o in bpy.data.objects:
- if not compat.get_object_select(o):
- continue
- if o.type != 'MESH':
- continue
-
- bm = bmesh.from_edit_mesh(o.data)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
uv_layer = bm.loops.layers.uv.verify()
@@ -359,137 +350,156 @@ class MUV_OT_UVInspection_PaintUVIsland(bpy.types.Operator):
return None
def execute(self, context):
- obj = context.active_object
+ selected_objs_orig = [o for o in bpy.data.objects
+ if compat.get_object_select(o)]
+ active_obj_orig = compat.get_active_object(context)
+
+ objs = common.get_uv_editable_objects(context)
mode_orig = context.object.mode
override_context = self._get_override_context(context)
if override_context is None:
self.report({'WARNING'}, "More than one 'VIEW_3D' area must exist")
return {'CANCELLED'}
- # Setup material of drawing target.
- target_image = self._get_or_new_image(
- "MagicUV_PaintUVIsland", 4096, 4096)
- target_mtrl = self._get_or_new_material("MagicUV_PaintUVMaterial")
- if compat.check_version(2, 80, 0) >= 0:
- target_mtrl.use_nodes = True
- output_node = target_mtrl.node_tree.nodes["Material Output"]
- nodes_to_remove = [n for n in target_mtrl.node_tree.nodes
- if n != output_node]
- for n in nodes_to_remove:
- target_mtrl.node_tree.nodes.remove(n)
- texture_node = \
- target_mtrl.node_tree.nodes.new("ShaderNodeTexImage")
- texture_node.image = target_image
- target_mtrl.node_tree.links.new(output_node.inputs["Surface"],
- texture_node.outputs["Color"])
- obj.data.use_paint_mask = True
-
- # Apply material to object (all faces).
- found = False
- for mtrl_idx, mtrl_slot in enumerate(obj.material_slots):
- if mtrl_slot.material == target_mtrl:
- found = True
- break
- if not found:
- bpy.ops.object.material_slot_add()
- mtrl_idx = len(obj.material_slots) - 1
- obj.material_slots[mtrl_idx].material = target_mtrl
- bpy.ops.object.mode_set(mode='EDIT')
- bm = bmesh.from_edit_mesh(obj.data)
- bm.faces.ensure_lookup_table()
- for f in bm.faces:
- f.select = True
- bmesh.update_edit_mesh(obj.data)
- obj.active_material_index = mtrl_idx
- obj.active_material = target_mtrl
- bpy.ops.object.material_slot_assign()
- else:
- target_tex_slot = target_mtrl.texture_slots.add()
- target_tex = self._get_or_new_texture("MagicUV_PaintUVTexture")
- target_tex_slot.texture = target_tex
- obj.data.use_paint_mask = True
-
- # Apply material to object (all faces).
- found = False
- for mtrl_idx, mtrl_slot in enumerate(obj.material_slots):
- if mtrl_slot.material == target_mtrl:
- found = True
- break
- if not found:
- bpy.ops.object.material_slot_add()
- mtrl_idx = len(obj.material_slots) - 1
- obj.material_slots[mtrl_idx].material = target_mtrl
- bpy.ops.object.mode_set(mode='EDIT')
- bm = bmesh.from_edit_mesh(obj.data)
- bm.faces.ensure_lookup_table()
- for f in bm.faces:
- f.select = True
- bmesh.update_edit_mesh(obj.data)
- obj.active_material_index = mtrl_idx
- obj.active_material = target_mtrl
- bpy.ops.object.material_slot_assign()
-
- # Update active image in Image Editor.
- _, _, space = common.get_space(
- 'IMAGE_EDITOR', 'WINDOW', 'IMAGE_EDITOR')
- if space is None:
- return {'CANCELLED'}
- space.image = target_image
-
- # Analyze island to make map between face and paint color.
- islands = common.get_island_info_from_bmesh(bm)
- color_to_faces = []
- for isl in islands:
- color = self._create_unique_color([c[0] for c in color_to_faces])
- if color is None:
- self.report({'WARNING'},
- "Failed to create color. Please try again")
- return {'CANCELLED'}
- indices = [f["face"].index for f in isl["faces"]]
- color_to_faces.append((color, indices))
-
- for cf in color_to_faces:
- # Update selection information.
- bpy.ops.object.mode_set(mode='EDIT')
- bm = bmesh.from_edit_mesh(obj.data)
- bm.faces.ensure_lookup_table()
- for f in bm.faces:
- f.select = False
- for fidx in cf[1]:
- bm.faces[fidx].select = True
- bmesh.update_edit_mesh(obj.data)
- bpy.ops.object.mode_set(mode='OBJECT')
-
- # Update brush color.
- bpy.data.brushes["Fill"].color = cf[0]
-
- # Paint.
- bpy.ops.object.mode_set(mode='TEXTURE_PAINT')
+ for i, obj in enumerate(objs):
+ # Select/Active only one object to paint.
+ for o in objs:
+ compat.set_object_select(o, False)
+ compat.set_object_select(obj, True)
+ compat.set_active_object(obj)
+
+ # Setup material of drawing target.
+ target_image = self._get_or_new_image(
+ "MagicUV_PaintUVIsland_{}".format(i), 4096, 4096)
+ target_mtrl = self._get_or_new_material(
+ "MagicUV_PaintUVMaterial_{}".format(i))
if compat.check_version(2, 80, 0) >= 0:
- bpy.ops.paint.brush_select(override_context, image_tool='FILL')
+ target_mtrl.use_nodes = True
+ output_node = target_mtrl.node_tree.nodes["Material Output"]
+ nodes_to_remove = [n for n in target_mtrl.node_tree.nodes
+ if n != output_node]
+ for n in nodes_to_remove:
+ target_mtrl.node_tree.nodes.remove(n)
+ texture_node = \
+ target_mtrl.node_tree.nodes.new("ShaderNodeTexImage")
+ texture_node.image = target_image
+ target_mtrl.node_tree.links.new(output_node.inputs["Surface"],
+ texture_node.outputs["Color"])
+ obj.data.use_paint_mask = True
+
+ # Apply material to object (all faces).
+ found = False
+ for mtrl_idx, mtrl_slot in enumerate(obj.material_slots):
+ if mtrl_slot.material == target_mtrl:
+ found = True
+ break
+ if not found:
+ bpy.ops.object.material_slot_add()
+ mtrl_idx = len(obj.material_slots) - 1
+ obj.material_slots[mtrl_idx].material = target_mtrl
+ bpy.ops.object.mode_set(mode='EDIT')
+ bm = bmesh.from_edit_mesh(obj.data)
+ bm.faces.ensure_lookup_table()
+ for f in bm.faces:
+ f.select = True
+ bmesh.update_edit_mesh(obj.data)
+ obj.active_material_index = mtrl_idx
+ obj.active_material = target_mtrl
+ bpy.ops.object.material_slot_assign()
else:
- paint_settings = \
- bpy.data.scenes['Scene'].tool_settings.image_paint
- paint_mode_orig = paint_settings.mode
- paint_canvas_orig = paint_settings.canvas
- paint_settings.mode = 'IMAGE'
- paint_settings.canvas = target_image
- bpy.ops.paint.brush_select(override_context,
- texture_paint_tool='FILL')
- bpy.ops.paint.image_paint(override_context, stroke=[{
- "name": "",
- "location": (0, 0, 0),
- "mouse": (0, 0),
- "size": 0,
- "pressure": 0,
- "pen_flip": False,
- "time": 0,
- "is_start": False
- }])
-
- if compat.check_version(2, 80, 0) < 0:
- paint_settings.mode = paint_mode_orig
- paint_settings.canvas = paint_canvas_orig
+ target_tex_slot = target_mtrl.texture_slots.add()
+ target_tex = self._get_or_new_texture(
+ "MagicUV_PaintUVTexture_{}".format(i))
+ target_tex_slot.texture = target_tex
+ obj.data.use_paint_mask = True
+
+ # Apply material to object (all faces).
+ found = False
+ for mtrl_idx, mtrl_slot in enumerate(obj.material_slots):
+ if mtrl_slot.material == target_mtrl:
+ found = True
+ break
+ if not found:
+ bpy.ops.object.material_slot_add()
+ mtrl_idx = len(obj.material_slots) - 1
+ obj.material_slots[mtrl_idx].material = target_mtrl
+ bpy.ops.object.mode_set(mode='EDIT')
+ bm = bmesh.from_edit_mesh(obj.data)
+ bm.faces.ensure_lookup_table()
+ for f in bm.faces:
+ f.select = True
+ bmesh.update_edit_mesh(obj.data)
+ obj.active_material_index = mtrl_idx
+ obj.active_material = target_mtrl
+ bpy.ops.object.material_slot_assign()
+
+ # Update active image in Image Editor.
+ _, _, space = common.get_space(
+ 'IMAGE_EDITOR', 'WINDOW', 'IMAGE_EDITOR')
+ if space is None:
+ return {'CANCELLED'}
+ space.image = target_image
+
+ # Analyze island to make map between face and paint color.
+ islands = common.get_island_info_from_bmesh(bm)
+ color_to_faces = []
+ for isl in islands:
+ color = self._create_unique_color(
+ [c[0] for c in color_to_faces])
+ if color is None:
+ self.report({'WARNING'},
+ "Failed to create color. Please try again")
+ return {'CANCELLED'}
+ indices = [f["face"].index for f in isl["faces"]]
+ color_to_faces.append((color, indices))
+
+ for cf in color_to_faces:
+ # Update selection information.
+ bpy.ops.object.mode_set(mode='EDIT')
+ bm = bmesh.from_edit_mesh(obj.data)
+ bm.faces.ensure_lookup_table()
+ for f in bm.faces:
+ f.select = False
+ for fidx in cf[1]:
+ bm.faces[fidx].select = True
+ bmesh.update_edit_mesh(obj.data)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Update brush color.
+ bpy.data.brushes["Fill"].color = cf[0]
+
+ # Paint.
+ bpy.ops.object.mode_set(mode='TEXTURE_PAINT')
+ if compat.check_version(2, 80, 0) >= 0:
+ bpy.ops.paint.brush_select(override_context,
+ image_tool='FILL')
+ else:
+ paint_settings = \
+ bpy.data.scenes['Scene'].tool_settings.image_paint
+ paint_mode_orig = paint_settings.mode
+ paint_canvas_orig = paint_settings.canvas
+ paint_settings.mode = 'IMAGE'
+ paint_settings.canvas = target_image
+ bpy.ops.paint.brush_select(override_context,
+ texture_paint_tool='FILL')
+ bpy.ops.paint.image_paint(override_context, stroke=[{
+ "name": "",
+ "location": (0, 0, 0),
+ "mouse": (0, 0),
+ "size": 0,
+ "pressure": 0,
+ "pen_flip": False,
+ "time": 0,
+ "is_start": False
+ }])
+
+ if compat.check_version(2, 80, 0) < 0:
+ paint_settings.mode = paint_mode_orig
+ paint_settings.canvas = paint_canvas_orig
+
+ for obj in selected_objs_orig:
+ compat.set_object_select(obj, True)
+ compat.set_active_object(active_obj_orig)
bpy.ops.object.mode_set(mode=mode_orig)
diff --git a/magic_uv/op/uv_sculpt.py b/magic_uv/op/uv_sculpt.py
index f40ab253..c9ed4f34 100644
--- a/magic_uv/op/uv_sculpt.py
+++ b/magic_uv/op/uv_sculpt.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from math import pi, cos, tan, sin
@@ -51,21 +51,16 @@ else:
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -255,7 +250,7 @@ class MUV_OT_UVSculpt(bpy.types.Operator):
bgl.glEnd()
def __init__(self):
- self.__loop_info = []
+ self.__loop_info = {} # { Object: loop_info }
self.__stroking = False
self.current_mco = Vector((0.0, 0.0))
self.__initial_mco = Vector((0.0, 0.0))
@@ -265,52 +260,17 @@ class MUV_OT_UVSculpt(bpy.types.Operator):
self.__initial_mco = self.current_mco
- # get influenced UV
- obj = context.active_object
- world_mat = obj.matrix_world
- bm = bmesh.from_edit_mesh(obj.data)
- uv_layer = bm.loops.layers.uv.verify()
- _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
-
- self.__loop_info = []
- for f in bm.faces:
- if not f.select:
- continue
- for i, l in enumerate(f.loops):
- loc_2d = location_3d_to_region_2d_extra(
- region, space.region_3d,
- compat.matmul(world_mat, l.vert.co))
- diff = loc_2d - self.__initial_mco
- if diff.length < sc.muv_uv_sculpt_radius:
- info = {
- "face_idx": f.index,
- "loop_idx": i,
- "initial_vco": l.vert.co.copy(),
- "initial_vco_2d": loc_2d,
- "initial_uv": l[uv_layer].uv.copy(),
- "strength": _get_strength(
- diff.length, sc.muv_uv_sculpt_radius,
- sc.muv_uv_sculpt_strength)
- }
- self.__loop_info.append(info)
+ objs = common.get_uv_editable_objects(context)
- def __stroke_apply(self, context, _):
- sc = context.scene
- obj = context.active_object
- world_mat = obj.matrix_world
- bm = bmesh.from_edit_mesh(obj.data)
- uv_layer = bm.loops.layers.uv.verify()
- mco = self.current_mco
-
- if sc.muv_uv_sculpt_tools == 'GRAB':
- for info in self.__loop_info:
- diff_uv = (mco - self.__initial_mco) * info["strength"]
- l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
- l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
-
- elif sc.muv_uv_sculpt_tools == 'PINCH':
+ # get influenced UV
+ self.__loop_info = {}
+ for obj in objs:
+ world_mat = obj.matrix_world
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
_, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
- loop_info = []
+
+ self.__loop_info[obj] = []
for f in bm.faces:
if not f.select:
continue
@@ -330,123 +290,170 @@ class MUV_OT_UVSculpt(bpy.types.Operator):
diff.length, sc.muv_uv_sculpt_radius,
sc.muv_uv_sculpt_strength)
}
- loop_info.append(info)
-
- # mouse coordinate to UV coordinate
- ray_vec = view3d_utils.region_2d_to_vector_3d(region,
- space.region_3d, mco)
- ray_vec.normalize()
- ray_orig = view3d_utils.region_2d_to_origin_3d(region,
- space.region_3d,
- mco)
- ray_tgt = ray_orig + ray_vec * 1000000.0
- mwi = world_mat.inverted()
- ray_orig_obj = compat.matmul(mwi, ray_orig)
- ray_tgt_obj = compat.matmul(mwi, ray_tgt)
- ray_dir_obj = ray_tgt_obj - ray_orig_obj
- ray_dir_obj.normalize()
- tree = BVHTree.FromBMesh(bm)
- loc, _, fidx, _ = tree.ray_cast(ray_orig_obj, ray_dir_obj)
- if not loc:
- return
- loops = [l for l in bm.faces[fidx].loops]
- uvs = [Vector((l[uv_layer].uv.x, l[uv_layer].uv.y, 0.0))
- for l in loops]
- target_uv = barycentric_transform(
- loc, loops[0].vert.co, loops[1].vert.co, loops[2].vert.co,
- uvs[0], uvs[1], uvs[2])
- target_uv = Vector((target_uv.x, target_uv.y))
-
- # move to target UV coordinate
- for info in loop_info:
- l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
- if sc.muv_uv_sculpt_pinch_invert:
- diff_uv = (l[uv_layer].uv - target_uv) * info["strength"]
- else:
- diff_uv = (target_uv - l[uv_layer].uv) * info["strength"]
- l[uv_layer].uv = l[uv_layer].uv + diff_uv / 10.0
-
- elif sc.muv_uv_sculpt_tools == 'RELAX':
- _, region, space = common.get_space('VIEW_3D', 'WINDOW', 'VIEW_3D')
+ self.__loop_info[obj].append(info)
- # get vertex and loop relation
- vert_db = {}
- for f in bm.faces:
- for l in f.loops:
- if l.vert in vert_db:
- vert_db[l.vert]["loops"].append(l)
- else:
- vert_db[l.vert] = {"loops": [l]}
-
- # get relaxation information
- for k in vert_db.keys():
- d = vert_db[k]
- d["uv_sum"] = Vector((0.0, 0.0))
- d["uv_count"] = 0
-
- for l in d["loops"]:
- ln = l.link_loop_next
- lp = l.link_loop_prev
- d["uv_sum"] = d["uv_sum"] + ln[uv_layer].uv
- d["uv_sum"] = d["uv_sum"] + lp[uv_layer].uv
- d["uv_count"] = d["uv_count"] + 2
- d["uv_p"] = d["uv_sum"] / d["uv_count"]
- d["uv_b"] = d["uv_p"] - d["loops"][0][uv_layer].uv
- for k in vert_db.keys():
- d = vert_db[k]
- d["uv_sum_b"] = Vector((0.0, 0.0))
- for l in d["loops"]:
- ln = l.link_loop_next
- lp = l.link_loop_prev
- dn = vert_db[ln.vert]
- dp = vert_db[lp.vert]
- d["uv_sum_b"] = d["uv_sum_b"] + dn["uv_b"] + dp["uv_b"]
-
- # apply
- for f in bm.faces:
- if not f.select:
- continue
- for i, l in enumerate(f.loops):
- loc_2d = location_3d_to_region_2d_extra(
- region, space.region_3d,
- compat.matmul(world_mat, l.vert.co))
- diff = loc_2d - self.__initial_mco
- if diff.length >= sc.muv_uv_sculpt_radius:
+ def __stroke_apply(self, context, _):
+ sc = context.scene
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ world_mat = obj.matrix_world
+ bm = bmesh.from_edit_mesh(obj.data)
+ uv_layer = bm.loops.layers.uv.verify()
+ mco = self.current_mco
+
+ if sc.muv_uv_sculpt_tools == 'GRAB':
+ for info in self.__loop_info[obj]:
+ diff_uv = (mco - self.__initial_mco) * info["strength"]
+ l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
+ l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
+
+ elif sc.muv_uv_sculpt_tools == 'PINCH':
+ _, region, space = common.get_space(
+ 'VIEW_3D', 'WINDOW', 'VIEW_3D')
+ loop_info = []
+ for f in bm.faces:
+ if not f.select:
continue
- db = vert_db[l.vert]
- strength = _get_strength(diff.length,
- sc.muv_uv_sculpt_radius,
- sc.muv_uv_sculpt_strength)
-
- base = (1.0 - strength) * l[uv_layer].uv
- if sc.muv_uv_sculpt_relax_method == 'HC':
- t = 0.5 * (db["uv_b"] + db["uv_sum_b"] / d["uv_count"])
- diff = strength * (db["uv_p"] - t)
- target_uv = base + diff
- elif sc.muv_uv_sculpt_relax_method == 'LAPLACIAN':
- diff = strength * db["uv_p"]
- target_uv = base + diff
+ for i, l in enumerate(f.loops):
+ loc_2d = location_3d_to_region_2d_extra(
+ region, space.region_3d,
+ compat.matmul(world_mat, l.vert.co))
+ diff = loc_2d - self.__initial_mco
+ if diff.length < sc.muv_uv_sculpt_radius:
+ info = {
+ "face_idx": f.index,
+ "loop_idx": i,
+ "initial_vco": l.vert.co.copy(),
+ "initial_vco_2d": loc_2d,
+ "initial_uv": l[uv_layer].uv.copy(),
+ "strength": _get_strength(
+ diff.length, sc.muv_uv_sculpt_radius,
+ sc.muv_uv_sculpt_strength)
+ }
+ loop_info.append(info)
+
+ # mouse coordinate to UV coordinate
+ ray_vec = view3d_utils.region_2d_to_vector_3d(
+ region, space.region_3d, mco)
+ ray_vec.normalize()
+ ray_orig = view3d_utils.region_2d_to_origin_3d(
+ region, space.region_3d, mco)
+ ray_tgt = ray_orig + ray_vec * 1000000.0
+ mwi = world_mat.inverted()
+ ray_orig_obj = compat.matmul(mwi, ray_orig)
+ ray_tgt_obj = compat.matmul(mwi, ray_tgt)
+ ray_dir_obj = ray_tgt_obj - ray_orig_obj
+ ray_dir_obj.normalize()
+ tree = BVHTree.FromBMesh(bm)
+ loc, _, fidx, _ = tree.ray_cast(ray_orig_obj, ray_dir_obj)
+ if not loc:
+ return
+ loops = [l for l in bm.faces[fidx].loops]
+ uvs = [Vector((l[uv_layer].uv.x, l[uv_layer].uv.y, 0.0))
+ for l in loops]
+ target_uv = barycentric_transform(
+ loc,
+ loops[0].vert.co, loops[1].vert.co, loops[2].vert.co,
+ uvs[0], uvs[1], uvs[2])
+ target_uv = Vector((target_uv.x, target_uv.y))
+
+ # move to target UV coordinate
+ for info in loop_info:
+ l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
+ if sc.muv_uv_sculpt_pinch_invert:
+ diff_uv = \
+ (l[uv_layer].uv - target_uv) * info["strength"]
else:
+ diff_uv = \
+ (target_uv - l[uv_layer].uv) * info["strength"]
+ l[uv_layer].uv = l[uv_layer].uv + diff_uv / 10.0
+
+ elif sc.muv_uv_sculpt_tools == 'RELAX':
+ _, region, space = common.get_space(
+ 'VIEW_3D', 'WINDOW', 'VIEW_3D')
+
+ # get vertex and loop relation
+ vert_db = {}
+ for f in bm.faces:
+ for l in f.loops:
+ if l.vert in vert_db:
+ vert_db[l.vert]["loops"].append(l)
+ else:
+ vert_db[l.vert] = {"loops": [l]}
+
+ # get relaxation information
+ for k in vert_db.keys():
+ d = vert_db[k]
+ d["uv_sum"] = Vector((0.0, 0.0))
+ d["uv_count"] = 0
+
+ for l in d["loops"]:
+ ln = l.link_loop_next
+ lp = l.link_loop_prev
+ d["uv_sum"] = d["uv_sum"] + ln[uv_layer].uv
+ d["uv_sum"] = d["uv_sum"] + lp[uv_layer].uv
+ d["uv_count"] = d["uv_count"] + 2
+ d["uv_p"] = d["uv_sum"] / d["uv_count"]
+ d["uv_b"] = d["uv_p"] - d["loops"][0][uv_layer].uv
+ for k in vert_db.keys():
+ d = vert_db[k]
+ d["uv_sum_b"] = Vector((0.0, 0.0))
+ for l in d["loops"]:
+ ln = l.link_loop_next
+ lp = l.link_loop_prev
+ dn = vert_db[ln.vert]
+ dp = vert_db[lp.vert]
+ d["uv_sum_b"] = d["uv_sum_b"] + dn["uv_b"] + dp["uv_b"]
+
+ # apply
+ for f in bm.faces:
+ if not f.select:
continue
-
- l[uv_layer].uv = target_uv
-
- bmesh.update_edit_mesh(obj.data)
+ for i, l in enumerate(f.loops):
+ loc_2d = location_3d_to_region_2d_extra(
+ region, space.region_3d,
+ compat.matmul(world_mat, l.vert.co))
+ diff = loc_2d - self.__initial_mco
+ if diff.length >= sc.muv_uv_sculpt_radius:
+ continue
+ db = vert_db[l.vert]
+ strength = _get_strength(diff.length,
+ sc.muv_uv_sculpt_radius,
+ sc.muv_uv_sculpt_strength)
+
+ base = (1.0 - strength) * l[uv_layer].uv
+ if sc.muv_uv_sculpt_relax_method == 'HC':
+ t = 0.5 * \
+ (db["uv_b"] + db["uv_sum_b"] / d["uv_count"])
+ diff = strength * (db["uv_p"] - t)
+ target_uv = base + diff
+ elif sc.muv_uv_sculpt_relax_method == 'LAPLACIAN':
+ diff = strength * db["uv_p"]
+ target_uv = base + diff
+ else:
+ continue
+
+ l[uv_layer].uv = target_uv
+
+ bmesh.update_edit_mesh(obj.data)
def __stroke_exit(self, context, _):
sc = context.scene
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- uv_layer = bm.loops.layers.uv.verify()
- mco = self.current_mco
-
- if sc.muv_uv_sculpt_tools == 'GRAB':
- for info in self.__loop_info:
- diff_uv = (mco - self.__initial_mco) * info["strength"]
- l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
- l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
-
- bmesh.update_edit_mesh(obj.data)
+ 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()
+ mco = self.current_mco
+
+ if sc.muv_uv_sculpt_tools == 'GRAB':
+ for info in self.__loop_info[obj]:
+ diff_uv = (mco - self.__initial_mco) * info["strength"]
+ l = bm.faces[info["face_idx"]].loops[info["loop_idx"]]
+ l[uv_layer].uv = info["initial_uv"] + diff_uv / 100.0
+
+ bmesh.update_edit_mesh(obj.data)
def modal(self, context, event):
if context.area:
diff --git a/magic_uv/op/uvw.py b/magic_uv/op/uvw.py
index fca72d2c..42918dd5 100644
--- a/magic_uv/op/uvw.py
+++ b/magic_uv/op/uvw.py
@@ -20,8 +20,8 @@
__author__ = "Alexander Milovsky, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from math import sin, cos, pi
@@ -41,21 +41,16 @@ from ..utils import compatibility as compat
def _is_valid_context(context):
- obj = context.object
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
+ return False
# 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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -228,15 +223,11 @@ class MUV_OT_UVW_BoxMap(bpy.types.Operator):
return True
return _is_valid_context(context)
- def execute(self, _):
- if compat.check_version(2, 80, 0) < 0:
- objs = [bpy.context.active_object]
- else:
- objs = [o for o in bpy.data.objects
- if compat.get_object_select(o) and o.type == 'MESH']
+ def execute(self, context):
+ objs = common.get_uv_editable_objects(context)
- for o in objs:
- bm = bmesh.from_edit_mesh(o.data)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
@@ -247,7 +238,7 @@ class MUV_OT_UVW_BoxMap(bpy.types.Operator):
_apply_box_map(bm, uv_layer, self.size, self.offset, self.rotation,
self.tex_aspect)
- bmesh.update_edit_mesh(o.data)
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
@@ -291,15 +282,11 @@ class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator):
return True
return _is_valid_context(context)
- def execute(self, _):
- if compat.check_version(2, 80, 0) < 0:
- objs = [bpy.context.active_object]
- else:
- objs = [o for o in bpy.data.objects
- if compat.get_object_select(o) and o.type == 'MESH']
+ def execute(self, context):
+ objs = common.get_uv_editable_objects(context)
- for o in objs:
- bm = bmesh.from_edit_mesh(o.data)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
if common.check_version(2, 73, 0) >= 0:
bm.faces.ensure_lookup_table()
@@ -311,6 +298,6 @@ class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator):
_apply_planer_map(bm, uv_layer, self.size, self.offset,
self.rotation, self.tex_aspect)
- bmesh.update_edit_mesh(o.data)
+ bmesh.update_edit_mesh(obj.data)
return {'FINISHED'}
diff --git a/magic_uv/op/world_scale_uv.py b/magic_uv/op/world_scale_uv.py
index 9ed86eb0..a2806db5 100644
--- a/magic_uv/op/world_scale_uv.py
+++ b/magic_uv/op/world_scale_uv.py
@@ -20,8 +20,8 @@
__author__ = "McBuff, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from math import sqrt
@@ -41,22 +41,34 @@ from ..utils.property_class_registry import PropertyClassRegistry
from ..utils import compatibility as compat
-def _is_valid_context(context):
- obj = context.object
+def _is_valid_context_for_measure(context):
+ # Multiple objects editing mode is not supported in this feature.
+ objs = common.get_uv_editable_objects(context)
+ if len(objs) != 1:
+ return False
# only edit mode is allowed to execute
- if obj is None:
+ if context.object.mode != 'EDIT':
+ return False
+
+ # only 'VIEW_3D' space is allowed to execute
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
- if obj.type != 'MESH':
+
+ return True
+
+
+def _is_valid_context_for_apply(context):
+ objs = common.get_uv_editable_objects(context)
+ if not objs:
return False
+
+ # only edit mode is allowed to execute
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:
+ if not common.is_valid_space(context, ['VIEW_3D']):
return False
return True
@@ -191,7 +203,11 @@ def _apply(faces, uv_layer, origin, factor):
def _get_target_textures(_, __):
- images = common.find_images(bpy.context.active_object)
+ objs = common.get_uv_editable_objects(bpy.context)
+ images = []
+ for obj in objs:
+ images.extend(common.find_images(obj))
+
items = []
items.append(("[Average]", "[Average]", "Average of all textures"))
items.append(("[Max]", "[Max]", "Max of all textures"))
@@ -356,7 +372,7 @@ class MUV_OT_WorldScaleUV_Measure(bpy.types.Operator):
# we can not get area/space/region from console
if common.is_console_mode():
return True
- return _is_valid_context(context)
+ return _is_valid_context_for_measure(context)
@staticmethod
def setup_argument(ops, scene):
@@ -365,7 +381,9 @@ class MUV_OT_WorldScaleUV_Measure(bpy.types.Operator):
def execute(self, context):
sc = context.scene
- obj = context.active_object
+ objs = common.get_uv_editable_objects(context)
+ # poll() method ensures that only one object is selected.
+ obj = objs[0]
if self.tgt_texture == "[Average]":
uv_areas, mesh_areas, densities = _measure_wsuv_info(
@@ -469,7 +487,7 @@ class MUV_OT_WorldScaleUV_ApplyManual(bpy.types.Operator):
# we can not get area/space/region from console
if common.is_console_mode():
return True
- return _is_valid_context(context)
+ return _is_valid_context_for_apply(context)
@staticmethod
def setup_argument(ops, scene):
@@ -482,42 +500,49 @@ class MUV_OT_WorldScaleUV_ApplyManual(bpy.types.Operator):
ops.only_selected = scene.muv_world_scale_uv_apply_only_selected
def __apply_manual(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
-
- 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()
- tex_layer = common.find_texture_layer(bm)
- faces_list = common.get_faces_list(
- bm, self.tgt_area_calc_method, self.only_selected)
-
- tex_size = self.tgt_texture_size
+ objs = common.get_uv_editable_objects(context)
- factors = []
- for faces in faces_list:
- uv_area, _, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='USER_SPECIFIED', tex_size=tex_size)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
- if not uv_area:
+ if not bm.loops.layers.uv:
self.report({'WARNING'},
- "Object must have more than one UV map")
+ "Object {} must have more than one UV map"
+ .format(obj.name))
return {'CANCELLED'}
+ uv_layer = bm.loops.layers.uv.verify()
+ tex_layer = common.find_texture_layer(bm)
+ faces_list = common.get_faces_list(
+ bm, self.tgt_area_calc_method, self.only_selected)
- tgt_density = self.tgt_density
- factor = tgt_density / density
+ tex_size = self.tgt_texture_size
+
+ factors = []
+ for faces in faces_list:
+ uv_area, _, density = _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='USER_SPECIFIED', tex_size=tex_size)
- _apply(faces, uv_layer, self.origin, factor)
- factors.append(factor)
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
- bmesh.update_edit_mesh(obj.data)
- self.report({'INFO'}, "Scaling factor: {0}".format(factors))
+ tgt_density = self.tgt_density
+ factor = tgt_density / density
+
+ _apply(faces, uv_layer, self.origin, factor)
+ factors.append(factor)
+
+ bmesh.update_edit_mesh(obj.data)
+ self.report({'INFO'},
+ "Scaling factor of object {}: {}"
+ .format(obj.name, factors))
return {'FINISHED'}
@@ -624,7 +649,7 @@ class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator):
# we can not get area/space/region from console
if common.is_console_mode():
return True
- return _is_valid_context(context)
+ return _is_valid_context_for_apply(context)
@staticmethod
def setup_argument(ops, scene):
@@ -640,56 +665,62 @@ class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator):
ops.only_selected = scene.muv_world_scale_uv_apply_only_selected
def __apply_scaling_density(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
-
- 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()
- tex_layer = common.find_texture_layer(bm)
- faces_list = common.get_faces_list(
- bm, self.tgt_area_calc_method, self.only_selected)
-
- factors = []
- for faces in faces_list:
- if self.tgt_texture == "[Average]":
- uv_area, _, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='AVERAGE')
- elif self.tgt_texture == "[Max]":
- uv_area, _, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='MAX')
- elif self.tgt_texture == "[Min]":
- uv_area, _, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='MIN')
- else:
- tgt_texture = bpy.data.images[self.tgt_texture]
- uv_area, _, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='USER_SPECIFIED',
- tex_size=tgt_texture.size)
+ objs = common.get_uv_editable_objects(context)
+
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
- if not uv_area:
+ if not bm.loops.layers.uv:
self.report({'WARNING'},
- "Object must have more than one UV map and "
- "texture")
+ "Object {} must have more than one UV map"
+ .format(obj.name))
return {'CANCELLED'}
-
- tgt_density = self.src_density * self.tgt_scaling_factor
- factor = tgt_density / density
-
- _apply(faces, uv_layer, self.origin, factor)
- factors.append(factor)
-
- bmesh.update_edit_mesh(obj.data)
- self.report({'INFO'}, "Scaling factor: {0}".format(factors))
+ uv_layer = bm.loops.layers.uv.verify()
+ tex_layer = common.find_texture_layer(bm)
+ faces_list = common.get_faces_list(
+ bm, self.tgt_area_calc_method, self.only_selected)
+
+ factors = []
+ for faces in faces_list:
+ if self.tgt_texture == "[Average]":
+ uv_area, _, density = _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='AVERAGE')
+ elif self.tgt_texture == "[Max]":
+ uv_area, _, density = _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='MAX')
+ elif self.tgt_texture == "[Min]":
+ uv_area, _, density = _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='MIN')
+ else:
+ tgt_texture = bpy.data.images[self.tgt_texture]
+ uv_area, _, density = _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='USER_SPECIFIED',
+ tex_size=tgt_texture.size)
+
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map and "
+ "texture".format(obj.name))
+ return {'CANCELLED'}
+
+ tgt_density = self.src_density * self.tgt_scaling_factor
+ factor = tgt_density / density
+
+ _apply(faces, uv_layer, self.origin, factor)
+ factors.append(factor)
+
+ bmesh.update_edit_mesh(obj.data)
+ self.report({'INFO'},
+ "Scaling factor of object {}: {}"
+ .format(obj.name, factors))
return {'FINISHED'}
@@ -819,7 +850,7 @@ class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator):
# we can not get area/space/region from console
if common.is_console_mode():
return True
- return _is_valid_context(context)
+ return _is_valid_context_for_apply(context)
@staticmethod
def setup_argument(ops, scene):
@@ -834,56 +865,66 @@ class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator):
ops.only_selected = scene.muv_world_scale_uv_apply_only_selected
def __apply_proportional_to_mesh(self, context):
- obj = context.active_object
- bm = bmesh.from_edit_mesh(obj.data)
- if common.check_version(2, 73, 0) >= 0:
- bm.verts.ensure_lookup_table()
- bm.edges.ensure_lookup_table()
- bm.faces.ensure_lookup_table()
-
- 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()
- tex_layer = common.find_texture_layer(bm)
- faces_list = common.get_faces_list(
- bm, self.tgt_area_calc_method, self.only_selected)
-
- factors = []
- for faces in faces_list:
- if self.tgt_texture == "[Average]":
- uv_area, mesh_area, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='AVERAGE')
- elif self.tgt_texture == "[Max]":
- uv_area, mesh_area, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='MAX')
- elif self.tgt_texture == "[Min]":
- uv_area, mesh_area, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='MIN')
- else:
- tgt_texture = bpy.data.images[self.tgt_texture]
- uv_area, mesh_area, density = _measure_wsuv_info_from_faces(
- obj, faces, uv_layer, tex_layer,
- tex_selection_method='USER_SPECIFIED',
- tex_size=tgt_texture.size)
- if not uv_area:
- self.report({'WARNING'},
- "Object must have more than one UV map and "
- "texture")
- return {'CANCELLED'}
-
- tgt_density = self.src_density * sqrt(mesh_area) / sqrt(
- self.src_mesh_area)
- factor = tgt_density / density
+ objs = common.get_uv_editable_objects(context)
- _apply(faces, uv_layer, self.origin, factor)
- factors.append(factor)
+ for obj in objs:
+ bm = bmesh.from_edit_mesh(obj.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
- bmesh.update_edit_mesh(obj.data)
- self.report({'INFO'}, "Scaling factor: {0}".format(factors))
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map"
+ .format(obj.name))
+ return {'CANCELLED'}
+ uv_layer = bm.loops.layers.uv.verify()
+ tex_layer = common.find_texture_layer(bm)
+ faces_list = common.get_faces_list(
+ bm, self.tgt_area_calc_method, self.only_selected)
+
+ factors = []
+ for faces in faces_list:
+ if self.tgt_texture == "[Average]":
+ uv_area, mesh_area, density = \
+ _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='AVERAGE')
+ elif self.tgt_texture == "[Max]":
+ uv_area, mesh_area, density = \
+ _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='MAX')
+ elif self.tgt_texture == "[Min]":
+ uv_area, mesh_area, density = \
+ _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='MIN')
+ else:
+ tgt_texture = bpy.data.images[self.tgt_texture]
+ uv_area, mesh_area, density = \
+ _measure_wsuv_info_from_faces(
+ obj, faces, uv_layer, tex_layer,
+ tex_selection_method='USER_SPECIFIED',
+ tex_size=tgt_texture.size)
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object {} must have more than one UV map and "
+ "texture".format(obj.name))
+ return {'CANCELLED'}
+
+ tgt_density = self.src_density * sqrt(mesh_area) / sqrt(
+ self.src_mesh_area)
+ factor = tgt_density / density
+
+ _apply(faces, uv_layer, self.origin, factor)
+ factors.append(factor)
+
+ bmesh.update_edit_mesh(obj.data)
+ self.report({'INFO'},
+ "Scaling factor of object {}: {}"
+ .format(obj.name, factors))
return {'FINISHED'}
diff --git a/magic_uv/preferences.py b/magic_uv/preferences.py
index 926ec728..ea8e7434 100644
--- a/magic_uv/preferences.py
+++ b/magic_uv/preferences.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
from bpy.props import (
diff --git a/magic_uv/properites.py b/magic_uv/properites.py
index b269cbed..28b9216d 100644
--- a/magic_uv/properites.py
+++ b/magic_uv/properites.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from .utils.property_class_registry import PropertyClassRegistry
diff --git a/magic_uv/ui/IMAGE_MT_uvs.py b/magic_uv/ui/IMAGE_MT_uvs.py
index 00d95d9e..3984c20f 100644
--- a/magic_uv/ui/IMAGE_MT_uvs.py
+++ b/magic_uv/ui/IMAGE_MT_uvs.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
@@ -34,6 +34,8 @@ from ..op.align_uv import (
MUV_OT_AlignUV_Circle,
MUV_OT_AlignUV_Straighten,
MUV_OT_AlignUV_Axis,
+ MUV_OT_AlignUV_SnapToPoint,
+ MUV_OT_AlignUV_SnapToEdge,
)
from ..op.select_uv import (
MUV_OT_SelectUV_SelectOverlapped,
@@ -78,6 +80,8 @@ class MUV_MT_AlignUV(bpy.types.Menu):
layout = self.layout
sc = context.scene
+ layout.label(text="Align")
+
ops = layout.operator(MUV_OT_AlignUV_Circle.bl_idname, text="Circle")
ops.transmission = sc.muv_align_uv_transmission
ops.select = sc.muv_align_uv_select
@@ -96,6 +100,18 @@ class MUV_MT_AlignUV(bpy.types.Menu):
ops.horizontal = sc.muv_align_uv_horizontal
ops.location = sc.muv_align_uv_location
+ layout.label(text="Snap")
+
+ ops = layout.operator(MUV_OT_AlignUV_SnapToPoint.bl_idname,
+ text="Snap to Point")
+ ops.group = sc.muv_align_uv_snap_point_group
+ ops.target = sc.muv_align_uv_snap_point_target
+
+ ops = layout.operator(MUV_OT_AlignUV_SnapToEdge, text="Snap to Edge")
+ ops.group = sc.muv_align_uv_snap_edge_group
+ ops.target_1 = sc.muv_align_uv_snap_edge_target_1
+ ops.target_2 = sc.muv_align_uv_snap_edge_target_2
+
@BlClassRegistry()
class MUV_MT_SelectUV(bpy.types.Menu):
diff --git a/magic_uv/ui/VIEW3D_MT_object.py b/magic_uv/ui/VIEW3D_MT_object.py
index f34c74f9..c8980592 100644
--- a/magic_uv/ui/VIEW3D_MT_object.py
+++ b/magic_uv/ui/VIEW3D_MT_object.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/ui/VIEW3D_MT_uv_map.py b/magic_uv/ui/VIEW3D_MT_uv_map.py
index 7ab50ace..e6574f4d 100644
--- a/magic_uv/ui/VIEW3D_MT_uv_map.py
+++ b/magic_uv/ui/VIEW3D_MT_uv_map.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy.utils
diff --git a/magic_uv/ui/__init__.py b/magic_uv/ui/__init__.py
index bb16a847..083590a6 100644
--- a/magic_uv/ui/__init__.py
+++ b/magic_uv/ui/__init__.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
if "bpy" in locals():
import importlib
diff --git a/magic_uv/ui/uvedit_copy_paste_uv.py b/magic_uv/ui/uvedit_copy_paste_uv.py
index 211737c8..5f029f6f 100644
--- a/magic_uv/ui/uvedit_copy_paste_uv.py
+++ b/magic_uv/ui/uvedit_copy_paste_uv.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/ui/uvedit_editor_enhancement.py b/magic_uv/ui/uvedit_editor_enhancement.py
index f98e5193..a0eba3a9 100644
--- a/magic_uv/ui/uvedit_editor_enhancement.py
+++ b/magic_uv/ui/uvedit_editor_enhancement.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/ui/uvedit_uv_manipulation.py b/magic_uv/ui/uvedit_uv_manipulation.py
index 79a1731a..1b05cb00 100644
--- a/magic_uv/ui/uvedit_uv_manipulation.py
+++ b/magic_uv/ui/uvedit_uv_manipulation.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
@@ -29,6 +29,11 @@ from ..op.align_uv import (
MUV_OT_AlignUV_Circle,
MUV_OT_AlignUV_Straighten,
MUV_OT_AlignUV_Axis,
+ MUV_OT_AlignUV_SnapToPoint,
+ MUV_OT_AlignUV_Snap_SetPointTargetToCursor,
+ MUV_OT_AlignUV_Snap_SetPointTargetToVertexGroup,
+ MUV_OT_AlignUV_SnapToEdge,
+ MUV_OT_AlignUV_Snap_SetEdgeTargetToEdgeCenter,
)
from ..op.smooth_uv import (
MUV_OT_SmoothUV,
@@ -67,6 +72,8 @@ class MUV_PT_UVEdit_UVManipulation(bpy.types.Panel):
box = layout.box()
box.prop(sc, "muv_align_uv_enabled", text="Align UV")
if sc.muv_align_uv_enabled:
+ box.label(text="Align:")
+
col = box.column()
row = col.row(align=True)
ops = row.operator(MUV_OT_AlignUV_Circle.bl_idname, text="Circle")
@@ -98,6 +105,60 @@ class MUV_PT_UVEdit_UVManipulation(bpy.types.Panel):
row.prop(sc, "muv_align_uv_horizontal", text="Horizontal")
col.prop(sc, "muv_align_uv_mesh_infl", text="Mesh Influence")
+ box.separator()
+
+ sp = compat.layout_split(box, factor=0.5)
+ sp.label(text="Snap:")
+ sp = compat.layout_split(sp, factor=1.0)
+ sp.prop(sc, "muv_align_uv_snap_method", text="")
+
+ if sc.muv_align_uv_snap_method == 'POINT':
+ row = box.row(align=True)
+ ops = row.operator(MUV_OT_AlignUV_SnapToPoint.bl_idname,
+ text="Snap to Point")
+ ops.group = sc.muv_align_uv_snap_point_group
+ ops.target = sc.muv_align_uv_snap_point_target
+
+ col = box.column(align=True)
+ row = col.row(align=True)
+ row.prop(sc, "muv_align_uv_snap_point_group", text="Group")
+
+ col.label(text="Target Point:")
+ row = col.row(align=True)
+ row.prop(sc, "muv_align_uv_snap_point_target", text="")
+ row.operator(
+ MUV_OT_AlignUV_Snap_SetPointTargetToCursor.bl_idname,
+ text="", icon=compat.icon('CURSOR'))
+ row.operator(
+ MUV_OT_AlignUV_Snap_SetPointTargetToVertexGroup.bl_idname,
+ text="", icon=compat.icon('UV_VERTEXSEL'))
+
+ elif sc.muv_align_uv_snap_method == 'EDGE':
+ row = box.row(align=True)
+ ops = row.operator(MUV_OT_AlignUV_SnapToEdge.bl_idname,
+ text="Snap to Edge")
+ ops.group = sc.muv_align_uv_snap_edge_group
+ ops.target_1 = sc.muv_align_uv_snap_edge_target_1
+ ops.target_2 = sc.muv_align_uv_snap_edge_target_2
+
+ col = box.column(align=True)
+ row = col.row(align=True)
+ row.prop(sc, "muv_align_uv_snap_edge_group", text="Group")
+
+ col.label(text="Target Edge:")
+ sp = compat.layout_split(col, factor=0.33)
+ subcol = sp.column()
+ subcol.label(text="Vertex 1:")
+ subcol.prop(sc, "muv_align_uv_snap_edge_target_1", text="")
+ sp = compat.layout_split(sp, factor=0.5)
+ subcol = sp.column()
+ subcol.label(text="Vertex 2:")
+ subcol.prop(sc, "muv_align_uv_snap_edge_target_2", text="")
+ sp = compat.layout_split(sp, factor=1.0)
+ sp.operator(
+ MUV_OT_AlignUV_Snap_SetEdgeTargetToEdgeCenter.bl_idname,
+ text="", icon=compat.icon('UV_EDGESEL'))
+
box = layout.box()
box.prop(sc, "muv_smooth_uv_enabled", text="Smooth UV")
if sc.muv_smooth_uv_enabled:
diff --git a/magic_uv/ui/view3d_copy_paste_uv_editmode.py b/magic_uv/ui/view3d_copy_paste_uv_editmode.py
index 0c7273a3..762fd9d9 100644
--- a/magic_uv/ui/view3d_copy_paste_uv_editmode.py
+++ b/magic_uv/ui/view3d_copy_paste_uv_editmode.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/ui/view3d_copy_paste_uv_objectmode.py b/magic_uv/ui/view3d_copy_paste_uv_objectmode.py
index b2a33e9a..71d30755 100644
--- a/magic_uv/ui/view3d_copy_paste_uv_objectmode.py
+++ b/magic_uv/ui/view3d_copy_paste_uv_objectmode.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/ui/view3d_uv_manipulation.py b/magic_uv/ui/view3d_uv_manipulation.py
index 1d10eb65..6d0fce6d 100644
--- a/magic_uv/ui/view3d_uv_manipulation.py
+++ b/magic_uv/ui/view3d_uv_manipulation.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/ui/view3d_uv_mapping.py b/magic_uv/ui/view3d_uv_mapping.py
index 4344adb7..6a4217c0 100644
--- a/magic_uv/ui/view3d_uv_mapping.py
+++ b/magic_uv/ui/view3d_uv_mapping.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/updater.py b/magic_uv/updater.py
index 8d610b16..a9e09bfa 100644
--- a/magic_uv/updater.py
+++ b/magic_uv/updater.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import os
diff --git a/magic_uv/utils/__init__.py b/magic_uv/utils/__init__.py
index c96b9225..918bc207 100644
--- a/magic_uv/utils/__init__.py
+++ b/magic_uv/utils/__init__.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
if "bpy" in locals():
import importlib
diff --git a/magic_uv/utils/addon_updater.py b/magic_uv/utils/addon_updater.py
index 5df59fd4..813813eb 100644
--- a/magic_uv/utils/addon_updater.py
+++ b/magic_uv/utils/addon_updater.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from threading import Lock
import urllib
diff --git a/magic_uv/utils/bl_class_registry.py b/magic_uv/utils/bl_class_registry.py
index f9f05faf..080a1a45 100644
--- a/magic_uv/utils/bl_class_registry.py
+++ b/magic_uv/utils/bl_class_registry.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
diff --git a/magic_uv/utils/compatibility.py b/magic_uv/utils/compatibility.py
index b4c7c4ea..517c33af 100644
--- a/magic_uv/utils/compatibility.py
+++ b/magic_uv/utils/compatibility.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
import bpy
import bgl
@@ -100,6 +100,13 @@ def get_object_select(obj):
return obj.select_get()
+def set_object_select(obj, select):
+ if check_version(2, 80, 0) < 0:
+ obj.select = select
+ else:
+ obj.select_set(select)
+
+
def set_active_object(obj):
if check_version(2, 80, 0) < 0:
bpy.context.scene.objects.active = obj
@@ -108,10 +115,10 @@ def set_active_object(obj):
def get_active_object(context):
- if check_version(2, 80, 0) >= 0:
- return context.scene.active_object
+ if check_version(2, 80, 0) < 0:
+ return context.scene.objects.active
else:
- return context.active_object
+ return context.view_layer.objects.active
def object_has_uv_layers(obj):
diff --git a/magic_uv/utils/property_class_registry.py b/magic_uv/utils/property_class_registry.py
index 9caa735c..f107aed3 100644
--- a/magic_uv/utils/property_class_registry.py
+++ b/magic_uv/utils/property_class_registry.py
@@ -20,8 +20,8 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.3"
-__date__ = "10 Aug 2020"
+__version__ = "6.4"
+__date__ = "23 Oct 2020"
from .. import common