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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNutti <nutti.metro@gmail.com>2019-01-26 05:22:38 +0300
committerNutti <nutti.metro@gmail.com>2019-01-26 05:22:38 +0300
commitc034e1968465acb939efc089e63c5c51302947f5 (patch)
tree84f04a329e082ff4d7f860a926f8848cadb63428 /magic_uv/common.py
parent2eb519ceca77a4fe2fd5f8d071767db06aa01aa5 (diff)
Magic UV: Release v6.0
Support Blender 2.8.
Diffstat (limited to 'magic_uv/common.py')
-rw-r--r--magic_uv/common.py1134
1 files changed, 1134 insertions, 0 deletions
diff --git a/magic_uv/common.py b/magic_uv/common.py
new file mode 100644
index 00000000..78a88308
--- /dev/null
+++ b/magic_uv/common.py
@@ -0,0 +1,1134 @@
+# <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__ = "6.0"
+__date__ = "26 Jan 2019"
+
+from collections import defaultdict
+from pprint import pprint
+from math import fabs, sqrt
+import os
+
+import bpy
+from mathutils import Vector
+import bmesh
+
+from .utils import compatibility as compat
+
+
+__DEBUG_MODE = False
+
+
+def is_console_mode():
+ if "MUV_CONSOLE_MODE" not in os.environ:
+ return False
+ return os.environ["MUV_CONSOLE_MODE"] == "True"
+
+
+def is_debug_mode():
+ return __DEBUG_MODE
+
+
+def enable_debugg_mode():
+ # pylint: disable=W0603
+ global __DEBUG_MODE
+ __DEBUG_MODE = True
+
+
+def disable_debug_mode():
+ # pylint: disable=W0603
+ global __DEBUG_MODE
+ __DEBUG_MODE = False
+
+
+def debug_print(*s):
+ """
+ Print message to console in debugging mode
+ """
+
+ if is_debug_mode():
+ pprint(s)
+
+
+def check_version(major, minor, _):
+ """
+ Check blender version
+ """
+
+ if bpy.app.version[0] == major and bpy.app.version[1] == minor:
+ return 0
+ if bpy.app.version[0] > major:
+ return 1
+ if bpy.app.version[1] > minor:
+ return 1
+ return -1
+
+
+def redraw_all_areas():
+ """
+ Redraw all areas
+ """
+
+ for area in bpy.context.screen.areas:
+ area.tag_redraw()
+
+
+def get_space(area_type, region_type, space_type):
+ """
+ Get current area/region/space
+ """
+
+ area = None
+ region = None
+ space = None
+
+ for area in bpy.context.screen.areas:
+ if area.type == area_type:
+ break
+ else:
+ return (None, None, None)
+ for region in area.regions:
+ if region.type == region_type:
+ if compat.check_version(2, 80, 0) >= 0:
+ if region.width <= 1 or region.height <= 1:
+ continue
+ break
+ else:
+ return (area, None, None)
+ for space in area.spaces:
+ if space.type == space_type:
+ break
+ else:
+ return (area, region, None)
+
+ return (area, region, space)
+
+
+def mouse_on_region(event, area_type, region_type):
+ pos = Vector((event.mouse_x, event.mouse_y))
+
+ _, region, _ = get_space(area_type, region_type, "")
+ if region is None:
+ return False
+
+ if (pos.x > region.x) and (pos.x < region.x + region.width) and \
+ (pos.y > region.y) and (pos.y < region.y + region.height):
+ return True
+
+ return False
+
+
+def mouse_on_area(event, area_type):
+ pos = Vector((event.mouse_x, event.mouse_y))
+
+ area, _, _ = get_space(area_type, "", "")
+ if area is None:
+ return False
+
+ if (pos.x > area.x) and (pos.x < area.x + area.width) and \
+ (pos.y > area.y) and (pos.y < area.y + area.height):
+ return True
+
+ return False
+
+
+def mouse_on_regions(event, area_type, regions):
+ if not mouse_on_area(event, area_type):
+ return False
+
+ for region in regions:
+ result = mouse_on_region(event, area_type, region)
+ if result:
+ return True
+
+ return False
+
+
+def create_bmesh(obj):
+ bm = bmesh.from_edit_mesh(obj.data)
+ if check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ return bm
+
+
+def create_new_uv_map(obj, name=None):
+ uv_maps_old = {l.name for l in obj.data.uv_layers}
+ bpy.ops.mesh.uv_texture_add()
+ uv_maps_new = {l.name for l in obj.data.uv_layers}
+ diff = uv_maps_new - uv_maps_old
+
+ if not list(diff):
+ return None # no more UV maps can not be created
+
+ # rename UV map
+ new = obj.data.uv_layers[list(diff)[0]]
+ if name:
+ new.name = name
+
+ return new
+
+
+def __get_island_info(uv_layer, islands):
+ """
+ get information about each island
+ """
+
+ island_info = []
+ for isl in islands:
+ info = {}
+ max_uv = Vector((-10000000.0, -10000000.0))
+ min_uv = Vector((10000000.0, 10000000.0))
+ ave_uv = Vector((0.0, 0.0))
+ num_uv = 0
+ for face in isl:
+ n = 0
+ a = Vector((0.0, 0.0))
+ ma = Vector((-10000000.0, -10000000.0))
+ mi = Vector((10000000.0, 10000000.0))
+ for l in face['face'].loops:
+ uv = l[uv_layer].uv
+ ma.x = max(uv.x, ma.x)
+ ma.y = max(uv.y, ma.y)
+ mi.x = min(uv.x, mi.x)
+ mi.y = min(uv.y, mi.y)
+ a = a + uv
+ n = n + 1
+ ave_uv = ave_uv + a
+ num_uv = num_uv + n
+ a = a / n
+ max_uv.x = max(ma.x, max_uv.x)
+ max_uv.y = max(ma.y, max_uv.y)
+ min_uv.x = min(mi.x, min_uv.x)
+ min_uv.y = min(mi.y, min_uv.y)
+ face['max_uv'] = ma
+ face['min_uv'] = mi
+ face['ave_uv'] = a
+ ave_uv = ave_uv / num_uv
+
+ info['center'] = ave_uv
+ info['size'] = max_uv - min_uv
+ info['num_uv'] = num_uv
+ info['group'] = -1
+ info['faces'] = isl
+ info['max'] = max_uv
+ info['min'] = min_uv
+
+ island_info.append(info)
+
+ return island_info
+
+
+def __parse_island(bm, face_idx, faces_left, island,
+ face_to_verts, vert_to_faces):
+ """
+ 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:
+ for cf in connected_faces:
+ __parse_island(bm, cf, faces_left, island, face_to_verts,
+ vert_to_faces)
+
+
+def __get_island(bm, face_to_verts, vert_to_faces):
+ """
+ Get island list
+ """
+
+ uv_island_lists = []
+ faces_left = set(face_to_verts.keys())
+ while faces_left:
+ current_island = []
+ face_idx = list(faces_left)[0]
+ __parse_island(bm, face_idx, faces_left, current_island,
+ face_to_verts, vert_to_faces)
+ uv_island_lists.append(current_island)
+
+ return uv_island_lists
+
+
+def __create_vert_face_db(faces, uv_layer):
+ # create mesh database for all faces
+ face_to_verts = defaultdict(set)
+ vert_to_faces = defaultdict(set)
+ for f in faces:
+ for l in f.loops:
+ id_ = l[uv_layer].uv.to_tuple(5), l.vert.index
+ face_to_verts[f.index].add(id_)
+ vert_to_faces[id_].add(f.index)
+
+ return (face_to_verts, vert_to_faces)
+
+
+def get_island_info(obj, only_selected=True):
+ bm = bmesh.from_edit_mesh(obj.data)
+ if check_version(2, 73, 0) >= 0:
+ bm.faces.ensure_lookup_table()
+
+ return get_island_info_from_bmesh(bm, only_selected)
+
+
+def get_island_info_from_bmesh(bm, only_selected=True):
+ if not bm.loops.layers.uv:
+ return None
+ uv_layer = bm.loops.layers.uv.verify()
+
+ # create database
+ if only_selected:
+ selected_faces = [f for f in bm.faces if f.select]
+ else:
+ selected_faces = [f for f in bm.faces]
+
+ return get_island_info_from_faces(bm, selected_faces, uv_layer)
+
+
+def get_island_info_from_faces(bm, faces, uv_layer):
+ ftv, vtf = __create_vert_face_db(faces, uv_layer)
+
+ # Get island information
+ uv_island_lists = __get_island(bm, ftv, vtf)
+ island_info = __get_island_info(uv_layer, uv_island_lists)
+
+ return island_info
+
+
+def get_uvimg_editor_board_size(area):
+ if area.spaces.active.image:
+ return area.spaces.active.image.size
+
+ return (255.0, 255.0)
+
+
+def calc_polygon_2d_area(points):
+ area = 0.0
+ for i, p1 in enumerate(points):
+ p2 = points[(i + 1) % len(points)]
+ v1 = p1 - points[0]
+ v2 = p2 - points[0]
+ a = v1.x * v2.y - v1.y * v2.x
+ area = area + a
+
+ return fabs(0.5 * area)
+
+
+def calc_polygon_3d_area(points):
+ area = 0.0
+ for i, p1 in enumerate(points):
+ p2 = points[(i + 1) % len(points)]
+ v1 = p1 - points[0]
+ v2 = p2 - points[0]
+ cx = v1.y * v2.z - v1.z * v2.y
+ cy = v1.z * v2.x - v1.x * v2.z
+ cz = v1.x * v2.y - v1.y * v2.x
+ a = sqrt(cx * cx + cy * cy + cz * cz)
+ area = area + a
+
+ return 0.5 * area
+
+
+def measure_mesh_area(obj):
+ 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]
+
+ # measure
+ mesh_area = 0.0
+ for f in sel_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
+
+ return mesh_area
+
+
+def find_texture_layer(bm):
+ if check_version(2, 80, 0) >= 0:
+ return None
+ if bm.faces.layers.tex is None:
+ return None
+
+ return bm.faces.layers.tex.verify()
+
+
+def find_texture_nodes(obj):
+ nodes = []
+ for mat in obj.material_slots:
+ if not mat.material.node_tree:
+ continue
+ for node in mat.material.node_tree.nodes:
+ tex_node_types = [
+ 'TEX_ENVIRONMENT',
+ 'TEX_IMAGE',
+ ]
+ if node.type not in tex_node_types:
+ continue
+ if not node.image:
+ continue
+ nodes.append(node)
+
+ return nodes
+
+
+def find_image(obj, face=None, tex_layer=None):
+ # try to find from texture_layer
+ img = None
+ if tex_layer and face:
+ img = face[tex_layer].image
+
+ # not found, then try to search from node
+ if not img:
+ nodes = find_texture_nodes(obj)
+ if len(nodes) >= 2:
+ raise RuntimeError("Find more than 2 texture nodes")
+ if len(nodes) == 1:
+ img = nodes[0].image
+
+ return img
+
+
+def measure_uv_area(obj, 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
+ uv_area = 0.0
+ for f in sel_faces:
+ uvs = [l[uv_layer].uv for l in f.loops]
+ f_uv_area = calc_polygon_2d_area(uvs)
+
+ # user specified
+ if tex_size:
+ uv_area = uv_area + f_uv_area * tex_size[0] * tex_size[1]
+ continue
+
+ 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
+ uv_area = uv_area + f_uv_area * img_size[0] * img_size[1]
+
+ return uv_area
+
+
+def diff_point_to_segment(a, b, p):
+ ab = b - a
+ normal_ab = ab.normalized()
+
+ ap = p - a
+ dist_ax = normal_ab.dot(ap)
+
+ # cross point
+ x = a + normal_ab * dist_ax
+
+ # difference between cross point and point
+ xp = p - x
+
+ return xp, x
+
+
+# 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:
+ # forward direction
+ lln = ll.link_loop_next
+ # if there is same pair, skip it
+ found = False
+ 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 not found:
+ pairs_.append([ll, lln])
+ if lln not in parsed_:
+ __get_loop_pairs_internal(lln, pairs_, uv_layer_, parsed_)
+
+ # backward direction
+ llp = ll.link_loop_prev
+ # if there is same pair, skip it
+ found = False
+ 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 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)
+
+ return pairs
+
+
+# sort pair by vertex
+# (v0, v1) - (v1, v2) - (v2, v3) ....
+def __sort_loop_pairs(uv_layer, pairs, closed):
+ rest = pairs
+ sorted_pairs = [rest[0]]
+ rest.remove(rest[0])
+
+ # prepend
+ while True:
+ p1 = sorted_pairs[0]
+ for p2 in rest:
+ if p1[0].vert == p2[0].vert:
+ sorted_pairs.insert(0, [p2[1], p2[0]])
+ rest.remove(p2)
+ break
+ elif p1[0].vert == p2[1].vert:
+ sorted_pairs.insert(0, [p2[0], p2[1]])
+ rest.remove(p2)
+ break
+ else:
+ break
+
+ # append
+ while True:
+ p1 = sorted_pairs[-1]
+ for p2 in rest:
+ if p1[1].vert == p2[0].vert:
+ sorted_pairs.append([p2[0], p2[1]])
+ rest.remove(p2)
+ break
+ elif p1[1].vert == p2[1].vert:
+ sorted_pairs.append([p2[1], p2[0]])
+ rest.remove(p2)
+ break
+ else:
+ break
+
+ begin_vert = sorted_pairs[0][0].vert
+ end_vert = sorted_pairs[-1][-1].vert
+ if begin_vert != end_vert:
+ return sorted_pairs, ""
+ if closed and (begin_vert == end_vert):
+ # if the sequence of UV is circular, it is ok
+ return sorted_pairs, ""
+
+ # if the begin vertex and the end vertex are same, search the UVs which
+ # are separated each other
+ tmp_pairs = sorted_pairs
+ for i, (p1, p2) in enumerate(zip(tmp_pairs[:-1], tmp_pairs[1:])):
+ diff = p2[0][uv_layer].uv - p1[-1][uv_layer].uv
+ if diff.length > 0.000000001:
+ # UVs are separated
+ sorted_pairs = tmp_pairs[i + 1:]
+ sorted_pairs.extend(tmp_pairs[:i + 1])
+ break
+ else:
+ p1 = tmp_pairs[0]
+ p2 = tmp_pairs[-1]
+ diff = p2[-1][uv_layer].uv - p1[0][uv_layer].uv
+ if diff.length < 0.000000001:
+ # all UVs are not separated
+ return None, "All UVs are not separated"
+
+ return sorted_pairs, ""
+
+
+# get index of the island group which includes loop
+def __get_island_group_include_loop(loop, island_info):
+ for i, isl in enumerate(island_info):
+ for f in isl['faces']:
+ for l in f['face'].loops:
+ if l == loop:
+ return i # found
+
+ return -1 # not found
+
+
+# get index of the island group which includes pair.
+# if island group is not same between loops, it will be invalid
+def __get_island_group_include_pair(pair, island_info):
+ l1_grp = __get_island_group_include_loop(pair[0], island_info)
+ if l1_grp == -1:
+ return -1 # not found
+
+ for p in pair[1:]:
+ l2_grp = __get_island_group_include_loop(p, island_info)
+ if (l2_grp == -1) or (l1_grp != l2_grp):
+ return -1 # not found or invalid
+
+ return l1_grp
+
+
+# x ---- x <- next_loop_pair
+# | |
+# o ---- o <- pair
+def __get_next_loop_pair(pair):
+ lp = pair[0].link_loop_prev
+ if lp.vert == pair[1].vert:
+ lp = pair[0].link_loop_next
+ if lp.vert == pair[1].vert:
+ # no loop is found
+ return None
+
+ ln = pair[1].link_loop_next
+ if ln.vert == pair[0].vert:
+ ln = pair[1].link_loop_prev
+ if ln.vert == pair[0].vert:
+ # no loop is found
+ return None
+
+ # tri-face
+ if lp == ln:
+ return [lp]
+
+ # quad-face
+ return [lp, ln]
+
+
+# | ---- |
+# % ---- % <- next_poly_loop_pair
+# x ---- x <- next_loop_pair
+# | |
+# o ---- o <- pair
+def __get_next_poly_loop_pair(pair):
+ v1 = pair[0].vert
+ v2 = pair[1].vert
+ for l1 in v1.link_loops:
+ if l1 == pair[0]:
+ continue
+ for l2 in v2.link_loops:
+ if l2 == pair[1]:
+ continue
+ if l1.link_loop_next == l2:
+ return [l1, l2]
+ elif l1.link_loop_prev == l2:
+ return [l1, l2]
+
+ # no next poly loop is found
+ return None
+
+
+# get loop sequence in the same island
+def __get_loop_sequence_internal(uv_layer, pairs, island_info, closed):
+ loop_sequences = []
+ for pair in pairs:
+ seqs = [pair]
+ p = pair
+ isl_grp = __get_island_group_include_pair(pair, island_info)
+ if isl_grp == -1:
+ return None, "Can not find the island or invalid island"
+
+ while True:
+ nlp = __get_next_loop_pair(p)
+ if not nlp:
+ break # no more loop pair
+ nlp_isl_grp = __get_island_group_include_pair(nlp, island_info)
+ if nlp_isl_grp != isl_grp:
+ break # another island
+ for nlpl in nlp:
+ if nlpl[uv_layer].select:
+ return None, "Do not select UV which does not belong to " \
+ "the end edge"
+
+ seqs.append(nlp)
+
+ # when face is triangle, it indicates CLOSED
+ if (len(nlp) == 1) and closed:
+ break
+
+ nplp = __get_next_poly_loop_pair(nlp)
+ if not nplp:
+ break # no more loop pair
+ nplp_isl_grp = __get_island_group_include_pair(nplp, island_info)
+ if nplp_isl_grp != isl_grp:
+ break # another island
+
+ # check if the UVs are already parsed.
+ # this check is needed for the mesh which has the circular
+ # sequence of the vertices
+ matched = False
+ for p1 in seqs:
+ p2 = nplp
+ if ((p1[0] == p2[0]) and (p1[1] == p2[1])) or \
+ ((p1[0] == p2[1]) and (p1[1] == p2[0])):
+ matched = True
+ if matched:
+ debug_print("This is a circular sequence")
+ break
+
+ for nlpl in nplp:
+ if nlpl[uv_layer].select:
+ return None, "Do not select UV which does not belong to " \
+ "the end edge"
+
+ seqs.append(nplp)
+
+ p = nplp
+
+ loop_sequences.append(seqs)
+ return loop_sequences, ""
+
+
+def get_loop_sequences(bm, uv_layer, closed=False):
+ sel_faces = [f for f in bm.faces if f.select]
+
+ # get candidate loops
+ cand_loops = []
+ for f in sel_faces:
+ for l in f.loops:
+ if l[uv_layer].select:
+ cand_loops.append(l)
+
+ if len(cand_loops) < 2:
+ return None, "More than 2 UVs must be selected"
+
+ first_loop = cand_loops[0]
+ isl_info = get_island_info_from_bmesh(bm, False)
+ loop_pairs = __get_loop_pairs(first_loop, uv_layer)
+ loop_pairs, err = __sort_loop_pairs(uv_layer, loop_pairs, closed)
+ if not loop_pairs:
+ return None, err
+ loop_seqs, err = __get_loop_sequence_internal(uv_layer, loop_pairs,
+ isl_info, closed)
+ if not loop_seqs:
+ return None, err
+
+ return loop_seqs, ""
+
+
+def __is_segment_intersect(start1, end1, start2, end2):
+ seg1 = end1 - start1
+ seg2 = end2 - start2
+
+ a1 = -seg1.y
+ b1 = seg1.x
+ d1 = -(a1 * start1.x + b1 * start1.y)
+
+ a2 = -seg2.y
+ b2 = seg2.x
+ d2 = -(a2 * start2.x + b2 * start2.y)
+
+ seg1_line2_start = a2 * start1.x + b2 * start1.y + d2
+ seg1_line2_end = a2 * end1.x + b2 * end1.y + d2
+
+ seg2_line1_start = a1 * start2.x + b1 * start2.y + d1
+ seg2_line1_end = a1 * end2.x + b1 * end2.y + d1
+
+ if (seg1_line2_start * seg1_line2_end >= 0) or \
+ (seg2_line1_start * seg2_line1_end >= 0):
+ return False, None
+
+ u = seg1_line2_start / (seg1_line2_start - seg1_line2_end)
+ out = start1 + u * seg1
+
+ return True, out
+
+
+class RingBuffer:
+ def __init__(self, arr):
+ self.__buffer = arr.copy()
+ self.__pointer = 0
+
+ def __repr__(self):
+ return repr(self.__buffer)
+
+ def __len__(self):
+ return len(self.__buffer)
+
+ def insert(self, val, offset=0):
+ self.__buffer.insert(self.__pointer + offset, val)
+
+ def head(self):
+ return self.__buffer[0]
+
+ def tail(self):
+ return self.__buffer[-1]
+
+ def get(self, offset=0):
+ size = len(self.__buffer)
+ val = self.__buffer[(self.__pointer + offset) % size]
+ return val
+
+ def next(self):
+ size = len(self.__buffer)
+ self.__pointer = (self.__pointer + 1) % size
+
+ def reset(self):
+ self.__pointer = 0
+
+ def find(self, obj):
+ try:
+ idx = self.__buffer.index(obj)
+ except ValueError:
+ return None
+ return self.__buffer[idx]
+
+ def find_and_next(self, obj):
+ size = len(self.__buffer)
+ idx = self.__buffer.index(obj)
+ self.__pointer = (idx + 1) % size
+
+ def find_and_set(self, obj):
+ idx = self.__buffer.index(obj)
+ self.__pointer = idx
+
+ def as_list(self):
+ return self.__buffer.copy()
+
+ def reverse(self):
+ self.__buffer.reverse()
+ self.reset()
+
+
+# clip: reference polygon
+# subject: tested polygon
+def __do_weiler_atherton_cliping(clip, subject, uv_layer, mode):
+
+ clip_uvs = RingBuffer([l[uv_layer].uv.copy() for l in clip.loops])
+ if __is_polygon_flipped(clip_uvs):
+ clip_uvs.reverse()
+ subject_uvs = RingBuffer([l[uv_layer].uv.copy() for l in subject.loops])
+ if __is_polygon_flipped(subject_uvs):
+ subject_uvs.reverse()
+
+ debug_print("===== Clip UV List =====")
+ debug_print(clip_uvs)
+ debug_print("===== Subject UV List =====")
+ debug_print(subject_uvs)
+
+ # check if clip and subject is overlapped completely
+ if __is_polygon_same(clip_uvs, subject_uvs):
+ polygons = [subject_uvs.as_list()]
+ debug_print("===== Polygons Overlapped Completely =====")
+ debug_print(polygons)
+ return True, polygons
+
+ # check if subject is in clip
+ if __is_points_in_polygon(subject_uvs, clip_uvs):
+ polygons = [subject_uvs.as_list()]
+ return True, polygons
+
+ # check if clip is in subject
+ if __is_points_in_polygon(clip_uvs, subject_uvs):
+ polygons = [subject_uvs.as_list()]
+ return True, polygons
+
+ # check if clip and subject is overlapped partially
+ intersections = []
+ while True:
+ subject_uvs.reset()
+ while True:
+ uv_start1 = clip_uvs.get()
+ uv_end1 = clip_uvs.get(1)
+ uv_start2 = subject_uvs.get()
+ uv_end2 = subject_uvs.get(1)
+ intersected, point = __is_segment_intersect(uv_start1, uv_end1,
+ uv_start2, uv_end2)
+ if intersected:
+ clip_uvs.insert(point, 1)
+ subject_uvs.insert(point, 1)
+ intersections.append([point,
+ [clip_uvs.get(), clip_uvs.get(1)]])
+ subject_uvs.next()
+ if subject_uvs.get() == subject_uvs.head():
+ break
+ clip_uvs.next()
+ if clip_uvs.get() == clip_uvs.head():
+ break
+
+ debug_print("===== Intersection List =====")
+ debug_print(intersections)
+
+ # no intersection, so subject and clip is not overlapped
+ if not intersections:
+ return False, None
+
+ def get_intersection_pair(intersects, key):
+ for sect in intersects:
+ if sect[0] == key:
+ return sect[1]
+
+ return None
+
+ # make enter/exit pair
+ subject_uvs.reset()
+ subject_entering = []
+ subject_exiting = []
+ clip_entering = []
+ clip_exiting = []
+ intersect_uv_list = []
+ while True:
+ pair = get_intersection_pair(intersections, subject_uvs.get())
+ if pair:
+ sub = subject_uvs.get(1) - subject_uvs.get(-1)
+ inter = pair[1] - pair[0]
+ cross = sub.x * inter.y - inter.x * sub.y
+ if cross < 0:
+ subject_entering.append(subject_uvs.get())
+ clip_exiting.append(subject_uvs.get())
+ else:
+ subject_exiting.append(subject_uvs.get())
+ clip_entering.append(subject_uvs.get())
+ intersect_uv_list.append(subject_uvs.get())
+
+ subject_uvs.next()
+ if subject_uvs.get() == subject_uvs.head():
+ break
+
+ debug_print("===== Enter List =====")
+ debug_print(clip_entering)
+ debug_print(subject_entering)
+ debug_print("===== Exit List =====")
+ debug_print(clip_exiting)
+ debug_print(subject_exiting)
+
+ # for now, can't handle the situation when fulfill all below conditions
+ # * two faces have common edge
+ # * each face is intersected
+ # * Show Mode is "Part"
+ # so for now, ignore this situation
+ if len(subject_entering) != len(subject_exiting):
+ if mode == 'FACE':
+ polygons = [subject_uvs.as_list()]
+ return True, polygons
+ return False, None
+
+ def traverse(current_list, entering, exiting, p, current, other_list):
+ result = current_list.find(current)
+ if not result:
+ return None
+ if result != current:
+ print("Internal Error")
+ return None
+ if not exiting:
+ print("Internal Error: No exiting UV")
+ return None
+
+ # enter
+ if entering.count(current) >= 1:
+ entering.remove(current)
+
+ current_list.find_and_next(current)
+ current = current_list.get()
+
+ prev = None
+ error = False
+ while exiting.count(current) == 0:
+ p.append(current.copy())
+ current_list.find_and_next(current)
+ current = current_list.get()
+ if prev == current:
+ error = True
+ break
+ prev = current
+
+ if error:
+ print("Internal Error: Infinite loop")
+ return None
+
+ # exit
+ p.append(current.copy())
+ exiting.remove(current)
+
+ other_list.find_and_set(current)
+ return other_list.get()
+
+ # Traverse
+ polygons = []
+ current_uv_list = subject_uvs
+ other_uv_list = clip_uvs
+ current_entering = subject_entering
+ current_exiting = subject_exiting
+
+ poly = []
+ current_uv = current_entering[0]
+
+ while True:
+ current_uv = traverse(current_uv_list, current_entering,
+ current_exiting, poly, current_uv, other_uv_list)
+
+ if current_uv is None:
+ break
+
+ if current_uv_list == subject_uvs:
+ current_uv_list = clip_uvs
+ other_uv_list = subject_uvs
+ current_entering = clip_entering
+ current_exiting = clip_exiting
+ debug_print("-- Next: Clip --")
+ else:
+ current_uv_list = subject_uvs
+ other_uv_list = clip_uvs
+ current_entering = subject_entering
+ current_exiting = subject_exiting
+ debug_print("-- Next: Subject --")
+
+ debug_print(clip_entering)
+ debug_print(clip_exiting)
+ debug_print(subject_entering)
+ debug_print(subject_exiting)
+
+ if not clip_entering and not clip_exiting \
+ and not subject_entering and not subject_exiting:
+ break
+
+ polygons.append(poly)
+
+ debug_print("===== Polygons Overlapped Partially =====")
+ debug_print(polygons)
+
+ return True, polygons
+
+
+def __is_polygon_flipped(points):
+ area = 0.0
+ for i in range(len(points)):
+ uv1 = points.get(i)
+ uv2 = points.get(i + 1)
+ a = uv1.x * uv2.y - uv1.y * uv2.x
+ area = area + a
+ if area < 0:
+ # clock-wise
+ return True
+ return False
+
+
+def __is_point_in_polygon(point, subject_points):
+ count = 0
+ for i in range(len(subject_points)):
+ uv_start1 = subject_points.get(i)
+ uv_end1 = subject_points.get(i + 1)
+ uv_start2 = point
+ uv_end2 = Vector((1000000.0, point.y))
+ intersected, _ = __is_segment_intersect(uv_start1, uv_end1,
+ uv_start2, uv_end2)
+ if intersected:
+ count = count + 1
+
+ return count % 2
+
+
+def __is_points_in_polygon(points, subject_points):
+ for i in range(len(points)):
+ internal = __is_point_in_polygon(points.get(i), subject_points)
+ if not internal:
+ return False
+
+ return True
+
+
+def get_overlapped_uv_info(bm, faces, uv_layer, mode):
+ # at first, check island overlapped
+ isl = get_island_info_from_faces(bm, faces, uv_layer)
+ overlapped_isl_pairs = []
+ for i, i1 in enumerate(isl):
+ for i2 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])
+
+ # next, check polygon overlapped
+ overlapped_uvs = []
+ for oip in overlapped_isl_pairs:
+ for clip in oip[0]["faces"]:
+ f_clip = clip["face"]
+ for subject in oip[1]["faces"]:
+ f_subject = subject["face"]
+
+ # fast operation, apply bounding box algorithm
+ if (clip["max_uv"].x < subject["min_uv"].x) or \
+ (subject["max_uv"].x < clip["min_uv"].x) or \
+ (clip["max_uv"].y < subject["min_uv"].y) or \
+ (subject["max_uv"].y < clip["min_uv"].y):
+ continue
+
+ # slow operation, apply Weiler-Atherton cliping algorithm
+ result, polygons = __do_weiler_atherton_cliping(f_clip,
+ f_subject,
+ uv_layer, 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,
+ "subject_uvs": subject_uvs,
+ "polygons": polygons})
+
+ return overlapped_uvs
+
+
+def get_flipped_uv_info(faces, uv_layer):
+ 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()]})
+
+ return flipped_uvs
+
+
+def __is_polygon_same(points1, points2):
+ if len(points1) != len(points2):
+ return False
+
+ pts1 = points1.as_list()
+ pts2 = points2.as_list()
+
+ for p1 in pts1:
+ for p2 in pts2:
+ diff = p2 - p1
+ if diff.length < 0.0000001:
+ pts2.remove(p2)
+ break
+ else:
+ return False
+
+ return True