diff options
Diffstat (limited to 'uv_magic_uv/op/uv_inspection.py')
-rw-r--r-- | uv_magic_uv/op/uv_inspection.py | 643 |
1 files changed, 146 insertions, 497 deletions
diff --git a/uv_magic_uv/op/uv_inspection.py b/uv_magic_uv/op/uv_inspection.py index 60a754a3..0c05e03d 100644 --- a/uv_magic_uv/op/uv_inspection.py +++ b/uv_magic_uv/op/uv_inspection.py @@ -20,343 +20,161 @@ __author__ = "Nutti <nutti.metro@gmail.com>" __status__ = "production" -__version__ = "5.1" -__date__ = "24 Feb 2018" +__version__ = "5.2" +__date__ = "17 Nov 2018" import bpy import bmesh import bgl -from mathutils import Vector +from bpy.props import BoolProperty, EnumProperty 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) +__all__ = [ + 'Properties', + 'OperatorRender', + 'OperatorUpdate', +] - 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 +def is_valid_context(context): + obj = context.object - 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 + # only edit mode is allowed to execute + if obj is None: + return False + if obj.type != 'MESH': + return False + if context.object.mode != 'EDIT': + return False - 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: + # '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 - polygons.append(poly) - - common.debug_print("===== Polygons Overlapped Partially =====") - common.debug_print(polygons) - - return True, polygons + return True -class MUV_UVInspRenderer(bpy.types.Operator): +class Properties: + @classmethod + def init_props(cls, scene): + class Props(): + overlapped_info = [] + flipped_info = [] + + scene.muv_props.uv_inspection = Props() + + def get_func(_): + return OperatorRender.is_running(bpy.context) + + def set_func(_, __): + pass + + def update_func(_, __): + bpy.ops.uv.muv_uv_inspection_operator_render('INVOKE_REGION_WIN') + + scene.muv_uv_inspection_enabled = BoolProperty( + name="UV Inspection Enabled", + description="UV Inspection is enabled", + default=False + ) + scene.muv_uv_inspection_show = BoolProperty( + name="UV Inspection Showed", + description="UV Inspection is showed", + default=False, + get=get_func, + set=set_func, + update=update_func + ) + scene.muv_uv_inspection_show_overlapped = BoolProperty( + name="Overlapped", + description="Show overlapped UVs", + default=False + ) + scene.muv_uv_inspection_show_flipped = BoolProperty( + name="Flipped", + description="Show flipped UVs", + default=False + ) + scene.muv_uv_inspection_show_mode = EnumProperty( + name="Mode", + description="Show mode", + items=[ + ('PART', "Part", "Show only overlapped/flipped part"), + ('FACE', "Face", "Show overlapped/flipped face") + ], + default='PART' + ) + + @classmethod + def del_props(cls, scene): + del scene.muv_props.uv_inspection + del scene.muv_uv_inspection_enabled + del scene.muv_uv_inspection_show + del scene.muv_uv_inspection_show_overlapped + del scene.muv_uv_inspection_show_flipped + del scene.muv_uv_inspection_show_mode + + +class OperatorRender(bpy.types.Operator): """ Operation class: Render UV Inspection No operation (only rendering) """ - bl_idname = "uv.muv_uvinsp_renderer" + bl_idname = "uv.muv_uv_inspection_operator_render" bl_description = "Render overlapped/flipped UVs" bl_label = "Overlapped/Flipped UV renderer" __handle = None - @staticmethod - def handle_add(obj, context): + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return False + return is_valid_context(context) + + @classmethod + def is_running(cls, _): + return 1 if cls.__handle else 0 + + @classmethod + def handle_add(cls, obj, context): sie = bpy.types.SpaceImageEditor - MUV_UVInspRenderer.__handle = sie.draw_handler_add( - MUV_UVInspRenderer.draw, (obj, context), 'WINDOW', 'POST_PIXEL') + cls.__handle = sie.draw_handler_add( + OperatorRender.draw, (obj, context), 'WINDOW', 'POST_PIXEL') - @staticmethod - def handle_remove(): - if MUV_UVInspRenderer.__handle is not None: + @classmethod + def handle_remove(cls): + if cls.__handle is not None: bpy.types.SpaceImageEditor.draw_handler_remove( - MUV_UVInspRenderer.__handle, 'WINDOW') - MUV_UVInspRenderer.__handle = None + cls.__handle, 'WINDOW') + cls.__handle = None @staticmethod def draw(_, context): sc = context.scene - props = sc.muv_props.uvinsp + props = sc.muv_props.uv_inspection prefs = context.user_preferences.addons["uv_magic_uv"].preferences + if not OperatorRender.is_running(context): + return + # OpenGL configuration bgl.glEnable(bgl.GL_BLEND) # render overlapped UV - if sc.muv_uvinsp_show_overlapped: - color = prefs.uvinsp_overlapped_color + if sc.muv_uv_inspection_show_overlapped: + color = prefs.uv_inspection_overlapped_color for info in props.overlapped_info: - if sc.muv_uvinsp_show_mode == 'PART': + if sc.muv_uv_inspection_show_mode == 'PART': for poly in info["polygons"]: bgl.glBegin(bgl.GL_TRIANGLE_FAN) bgl.glColor4f(color[0], color[1], color[2], color[3]) @@ -365,7 +183,7 @@ class MUV_UVInspRenderer(bpy.types.Operator): uv.x, uv.y) bgl.glVertex2f(x, y) bgl.glEnd() - elif sc.muv_uvinsp_show_mode == 'FACE': + 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"]: @@ -374,10 +192,10 @@ class MUV_UVInspRenderer(bpy.types.Operator): bgl.glEnd() # render flipped UV - if sc.muv_uvinsp_show_flipped: - color = prefs.uvinsp_flipped_color + if sc.muv_uv_inspection_show_flipped: + color = prefs.uv_inspection_flipped_color for info in props.flipped_info: - if sc.muv_uvinsp_show_mode == 'PART': + if sc.muv_uv_inspection_show_mode == 'PART': for poly in info["polygons"]: bgl.glBegin(bgl.GL_TRIANGLE_FAN) bgl.glColor4f(color[0], color[1], color[2], color[3]) @@ -386,7 +204,7 @@ class MUV_UVInspRenderer(bpy.types.Operator): uv.x, uv.y) bgl.glVertex2f(x, y) bgl.glEnd() - elif sc.muv_uvinsp_show_mode == 'FACE': + 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"]: @@ -394,100 +212,22 @@ class MUV_UVInspRenderer(bpy.types.Operator): bgl.glVertex2f(x, y) bgl.glEnd() + def invoke(self, context, _): + if not OperatorRender.is_running(context): + update_uvinsp_info(context) + OperatorRender.handle_add(self, context) + else: + OperatorRender.handle_remove() -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 - + if context.area: + context.area.tag_redraw() -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 + return {'FINISHED'} def update_uvinsp_info(context): sc = context.scene - props = sc.muv_props.uvinsp + props = sc.muv_props.uv_inspection obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) @@ -499,125 +239,34 @@ def update_uvinsp_info(context): 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) + 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) -class MUV_UVInspUpdate(bpy.types.Operator): +class OperatorUpdate(bpy.types.Operator): """ Operation class: Update """ - bl_idname = "uv.muv_uvinsp_update" - bl_label = "Update" - bl_description = "Update Overlapped/Flipped UV" + bl_idname = "uv.muv_uv_inspection_operator_update" + bl_label = "Update UV Inspection" + bl_description = "Update UV Inspection" 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'} + @classmethod + def poll(cls, context): + # we can not get area/space/region from console + if common.is_console_mode(): + return True + if not OperatorRender.is_running(context): + return False + return is_valid_context(context) 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 + update_uvinsp_info(context) 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'} |