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-08-10 10:07:26 +0300
committernutti <nutti.metro@gmail.com>2020-08-10 10:24:28 +0300
commit8db46434a4b25569372a7172f83733ec14dffb31 (patch)
treef981b15847246b0ffb054cc4d87083b2ec394fda
parent7084d19ef5886b740b65ba6a4234849859e0e5b8 (diff)
Magic UV: Release v6.3
Added features - Clip UV Updated features - World Scale UV * Add option "Area Calculation Method" * Add option "Only Selected" - UVW * Support multiple objects - Select UV * Support multiple objects - UV Inspection * Add Paint UV Island feature * Support multiple objects Other updates - Fix bugs - Optimization
-rw-r--r--magic_uv/__init__.py10
-rw-r--r--magic_uv/common.py220
-rw-r--r--magic_uv/lib/__init__.py4
-rw-r--r--magic_uv/op/__init__.py6
-rw-r--r--magic_uv/op/align_uv.py10
-rw-r--r--magic_uv/op/align_uv_cursor.py4
-rw-r--r--magic_uv/op/clip_uv.py227
-rw-r--r--magic_uv/op/copy_paste_uv.py11
-rw-r--r--magic_uv/op/copy_paste_uv_object.py4
-rw-r--r--magic_uv/op/copy_paste_uv_uvedit.py4
-rw-r--r--magic_uv/op/flip_rotate_uv.py4
-rw-r--r--magic_uv/op/mirror_uv.py4
-rw-r--r--magic_uv/op/move_uv.py63
-rw-r--r--magic_uv/op/pack_uv.py4
-rw-r--r--magic_uv/op/preserve_uv_aspect.py4
-rw-r--r--magic_uv/op/select_uv.py83
-rw-r--r--magic_uv/op/smooth_uv.py10
-rw-r--r--magic_uv/op/texture_lock.py6
-rw-r--r--magic_uv/op/texture_projection.py4
-rw-r--r--magic_uv/op/texture_wrap.py4
-rw-r--r--magic_uv/op/transfer_uv.py4
-rw-r--r--magic_uv/op/unwrap_constraint.py4
-rw-r--r--magic_uv/op/uv_bounding_box.py10
-rw-r--r--magic_uv/op/uv_inspection.py249
-rw-r--r--magic_uv/op/uv_sculpt.py24
-rw-r--r--magic_uv/op/uvw.py64
-rw-r--r--magic_uv/op/world_scale_uv.py394
-rw-r--r--magic_uv/preferences.py15
-rw-r--r--magic_uv/properites.py4
-rw-r--r--magic_uv/ui/IMAGE_MT_uvs.py14
-rw-r--r--magic_uv/ui/VIEW3D_MT_object.py4
-rw-r--r--magic_uv/ui/VIEW3D_MT_uv_map.py17
-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.py7
-rw-r--r--magic_uv/ui/uvedit_uv_manipulation.py18
-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.py114
-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.py8
-rw-r--r--magic_uv/utils/bl_class_registry.py4
-rw-r--r--magic_uv/utils/compatibility.py4
-rw-r--r--magic_uv/utils/property_class_registry.py4
46 files changed, 1235 insertions, 441 deletions
diff --git a/magic_uv/__init__.py b/magic_uv/__init__.py
index 8630038a..b92714fe 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
bl_info = {
"name": "Magic UV",
"author": "Nutti, Mifth, Jace Priester, kgeogeo, mem, imdjs"
"Keith (Wahooney) Boshoff, McBuff, MaxRobinot, "
- "Alexander Milovsky",
- "version": (6, 2, 0),
+ "Alexander Milovsky, Dusan Stevanovic",
+ "version": (6, 3, 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 df3597be..11696667 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from collections import defaultdict
from pprint import pprint
@@ -244,15 +244,16 @@ def __parse_island(bm, face_idx, faces_left, island,
Parse island
"""
- if face_idx in faces_left:
- faces_left.remove(face_idx)
- island.append({'face': bm.faces[face_idx]})
- for v in face_to_verts[face_idx]:
- connected_faces = vert_to_faces[v]
- if connected_faces:
+ faces_to_parse = [face_idx]
+ while faces_to_parse:
+ fidx = faces_to_parse.pop(0)
+ if fidx in faces_left:
+ faces_left.remove(fidx)
+ island.append({'face': bm.faces[fidx]})
+ for v in face_to_verts[fidx]:
+ connected_faces = vert_to_faces[v]
for cf in connected_faces:
- __parse_island(bm, cf, faces_left, island, face_to_verts,
- vert_to_faces)
+ faces_to_parse.append(cf)
def __get_island(bm, face_to_verts, vert_to_faces):
@@ -351,18 +352,60 @@ def calc_polygon_3d_area(points):
return 0.5 * area
-def measure_mesh_area(obj):
+def get_faces_list(bm, method, only_selected):
+ faces_list = []
+ if method == 'MESH':
+ if only_selected:
+ faces_list.append([f for f in bm.faces if f.select])
+ else:
+ faces_list.append([f for f in bm.faces])
+ elif method == 'UV ISLAND':
+ if not bm.loops.layers.uv:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+ if only_selected:
+ faces = [f for f in bm.faces if f.select]
+ islands = get_island_info_from_faces(bm, faces, uv_layer)
+ for isl in islands:
+ faces_list.append([f["face"] for f in isl["faces"]])
+ else:
+ faces = [f for f in bm.faces]
+ islands = get_island_info_from_faces(bm, faces, uv_layer)
+ for isl in islands:
+ faces_list.append([f["face"] for f in isl["faces"]])
+ elif method == 'FACE':
+ if only_selected:
+ for f in bm.faces:
+ if f.select:
+ faces_list.append([f])
+ else:
+ for f in bm.faces:
+ faces_list.append([f])
+ else:
+ raise ValueError("Invalid method: {}".format(method))
+
+ return faces_list
+
+
+def measure_mesh_area(obj, calc_method, only_selected):
bm = bmesh.from_edit_mesh(obj.data)
if check_version(2, 73, 0) >= 0:
bm.verts.ensure_lookup_table()
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
- sel_faces = [f for f in bm.faces if f.select]
+ faces_list = get_faces_list(bm, calc_method, only_selected)
- # measure
+ areas = []
+ for faces in faces_list:
+ areas.append(measure_mesh_area_from_faces(faces))
+
+ return areas
+
+
+def measure_mesh_area_from_faces(faces):
mesh_area = 0.0
- for f in sel_faces:
+ for f in faces:
verts = [l.vert.co for l in f.loops]
f_mesh_area = calc_polygon_3d_area(verts)
mesh_area = mesh_area + f_mesh_area
@@ -405,7 +448,7 @@ def find_image(obj, face=None, tex_layer=None):
if len(images) >= 2:
raise RuntimeError("Find more than 2 images")
- if len(images) == 0:
+ if not images:
return None
return images[0]
@@ -428,40 +471,26 @@ def find_images(obj, face=None, tex_layer=None):
return images
-def measure_uv_area(obj, method='FIRST', tex_size=None):
- bm = bmesh.from_edit_mesh(obj.data)
- if 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:
- return None
- uv_layer = bm.loops.layers.uv.verify()
-
- tex_layer = find_texture_layer(bm)
-
- sel_faces = [f for f in bm.faces if f.select]
-
- # measure
+def measure_uv_area_from_faces(obj, faces, uv_layer, tex_layer,
+ tex_selection_method, tex_size):
uv_area = 0.0
- for f in sel_faces:
+ for f in faces:
uvs = [l[uv_layer].uv for l in f.loops]
f_uv_area = calc_polygon_2d_area(uvs)
# user specified
- if method == 'USER_SPECIFIED' and tex_size is not None:
+ if tex_selection_method == 'USER_SPECIFIED' and tex_size is not None:
img_size = tex_size
# first texture if there are more than 2 textures assigned
# to the object
- elif method == 'FIRST':
+ elif tex_selection_method == 'FIRST':
img = find_image(obj, f, tex_layer)
# can not find from node, so we can not get texture size
if not img:
return None
img_size = img.size
# average texture size
- elif method == 'AVERAGE':
+ elif tex_selection_method == 'AVERAGE':
imgs = find_images(obj, f, tex_layer)
if not imgs:
return None
@@ -473,7 +502,7 @@ def measure_uv_area(obj, method='FIRST', tex_size=None):
img_size = [img_size_total[0] / len(imgs),
img_size_total[1] / len(imgs)]
# max texture size
- elif method == 'MAX':
+ elif tex_selection_method == 'MAX':
imgs = find_images(obj, f, tex_layer)
if not imgs:
return None
@@ -484,7 +513,7 @@ def measure_uv_area(obj, method='FIRST', tex_size=None):
max(img_size_max[1], img.size[1])]
img_size = img_size_max
# min texture size
- elif method == 'MIN':
+ elif tex_selection_method == 'MIN':
imgs = find_images(obj, f, tex_layer)
if not imgs:
return None
@@ -495,13 +524,40 @@ def measure_uv_area(obj, method='FIRST', tex_size=None):
min(img_size_min[1], img.size[1])]
img_size = img_size_min
else:
- raise RuntimeError("Unexpected method: {}".format(method))
+ raise RuntimeError("Unexpected method: {}"
+ .format(tex_selection_method))
- uv_area = uv_area + f_uv_area * img_size[0] * img_size[1]
+ uv_area += f_uv_area * img_size[0] * img_size[1]
return uv_area
+def measure_uv_area(obj, calc_method, tex_selection_method, tex_size,
+ only_selected):
+ bm = bmesh.from_edit_mesh(obj.data)
+ if 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:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+ tex_layer = find_texture_layer(bm)
+ faces_list = get_faces_list(bm, calc_method, only_selected)
+
+ # measure
+ uv_areas = []
+ for faces in faces_list:
+ uv_area = measure_uv_area_from_faces(
+ obj, faces, uv_layer, tex_layer, tex_selection_method, tex_size)
+ if uv_area is None:
+ return None
+ uv_areas.append(uv_area)
+
+ return uv_areas
+
+
def diff_point_to_segment(a, b, p):
ab = b - a
normal_ab = ab.normalized()
@@ -520,43 +576,42 @@ def diff_point_to_segment(a, b, p):
# get selected loop pair whose loops are connected each other
def __get_loop_pairs(l, uv_layer):
-
- def __get_loop_pairs_internal(l_, pairs_, uv_layer_, parsed_):
- parsed_.append(l_)
- for ll in l_.vert.link_loops:
+ pairs = []
+ parsed = []
+ loops_ready = [l]
+ while loops_ready:
+ l = loops_ready.pop(0)
+ parsed.append(l)
+ for ll in l.vert.link_loops:
# forward direction
lln = ll.link_loop_next
# if there is same pair, skip it
found = False
- for p in pairs_:
+ for p in pairs:
if (ll in p) and (lln in p):
found = True
break
# two loops must be selected
- if ll[uv_layer_].select and lln[uv_layer_].select:
+ if ll[uv_layer].select and lln[uv_layer].select:
if not found:
- pairs_.append([ll, lln])
- if lln not in parsed_:
- __get_loop_pairs_internal(lln, pairs_, uv_layer_, parsed_)
+ pairs.append([ll, lln])
+ if (lln not in parsed) and (lln not in loops_ready):
+ loops_ready.append(lln)
# backward direction
llp = ll.link_loop_prev
# if there is same pair, skip it
found = False
- for p in pairs_:
+ for p in pairs:
if (ll in p) and (llp in p):
found = True
break
# two loops must be selected
- if ll[uv_layer_].select and llp[uv_layer_].select:
+ if ll[uv_layer].select and llp[uv_layer].select:
if not found:
- pairs_.append([ll, llp])
- if llp not in parsed_:
- __get_loop_pairs_internal(llp, pairs_, uv_layer_, parsed_)
-
- pairs = []
- parsed = []
- __get_loop_pairs_internal(l, pairs, uv_layer, parsed)
+ pairs.append([ll, llp])
+ if (llp not in parsed) and (llp not in loops_ready):
+ loops_ready.append(llp)
return pairs
@@ -876,12 +931,12 @@ class RingBuffer:
# clip: reference polygon
# subject: tested polygon
-def __do_weiler_atherton_cliping(clip, subject, uv_layer, mode):
+def __do_weiler_atherton_cliping(clip_uvs, subject_uvs, mode):
- clip_uvs = RingBuffer([l[uv_layer].uv.copy() for l in clip.loops])
+ clip_uvs = RingBuffer(clip_uvs)
if __is_polygon_flipped(clip_uvs):
clip_uvs.reverse()
- subject_uvs = RingBuffer([l[uv_layer].uv.copy() for l in subject.loops])
+ subject_uvs = RingBuffer(subject_uvs)
if __is_polygon_flipped(subject_uvs):
subject_uvs.reverse()
@@ -1111,22 +1166,29 @@ def __is_points_in_polygon(points, subject_points):
return True
-def get_overlapped_uv_info(bm, faces, uv_layer, mode):
+def get_overlapped_uv_info(bm_list, faces_list, uv_layer_list, mode):
# at first, check island overlapped
- isl = get_island_info_from_faces(bm, faces, uv_layer)
+ isl = []
+ for bm, uv_layer, faces in zip(bm_list, uv_layer_list, faces_list):
+ info = get_island_info_from_faces(bm, faces, uv_layer)
+ isl.extend([(i, uv_layer) for i in info])
+
overlapped_isl_pairs = []
- for i, i1 in enumerate(isl):
- for i2 in isl[i + 1:]:
+ overlapped_uv_layer_pairs = []
+ for i, (i1, uv_layer_1) in enumerate(isl):
+ for i2, uv_layer_2 in isl[i + 1:]:
if (i1["max"].x < i2["min"].x) or (i2["max"].x < i1["min"].x) or \
(i1["max"].y < i2["min"].y) or (i2["max"].y < i1["min"].y):
continue
overlapped_isl_pairs.append([i1, i2])
+ overlapped_uv_layer_pairs.append([uv_layer_1, uv_layer_2])
# next, check polygon overlapped
overlapped_uvs = []
- for oip in overlapped_isl_pairs:
+ for oip, uvlp in zip(overlapped_isl_pairs, overlapped_uv_layer_pairs):
for clip in oip[0]["faces"]:
f_clip = clip["face"]
+ clip_uvs = [l[uvlp[0]].uv.copy() for l in f_clip.loops]
for subject in oip[1]["faces"]:
f_subject = subject["face"]
@@ -1137,29 +1199,33 @@ def get_overlapped_uv_info(bm, faces, uv_layer, mode):
(subject["max_uv"].y < clip["min_uv"].y):
continue
+ subject_uvs = [l[uvlp[1]].uv.copy() for l in f_subject.loops]
# slow operation, apply Weiler-Atherton cliping algorithm
- result, polygons = __do_weiler_atherton_cliping(f_clip,
- f_subject,
- uv_layer, mode)
+ result, polygons = __do_weiler_atherton_cliping(clip_uvs,
+ subject_uvs,
+ mode)
if result:
- subject_uvs = [l[uv_layer].uv.copy()
- for l in f_subject.loops]
overlapped_uvs.append({"clip_face": f_clip,
"subject_face": f_subject,
+ "clip_uv_layer": uvlp[0],
+ "subject_uv_layer": uvlp[1],
"subject_uvs": subject_uvs,
"polygons": polygons})
return overlapped_uvs
-def get_flipped_uv_info(faces, uv_layer):
+def get_flipped_uv_info(faces_list, uv_layer_list):
flipped_uvs = []
- for f in faces:
- polygon = RingBuffer([l[uv_layer].uv.copy() for l in f.loops])
- if __is_polygon_flipped(polygon):
- uvs = [l[uv_layer].uv.copy() for l in f.loops]
- flipped_uvs.append({"face": f, "uvs": uvs,
- "polygons": [polygon.as_list()]})
+ for faces, uv_layer in zip(faces_list, uv_layer_list):
+ for f in faces:
+ polygon = RingBuffer([l[uv_layer].uv.copy() for l in f.loops])
+ if __is_polygon_flipped(polygon):
+ uvs = [l[uv_layer].uv.copy() for l in f.loops]
+ flipped_uvs.append({"face": f,
+ "uv_layer": uv_layer,
+ "uvs": uvs,
+ "polygons": [polygon.as_list()]})
return flipped_uvs
diff --git a/magic_uv/lib/__init__.py b/magic_uv/lib/__init__.py
index 3258b6eb..5e06552d 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
if "bpy" in locals():
import importlib
diff --git a/magic_uv/op/__init__.py b/magic_uv/op/__init__.py
index cd743b48..b7316192 100644
--- a/magic_uv/op/__init__.py
+++ b/magic_uv/op/__init__.py
@@ -20,13 +20,14 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
if "bpy" in locals():
import importlib
importlib.reload(align_uv)
importlib.reload(align_uv_cursor)
+ importlib.reload(clip_uv)
importlib.reload(copy_paste_uv)
importlib.reload(copy_paste_uv_object)
importlib.reload(copy_paste_uv_uvedit)
@@ -50,6 +51,7 @@ if "bpy" in locals():
else:
from . import align_uv
from . import align_uv_cursor
+ from . import clip_uv
from . import copy_paste_uv
from . import copy_paste_uv_object
from . import copy_paste_uv_uvedit
diff --git a/magic_uv/op/align_uv.py b/magic_uv/op/align_uv.py
index 31f7cbe8..77afcc25 100644
--- a/magic_uv/op/align_uv.py
+++ b/magic_uv/op/align_uv.py
@@ -20,8 +20,8 @@
__author__ = "imdjs, Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import math
from math import atan2, tan, sin, cos
@@ -164,8 +164,7 @@ def _get_hdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pidx, infl):
# calculate target UV
for i in range(len(accum_uvlens[:-1])):
# get line segment which UV will be placed
- if ((accum_uvlens[i] <= target_length) and
- (accum_uvlens[i + 1] > target_length)):
+ if accum_uvlens[i] <= target_length < accum_uvlens[i + 1]:
tgt_seg_len = target_length - accum_uvlens[i]
seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
uv1 = orig_uvs[i]
@@ -245,8 +244,7 @@ def _get_vdiff_uv_vinfl(uv_layer, loop_seqs, vidx, hidx, pidx, infl):
# calculate target UV
for i in range(len(accum_uvlens[:-1])):
# get line segment which UV will be placed
- if ((accum_uvlens[i] <= target_length) and
- (accum_uvlens[i + 1] > target_length)):
+ if accum_uvlens[i] <= target_length < accum_uvlens[i + 1]:
tgt_seg_len = target_length - accum_uvlens[i]
seg_len = accum_uvlens[i + 1] - accum_uvlens[i]
uv1 = orig_uvs[i]
diff --git a/magic_uv/op/align_uv_cursor.py b/magic_uv/op/align_uv_cursor.py
index b103de31..884f645a 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from mathutils import Vector
diff --git a/magic_uv/op/clip_uv.py b/magic_uv/op/clip_uv.py
new file mode 100644
index 00000000..c6f006e2
--- /dev/null
+++ b/magic_uv/op/clip_uv.py
@@ -0,0 +1,227 @@
+# <pep8-80 compliant>
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = "Dusan Stevanovic, Nutti <nutti.metro@gmail.com>"
+__status__ = "production"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
+
+
+import math
+
+import bpy
+import bmesh
+from mathutils import Vector
+from bpy.props import BoolProperty, FloatVectorProperty
+
+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):
+ # '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:
+ return False
+
+ return True
+
+
+def round_clip_uv_range(v):
+ sign = 1 if v >= 0.0 else -1
+ return int((math.fabs(v) + 0.25) / 0.5) * 0.5 * sign
+
+
+def get_clip_uv_range_max(self):
+ return self.get('muv_clip_uv_range_max', (0.5, 0.5))
+
+
+def set_clip_uv_range_max(self, value):
+ u = round_clip_uv_range(value[0])
+ u = 0.5 if u <= 0.5 else u
+ v = round_clip_uv_range(value[1])
+ v = 0.5 if v <= 0.5 else v
+ self['muv_clip_uv_range_max'] = (u, v)
+
+
+def get_clip_uv_range_min(self):
+ return self.get('muv_clip_uv_range_min', (-0.5, -0.5))
+
+
+def set_clip_uv_range_min(self, value):
+ u = round_clip_uv_range(value[0])
+ u = -0.5 if u >= -0.5 else u
+ v = round_clip_uv_range(value[1])
+ v = -0.5 if v >= -0.5 else v
+ self['muv_clip_uv_range_min'] = (u, v)
+
+
+@PropertyClassRegistry()
+class _Properties:
+ idname = "clip_uv"
+
+ @classmethod
+ def init_props(cls, scene):
+ scene.muv_clip_uv_enabled = BoolProperty(
+ name="Clip UV Enabled",
+ description="Clip UV is enabled",
+ default=False
+ )
+
+ scene.muv_clip_uv_range_max = FloatVectorProperty(
+ name="Range Max",
+ description="Max UV coordinates of the range to be clipped",
+ size=2,
+ default=(0.5, 0.5),
+ min=0.5,
+ step=50,
+ get=get_clip_uv_range_max,
+ set=set_clip_uv_range_max,
+ )
+
+ scene.muv_clip_uv_range_min = FloatVectorProperty(
+ name="Range Min",
+ description="Min UV coordinates of the range to be clipped",
+ size=2,
+ default=(-0.5, -0.5),
+ max=-0.5,
+ step=50,
+ get=get_clip_uv_range_min,
+ set=set_clip_uv_range_min,
+ )
+
+ # TODO: add option to preserve UV island
+
+ @classmethod
+ def del_props(cls, scene):
+ del scene.muv_clip_uv_range_max
+ del scene.muv_clip_uv_range_min
+
+
+@BlClassRegistry()
+@compat.make_annotations
+class MUV_OT_ClipUV(bpy.types.Operator):
+
+ bl_idname = "uv.muv_clip_uv"
+ bl_label = "Clip UV"
+ bl_description = "Clip selected UV in the specified range"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ clip_uv_range_max = FloatVectorProperty(
+ name="Range Max",
+ description="Max UV coordinates of the range to be clipped",
+ size=2,
+ default=(0.5, 0.5),
+ min=0.5,
+ step=50,
+ )
+
+ clip_uv_range_min = FloatVectorProperty(
+ name="Range Min",
+ description="Min UV coordinates of the range to be clipped",
+ size=2,
+ default=(-0.5, -0.5),
+ max=-0.5,
+ step=50,
+ )
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return _is_valid_context(context)
+
+ def execute(self, context):
+ 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)
+
+ return {'FINISHED'}
diff --git a/magic_uv/op/copy_paste_uv.py b/magic_uv/op/copy_paste_uv.py
index 5126e241..ba754425 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bmesh
import bpy.utils
@@ -75,7 +75,7 @@ def get_copy_uv_layers(ops_obj, bm, uv_map):
else:
uv_layers.append(bm.loops.layers.uv[uv_map])
ops_obj.report(
- {'INFO'}, "Copy UV coordinate (UV map:{})".format(uv_map))
+ {'INFO'}, "Copy UV coordinate (UV map: {})".format(uv_map))
return uv_layers
@@ -97,7 +97,8 @@ def get_paste_uv_layers(ops_obj, obj, bm, src_info, uv_map):
return None
uv_layers.append(bm.loops.layers.uv[new_uv_map.name])
ops_obj.report(
- {'INFO'}, "Paste UV coordinate (UV map:{})".format(new_uv_map))
+ {'INFO'},
+ "Paste UV coordinate (UV map: {})".format(new_uv_map.name))
elif uv_map == "__all":
for src_layer in src_info.keys():
if src_layer not in bm.loops.layers.uv.keys():
@@ -111,7 +112,7 @@ def get_paste_uv_layers(ops_obj, obj, bm, src_info, uv_map):
else:
uv_layers.append(bm.loops.layers.uv[uv_map])
ops_obj.report(
- {'INFO'}, "Paste UV coordinate (UV map:{})".format(uv_map))
+ {'INFO'}, "Paste UV coordinate (UV map: {})".format(uv_map))
return uv_layers
diff --git a/magic_uv/op/copy_paste_uv_object.py b/magic_uv/op/copy_paste_uv_object.py
index 3297f2b8..1b812b82 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bmesh
import bpy
diff --git a/magic_uv/op/copy_paste_uv_uvedit.py b/magic_uv/op/copy_paste_uv_uvedit.py
index 7704d1c9..f12851dd 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import math
from math import atan2, sin, cos
diff --git a/magic_uv/op/flip_rotate_uv.py b/magic_uv/op/flip_rotate_uv.py
index da8af4c3..d0ac6a83 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
import bmesh
diff --git a/magic_uv/op/mirror_uv.py b/magic_uv/op/mirror_uv.py
index d28cf826..dcbaad5e 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import (
diff --git a/magic_uv/op/move_uv.py b/magic_uv/op/move_uv.py
index 881ab378..19160a46 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import BoolProperty
@@ -54,20 +54,6 @@ def _is_valid_context(context):
return True
-def _find_uv(context):
- bm = bmesh.from_edit_mesh(context.object.data)
- topology_dict = []
- uvs = []
- active_uv = bm.loops.layers.uv.active
- for fidx, f in enumerate(bm.faces):
- for vidx, v in enumerate(f.verts):
- if v.select:
- uvs.append(f.loops[vidx][active_uv].uv.copy())
- topology_dict.append([fidx, vidx])
-
- return topology_dict, uvs
-
-
@PropertyClassRegistry()
class _Properties:
idname = "move_uv"
@@ -106,6 +92,9 @@ class MUV_OT_MoveUV(bpy.types.Operator):
self.__ini_uvs = []
self.__operating = False
+ # Creation of BMesh is high cost, so cache related objects.
+ self.__cache = {}
+
@classmethod
def poll(cls, context):
# we can not get area/space/region from console
@@ -119,7 +108,18 @@ class MUV_OT_MoveUV(bpy.types.Operator):
def is_running(cls, _):
return cls.__running
- def modal(self, context, event):
+ def _find_uv(self, bm, active_uv):
+ topology_dict = []
+ uvs = []
+ for fidx, f in enumerate(bm.faces):
+ for vidx, v in enumerate(f.verts):
+ if v.select:
+ uvs.append(f.loops[vidx][active_uv].uv.copy())
+ topology_dict.append([fidx, vidx])
+
+ return topology_dict, uvs
+
+ def modal(self, _, event):
if self.__first_time is True:
self.__prev_mouse = Vector((
event.mouse_region_x, event.mouse_region_y))
@@ -146,12 +146,11 @@ class MUV_OT_MoveUV(bpy.types.Operator):
return {'RUNNING_MODAL'}
# update UV
- obj = context.object
- bm = bmesh.from_edit_mesh(obj.data)
- active_uv = bm.loops.layers.uv.active
- for fidx, vidx in self.__topology_dict:
- l = bm.faces[fidx].loops[vidx]
- l[active_uv].uv = l[active_uv].uv + dv
+ obj = self.__cache["active_object"]
+ bm = self.__cache["bmesh"]
+ active_uv = self.__cache["active_uv"]
+ for uv in self.__cache["target_uv"]:
+ uv += dv
bmesh.update_edit_mesh(obj.data)
# check mouse preference
@@ -163,10 +162,12 @@ class MUV_OT_MoveUV(bpy.types.Operator):
for (fidx, vidx), uv in zip(self.__topology_dict, self.__ini_uvs):
bm.faces[fidx].loops[vidx][active_uv].uv = uv
MUV_OT_MoveUV.__running = False
+ self.__cache = {}
return {'FINISHED'}
# confirmed
if event.type == confirm_btn and event.value == 'PRESS':
MUV_OT_MoveUV.__running = False
+ self.__cache = {}
return {'FINISHED'}
return {'RUNNING_MODAL'}
@@ -177,7 +178,21 @@ class MUV_OT_MoveUV(bpy.types.Operator):
self.__first_time = True
context.window_manager.modal_handler_add(self)
- self.__topology_dict, self.__ini_uvs = _find_uv(context)
+
+ obj = context.active_object
+ 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)
+
+ # Optimization: Store temporary variables which cause heavy
+ # calculation.
+ self.__cache["active_object"] = obj
+ self.__cache["bmesh"] = bm
+ self.__cache["active_uv"] = active_uv
+ self.__cache["target_uv"] = []
+ for fidx, vidx in self.__topology_dict:
+ l = bm.faces[fidx].loops[vidx]
+ self.__cache["target_uv"].append(l[active_uv].uv)
if context.area:
context.area.tag_redraw()
diff --git a/magic_uv/op/pack_uv.py b/magic_uv/op/pack_uv.py
index 3589231a..0d7ed966 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from math import fabs
diff --git a/magic_uv/op/preserve_uv_aspect.py b/magic_uv/op/preserve_uv_aspect.py
index c9ba7204..5b3e50cf 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import StringProperty, EnumProperty, BoolProperty
diff --git a/magic_uv/op/select_uv.py b/magic_uv/op/select_uv.py
index 223f9e2f..d80b43a8 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import BoolProperty
@@ -30,6 +30,7 @@ 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):
@@ -91,28 +92,42 @@ class MUV_OT_SelectUV_SelectOverlapped(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()
+ objs = [o for o in bpy.data.objects if compat.get_object_select(o)]
+
+ 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)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
- if context.tool_settings.use_uv_select_sync:
- sel_faces = [f for f in bm.faces]
- else:
- sel_faces = [f for f in bm.faces if f.select]
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+ bm_list.append(bm)
+ uv_layer_list.append(uv_layer)
+ faces_list.append(sel_faces)
- overlapped_info = common.get_overlapped_uv_info(bm, sel_faces,
- uv_layer, 'FACE')
+ overlapped_info = common.get_overlapped_uv_info(bm_list, faces_list,
+ uv_layer_list, 'FACE')
for info in overlapped_info:
if context.tool_settings.use_uv_select_sync:
info["subject_face"].select = True
else:
for l in info["subject_face"].loops:
- l[uv_layer].select = True
+ l[info["subject_uv_layer"]].select = True
- bmesh.update_edit_mesh(obj.data)
+ for o in objs:
+ bmesh.update_edit_mesh(o.data)
return {'FINISHED'}
@@ -136,26 +151,40 @@ class MUV_OT_SelectUV_SelectFlipped(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()
+ objs = [o for o in bpy.data.objects if compat.get_object_select(o)]
+
+ 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)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
- if context.tool_settings.use_uv_select_sync:
- sel_faces = [f for f in bm.faces]
- else:
- sel_faces = [f for f in bm.faces if f.select]
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+ bm_list.append(bm)
+ uv_layer_list.append(uv_layer)
+ faces_list.append(sel_faces)
- flipped_info = common.get_flipped_uv_info(sel_faces, uv_layer)
+ flipped_info = common.get_flipped_uv_info(faces_list, uv_layer_list)
for info in flipped_info:
if context.tool_settings.use_uv_select_sync:
info["face"].select = True
else:
for l in info["face"].loops:
- l[uv_layer].select = True
+ l[info["uv_layer"]].select = True
- bmesh.update_edit_mesh(obj.data)
+ for o in objs:
+ bmesh.update_edit_mesh(o.data)
return {'FINISHED'}
diff --git a/magic_uv/op/smooth_uv.py b/magic_uv/op/smooth_uv.py
index 17068308..94e41367 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import BoolProperty, FloatProperty
@@ -167,8 +167,7 @@ class MUV_OT_SmoothUV(bpy.types.Operator):
# get target UV
for i in range(len(accm_uvlens[:-1])):
# get line segment which UV will be placed
- if ((accm_uvlens[i] <= target_length) and
- (accm_uvlens[i + 1] > target_length)):
+ if accm_uvlens[i] <= target_length < accm_uvlens[i + 1]:
tgt_seg_len = target_length - accm_uvlens[i]
seg_len = accm_uvlens[i + 1] - accm_uvlens[i]
uv1 = orig_uvs[i]
@@ -240,8 +239,7 @@ class MUV_OT_SmoothUV(bpy.types.Operator):
# get target UV
for i in range(len(accm_uv[:-1])):
# get line segment to be placed
- if ((accm_uv[i] <= target_length) and
- (accm_uv[i + 1] > target_length)):
+ if accm_uv[i] <= target_length < accm_uv[i + 1]:
tgt_seg_len = target_length - accm_uv[i]
seg_len = accm_uv[i + 1] - accm_uv[i]
uv1 = uvs[i]
diff --git a/magic_uv/op/texture_lock.py b/magic_uv/op/texture_lock.py
index 43d78549..ddcaf315 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import math
from math import atan2, cos, sqrt, sin, fabs
@@ -435,7 +435,7 @@ class MUV_OT_TextureLock_Intr(bpy.types.Operator):
bm.faces.ensure_lookup_table()
prev = set(self.__intr_verts)
- now = set([v.index for v in bm.verts if v.select])
+ now = {v.index for v in bm.verts if v.select}
return prev != now
diff --git a/magic_uv/op/texture_projection.py b/magic_uv/op/texture_projection.py
index 6ef6b1ce..b754dd88 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from collections import namedtuple
diff --git a/magic_uv/op/texture_wrap.py b/magic_uv/op/texture_wrap.py
index 9936a510..92512438 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import (
diff --git a/magic_uv/op/transfer_uv.py b/magic_uv/op/transfer_uv.py
index b63376c9..ce9639a7 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from collections import OrderedDict
diff --git a/magic_uv/op/unwrap_constraint.py b/magic_uv/op/unwrap_constraint.py
index bd78dafc..3c23575a 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import (
diff --git a/magic_uv/op/uv_bounding_box.py b/magic_uv/op/uv_bounding_box.py
index 589abcc4..d4edac9c 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from enum import IntEnum
import math
@@ -438,10 +438,8 @@ class StateNone(StateBase):
mouse_view.x, mouse_view.y)
for i, p in enumerate(ctrl_points):
px, py = context.region.view2d.view_to_region(p.x, p.y)
- in_cp_x = (px + cp_react_size > x and
- px - cp_react_size < x)
- in_cp_y = (py + cp_react_size > y and
- py - cp_react_size < y)
+ in_cp_x = px - cp_react_size < x < px + cp_react_size
+ in_cp_y = py - cp_react_size < y < py + cp_react_size
if in_cp_x and in_cp_y:
if is_uscaling:
arr = [1, 3, 6, 8]
diff --git a/magic_uv/op/uv_inspection.py b/magic_uv/op/uv_inspection.py
index c5f92004..8aae181e 100644
--- a/magic_uv/op/uv_inspection.py
+++ b/magic_uv/op/uv_inspection.py
@@ -20,8 +20,11 @@
__author__ = "Nutti <nutti.metro@gmail.com>"
__status__ = "production"
-__version__ = "6.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
+
+import random
+from math import fabs
import bpy
from bpy.props import BoolProperty, EnumProperty
@@ -65,19 +68,31 @@ def _update_uvinsp_info(context):
sc = context.scene
props = sc.muv_props.uv_inspection
- 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()
+ 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)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+ uv_layer = bm.loops.layers.uv.verify()
+
+ if context.tool_settings.use_uv_select_sync:
+ sel_faces = [f for f in bm.faces]
+ else:
+ sel_faces = [f for f in bm.faces if f.select]
+ bm_list.append(bm)
+ uv_layer_list.append(uv_layer)
+ faces_list.append(sel_faces)
- if context.tool_settings.use_uv_select_sync:
- sel_faces = [f for f in bm.faces]
- else:
- sel_faces = [f for f in bm.faces if f.select]
props.overlapped_info = common.get_overlapped_uv_info(
- bm, sel_faces, uv_layer, sc.muv_uv_inspection_show_mode)
- props.flipped_info = common.get_flipped_uv_info(sel_faces, uv_layer)
+ bm_list, faces_list, uv_layer_list, sc.muv_uv_inspection_show_mode)
+ props.flipped_info = common.get_flipped_uv_info(faces_list, uv_layer_list)
@PropertyClassRegistry()
@@ -205,14 +220,15 @@ class MUV_OT_UVInspection_Render(bpy.types.Operator):
bgl.glColor4f(color[0], color[1], color[2], color[3])
for uv in poly:
x, y = context.region.view2d.view_to_region(
- uv.x, uv.y)
+ uv.x, uv.y, clip=False)
bgl.glVertex2f(x, y)
bgl.glEnd()
elif sc.muv_uv_inspection_show_mode == 'FACE':
bgl.glBegin(bgl.GL_TRIANGLE_FAN)
bgl.glColor4f(color[0], color[1], color[2], color[3])
for uv in info["subject_uvs"]:
- x, y = context.region.view2d.view_to_region(uv.x, uv.y)
+ x, y = context.region.view2d.view_to_region(
+ uv.x, uv.y, clip=False)
bgl.glVertex2f(x, y)
bgl.glEnd()
@@ -226,14 +242,15 @@ class MUV_OT_UVInspection_Render(bpy.types.Operator):
bgl.glColor4f(color[0], color[1], color[2], color[3])
for uv in poly:
x, y = context.region.view2d.view_to_region(
- uv.x, uv.y)
+ uv.x, uv.y, clip=False)
bgl.glVertex2f(x, y)
bgl.glEnd()
elif sc.muv_uv_inspection_show_mode == 'FACE':
bgl.glBegin(bgl.GL_TRIANGLE_FAN)
bgl.glColor4f(color[0], color[1], color[2], color[3])
for uv in info["uvs"]:
- x, y = context.region.view2d.view_to_region(uv.x, uv.y)
+ x, y = context.region.view2d.view_to_region(
+ uv.x, uv.y, clip=False)
bgl.glVertex2f(x, y)
bgl.glEnd()
@@ -279,3 +296,201 @@ class MUV_OT_UVInspection_Update(bpy.types.Operator):
context.area.tag_redraw()
return {'FINISHED'}
+
+
+@BlClassRegistry()
+class MUV_OT_UVInspection_PaintUVIsland(bpy.types.Operator):
+ """
+ Operation class: Paint UV island with random color.
+ """
+
+ bl_idname = "uv.muv_uv_inspection_paint_uv_island"
+ bl_label = "Paint UV Island"
+ bl_description = "Paint UV island with random color"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ # we can not get area/space/region from console
+ if common.is_console_mode():
+ return True
+ return _is_valid_context(context)
+
+ def _get_or_new_image(self, name, width, height):
+ if name in bpy.data.images.keys():
+ return bpy.data.images[name]
+ return bpy.data.images.new(name, width, height)
+
+ def _get_or_new_material(self, name):
+ if name in bpy.data.materials.keys():
+ return bpy.data.materials[name]
+ return bpy.data.materials.new(name)
+
+ def _get_or_new_texture(self, name):
+ if name in bpy.data.textures.keys():
+ return bpy.data.textures[name]
+ return bpy.data.textures.new(name, 'IMAGE')
+
+ def _get_override_context(self, context):
+ for window in context.window_manager.windows:
+ screen = window.screen
+ for area in screen.areas:
+ if area.type == 'VIEW_3D':
+ for region in area.regions:
+ if region.type == 'WINDOW':
+ return {'window': window, 'screen': screen,
+ 'area': area, 'region': region}
+ return None
+
+ def _create_unique_color(self, exist_colors, allowable=0.1):
+ retry = 0
+ while retry < 20:
+ r = random.random()
+ g = random.random()
+ b = random.random()
+ new_color = [r, g, b]
+ for color in exist_colors:
+ if ((fabs(new_color[0] - color[0]) < allowable) and
+ (fabs(new_color[1] - color[1]) < allowable) and
+ (fabs(new_color[2] - color[2]) < allowable)):
+ break
+ else:
+ return new_color
+ return None
+
+ def execute(self, context):
+ obj = context.active_object
+ 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')
+ 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
+
+ bpy.ops.object.mode_set(mode=mode_orig)
+
+ return {'FINISHED'}
diff --git a/magic_uv/op/uv_sculpt.py b/magic_uv/op/uv_sculpt.py
index ff3a9db3..f40ab253 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from math import pi, cos, tan, sin
@@ -168,6 +168,20 @@ class _Properties:
del scene.muv_uv_sculpt_relax_method
+def location_3d_to_region_2d_extra(region, rv3d, coord):
+ coord_2d = view3d_utils.location_3d_to_region_2d(region, rv3d, coord)
+ if coord_2d is None:
+ prj = rv3d.perspective_matrix @ Vector(
+ (coord[0], coord[1], coord[2], 1.0))
+ width_half = region.width / 2.0
+ height_half = region.height / 2.0
+ coord_2d = Vector((
+ width_half + width_half * (prj.x / prj.w),
+ height_half + height_half * (prj.y / prj.w)
+ ))
+ return coord_2d
+
+
@BlClassRegistry()
class MUV_OT_UVSculpt(bpy.types.Operator):
"""
@@ -263,7 +277,7 @@ class MUV_OT_UVSculpt(bpy.types.Operator):
if not f.select:
continue
for i, l in enumerate(f.loops):
- loc_2d = view3d_utils.location_3d_to_region_2d(
+ 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
@@ -301,7 +315,7 @@ class MUV_OT_UVSculpt(bpy.types.Operator):
if not f.select:
continue
for i, l in enumerate(f.loops):
- loc_2d = view3d_utils.location_3d_to_region_2d(
+ 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
@@ -393,7 +407,7 @@ class MUV_OT_UVSculpt(bpy.types.Operator):
if not f.select:
continue
for i, l in enumerate(f.loops):
- loc_2d = view3d_utils.location_3d_to_region_2d(
+ 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
diff --git a/magic_uv/op/uvw.py b/magic_uv/op/uvw.py
index 4b4a4f04..fca72d2c 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from math import sin, cos, pi
@@ -228,20 +228,26 @@ class MUV_OT_UVW_BoxMap(bpy.types.Operator):
return True
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()
+ 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']
+
+ for o in objs:
+ bm = bmesh.from_edit_mesh(o.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
- # get UV layer
- uv_layer = _get_uv_layer(self, bm, self.assign_uvmap)
- if not uv_layer:
- return {'CANCELLED'}
+ # get UV layer
+ uv_layer = _get_uv_layer(self, bm, self.assign_uvmap)
+ if not uv_layer:
+ return {'CANCELLED'}
- _apply_box_map(bm, uv_layer, self.size, self.offset, self.rotation,
- self.tex_aspect)
- bmesh.update_edit_mesh(obj.data)
+ _apply_box_map(bm, uv_layer, self.size, self.offset, self.rotation,
+ self.tex_aspect)
+ bmesh.update_edit_mesh(o.data)
return {'FINISHED'}
@@ -285,20 +291,26 @@ class MUV_OT_UVW_BestPlanerMap(bpy.types.Operator):
return True
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()
+ 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']
+
+ for o in objs:
+ bm = bmesh.from_edit_mesh(o.data)
+ if common.check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
- # get UV layer
- uv_layer = _get_uv_layer(self, bm, self.assign_uvmap)
- if not uv_layer:
- return {'CANCELLED'}
+ # get UV layer
+ uv_layer = _get_uv_layer(self, bm, self.assign_uvmap)
+ if not uv_layer:
+ return {'CANCELLED'}
- _apply_planer_map(bm, uv_layer, self.size, self.offset, self.rotation,
- self.tex_aspect)
+ _apply_planer_map(bm, uv_layer, self.size, self.offset,
+ self.rotation, self.tex_aspect)
- bmesh.update_edit_mesh(obj.data)
+ bmesh.update_edit_mesh(o.data)
return {'FINISHED'}
diff --git a/magic_uv/op/world_scale_uv.py b/magic_uv/op/world_scale_uv.py
index 0107fc6f..9ed86eb0 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from math import sqrt
@@ -31,7 +31,6 @@ from bpy.props import (
FloatProperty,
IntVectorProperty,
BoolProperty,
- StringProperty,
)
import bmesh
from mathutils import Vector
@@ -63,9 +62,34 @@ def _is_valid_context(context):
return True
-def _measure_wsuv_info(obj, method='FIRST', tex_size=None):
- mesh_area = common.measure_mesh_area(obj)
- uv_area = common.measure_uv_area(obj, method, tex_size)
+def _measure_wsuv_info(obj, calc_method='MESH',
+ tex_selection_method='FIRST', tex_size=None,
+ only_selected=True):
+ mesh_areas = common.measure_mesh_area(obj, calc_method, only_selected)
+ uv_areas = common.measure_uv_area(obj, calc_method, tex_selection_method,
+ tex_size, only_selected)
+
+ if not uv_areas:
+ return None, mesh_areas, None
+
+ if len(mesh_areas) != len(uv_areas):
+ raise ValueError("mesh_area and uv_area must be same length")
+
+ densities = []
+ for mesh_area, uv_area in zip(mesh_areas, uv_areas):
+ if mesh_area == 0.0:
+ densities.append(0.0)
+ else:
+ densities.append(sqrt(uv_area) / sqrt(mesh_area))
+
+ return uv_areas, mesh_areas, densities
+
+
+def _measure_wsuv_info_from_faces(obj, faces, uv_layer, tex_layer,
+ tex_selection_method='FIRST', tex_size=None):
+ mesh_area = common.measure_mesh_area_from_faces(faces)
+ uv_area = common.measure_uv_area_from_faces(
+ obj, faces, uv_layer, tex_layer, tex_selection_method, tex_size)
if not uv_area:
return None, mesh_area, None
@@ -78,22 +102,12 @@ def _measure_wsuv_info(obj, method='FIRST', tex_size=None):
return uv_area, mesh_area, density
-def _apply(obj, origin, factor):
- 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()
-
- sel_faces = [f for f in bm.faces if f.select]
-
- uv_layer = bm.loops.layers.uv.verify()
-
+def _apply(faces, uv_layer, origin, factor):
# calculate origin
if origin == 'CENTER':
origin = Vector((0.0, 0.0))
num = 0
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin = origin + uv
@@ -101,7 +115,7 @@ def _apply(obj, origin, factor):
origin = origin / num
elif origin == 'LEFT_TOP':
origin = Vector((100000.0, -100000.0))
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin.x = min(origin.x, uv.x)
@@ -109,7 +123,7 @@ def _apply(obj, origin, factor):
elif origin == 'LEFT_CENTER':
origin = Vector((100000.0, 0.0))
num = 0
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin.x = min(origin.x, uv.x)
@@ -118,7 +132,7 @@ def _apply(obj, origin, factor):
origin.y = origin.y / num
elif origin == 'LEFT_BOTTOM':
origin = Vector((100000.0, 100000.0))
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin.x = min(origin.x, uv.x)
@@ -126,7 +140,7 @@ def _apply(obj, origin, factor):
elif origin == 'CENTER_TOP':
origin = Vector((0.0, -100000.0))
num = 0
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin.x = origin.x + uv.x
@@ -136,7 +150,7 @@ def _apply(obj, origin, factor):
elif origin == 'CENTER_BOTTOM':
origin = Vector((0.0, 100000.0))
num = 0
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin.x = origin.x + uv.x
@@ -145,7 +159,7 @@ def _apply(obj, origin, factor):
origin.x = origin.x / num
elif origin == 'RIGHT_TOP':
origin = Vector((-100000.0, -100000.0))
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin.x = max(origin.x, uv.x)
@@ -153,7 +167,7 @@ def _apply(obj, origin, factor):
elif origin == 'RIGHT_CENTER':
origin = Vector((-100000.0, 0.0))
num = 0
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
origin.x = max(origin.x, uv.x)
@@ -162,21 +176,19 @@ def _apply(obj, origin, factor):
origin.y = origin.y / num
elif origin == 'RIGHT_BOTTOM':
origin = Vector((-100000.0, 100000.0))
- for f in sel_faces:
+ for f in 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)
# update UV coordinate
- for f in sel_faces:
+ for f in faces:
for l in f.loops:
uv = l[uv_layer].uv
diff = uv - origin
l[uv_layer].uv = origin + diff * factor
- bmesh.update_edit_mesh(obj.data)
-
def _get_target_textures(_, __):
images = common.find_images(bpy.context.active_object)
@@ -207,7 +219,8 @@ class _Properties:
)
scene.muv_world_scale_uv_src_uv_area = FloatProperty(
name="UV Area",
- description="Source UV Area",
+ description="Source UV Area (Average if calculation method is UV "
+ "Island or Face)",
default=0.0,
min=0.0
)
@@ -277,6 +290,26 @@ class _Properties:
description="Texture to be applied",
items=_get_target_textures
)
+ scene.muv_world_scale_uv_tgt_area_calc_method = EnumProperty(
+ name="Area Calculation Method",
+ description="How to calculate target area",
+ items=[
+ ('MESH', "Mesh", "Calculate area by whole faces in mesh"),
+ ('UV ISLAND', "UV Island", "Calculate area each UV islands"),
+ ('FACE', "Face", "Calculate area each face")
+ ],
+ default='MESH'
+ )
+ scene.muv_world_scale_uv_measure_only_selected = BoolProperty(
+ name="Only Selected",
+ description="Measure with only selected faces",
+ default=True,
+ )
+ scene.muv_world_scale_uv_apply_only_selected = BoolProperty(
+ name="Only Selected",
+ description="Apply to only selected faces",
+ default=True,
+ )
@classmethod
def del_props(cls, scene):
@@ -290,6 +323,9 @@ class _Properties:
del scene.muv_world_scale_uv_origin
del scene.muv_world_scale_uv_measure_tgt_texture
del scene.muv_world_scale_uv_apply_tgt_texture
+ del scene.muv_world_scale_uv_tgt_area_calc_method
+ del scene.muv_world_scale_uv_measure_only_selected
+ del scene.muv_world_scale_uv_apply_only_selected
@BlClassRegistry()
@@ -304,10 +340,15 @@ class MUV_OT_WorldScaleUV_Measure(bpy.types.Operator):
bl_description = "Measure face size for scale calculation"
bl_options = {'REGISTER', 'UNDO'}
- tgt_texture = StringProperty(
+ tgt_texture = EnumProperty(
name="Texture",
- description="Texture to be measured",
- default="[Average]"
+ description="Texture to be applied",
+ items=_get_target_textures
+ )
+ only_selected = BoolProperty(
+ name="Only Selected",
+ description="Measure with only selected faces",
+ default=True,
)
@classmethod
@@ -317,32 +358,44 @@ class MUV_OT_WorldScaleUV_Measure(bpy.types.Operator):
return True
return _is_valid_context(context)
+ @staticmethod
+ def setup_argument(ops, scene):
+ ops.tgt_texture = scene.muv_world_scale_uv_measure_tgt_texture
+ ops.only_selected = scene.muv_world_scale_uv_measure_only_selected
+
def execute(self, context):
sc = context.scene
obj = context.active_object
if self.tgt_texture == "[Average]":
- uv_area, mesh_area, density = _measure_wsuv_info(obj, 'AVERAGE')
+ uv_areas, mesh_areas, densities = _measure_wsuv_info(
+ obj, calc_method='MESH', tex_selection_method='AVERAGE',
+ only_selected=self.only_selected)
elif self.tgt_texture == "[Max]":
- uv_area, mesh_area, density = _measure_wsuv_info(obj, 'MAX')
+ uv_areas, mesh_areas, densities = _measure_wsuv_info(
+ obj, calc_method='MESH', tex_selection_method='MAX',
+ only_selected=self.only_selected)
elif self.tgt_texture == "[Min]":
- uv_area, mesh_area, density = _measure_wsuv_info(obj, 'MIN')
+ uv_areas, mesh_areas, densities = _measure_wsuv_info(
+ obj, calc_method='MESH', tex_selection_method='MIN',
+ only_selected=self.only_selected)
else:
texture = bpy.data.images[self.tgt_texture]
- uv_area, mesh_area, density = _measure_wsuv_info(
- obj, 'USER_SPECIFIED', texture.size)
- if not uv_area:
+ uv_areas, mesh_areas, densities = _measure_wsuv_info(
+ obj, calc_method='MESH', tex_selection_method='USER_SPECIFIED',
+ only_selected=self.only_selected, tex_size=texture.size)
+ if not uv_areas:
self.report({'WARNING'},
"Object must have more than one UV map and texture")
return {'CANCELLED'}
- sc.muv_world_scale_uv_src_uv_area = uv_area
- sc.muv_world_scale_uv_src_mesh_area = mesh_area
- sc.muv_world_scale_uv_src_density = density
+ sc.muv_world_scale_uv_src_uv_area = uv_areas[0]
+ sc.muv_world_scale_uv_src_mesh_area = mesh_areas[0]
+ sc.muv_world_scale_uv_src_density = densities[0]
self.report({'INFO'},
"UV Area: {0}, Mesh Area: {1}, Texel Density: {2}"
- .format(uv_area, mesh_area, density))
+ .format(uv_areas[0], mesh_areas[0], densities[0]))
return {'FINISHED'}
@@ -395,6 +448,21 @@ class MUV_OT_WorldScaleUV_ApplyManual(bpy.types.Operator):
default=True,
options={'HIDDEN', 'SKIP_SAVE'}
)
+ tgt_area_calc_method = EnumProperty(
+ name="Area Calculation Method",
+ description="How to calculate target area",
+ items=[
+ ('MESH', "Mesh", "Calculate area by whole faces in mesh"),
+ ('UV ISLAND', "UV Island", "Calculate area each UV islands"),
+ ('FACE', "Face", "Calculate area each face")
+ ],
+ default='MESH'
+ )
+ only_selected = BoolProperty(
+ name="Only Selected",
+ description="Apply to only selected faces",
+ default=True,
+ )
@classmethod
def poll(cls, context):
@@ -403,6 +471,16 @@ class MUV_OT_WorldScaleUV_ApplyManual(bpy.types.Operator):
return True
return _is_valid_context(context)
+ @staticmethod
+ def setup_argument(ops, scene):
+ ops.tgt_density = scene.muv_world_scale_uv_tgt_density
+ ops.tgt_texture_size = scene.muv_world_scale_uv_tgt_texture_size
+ ops.origin = scene.muv_world_scale_uv_origin
+ ops.show_dialog = False
+ ops.tgt_area_calc_method = \
+ scene.muv_world_scale_uv_tgt_area_calc_method
+ 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)
@@ -411,27 +489,47 @@ class MUV_OT_WorldScaleUV_ApplyManual(bpy.types.Operator):
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
- tex_size = self.tgt_texture_size
- uv_area, _, density = _measure_wsuv_info(obj, 'USER_SPECIFIED',
- tex_size)
- if not uv_area:
+ 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
+
+ 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)
- tgt_density = self.tgt_density
- factor = tgt_density / density
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object must have more than one UV map")
+ return {'CANCELLED'}
- _apply(context.active_object, self.origin, factor)
- self.report({'INFO'}, "Scaling factor: {0}".format(factor))
+ 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: {0}".format(factors))
return {'FINISHED'}
def draw(self, _):
layout = self.layout
- layout.prop(self, "tgt_density")
+ layout.label(text="Target:")
+ layout.prop(self, "only_selected")
layout.prop(self, "tgt_texture_size")
+ layout.prop(self, "tgt_density")
layout.prop(self, "origin")
+ layout.prop(self, "tgt_area_calc_method")
layout.separator()
@@ -500,10 +598,25 @@ class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator):
default=True,
options={'HIDDEN', 'SKIP_SAVE'}
)
- tgt_texture = StringProperty(
+ tgt_texture = EnumProperty(
name="Texture",
description="Texture to be applied",
- default="[Average]"
+ items=_get_target_textures
+ )
+ tgt_area_calc_method = EnumProperty(
+ name="Area Calculation Method",
+ description="How to calculate target area",
+ items=[
+ ('MESH', "Mesh", "Calculate area by whole faces in mesh"),
+ ('UV ISLAND', "UV Island", "Calculate area each UV islands"),
+ ('FACE', "Face", "Calculate area each face")
+ ],
+ default='MESH'
+ )
+ only_selected = BoolProperty(
+ name="Only Selected",
+ description="Apply to only selected faces",
+ default=True,
)
@classmethod
@@ -513,6 +626,19 @@ class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator):
return True
return _is_valid_context(context)
+ @staticmethod
+ def setup_argument(ops, scene):
+ ops.tgt_scaling_factor = \
+ scene.muv_world_scale_uv_tgt_scaling_factor
+ ops.origin = scene.muv_world_scale_uv_origin
+ ops.src_density = scene.muv_world_scale_uv_src_density
+ ops.same_density = False
+ ops.show_dialog = False
+ ops.tgt_texture = scene.muv_world_scale_uv_apply_tgt_texture
+ ops.tgt_area_calc_method = \
+ scene.muv_world_scale_uv_tgt_area_calc_method
+ 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)
@@ -521,26 +647,49 @@ class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator):
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
- if self.tgt_texture == "[Average]":
- uv_area, _, density = _measure_wsuv_info(obj, 'AVERAGE')
- elif self.tgt_texture == "[Max]":
- uv_area, _, density = _measure_wsuv_info(obj, 'MAX')
- elif self.tgt_texture == "[Min]":
- uv_area, _, density = _measure_wsuv_info(obj, 'MIN')
- else:
- tgt_texture = bpy.data.images[self.tgt_texture]
- uv_area, _, density = _measure_wsuv_info(obj, 'USER_SPECIFIED',
- tgt_texture.size)
- if not uv_area:
- self.report({'WARNING'},
- "Object must have more than one UV map and texture")
+ 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)
+
+ if not uv_area:
+ self.report({'WARNING'},
+ "Object must have more than one UV map and "
+ "texture")
+ return {'CANCELLED'}
- tgt_density = self.src_density * self.tgt_scaling_factor
- factor = tgt_density / density
+ tgt_density = self.src_density * self.tgt_scaling_factor
+ factor = tgt_density / density
- _apply(context.active_object, self.origin, factor)
- self.report({'INFO'}, "Scaling factor: {0}".format(factor))
+ _apply(faces, uv_layer, self.origin, factor)
+ factors.append(factor)
+
+ bmesh.update_edit_mesh(obj.data)
+ self.report({'INFO'}, "Scaling factor: {0}".format(factors))
return {'FINISHED'}
@@ -554,9 +703,13 @@ class MUV_OT_WorldScaleUV_ApplyScalingDensity(bpy.types.Operator):
layout.separator()
+ layout.label(text="Target:")
if not self.same_density:
layout.prop(self, "tgt_scaling_factor")
+ layout.prop(self, "only_selected")
+ layout.prop(self, "tgt_texture")
layout.prop(self, "origin")
+ layout.prop(self, "tgt_area_calc_method")
layout.separator()
@@ -640,10 +793,25 @@ class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator):
default=True,
options={'HIDDEN', 'SKIP_SAVE'}
)
- tgt_texture = StringProperty(
+ tgt_texture = EnumProperty(
name="Texture",
description="Texture to be applied",
- default="[Average]"
+ items=_get_target_textures
+ )
+ tgt_area_calc_method = EnumProperty(
+ name="Area Calculation Method",
+ description="How to calculate target area",
+ items=[
+ ('MESH', "Mesh", "Calculate area by whole faces in mesh"),
+ ('UV ISLAND', "UV Island", "Calculate area each UV islands"),
+ ('FACE', "Face", "Calculate area each face")
+ ],
+ default='MESH'
+ )
+ only_selected = BoolProperty(
+ name="Only Selected",
+ description="Apply to only selected faces",
+ default=True,
)
@classmethod
@@ -653,6 +821,18 @@ class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator):
return True
return _is_valid_context(context)
+ @staticmethod
+ def setup_argument(ops, scene):
+ ops.origin = scene.muv_world_scale_uv_origin
+ ops.src_density = scene.muv_world_scale_uv_src_density
+ ops.src_uv_area = scene.muv_world_scale_uv_src_uv_area
+ ops.src_mesh_area = scene.muv_world_scale_uv_src_mesh_area
+ ops.show_dialog = False
+ ops.tgt_texture = scene.muv_world_scale_uv_apply_tgt_texture
+ ops.tgt_area_calc_method = \
+ scene.muv_world_scale_uv_tgt_area_calc_method
+ 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)
@@ -661,28 +841,49 @@ class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator):
bm.edges.ensure_lookup_table()
bm.faces.ensure_lookup_table()
- if self.tgt_texture == "[Average]":
- uv_area, mesh_area, density = _measure_wsuv_info(obj, 'AVERAGE')
- elif self.tgt_texture == "[Max]":
- uv_area, mesh_area, density = _measure_wsuv_info(obj, 'MAX')
- elif self.tgt_texture == "[Min]":
- uv_area, mesh_area, density = _measure_wsuv_info(obj, 'MIN')
- else:
- tgt_texture = bpy.data.images[self.tgt_texture]
- uv_area, mesh_area, density = _measure_wsuv_info(
- obj, 'USER_SPECIFIED', tgt_texture.size)
- if not uv_area:
- self.report({'WARNING'},
- "Object must have more than one UV map and texture")
+ if not bm.loops.layers.uv:
+ self.report({'WARNING'}, "Object must have more than one UV map")
return {'CANCELLED'}
-
- tgt_density = self.src_density * sqrt(mesh_area) / sqrt(
- self.src_mesh_area)
-
- factor = tgt_density / density
-
- _apply(context.active_object, self.origin, factor)
- self.report({'INFO'}, "Scaling factor: {0}".format(factor))
+ 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
+
+ _apply(faces, uv_layer, self.origin, factor)
+ factors.append(factor)
+
+ bmesh.update_edit_mesh(obj.data)
+ self.report({'INFO'}, "Scaling factor: {0}".format(factors))
return {'FINISHED'}
@@ -697,7 +898,12 @@ class MUV_OT_WorldScaleUV_ApplyProportionalToMesh(bpy.types.Operator):
col.enabled = False
layout.separator()
+
+ layout.label(text="Target:")
+ layout.prop(self, "only_selected")
layout.prop(self, "origin")
+ layout.prop(self, "tgt_area_calc_method")
+ layout.prop(self, "tgt_texture")
layout.separator()
diff --git a/magic_uv/preferences.py b/magic_uv/preferences.py
index 6d66b308..926ec728 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
from bpy.props import (
@@ -33,6 +33,7 @@ from bpy.props import (
from bpy.types import AddonPreferences
from . import common
+from .op.clip_uv import MUV_OT_ClipUV
from .op.flip_rotate_uv import MUV_OT_FlipRotateUV
from .op.mirror_uv import MUV_OT_MirrorUV
from .op.move_uv import MUV_OT_MoveUV
@@ -122,13 +123,17 @@ def image_uvs_menu_fn(self, context):
sc = context.scene
layout.separator()
- # Copy/Paste UV (on UV/Image Editor)
layout.label(text="Copy/Paste UV", icon=compat.icon('IMAGE'))
+ # Copy/Paste UV (on UV/Image Editor)
layout.menu(MUV_MT_CopyPasteUV_UVEdit.bl_idname, text="Copy/Paste UV")
layout.separator()
- # Pack UV
layout.label(text="UV Manipulation", icon=compat.icon('IMAGE'))
+ # Clip UV
+ ops = layout.operator(MUV_OT_ClipUV.bl_idname, text="Clip UV")
+ ops.clip_uv_range_max = sc.muv_clip_uv_range_max
+ ops.clip_uv_range_min = sc.muv_clip_uv_range_min
+ # Pack UV
ops = layout.operator(MUV_OT_PackUV.bl_idname, text="Pack UV")
ops.allowable_center_deviation = sc.muv_pack_uv_allowable_center_deviation
ops.allowable_size_deviation = sc.muv_pack_uv_allowable_size_deviation
@@ -143,8 +148,8 @@ def image_uvs_menu_fn(self, context):
layout.menu(MUV_MT_AlignUV.bl_idname, text="Align UV")
layout.separator()
- # Align UV Cursor
layout.label(text="Editor Enhancement", icon=compat.icon('IMAGE'))
+ # Align UV Cursor
layout.menu(MUV_MT_AlignUVCursor.bl_idname, text="Align UV Cursor")
# UV Bounding Box
layout.prop(sc, "muv_uv_bounding_box_show", text="UV Bounding Box")
diff --git a/magic_uv/properites.py b/magic_uv/properites.py
index e553816b..b269cbed 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 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 74e796cc..00d95d9e 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
@@ -39,7 +39,10 @@ from ..op.select_uv import (
MUV_OT_SelectUV_SelectOverlapped,
MUV_OT_SelectUV_SelectFlipped,
)
-from ..op.uv_inspection import MUV_OT_UVInspection_Update
+from ..op.uv_inspection import (
+ MUV_OT_UVInspection_Update,
+ MUV_OT_UVInspection_PaintUVIsland,
+)
from ..utils.bl_class_registry import BlClassRegistry
@@ -184,5 +187,8 @@ class MUV_MT_UVInspection(bpy.types.Menu):
layout = self.layout
sc = context.scene
- layout.prop(sc, "muv_uv_inspection_show", text="UV Inspection")
+ layout.prop(sc, "muv_uv_inspection_show",
+ text="Show Overlapped/Flipped")
layout.operator(MUV_OT_UVInspection_Update.bl_idname, text="Update")
+ layout.separator()
+ layout.operator(MUV_OT_UVInspection_PaintUVIsland.bl_idname)
diff --git a/magic_uv/ui/VIEW3D_MT_object.py b/magic_uv/ui/VIEW3D_MT_object.py
index b4fca522..f34c74f9 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
diff --git a/magic_uv/ui/VIEW3D_MT_uv_map.py b/magic_uv/ui/VIEW3D_MT_uv_map.py
index 853d1855..7ab50ace 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy.utils
@@ -147,24 +147,25 @@ class MUV_MT_WorldScaleUV(bpy.types.Menu):
layout = self.layout
sc = context.scene
- layout.operator(MUV_OT_WorldScaleUV_Measure.bl_idname,
- text="Measure")
+ layout.operator(MUV_OT_WorldScaleUV_Measure.bl_idname, text="Measure")
- layout.operator(MUV_OT_WorldScaleUV_ApplyManual.bl_idname,
- text="Apply (Manual)")
+ ops = layout.operator(MUV_OT_WorldScaleUV_ApplyManual.bl_idname,
+ text="Apply (Manual)")
+ ops.show_dialog = True
ops = layout.operator(
MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
text="Apply (Same Desity)")
ops.src_density = sc.muv_world_scale_uv_src_density
ops.same_density = True
+ ops.show_dialog = True
ops = layout.operator(
MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
text="Apply (Scaling Desity)")
ops.src_density = sc.muv_world_scale_uv_src_density
ops.same_density = False
- ops.tgt_scaling_factor = sc.muv_world_scale_uv_tgt_scaling_factor
+ ops.show_dialog = True
ops = layout.operator(
MUV_OT_WorldScaleUV_ApplyProportionalToMesh.bl_idname,
@@ -172,7 +173,7 @@ class MUV_MT_WorldScaleUV(bpy.types.Menu):
ops.src_density = sc.muv_world_scale_uv_src_density
ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area
ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area
- ops.origin = sc.muv_world_scale_uv_origin
+ ops.show_dialog = True
@BlClassRegistry()
diff --git a/magic_uv/ui/__init__.py b/magic_uv/ui/__init__.py
index 50049251..bb16a847 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 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 987a24a0..211737c8 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
diff --git a/magic_uv/ui/uvedit_editor_enhancement.py b/magic_uv/ui/uvedit_editor_enhancement.py
index 6639650c..f98e5193 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
@@ -32,6 +32,7 @@ from ..op.uv_bounding_box import (
from ..op.uv_inspection import (
MUV_OT_UVInspection_Render,
MUV_OT_UVInspection_Update,
+ MUV_OT_UVInspection_PaintUVIsland,
)
from ..utils.bl_class_registry import BlClassRegistry
from ..utils import compatibility as compat
@@ -143,3 +144,5 @@ class MUV_PT_UVEdit_EditorEnhancement(bpy.types.Panel):
row.prop(sc, "muv_uv_inspection_show_flipped")
row = box.row()
row.prop(sc, "muv_uv_inspection_show_mode")
+ box.separator()
+ box.operator(MUV_OT_UVInspection_PaintUVIsland.bl_idname)
diff --git a/magic_uv/ui/uvedit_uv_manipulation.py b/magic_uv/ui/uvedit_uv_manipulation.py
index 5589b73e..79a1731a 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
@@ -38,6 +38,7 @@ from ..op.select_uv import (
MUV_OT_SelectUV_SelectFlipped,
)
from ..op.pack_uv import MUV_OT_PackUV
+from ..op.clip_uv import MUV_OT_ClipUV
from ..utils.bl_class_registry import BlClassRegistry
from ..utils import compatibility as compat
@@ -129,3 +130,16 @@ class MUV_PT_UVEdit_UVManipulation(bpy.types.Panel):
box.prop(sc, "muv_pack_uv_allowable_center_deviation", text="")
box.label(text="Allowable Size Deviation:")
box.prop(sc, "muv_pack_uv_allowable_size_deviation", text="")
+
+ box = layout.box()
+ box.prop(sc, "muv_clip_uv_enabled", text="Clip UV")
+ if sc.muv_clip_uv_enabled:
+ ops = box.operator(MUV_OT_ClipUV.bl_idname, text="Clip UV")
+ ops.clip_uv_range_max = sc.muv_clip_uv_range_max
+ ops.clip_uv_range_min = sc.muv_clip_uv_range_min
+ box.label(text="Range:")
+ row = box.row()
+ col = row.column()
+ col.prop(sc, "muv_clip_uv_range_max", text="Max")
+ col = row.column()
+ col.prop(sc, "muv_clip_uv_range_min", text="Min")
diff --git a/magic_uv/ui/view3d_copy_paste_uv_editmode.py b/magic_uv/ui/view3d_copy_paste_uv_editmode.py
index 041f279d..0c7273a3 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 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 21d2bc4c..b2a33e9a 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
diff --git a/magic_uv/ui/view3d_uv_manipulation.py b/magic_uv/ui/view3d_uv_manipulation.py
index 3a694008..1d10eb65 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
@@ -113,17 +113,13 @@ class MUV_PT_View3D_UVManipulation(bpy.types.Panel):
box.prop(sc, "muv_world_scale_uv_mode", text="")
if sc.muv_world_scale_uv_mode == 'MANUAL':
- sp = compat.layout_split(box, 0.4)
- col = sp.column(align=True)
- col.label(text="Target:")
- sp = compat.layout_split(sp, 1.0)
- col = sp.column(align=True)
- ops = col.operator(MUV_OT_WorldScaleUV_ApplyManual.bl_idname,
+ box.label(text="Target:")
+ row = box.row(align=True)
+ ops = row.operator(MUV_OT_WorldScaleUV_ApplyManual.bl_idname,
text="Apply")
- ops.tgt_density = sc.muv_world_scale_uv_tgt_density
- ops.tgt_texture_size = sc.muv_world_scale_uv_tgt_texture_size
- ops.origin = sc.muv_world_scale_uv_origin
+ MUV_OT_WorldScaleUV_ApplyManual.setup_argument(ops, sc)
ops.show_dialog = False
+ row.prop(sc, "muv_world_scale_uv_apply_only_selected")
sp = compat.layout_split(box, 0.5)
col = sp.column()
col.prop(sc, "muv_world_scale_uv_tgt_texture_size",
@@ -133,16 +129,15 @@ class MUV_PT_View3D_UVManipulation(bpy.types.Panel):
col.label(text="Density:")
col.prop(sc, "muv_world_scale_uv_tgt_density")
box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
+ box.prop(sc, "muv_world_scale_uv_tgt_area_calc_method")
elif sc.muv_world_scale_uv_mode == 'SAME_DENSITY':
- sp = compat.layout_split(box, 0.4)
- col = sp.column(align=True)
- col.label(text="Source:")
- sp = compat.layout_split(sp, 1.0)
- col = sp.column(align=True)
- ops = col.operator(MUV_OT_WorldScaleUV_Measure.bl_idname,
+ box.label(text="Source:")
+ row = box.row(align=True)
+ ops = row.operator(MUV_OT_WorldScaleUV_Measure.bl_idname,
text="Measure")
- ops.tgt_texture = sc.muv_world_scale_uv_measure_tgt_texture
+ MUV_OT_WorldScaleUV_Measure.setup_argument(ops, sc)
+ row.prop(sc, "muv_world_scale_uv_measure_only_selected")
col = box.column(align=True)
col.prop(sc, "muv_world_scale_uv_measure_tgt_texture")
sp = compat.layout_split(box, 0.7)
@@ -154,30 +149,27 @@ class MUV_PT_View3D_UVManipulation(bpy.types.Panel):
col.label(text="px2/cm2")
box.separator()
- sp = compat.layout_split(box, 0.4)
- col = sp.column(align=True)
- col.label(text="Target:")
- sp = compat.layout_split(sp, 1.0)
- col = sp.column(align=True)
- ops = col.operator(
+
+ box.label(text="Target:")
+ row = box.row(align=True)
+ ops = row.operator(
MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
text="Apply")
- ops.src_density = sc.muv_world_scale_uv_src_density
- ops.origin = sc.muv_world_scale_uv_origin
+ MUV_OT_WorldScaleUV_ApplyScalingDensity.setup_argument(ops, sc)
ops.same_density = True
ops.show_dialog = False
- ops.tgt_texture = sc.muv_world_scale_uv_apply_tgt_texture
+ row.prop(sc, "muv_world_scale_uv_apply_only_selected")
+ box.prop(sc, "muv_world_scale_uv_apply_tgt_texture")
box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
+ box.prop(sc, "muv_world_scale_uv_tgt_area_calc_method")
elif sc.muv_world_scale_uv_mode == 'SCALING_DENSITY':
- sp = compat.layout_split(box, 0.4)
- col = sp.column(align=True)
- col.label(text="Source:")
- sp = compat.layout_split(sp, 1.0)
- col = sp.column(align=True)
- ops = col.operator(MUV_OT_WorldScaleUV_Measure.bl_idname,
+ box.label(text="Source:")
+ row = box.row(align=True)
+ ops = row.operator(MUV_OT_WorldScaleUV_Measure.bl_idname,
text="Measure")
- ops.tgt_texture = sc.muv_world_scale_uv_measure_tgt_texture
+ MUV_OT_WorldScaleUV_Measure.setup_argument(ops, sc)
+ row.prop(sc, "muv_world_scale_uv_measure_only_selected")
col = box.column(align=True)
col.prop(sc, "muv_world_scale_uv_measure_tgt_texture")
sp = compat.layout_split(box, 0.7)
@@ -189,34 +181,29 @@ class MUV_PT_View3D_UVManipulation(bpy.types.Panel):
col.label(text="px2/cm2")
box.separator()
- sp = compat.layout_split(box, 0.4)
- col = sp.column(align=True)
- col.label(text="Target:")
- sp = compat.layout_split(sp, 1.0)
- col = sp.column(align=True)
- ops = col.operator(
+
+ box.label(text="Target:")
+ row = box.row(align=True)
+ ops = row.operator(
MUV_OT_WorldScaleUV_ApplyScalingDensity.bl_idname,
text="Apply")
- ops.src_density = sc.muv_world_scale_uv_src_density
- ops.origin = sc.muv_world_scale_uv_origin
+ MUV_OT_WorldScaleUV_ApplyScalingDensity.setup_argument(ops, sc)
ops.same_density = False
ops.show_dialog = False
- ops.tgt_scaling_factor = \
- sc.muv_world_scale_uv_tgt_scaling_factor
- ops.tgt_texture = sc.muv_world_scale_uv_apply_tgt_texture
+ row.prop(sc, "muv_world_scale_uv_apply_only_selected")
+ box.prop(sc, "muv_world_scale_uv_apply_tgt_texture")
box.prop(sc, "muv_world_scale_uv_tgt_scaling_factor",
text="Scaling Factor")
box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
+ box.prop(sc, "muv_world_scale_uv_tgt_area_calc_method")
elif sc.muv_world_scale_uv_mode == 'PROPORTIONAL_TO_MESH':
- sp = compat.layout_split(box, 0.4)
- col = sp.column(align=True)
- col.label(text="Source:")
- sp = compat.layout_split(sp, 1.0)
- col = sp.column(align=True)
- ops = col.operator(MUV_OT_WorldScaleUV_Measure.bl_idname,
+ box.label(text="Source:")
+ row = box.row(align=True)
+ ops = row.operator(MUV_OT_WorldScaleUV_Measure.bl_idname,
text="Measure")
- ops.tgt_texture = sc.muv_world_scale_uv_measure_tgt_texture
+ MUV_OT_WorldScaleUV_Measure.setup_argument(ops, sc)
+ row.prop(sc, "muv_world_scale_uv_measure_only_selected")
col = box.column(align=True)
col.prop(sc, "muv_world_scale_uv_measure_tgt_texture")
sp = compat.layout_split(box, 0.7)
@@ -234,24 +221,19 @@ class MUV_PT_View3D_UVManipulation(bpy.types.Panel):
col.enabled = False
box.separator()
- sp = compat.layout_split(box, 0.4)
- col = sp.column(align=True)
- col.label(text="Target:")
- sp = compat.layout_split(sp, 1.0)
- col = sp.column(align=True)
- ops = col.operator(
+
+ box.label(text="Target:")
+ row = box.row(align=True)
+ ops = row.operator(
MUV_OT_WorldScaleUV_ApplyProportionalToMesh.bl_idname,
text="Apply")
- ops.src_density = sc.muv_world_scale_uv_src_density
- ops.src_uv_area = sc.muv_world_scale_uv_src_uv_area
- ops.src_mesh_area = sc.muv_world_scale_uv_src_mesh_area
- ops.origin = sc.muv_world_scale_uv_origin
+ MUV_OT_WorldScaleUV_ApplyProportionalToMesh.setup_argument(
+ ops, sc)
ops.show_dialog = False
- ops.tgt_texture = sc.muv_world_scale_uv_apply_tgt_texture
+ row.prop(sc, "muv_world_scale_uv_apply_only_selected")
+ box.prop(sc, "muv_world_scale_uv_apply_tgt_texture")
box.prop(sc, "muv_world_scale_uv_origin", text="Origin")
-
- col = box.column(align=True)
- col.prop(sc, "muv_world_scale_uv_apply_tgt_texture")
+ box.prop(sc, "muv_world_scale_uv_tgt_area_calc_method")
box = layout.box()
box.prop(sc, "muv_preserve_uv_aspect_enabled",
diff --git a/magic_uv/ui/view3d_uv_mapping.py b/magic_uv/ui/view3d_uv_mapping.py
index 0e31620b..4344adb7 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
diff --git a/magic_uv/updater.py b/magic_uv/updater.py
index d522c009..8d610b16 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import os
diff --git a/magic_uv/utils/__init__.py b/magic_uv/utils/__init__.py
index 0e6ef744..c96b9225 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
if "bpy" in locals():
import importlib
diff --git a/magic_uv/utils/addon_updater.py b/magic_uv/utils/addon_updater.py
index 2f3d0c0f..5df59fd4 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from threading import Lock
import urllib
@@ -60,7 +60,7 @@ def _request(url, json_decode=True):
return json.JSONDecoder().decode(data.decode())
except Exception as e:
raise RuntimeError("API response has invalid JSON format ({})"
- .format(str(e.reason)))
+ .format(str(e)))
return data.decode()
@@ -153,7 +153,7 @@ def _compare_version(ver1, ver2):
if v1[idx] > v2[idx]:
return 1 # v1 > v2
- elif v1[idx] < v2[idx]:
+ if v1[idx] < v2[idx]:
return -1 # v1 < v2
return comp(v1, v2, idx + 1)
diff --git a/magic_uv/utils/bl_class_registry.py b/magic_uv/utils/bl_class_registry.py
index 826f1483..f9f05faf 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
diff --git a/magic_uv/utils/compatibility.py b/magic_uv/utils/compatibility.py
index 6b7da000..b4c7c4ea 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
import bpy
import bgl
diff --git a/magic_uv/utils/property_class_registry.py b/magic_uv/utils/property_class_registry.py
index dff4712f..9caa735c 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.2"
-__date__ = "31 Jul 2019"
+__version__ = "6.3"
+__date__ = "10 Aug 2020"
from .. import common