2019-06-08 23:42:50 +00:00
import bpy
import os
import bmesh
import math
import operator
from mathutils import Vector
from collections import defaultdict
from itertools import chain # 'flattens' collection of iterables
from . import utilities_uv
class op ( bpy . types . Operator ) :
bl_idname = " uv.textools_island_align_world "
bl_label = " Align World "
bl_description = " Align selected UV islands to world / gravity directions "
bl_options = { ' REGISTER ' , ' UNDO ' }
2020-05-24 04:42:43 +00:00
bool_face : bpy . props . BoolProperty ( name = " Per face " , default = False , description = " Use if every face is an island in uv space; this speeds up the script dramatically. " )
bool_simple : bpy . props . BoolProperty ( name = " Simple align " , default = False , description = " Only process one edge per island, enough for nearly undistorted uvs. " )
steps : bpy . props . IntProperty ( name = " Iterations " , min = 1 , max = 100 , soft_min = 1 , soft_max = 5 , default = 1 , description = " Using multiple steps (up to 5, usually 2 or 3) is useful in certain cases, especially uv hulls with high localized distortion. " )
2020-05-17 22:13:53 +00:00
2019-06-08 23:42:50 +00:00
# is_global = bpy.props.BoolProperty(
# name = "Global Axis",
# description = "Global or local object axis alignment",
# default = False
# )
# def draw(self, context):
# layout = self.layout
# layout.prop(self, "is_global")
@classmethod
def poll ( cls , context ) :
if not bpy . context . active_object :
return False
#Only in Edit mode
if bpy . context . active_object . mode != ' EDIT ' :
return False
#Requires UV map
if not bpy . context . object . data . uv_layers :
return False
if bpy . context . scene . tool_settings . use_uv_select_sync :
return False
#Only in UV editor mode
if bpy . context . area . type != ' IMAGE_EDITOR ' :
return False
return True
def execute ( self , context ) :
2020-05-17 22:13:53 +00:00
main ( self , context )
2019-06-08 23:42:50 +00:00
return { ' FINISHED ' }
2020-05-17 22:13:53 +00:00
def invoke ( self , context , event ) :
wm = context . window_manager
return wm . invoke_props_dialog ( self )
2019-06-08 23:42:50 +00:00
2020-05-17 22:13:53 +00:00
def main ( self , context ) :
2019-06-08 23:42:50 +00:00
print ( " \n ________________________ \n is_global " )
#Store selection
utilities_uv . selection_store ( )
bm = bmesh . from_edit_mesh ( bpy . context . active_object . data )
uv_layers = bm . loops . layers . uv . verify ( )
#Only in Face or Island mode
if bpy . context . scene . tool_settings . uv_select_mode is not ' FACE ' or ' ISLAND ' :
bpy . context . scene . tool_settings . uv_select_mode = ' FACE '
obj = bpy . context . object
bm = bmesh . from_edit_mesh ( bpy . context . active_object . data ) ;
uv_layers = bm . loops . layers . uv . verify ( ) ;
2020-05-17 22:13:53 +00:00
if self . bool_face :
islands = [ [ f ] for f in bm . faces if f . select and f . loops [ 0 ] [ uv_layers ] . select ]
else :
islands = utilities_uv . getSelectionIslands ( )
2019-06-08 23:42:50 +00:00
for faces in islands :
avg_normal = Vector ( ( 0 , 0 , 0 ) )
2020-05-17 22:13:53 +00:00
if self . bool_face :
avg_normal = faces [ 0 ] . normal
else :
# Get average viewport normal of UV island
for face in faces :
avg_normal + = face . normal
avg_normal / = len ( faces )
2019-06-08 23:42:50 +00:00
# Which Side
x = 0
y = 1
z = 2
max_size = max ( abs ( avg_normal . x ) , abs ( avg_normal . y ) , abs ( avg_normal . z ) )
2020-05-17 22:13:53 +00:00
for i in range ( self . steps ) : # Use multiple steps
if ( abs ( avg_normal . x ) == max_size ) :
print ( " x normal " )
if self . bool_simple :
align_island_simple ( obj , bm , uv_layers , faces , y , z , avg_normal . x < 0 , False )
else :
align_island ( obj , bm , uv_layers , faces , y , z , avg_normal . x < 0 , False )
elif ( abs ( avg_normal . y ) == max_size ) :
print ( " y normal " )
if self . bool_simple :
align_island_simple ( obj , bm , uv_layers , faces , x , z , avg_normal . y > 0 , False )
else :
align_island ( obj , bm , uv_layers , faces , x , z , avg_normal . y > 0 , False )
elif ( abs ( avg_normal . z ) == max_size ) :
print ( " z normal " )
if self . bool_simple :
2020-06-22 20:05:39 +00:00
align_island_simple ( obj , bm , uv_layers , faces , x , y , False , avg_normal . z < 0 )
2020-05-17 22:13:53 +00:00
else :
align_island ( obj , bm , uv_layers , faces , x , y , False , avg_normal . z < 0 )
2019-06-08 23:42:50 +00:00
print ( " align island: faces {} x n: {} , max: {} " . format ( len ( faces ) , avg_normal , max_size ) )
#Restore selection
utilities_uv . selection_restore ( )
def align_island ( obj , bm , uv_layers , faces , x = 0 , y = 1 , flip_x = False , flip_y = False ) :
# Find lowest and highest verts
minmax_val = [ 0 , 0 ]
minmax_vert = [ None , None ]
axis_names = [ ' x ' , ' y ' , ' z ' ]
print ( " Align shell {} x at {} , {} flip {} , {} " . format ( len ( faces ) , axis_names [ x ] , axis_names [ y ] , flip_x , flip_y ) )
# print(" Min #{} , Max #{} along '{}'".format(minmax_vert[0].index, minmax_vert[1].index, axis_names[y] ))
# print(" A1 {:.1f} , A2 {:.1f} along ".format(minmax_val[0], minmax_val[1] ))
2020-05-17 00:18:27 +00:00
2019-06-08 23:42:50 +00:00
# Collect UV to Vert
2020-05-17 00:18:27 +00:00
vert_to_uv = { }
for face in faces :
for loop in face . loops :
vert = loop . vert
uv = loop [ uv_layers ]
if vert not in vert_to_uv :
vert_to_uv [ vert ] = [ uv ] ;
else :
vert_to_uv [ vert ] . append ( uv )
#uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers)
2019-06-08 23:42:50 +00:00
processed_edges = [ ]
2020-05-17 00:18:27 +00:00
n_edges = 0
avg_angle = 0
2019-06-08 23:42:50 +00:00
for face in faces :
for edge in face . edges :
if edge not in processed_edges :
processed_edges . append ( edge )
delta = edge . verts [ 0 ] . co - edge . verts [ 1 ] . co
max_side = max ( abs ( delta . x ) , abs ( delta . y ) , abs ( delta . z ) )
# Check edges dominant in active axis
if ( abs ( delta [ x ] ) == max_side or abs ( delta [ y ] ) == max_side ) :
2020-05-17 00:18:27 +00:00
n_edges + = 1
uv0 = vert_to_uv [ edge . verts [ 0 ] ] [ 0 ]
uv1 = vert_to_uv [ edge . verts [ 1 ] ] [ 0 ]
delta_verts = Vector ( (
edge . verts [ 1 ] . co [ x ] - edge . verts [ 0 ] . co [ x ] ,
edge . verts [ 1 ] . co [ y ] - edge . verts [ 0 ] . co [ y ]
) )
if flip_x :
delta_verts . x = - edge . verts [ 1 ] . co [ x ] + edge . verts [ 0 ] . co [ x ]
if flip_y :
delta_verts . y = - edge . verts [ 1 ] . co [ y ] + edge . verts [ 0 ] . co [ y ]
delta_uvs = Vector ( (
uv1 . uv . x - uv0 . uv . x ,
uv1 . uv . y - uv0 . uv . y
) )
2019-06-08 23:42:50 +00:00
2020-05-17 00:18:27 +00:00
a0 = math . atan2 ( delta_verts . y , delta_verts . x ) #- math.pi/2
a1 = math . atan2 ( delta_uvs . y , delta_uvs . x ) #- math.pi/2
a_delta = math . atan2 ( math . sin ( a0 - a1 ) , math . cos ( a0 - a1 ) )
# Consolidation (math.atan2 gives the lower angle between -Pi and Pi, this triggers errors when using the average avg_angle /= n_edges for rotation angles close to Pi)
if n_edges > 1 :
if abs ( ( avg_angle / ( n_edges - 1 ) ) - a_delta ) > 2.8 :
if a_delta > 0 :
avg_angle + = ( a_delta - math . pi * 2 )
else :
avg_angle + = ( a_delta + math . pi * 2 )
else :
avg_angle + = a_delta
else :
avg_angle + = a_delta
avg_angle / = n_edges
print ( " Edges {} x " . format ( n_edges ) )
2019-06-08 23:42:50 +00:00
print ( " Turn {:.1f} " . format ( avg_angle * 180 / math . pi ) )
bpy . ops . uv . select_all ( action = ' DESELECT ' )
for face in faces :
for loop in face . loops :
loop [ uv_layers ] . select = True
bpy . context . tool_settings . transform_pivot_point = ' MEDIAN_POINT '
2020-05-17 00:18:27 +00:00
bpy . ops . transform . rotate ( value = - avg_angle , orient_axis = ' Z ' ) # minus angle; Blender uses unconventional rotation notation (positive for clockwise)
2019-06-08 23:42:50 +00:00
2020-05-17 22:13:53 +00:00
def align_island_simple ( obj , bm , uv_layers , faces , x = 0 , y = 1 , flip_x = False , flip_y = False ) :
# Find lowest and highest verts
minmax_val = [ 0 , 0 ]
minmax_vert = [ None , None ]
axis_names = [ ' x ' , ' y ' , ' z ' ]
print ( " Align shell {} x at {} , {} flip {} , {} " . format ( len ( faces ) , axis_names [ x ] , axis_names [ y ] , flip_x , flip_y ) )
# Collect UV to Vert
vert_to_uv = { }
face = faces [ 0 ]
for loop in face . loops :
vert = loop . vert
uv = loop [ uv_layers ]
vert_to_uv [ vert ] = [ uv ]
uv . select = True
edge = faces [ 0 ] . edges [ 0 ]
delta = edge . verts [ 0 ] . co - edge . verts [ 1 ] . co
max_side = max ( abs ( delta . x ) , abs ( delta . y ) , abs ( delta . z ) )
2020-06-22 20:05:39 +00:00
a_delta = 0
2020-05-17 22:13:53 +00:00
# Check edges dominant in active axis
if abs ( delta [ x ] ) == max_side or abs ( delta [ y ] ) == max_side :
uv0 = vert_to_uv [ edge . verts [ 0 ] ] [ 0 ]
uv1 = vert_to_uv [ edge . verts [ 1 ] ] [ 0 ]
delta_verts = Vector ( (
edge . verts [ 1 ] . co [ x ] - edge . verts [ 0 ] . co [ x ] ,
edge . verts [ 1 ] . co [ y ] - edge . verts [ 0 ] . co [ y ]
) )
if flip_x :
delta_verts . x = - edge . verts [ 1 ] . co [ x ] + edge . verts [ 0 ] . co [ x ]
if flip_y :
delta_verts . y = - edge . verts [ 1 ] . co [ y ] + edge . verts [ 0 ] . co [ y ]
delta_uvs = Vector ( (
uv1 . uv . x - uv0 . uv . x ,
uv1 . uv . y - uv0 . uv . y
) )
a0 = math . atan2 ( delta_verts . y , delta_verts . x )
a1 = math . atan2 ( delta_uvs . y , delta_uvs . x )
a_delta = math . atan2 ( math . sin ( a0 - a1 ) , math . cos ( a0 - a1 ) )
print ( " Turn {:.1f} " . format ( a_delta * 180 / math . pi ) )
bpy . ops . uv . select_all ( action = ' DESELECT ' )
for face in faces :
for loop in face . loops :
loop [ uv_layers ] . select = True
bpy . context . tool_settings . transform_pivot_point = ' MEDIAN_POINT '
bpy . ops . transform . rotate ( value = - a_delta , orient_axis = ' Z ' ) # minus angle; Blender uses unconventional rotation notation (positive for clockwise)
2019-06-08 23:42:50 +00:00
bpy . utils . register_class ( op )