diff options
author | Nutti <nutti.metro@gmail.com> | 2018-02-16 15:04:36 +0300 |
---|---|---|
committer | Nutti <nutti.metro@gmail.com> | 2018-02-16 15:04:36 +0300 |
commit | fdc914d653d4cf9e8c6ad5ea3d71fdefb9529491 (patch) | |
tree | f06d9e4b851a39c62ee8d4308a3587211df23bfa /uv_magic_uv/op/pack_uv.py | |
parent | bfab29085ca84ad74125aa18a26a0025a1e09ace (diff) |
Magic UV: Release v5.0
* Add features
- Align UV Cursor
- UV Cursor Location
- Align UV
- Smooth UV
- UV Inspection
- Select UV
- Texture Wrap
- UV Sculpt
* Improve features
- Copy/Paste UV: Add menu to UV/Image Editor
- World Scale UV: Add information about Texel Density
- UV Bounding Box: Add option "Bound"
- Texture Projection: Add option "Assign UVMap"
- UVW: Add option "Assign UVMap"
* Improve UI
* Fixed bugs
* Optimization/Refactoring
Diffstat (limited to 'uv_magic_uv/op/pack_uv.py')
-rw-r--r-- | uv_magic_uv/op/pack_uv.py | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/uv_magic_uv/op/pack_uv.py b/uv_magic_uv/op/pack_uv.py new file mode 100644 index 00000000..692fa93e --- /dev/null +++ b/uv_magic_uv/op/pack_uv.py @@ -0,0 +1,207 @@ +# <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__ = "Nutti <nutti.metro@gmail.com>" +__status__ = "production" +__version__ = "5.0" +__date__ = "16 Feb 2018" + +from math import fabs + +import bpy +import bmesh +import mathutils +from bpy.props import ( + FloatProperty, + FloatVectorProperty, + BoolProperty, +) +from mathutils import Vector + +from .. import common + + +class MUV_PackUV(bpy.types.Operator): + """ + Operation class: Pack UV with same UV islands are integrated + Island matching algorithm + - Same center of UV island + - Same size of UV island + - Same number of UV + """ + + bl_idname = "uv.muv_packuv" + bl_label = "Pack UV" + bl_description = "Pack UV (Same UV Islands are integrated)" + bl_options = {'REGISTER', 'UNDO'} + + rotate = BoolProperty( + name="Rotate", + description="Rotate option used by default pack UV function", + default=False) + margin = FloatProperty( + name="Margin", + description="Margin used by default pack UV function", + min=0, + max=1, + default=0.001) + allowable_center_deviation = FloatVectorProperty( + name="Allowable Center Deviation", + description="Allowable center deviation to judge same UV island", + min=0.000001, + max=0.1, + default=(0.001, 0.001), + size=2 + ) + allowable_size_deviation = FloatVectorProperty( + name="Allowable Size Deviation", + description="Allowable sizse deviation to judge same UV island", + min=0.000001, + max=0.1, + default=(0.001, 0.001), + size=2 + ) + + def execute(self, context): + obj = context.active_object + bm = bmesh.from_edit_mesh(obj.data) + if common.check_version(2, 73, 0) >= 0: + bm.faces.ensure_lookup_table() + if not bm.loops.layers.uv: + self.report({'WARNING'}, "Object must have more than one UV map") + return {'CANCELLED'} + uv_layer = bm.loops.layers.uv.verify() + + selected_faces = [f for f in bm.faces if f.select] + island_info = common.get_island_info(obj) + num_group = self.__group_island(island_info) + + loop_lists = [l for f in bm.faces for l in f.loops] + bpy.ops.mesh.select_all(action='DESELECT') + + # pack UV + for gidx in range(num_group): + group = list(filter( + lambda i, idx=gidx: i['group'] == idx, island_info)) + for f in group[0]['faces']: + f['face'].select = True + bmesh.update_edit_mesh(obj.data) + bpy.ops.uv.select_all(action='SELECT') + bpy.ops.uv.pack_islands(rotate=self.rotate, margin=self.margin) + + # copy/paste UV among same islands + for gidx in range(num_group): + group = list(filter( + lambda i, idx=gidx: i['group'] == idx, island_info)) + if len(group) <= 1: + continue + for g in group[1:]: + for (src_face, dest_face) in zip( + group[0]['sorted'], g['sorted']): + for (src_loop, dest_loop) in zip( + src_face['face'].loops, dest_face['face'].loops): + loop_lists[dest_loop.index][uv_layer].uv = loop_lists[ + src_loop.index][uv_layer].uv + + # restore face/UV selection + bpy.ops.uv.select_all(action='DESELECT') + bpy.ops.mesh.select_all(action='DESELECT') + for f in selected_faces: + f.select = True + bpy.ops.uv.select_all(action='SELECT') + + bmesh.update_edit_mesh(obj.data) + + return {'FINISHED'} + + def __sort_island_faces(self, kd, uvs, isl1, isl2): + """ + Sort faces in island + """ + + sorted_faces = [] + for f in isl1['sorted']: + _, idx, _ = kd.find( + Vector((f['ave_uv'].x, f['ave_uv'].y, 0.0))) + sorted_faces.append(isl2['faces'][uvs[idx]['face_idx']]) + return sorted_faces + + def __group_island(self, island_info): + """ + Group island + """ + + num_group = 0 + while True: + # search islands which is not parsed yet + isl_1 = None + for isl_1 in island_info: + if isl_1['group'] == -1: + break + else: + break # all faces are parsed + if isl_1 is None: + break + isl_1['group'] = num_group + isl_1['sorted'] = isl_1['faces'] + + # search same island + for isl_2 in island_info: + if isl_2['group'] == -1: + dcx = isl_2['center'].x - isl_1['center'].x + dcy = isl_2['center'].y - isl_1['center'].y + dsx = isl_2['size'].x - isl_1['size'].x + dsy = isl_2['size'].y - isl_1['size'].y + center_x_matched = ( + fabs(dcx) < self.allowable_center_deviation[0] + ) + center_y_matched = ( + fabs(dcy) < self.allowable_center_deviation[1] + ) + size_x_matched = ( + fabs(dsx) < self.allowable_size_deviation[0] + ) + size_y_matched = ( + fabs(dsy) < self.allowable_size_deviation[1] + ) + center_matched = center_x_matched and center_y_matched + size_matched = size_x_matched and size_y_matched + num_uv_matched = (isl_2['num_uv'] == isl_1['num_uv']) + # are islands have same? + if center_matched and size_matched and num_uv_matched: + isl_2['group'] = num_group + kd = mathutils.kdtree.KDTree(len(isl_2['faces'])) + uvs = [ + { + 'uv': Vector( + (f['ave_uv'].x, f['ave_uv'].y, 0.0) + ), + 'face_idx': fidx + } for fidx, f in enumerate(isl_2['faces']) + ] + for i, uv in enumerate(uvs): + kd.insert(uv['uv'], i) + kd.balance() + # sort faces for copy/paste UV + isl_2['sorted'] = self.__sort_island_faces( + kd, uvs, isl_1, isl_2) + num_group = num_group + 1 + + return num_group |