TexTools is a UV and Texture tool set for 3dsMax created several years ago. This open repository will port in time several of the UV tools to Blender in python. http://renderhjs.net/textools/blender
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

257 lines
8.5 KiB

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'}
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):
return {'FINISHED'}
def main(context):
# Store selection
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')
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
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:
# Restore selection
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]]
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:
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)))
# Select edges
uvs = list(
set([uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v]]))
for uv in uvs:
uv.select = True
# Pin UV's
bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
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
edge.select = True
# Isolate group within edges
group = [e for e in bm.edges if e.select and e in edges]
# Remove from unmatched
for e in group:
if e in unmatched:
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