mirror of
https://github.com/drewcassidy/TexTools-Blender
synced 2024-09-01 14:54:44 +00:00
258 lines
8.5 KiB
Python
258 lines
8.5 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)
|