You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Blender-TexTools/op_island_straighten_edge_l...

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
4 years ago
class op(bpy.types.Operator):
4 years ago
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'}
4 years ago
4 years ago
@classmethod
def poll(cls, context):
4 years ago
if not bpy.context.active_object:
return False
4 years ago
if bpy.context.active_object.type != 'MESH':
return False
4 years ago
# Only in Edit mode
4 years ago
if bpy.context.active_object.mode != 'EDIT':
return False
4 years ago
# Only in UV editor mode
4 years ago
if bpy.context.area.type != 'IMAGE_EDITOR':
return False
4 years ago
# Requires UV map
4 years ago
if not bpy.context.object.data.uv_layers:
return False
4 years ago
if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
4 years ago
return False
4 years ago
return True
4 years ago
def execute(self, context):
4 years ago
4 years ago
main(context)
return {'FINISHED'}
def main(context):
4 years ago
print("____________________________")
4 years ago
# Store selection
4 years ago
utilities_uv.selection_store()
4 years ago
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
uv_layers = bm.loops.layers.uv.verify()
4 years ago
4 years ago
edges = utilities_uv.get_selected_uv_edges(bm, uv_layers)
islands = utilities_uv.getSelectionIslands()
uvs = utilities_uv.get_selected_uvs(bm, uv_layers)
4 years ago
faces = [f for island in islands for f in island]
4 years ago
# Get island faces
4 years ago
# utilities_uv.selection_restore(bm, uv_layers)
4 years ago
groups = get_edge_groups(bm, uv_layers, faces, edges, uvs)
4 years ago
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
4 years ago
print("Edges {}x".format(len(edges)))
print("Groups {}x".format(len(groups)))
4 years ago
# Restore 3D face selection
4 years ago
# Restore UV seams and clear pins
bpy.ops.uv.seams_from_islands()
bpy.ops.uv.pin(clear=True)
4 years ago
edge_sets = []
for edges in groups:
4 years ago
edge_sets.append(EdgeSet(bm, uv_layers, edges, faces))
4 years ago
# straighten_edges(bm, uv_layers, edges, faces)
4 years ago
sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True)
4 years ago
for edge_set in sorted_sets:
edge_set.straighten()
4 years ago
# Restore selection
4 years ago
utilities_uv.selection_restore()
class EdgeSet:
4 years ago
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
4 years ago
self.length += self.edge_length[e]
4 years ago
def straighten(self):
4 years ago
print("Straight {}x at {:.2f} length ".format(
len(self.edges), self.length))
4 years ago
# 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
4 years ago
angle = math.atan2(delta.y, delta.x) % (math.pi/2)
4 years ago
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
4 years ago
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))
4 years ago
# 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
4 years ago
angle = math.atan2(diff.y, diff.x) % (math.pi/2)
4 years ago
if angle >= (math.pi/4):
angle = angle - (math.pi/2)
bpy.ops.uv.cursor_set(location=uv1 + diff/2)
4 years ago
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(
False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
4 years ago
# 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])
4 years ago
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]
4 years ago
4 years ago
print("Step, proc {} exp: {}".format(
[e.index for e in processed], [e.index for e in edges_expand]))
4 years ago
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
4 years ago
print(" E {} verts {} verts end: {}".format(edge.index, [
v.index for v in edge.verts], [v.index for v in verts_ends]))
4 years ago
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
4 years ago
previous_edge = [e for e in processed if e.verts[0]
in edge.verts or e.verts[1] in edge.verts][0]
4 years ago
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]
4 years ago
direction = (
self.vert_to_uv[prev_v2][0].uv - self.vert_to_uv[prev_v1][0].uv).normalized()
4 years ago
for uv in self.vert_to_uv[v2]:
4 years ago
uv.uv = self.vert_to_uv[v1][0].uv + \
direction * self.edge_length[edge]
4 years ago
4 years ago
print("Procesed {}x Expand {}x".format(
len(processed), len(edges_expand)))
4 years ago
print("verts_ends: {}x".format(len(verts_ends)))
processed.extend(edges_expand)
# Select edges
4 years ago
uvs = list(
set([uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v]]))
4 years ago
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):
4 years ago
print("Get edge groups, edges {}x".format(len(edges))+"x")
4 years ago
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
4 years ago
unmatched = edges.copy()
4 years ago
groups = []
4 years ago
for edge in edges:
if edge in unmatched:
4 years ago
# Loop select edge
bpy.ops.mesh.select_all(action='DESELECT')
edge.select = True
bpy.ops.mesh.loop_multi_select(ring=False)
4 years ago
# Isolate group within edges
group = [e for e in bm.edges if e.select and e in edges]
groups.append(group)
4 years ago
# Remove from unmatched
for e in group:
if e in unmatched:
unmatched.remove(e)
4 years ago
print(" Edge {} : Group: {}x , unmatched: {}".format(
edge.index, len(group), len(unmatched)))
4 years ago
# return
# group = [edge]
# for e in bm.edges:
# if e.select and e in unmatched:
# unmatched.remove(e)
# group.append(edge)
4 years ago
return groups
bpy.utils.register_class(op)