diff options
author | nutti <nutti.metro@gmail.com> | 2020-08-10 10:07:26 +0300 |
---|---|---|
committer | nutti <nutti.metro@gmail.com> | 2020-08-10 10:24:28 +0300 |
commit | 8db46434a4b25569372a7172f83733ec14dffb31 (patch) | |
tree | f981b15847246b0ffb054cc4d87083b2ec394fda | |
parent | 7084d19ef5886b740b65ba6a4234849859e0e5b8 (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
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 |