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:
Diffstat (limited to 'uv_magic_uv/op/uv_inspection.py')
-rw-r--r--uv_magic_uv/op/uv_inspection.py643
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'}