mirror of
https://github.com/drewcassidy/TexTools-Blender
synced 2024-09-01 14:54:44 +00:00
273 lines
7.1 KiB
Python
273 lines
7.1 KiB
Python
|
import bpy
|
||
|
import bmesh
|
||
|
import operator
|
||
|
import math
|
||
|
from mathutils import Vector
|
||
|
from collections import defaultdict
|
||
|
from math import pi
|
||
|
|
||
|
from . import utilities_uv
|
||
|
|
||
|
class op(bpy.types.Operator):
|
||
|
bl_idname = "uv.textools_island_straighten_edge_loops"
|
||
|
bl_label = "Straight edge loops"
|
||
|
bl_description = "Straighten edge loops of UV Island and relax rest"
|
||
|
bl_options = {'REGISTER', 'UNDO'}
|
||
|
|
||
|
@classmethod
|
||
|
def poll(cls, context):
|
||
|
|
||
|
if not bpy.context.active_object:
|
||
|
return False
|
||
|
|
||
|
if bpy.context.active_object.type != 'MESH':
|
||
|
return False
|
||
|
|
||
|
#Only in Edit mode
|
||
|
if bpy.context.active_object.mode != 'EDIT':
|
||
|
return False
|
||
|
|
||
|
#Only in UV editor mode
|
||
|
if bpy.context.area.type != 'IMAGE_EDITOR':
|
||
|
return False
|
||
|
|
||
|
#Requires UV map
|
||
|
if not bpy.context.object.data.uv_layers:
|
||
|
return False
|
||
|
|
||
|
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
|
||
|
return False
|
||
|
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
def execute(self, context):
|
||
|
|
||
|
main(context)
|
||
|
return {'FINISHED'}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
def main(context):
|
||
|
print("____________________________")
|
||
|
|
||
|
#Store selection
|
||
|
utilities_uv.selection_store()
|
||
|
|
||
|
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
|
||
|
uv_layers = bm.loops.layers.uv.verify()
|
||
|
|
||
|
edges = utilities_uv.get_selected_uv_edges(bm, uv_layers)
|
||
|
islands = utilities_uv.getSelectionIslands()
|
||
|
uvs = utilities_uv.get_selected_uvs(bm, uv_layers)
|
||
|
faces = [f for island in islands for f in island ]
|
||
|
|
||
|
|
||
|
# Get island faces
|
||
|
|
||
|
|
||
|
# utilities_uv.selection_restore(bm, uv_layers)
|
||
|
|
||
|
|
||
|
groups = get_edge_groups(bm, uv_layers, faces, edges, uvs)
|
||
|
|
||
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
|
||
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||
|
for face in faces:
|
||
|
face.select = True
|
||
|
|
||
|
|
||
|
print("Edges {}x".format(len(edges)))
|
||
|
print("Groups {}x".format(len(groups)))
|
||
|
|
||
|
# Restore 3D face selection
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# Restore UV seams and clear pins
|
||
|
bpy.ops.uv.seams_from_islands()
|
||
|
bpy.ops.uv.pin(clear=True)
|
||
|
|
||
|
edge_sets = []
|
||
|
for edges in groups:
|
||
|
edge_sets.append( EdgeSet(bm, uv_layers, edges, faces) )
|
||
|
# straighten_edges(bm, uv_layers, edges, faces)
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True)
|
||
|
|
||
|
for edge_set in sorted_sets:
|
||
|
edge_set.straighten()
|
||
|
|
||
|
#Restore selection
|
||
|
utilities_uv.selection_restore()
|
||
|
|
||
|
|
||
|
|
||
|
class EdgeSet:
|
||
|
bm = None
|
||
|
edges = []
|
||
|
faces = []
|
||
|
uv_layers = ''
|
||
|
vert_to_uv = {}
|
||
|
edge_length = {}
|
||
|
length = 0
|
||
|
|
||
|
def __init__(self, bm, uv_layers, edges, faces):
|
||
|
self.bm = bm
|
||
|
self.uv_layers = uv_layers
|
||
|
self.edges = edges
|
||
|
self.faces = faces
|
||
|
|
||
|
# Get Vert to UV within faces
|
||
|
self.vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
|
||
|
|
||
|
# Get edge lengths
|
||
|
self.edge_length = {}
|
||
|
self.length = 0
|
||
|
for e in edges:
|
||
|
uv1 = self.vert_to_uv[e.verts[0]][0].uv
|
||
|
uv2 = self.vert_to_uv[e.verts[1]][0].uv
|
||
|
self.edge_length[e] = (uv2 - uv1).length
|
||
|
self.length+=self.edge_length[e]
|
||
|
|
||
|
|
||
|
def straighten(self):
|
||
|
print("Straight {}x at {:.2f} length ".format(len(self.edges), self.length))
|
||
|
|
||
|
# Get edge angles in UV space
|
||
|
angles = {}
|
||
|
for edge in self.edges:
|
||
|
uv1 = self.vert_to_uv[edge.verts[0]][0].uv
|
||
|
uv2 = self.vert_to_uv[edge.verts[1]][0].uv
|
||
|
delta = uv2 - uv1
|
||
|
angle = math.atan2(delta.y, delta.x)%(math.pi/2)
|
||
|
if angle >= (math.pi/4):
|
||
|
angle = angle - (math.pi/2)
|
||
|
angles[edge] = abs(angle)
|
||
|
# print("Angle {:.2f} degr".format(angle * 180 / math.pi))
|
||
|
|
||
|
# Pick edge with least rotation offset to U or V axis
|
||
|
edge_main = sorted(angles.items(), key = operator.itemgetter(1))[0][0]
|
||
|
|
||
|
print("Main edge: {} at {:.2f} degr".format( edge_main.index, angles[edge_main] * 180 / math.pi ))
|
||
|
|
||
|
# Rotate main edge to closest axis
|
||
|
uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]]
|
||
|
bpy.ops.uv.select_all(action='DESELECT')
|
||
|
for uv in uvs:
|
||
|
uv.select = True
|
||
|
uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv
|
||
|
uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv
|
||
|
diff = uv2 - uv1
|
||
|
angle = math.atan2(diff.y, diff.x)%(math.pi/2)
|
||
|
if angle >= (math.pi/4):
|
||
|
angle = angle - (math.pi/2)
|
||
|
bpy.ops.uv.cursor_set(location=uv1 + diff/2)
|
||
|
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
|
||
|
|
||
|
# Expand edges and straighten
|
||
|
count = len(self.edges)
|
||
|
processed = [edge_main]
|
||
|
for i in range(count):
|
||
|
if(len(processed) < len(self.edges)):
|
||
|
verts = set([v for e in processed for v in e.verts])
|
||
|
edges_expand = [e for e in self.edges if e not in processed and (e.verts[0] in verts or e.verts[1] in verts)]
|
||
|
verts_ends = [v for e in edges_expand for v in e.verts if v in verts]
|
||
|
|
||
|
|
||
|
print("Step, proc {} exp: {}".format( [e.index for e in processed] , [e.index for e in edges_expand] ))
|
||
|
|
||
|
if len(edges_expand) == 0:
|
||
|
continue
|
||
|
|
||
|
for edge in edges_expand:
|
||
|
# if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends:
|
||
|
# print("Cancel at edge {}".format(edge.index))
|
||
|
# return
|
||
|
|
||
|
print(" E {} verts {} verts end: {}".format(edge.index, [v.index for v in edge.verts], [v.index for v in verts_ends]))
|
||
|
v1 = [v for v in edge.verts if v in verts_ends][0]
|
||
|
v2 = [v for v in edge.verts if v not in verts_ends][0]
|
||
|
# direction
|
||
|
previous_edge = [e for e in processed if e.verts[0] in edge.verts or e.verts[1] in edge.verts][0]
|
||
|
prev_v1 = [v for v in previous_edge.verts if v != v1][0]
|
||
|
prev_v2 = [v for v in previous_edge.verts if v == v1][0]
|
||
|
direction = (self.vert_to_uv[prev_v2][0].uv - self.vert_to_uv[prev_v1][0].uv).normalized()
|
||
|
|
||
|
for uv in self.vert_to_uv[v2]:
|
||
|
uv.uv = self.vert_to_uv[v1][0].uv + direction * self.edge_length[edge]
|
||
|
|
||
|
print("Procesed {}x Expand {}x".format(len(processed), len(edges_expand) ))
|
||
|
print("verts_ends: {}x".format(len(verts_ends)))
|
||
|
processed.extend(edges_expand)
|
||
|
|
||
|
# Select edges
|
||
|
uvs = list(set( [uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v] ] ))
|
||
|
bpy.ops.uv.select_all(action='DESELECT')
|
||
|
for uv in uvs:
|
||
|
uv.select = True
|
||
|
|
||
|
# Pin UV's
|
||
|
bpy.ops.uv.pin()
|
||
|
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
|
||
|
bpy.ops.uv.pin(clear=True)
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
def get_edge_groups(bm, uv_layers, faces, edges, uvs):
|
||
|
print("Get edge groups, edges {}x".format(len(edges))+"x")
|
||
|
|
||
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
|
||
|
|
||
|
unmatched = edges.copy()
|
||
|
|
||
|
groups = []
|
||
|
|
||
|
for edge in edges:
|
||
|
if edge in unmatched:
|
||
|
|
||
|
# Loop select edge
|
||
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||
|
edge.select = True
|
||
|
bpy.ops.mesh.loop_multi_select(ring=False)
|
||
|
|
||
|
# Isolate group within edges
|
||
|
group = [e for e in bm.edges if e.select and e in edges]
|
||
|
groups.append(group)
|
||
|
|
||
|
# Remove from unmatched
|
||
|
for e in group:
|
||
|
if e in unmatched:
|
||
|
unmatched.remove(e)
|
||
|
|
||
|
print(" Edge {} : Group: {}x , unmatched: {}".format(edge.index, len(group), len(unmatched)))
|
||
|
|
||
|
# return
|
||
|
# group = [edge]
|
||
|
# for e in bm.edges:
|
||
|
# if e.select and e in unmatched:
|
||
|
# unmatched.remove(e)
|
||
|
# group.append(edge)
|
||
|
|
||
|
|
||
|
|
||
|
return groups
|
||
|
|
||
|
|
||
|
bpy.utils.register_class(op)
|
||
|
|
||
|
|