@ -19,6 +19,10 @@ class op(bpy.types.Operator):
bl_description = " Align selected UV islands to world / gravity directions "
bl_options = { ' REGISTER ' , ' UNDO ' }
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. " )
# is_global = bpy.props.BoolProperty(
# name = "Global Axis",
# description = "Global or local object axis alignment",
@ -51,14 +55,16 @@ class op(bpy.types.Operator):
return True
def execute ( self , context ) :
main ( self )
main ( self , context )
return { ' FINISHED ' }
def invoke ( self , context , event ) :
wm = context . window_manager
return wm . invoke_props_dialog ( self )
def main ( context ) :
def main ( self , context ) :
print ( " \n ________________________ \n is_global " )
#Store selection
@ -75,18 +81,20 @@ def main(context):
bm = bmesh . from_edit_mesh ( bpy . context . active_object . data ) ;
uv_layers = bm . loops . layers . uv . verify ( ) ;
islands = utilities_uv . getSelectionIslands ( )
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 ( )
for faces in islands :
# Get average viewport normal of UV island
avg_normal = Vector ( ( 0 , 0 , 0 ) )
for face in faces :
avg_normal + = face . normal
avg_normal / = len ( faces )
# avg_normal = (obj.matrix_world*avg_normal).normalized()
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 )
# Which Side
x = 0
@ -94,29 +102,32 @@ def main(context):
z = 2
max_size = max ( abs ( avg_normal . x ) , abs ( avg_normal . y ) , abs ( avg_normal . z ) )
# Use multiple steps
for i in range ( 3 ) :
for i in range ( self . steps ) : # Use multiple steps
if ( abs ( avg_normal . x ) == max_size ) :
print ( " x normal " )
align_island ( obj , bm , uv_layers , faces , y , z , avg_normal . x < 0 , False )
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 " )
align_island ( obj , bm , uv_layers , faces , x , z , avg_normal . y > 0 , False )
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 " )
align_island ( obj , bm , uv_layers , faces , x , y , False , avg_normal . z < 0 )
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 , y , False , avg_normal . z < 0 )
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
@ -126,166 +137,135 @@ def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False
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] ))
# Collect UV to Vert
vert_to_uv = utilities_uv . get_vert_to_uv ( bm , uv_layers )
uv_to_vert = utilities_uv . get_uv_to_vert ( bm , uv_layers )
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)
processed_edges = [ ]
edges = [ ]
n_edges = 0
avg_angle = 0
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 ) :
# if( abs(delta[y]) == max_side):
edges . append ( edge )
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
) )
print ( " Edges {} x " . format ( len ( edges ) ) )
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 ) )
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
avg_angle = 0
for edge in edges :
bpy . context . tool_settings . transform_pivot_point = ' MEDIAN_POINT '
bpy . ops . transform . rotate ( value = - avg_angle , orient_axis = ' Z ' ) # minus angle; Blender uses unconventional rotation notation (positive for clockwise)
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 ) )
# 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_verts.y = edge.verts[0].co[y] - edge.verts[1].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 ) - math . pi / 2
a1 = math . atan2 ( delta_uvs . y , delta_uvs . x ) - math . pi / 2
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 ) )
# edge.verts[0].index, edge.verts[1].index
# print(" turn {:.1f} .. {:.1f} , {:.1f}".format(a_delta*180/math.pi, a0*180/math.pi,a1*180/math.pi))
avg_angle + = a_delta
avg_angle / = len ( edges ) # - math.pi/2
print ( " Turn {:.1f} " . format ( avg_angle * 180 / math . pi ) )
a_delta = math . atan2 ( math . sin ( a0 - a1 ) , math . cos ( a0 - a1 ) )
bpy . ops . uv . select_all ( action = ' DESELECT ' )
for face in faces :
for loop in face . loops :
loop [ uv_layers ] . select = True
print ( " Turn {:.1f} " . format ( a_delta * 180 / math . pi ) )
bpy . context . tool_settings . transform_pivot_point = ' MEDIAN_POINT '
bpy . ops . transform . rotate ( value = avg_angle , orient_axis = ' Z ' )
# bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348)
# processed = []
'''
bpy . ops . uv . select_all ( action = ' DESELECT ' )
for face in faces :
# Collect UV to Vert
for loop in face . loops :
loop [ uv_layers ] . select = True
vert = loop . vert
uv = loop [ uv_layers ]
# vert_to_uv
if vert not in vert_to_uv :
vert_to_uv [ vert ] = [ uv ] ;
else :
vert_to_uv [ vert ] . append ( uv )
# uv_to_vert
if uv not in uv_to_vert :
uv_to_vert [ uv ] = vert ;
for vert in face . verts :
if vert not in processed :
processed . append ( vert )
vert_y = ( vert . co ) [ y ] #obj.matrix_world *
print ( " idx {} = {} " . format ( vert . index , vert_y ) )
if not minmax_vert [ 0 ] or not minmax_vert [ 1 ] :
minmax_vert [ 0 ] = vert
minmax_vert [ 1 ] = vert
minmax_val [ 0 ] = vert_y
minmax_val [ 1 ] = vert_y
continue
if vert_y < minmax_val [ 0 ] :
# Not yet defined or smaller
minmax_vert [ 0 ] = vert
minmax_val [ 0 ] = vert_y
elif vert_y > minmax_val [ 1 ] :
minmax_vert [ 1 ] = vert
minmax_val [ 1 ] = vert_y
if minmax_vert [ 0 ] and minmax_vert [ 1 ] :
axis_names = [ ' x ' , ' y ' , ' z ' ]
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] ))
vert_A = minmax_vert [ 0 ]
vert_B = minmax_vert [ 1 ]
uv_A = vert_to_uv [ vert_A ] [ 0 ]
uv_B = vert_to_uv [ vert_B ] [ 0 ]
delta_verts = Vector ( (
vert_B . co [ x ] - vert_A . co [ x ] ,
vert_B . co [ y ] - vert_A . co [ y ]
) )
delta_uvs = Vector ( (
uv_B . uv . x - uv_A . uv . x ,
uv_B . uv . y - uv_A . uv . y ,
) )
# Get angles
angle_vert = math . atan2 ( delta_verts . y , delta_verts . x ) - math . pi / 2
angle_uv = math . atan2 ( delta_uvs . y , delta_uvs . x ) - math . pi / 2
angle_delta = math . atan2 ( math . sin ( angle_vert - angle_uv ) , math . cos ( angle_vert - angle_uv ) )
print ( " Angles {:.2f} | {:.2f} " . format ( angle_vert * 180 / math . pi , angle_uv * 180 / math . pi ) )
print ( " Angle Diff {:.2f} " . format ( angle_delta * 180 / math . pi ) )
bpy . context . tool_settings . transform_pivot_point = ' MEDIAN_POINT '
bpy . ops . transform . rotate ( value = angle_delta , axis = ' Z ' )
# bpy.ops.transform.rotate(value=0.58191, axis=(-0, -0, -1), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SPHERE', proportional_size=0.0267348)
# bpy.ops.mesh.select_all(action='DESELECT')
# vert_A.select = True
# vert_B.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)
# return
'''
bpy . utils . register_class ( op )