diff options
Diffstat (limited to 'uv_magic_uv/op/uv_inspection.py')
-rw-r--r-- | uv_magic_uv/op/uv_inspection.py | 623 |
1 files changed, 0 insertions, 623 deletions
diff --git a/uv_magic_uv/op/uv_inspection.py b/uv_magic_uv/op/uv_inspection.py deleted file mode 100644 index 60a754a3..00000000 --- a/uv_magic_uv/op/uv_inspection.py +++ /dev/null @@ -1,623 +0,0 @@ -# <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.1" -__date__ = "24 Feb 2018" - -import bpy -import bmesh -import bgl -from mathutils import Vector - -from .. import common - - -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 - - -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() - - common.debug_print("===== Clip UV List =====") - common.debug_print(clip_uvs) - common.debug_print("===== Subject UV List =====") - common.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()] - common.debug_print("===== Polygons Overlapped Completely =====") - common.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 - - common.debug_print("===== Intersection List =====") - common.debug_print(intersections) - - # no intersection, so subject and clip is not overlapped - if not intersections: - return False, None - - def get_intersection_pair(intersections, key): - for sect in intersections: - 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 - - common.debug_print("===== Enter List =====") - common.debug_print(clip_entering) - common.debug_print(subject_entering) - common.debug_print("===== Exit List =====") - common.debug_print(clip_exiting) - common.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, poly, current, other_list): - result = current_list.find(current) - if not result: - return None - if result != current: - print("Internal Error") - return None - - # enter - if entering.count(current) >= 1: - entering.remove(current) - - current_list.find_and_next(current) - current = current_list.get() - - while exiting.count(current) == 0: - poly.append(current.copy()) - current_list.find_and_next(current) - current = current_list.get() - - # exit - poly.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_list == subject_uvs: - current_uv_list = clip_uvs - other_uv_list = subject_uvs - current_entering = clip_entering - current_exiting = clip_exiting - common.debug_print("-- Next: Clip --") - else: - current_uv_list = subject_uvs - other_uv_list = clip_uvs - current_entering = subject_entering - current_exiting = subject_exiting - common.debug_print("-- Next: Subject --") - - common.debug_print(clip_entering) - common.debug_print(clip_exiting) - common.debug_print(subject_entering) - common.debug_print(subject_exiting) - - if not clip_entering and not clip_exiting \ - and not subject_entering and not subject_exiting: - break - - polygons.append(poly) - - common.debug_print("===== Polygons Overlapped Partially =====") - common.debug_print(polygons) - - return True, polygons - - -class MUV_UVInspRenderer(bpy.types.Operator): - """ - Operation class: Render UV Inspection - No operation (only rendering) - """ - - bl_idname = "uv.muv_uvinsp_renderer" - bl_description = "Render overlapped/flipped UVs" - bl_label = "Overlapped/Flipped UV renderer" - - __handle = None - - @staticmethod - def handle_add(obj, context): - sie = bpy.types.SpaceImageEditor - MUV_UVInspRenderer.__handle = sie.draw_handler_add( - MUV_UVInspRenderer.draw, (obj, context), 'WINDOW', 'POST_PIXEL') - - @staticmethod - def handle_remove(): - if MUV_UVInspRenderer.__handle is not None: - bpy.types.SpaceImageEditor.draw_handler_remove( - MUV_UVInspRenderer.__handle, 'WINDOW') - MUV_UVInspRenderer.__handle = None - - @staticmethod - def draw(_, context): - sc = context.scene - props = sc.muv_props.uvinsp - prefs = context.user_preferences.addons["uv_magic_uv"].preferences - - # OpenGL configuration - bgl.glEnable(bgl.GL_BLEND) - - # render overlapped UV - if sc.muv_uvinsp_show_overlapped: - color = prefs.uvinsp_overlapped_color - for info in props.overlapped_info: - if sc.muv_uvinsp_show_mode == 'PART': - for poly in info["polygons"]: - bgl.glBegin(bgl.GL_TRIANGLE_FAN) - 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) - bgl.glVertex2f(x, y) - bgl.glEnd() - elif sc.muv_uvinsp_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) - bgl.glVertex2f(x, y) - bgl.glEnd() - - # render flipped UV - if sc.muv_uvinsp_show_flipped: - color = prefs.uvinsp_flipped_color - for info in props.flipped_info: - if sc.muv_uvinsp_show_mode == 'PART': - for poly in info["polygons"]: - bgl.glBegin(bgl.GL_TRIANGLE_FAN) - 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) - bgl.glVertex2f(x, y) - bgl.glEnd() - elif sc.muv_uvinsp_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) - bgl.glVertex2f(x, y) - bgl.glEnd() - - -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 = common.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 update_uvinsp_info(context): - sc = context.scene - props = sc.muv_props.uvinsp - - 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() - - 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 = get_overlapped_uv_info(bm, sel_faces, uv_layer, - sc.muv_uvinsp_show_mode) - props.flipped_info = get_flipped_uv_info(sel_faces, uv_layer) - - -class MUV_UVInspUpdate(bpy.types.Operator): - """ - Operation class: Update - """ - - bl_idname = "uv.muv_uvinsp_update" - bl_label = "Update" - bl_description = "Update Overlapped/Flipped UV" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - update_uvinsp_info(context) - - if context.area: - context.area.tag_redraw() - - return {'FINISHED'} - - -class MUV_UVInspDisplay(bpy.types.Operator): - """ - Operation class: Display - """ - - bl_idname = "uv.muv_uvinsp_display" - bl_label = "Display" - bl_description = "Display Overlapped/Flipped UV" - bl_options = {'REGISTER', 'UNDO'} - - def execute(self, context): - sc = context.scene - props = sc.muv_props.uvinsp - if not props.display_running: - update_uvinsp_info(context) - MUV_UVInspRenderer.handle_add(self, context) - props.display_running = True - else: - MUV_UVInspRenderer.handle_remove() - props.display_running = False - - if context.area: - context.area.tag_redraw() - - return {'FINISHED'} - - -class MUV_UVInspSelectOverlapped(bpy.types.Operator): - """ - Operation class: Select faces which have overlapped UVs - """ - - bl_idname = "uv.muv_uvinsp_select_overlapped" - bl_label = "Overlapped" - bl_description = "Select faces which have overlapped UVs" - bl_options = {'REGISTER', 'UNDO'} - - 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() - - 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] - - overlapped_info = get_overlapped_uv_info(bm, sel_faces, uv_layer, - '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 - - bmesh.update_edit_mesh(obj.data) - - return {'FINISHED'} - - -class MUV_UVInspSelectFlipped(bpy.types.Operator): - """ - Operation class: Select faces which have flipped UVs - """ - - bl_idname = "uv.muv_uvinsp_select_flipped" - bl_label = "Flipped" - bl_description = "Select faces which have flipped UVs" - bl_options = {'REGISTER', 'UNDO'} - - 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() - - 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] - - flipped_info = get_flipped_uv_info(sel_faces, uv_layer) - - 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 - - bmesh.update_edit_mesh(obj.data) - - return {'FINISHED'} |