mirror of
https://github.com/drewcassidy/TexTools-Blender
synced 2024-09-01 14:54:44 +00:00
Merge remote-tracking branch 'SavMartin/master'
This commit is contained in:
commit
07e6fe4556
@ -1,6 +1,6 @@
|
|||||||
# TexTools for Blender #
|
# TexTools for Blender #
|
||||||
|
|
||||||
TexTools is a Free addon for Blender 3D with a set of professional UV and Texture toolsBack in 2009 I released the [Original TexTools](http://renderhjs.net/textools/) for 3dsMax. Current features include: Easy Texture Baking, UV Align and Selection tools and Texel Density tools.
|
TexTools is a Free addon for Blender 3D with a set of professional UV and Texture tools. Back in 2009 I released the [Original TexTools](http://renderhjs.net/textools/) for 3dsMax. Current features include: Easy Texture Baking, UV Align and Selection tools and Texel Density tools.
|
||||||
|
|
||||||
## Download & Documentation ##
|
## Download & Documentation ##
|
||||||
Visit the [Official Website & Documentation](http://renderhjs.net/textools/blender/) for in depth overview of all the tools. Alternatively visit this [release log](http://renderhjs.net/textools/blender/log.html)
|
Visit the [Official Website & Documentation](http://renderhjs.net/textools/blender/) for in depth overview of all the tools. Alternatively visit this [release log](http://renderhjs.net/textools/blender/log.html)
|
||||||
|
12
__init__.py
12
__init__.py
@ -240,7 +240,7 @@ class UV_OT_op_debug(bpy.types.Operator):
|
|||||||
|
|
||||||
|
|
||||||
class UV_OT_op_disable_uv_sync(bpy.types.Operator):
|
class UV_OT_op_disable_uv_sync(bpy.types.Operator):
|
||||||
bl_idname = "uv.op_disable_sync"
|
bl_idname = "uv.op_disable_uv_sync"
|
||||||
bl_label = "Disable Sync"
|
bl_label = "Disable Sync"
|
||||||
bl_description = "Disable UV sync mode"
|
bl_description = "Disable UV sync mode"
|
||||||
|
|
||||||
@ -1458,11 +1458,6 @@ def register():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
|
||||||
#GUI Utilities
|
|
||||||
# utilities_ui.unregister()
|
|
||||||
|
|
||||||
from bpy.utils import unregister_class
|
from bpy.utils import unregister_class
|
||||||
for cls in reversed(classes):
|
for cls in reversed(classes):
|
||||||
unregister_class(cls)
|
unregister_class(cls)
|
||||||
@ -1470,7 +1465,6 @@ def unregister():
|
|||||||
#Unregister Icons
|
#Unregister Icons
|
||||||
utilities_ui.icon_unregister()
|
utilities_ui.icon_unregister()
|
||||||
|
|
||||||
|
|
||||||
#Unregister Settings
|
#Unregister Settings
|
||||||
del bpy.types.Scene.texToolsSettings
|
del bpy.types.Scene.texToolsSettings
|
||||||
|
|
||||||
@ -1479,6 +1473,9 @@ def unregister():
|
|||||||
km.keymap_items.remove(kmi)
|
km.keymap_items.remove(kmi)
|
||||||
keymaps.clear()
|
keymaps.clear()
|
||||||
|
|
||||||
|
#GUI Utilities
|
||||||
|
utilities_ui.unregister()
|
||||||
|
|
||||||
bpy.types.IMAGE_MT_uvs.remove(menu_IMAGE_uvs)
|
bpy.types.IMAGE_MT_uvs.remove(menu_IMAGE_uvs)
|
||||||
bpy.types.IMAGE_MT_select.remove(menu_IMAGE_select)
|
bpy.types.IMAGE_MT_select.remove(menu_IMAGE_select)
|
||||||
bpy.types.IMAGE_MT_image.remove(menu_IMAGE_MT_image)
|
bpy.types.IMAGE_MT_image.remove(menu_IMAGE_MT_image)
|
||||||
@ -1487,7 +1484,6 @@ def unregister():
|
|||||||
bpy.types.VIEW3D_MT_uv_map.remove(menu_VIEW3D_MT_uv_map)
|
bpy.types.VIEW3D_MT_uv_map.remove(menu_VIEW3D_MT_uv_map)
|
||||||
bpy.types.VIEW3D_MT_object_context_menu.remove(menu_VIEW3D_MT_object_context_menu)
|
bpy.types.VIEW3D_MT_object_context_menu.remove(menu_VIEW3D_MT_object_context_menu)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
register()
|
register()
|
||||||
|
@ -97,7 +97,7 @@ def sort_objects(self):
|
|||||||
for obj_A in pairs_low_high:
|
for obj_A in pairs_low_high:
|
||||||
obj_B = pairs_low_high[obj_A]
|
obj_B = pairs_low_high[obj_A]
|
||||||
try:
|
try:
|
||||||
obj_B.name = utilities_bake.get_bake_name(obj_A)+" high"
|
obj_B.name = utilities_bake.get_set_name(obj_A)+" high"
|
||||||
|
|
||||||
obj_A.select_set( state = True, view_layer = None)
|
obj_A.select_set( state = True, view_layer = None)
|
||||||
obj_B.select_set( state = True, view_layer = None)
|
obj_B.select_set( state = True, view_layer = None)
|
||||||
|
@ -126,18 +126,20 @@ def align_island(uv_vert0, uv_vert1, faces):
|
|||||||
loop[uv_layers].select = True
|
loop[uv_layers].select = True
|
||||||
|
|
||||||
diff = uv_vert1 - uv_vert0
|
diff = uv_vert1 - uv_vert0
|
||||||
angle = math.atan2(diff.y, diff.x) % (math.pi/2)
|
current_angle = math.atan2(diff.x, diff.y)
|
||||||
|
angle_to_rotate = round(current_angle / (math.pi/2)) * (math.pi/2) - current_angle
|
||||||
|
|
||||||
|
# For some reason bpy.ops.transform.rotate rotates in the opposite
|
||||||
|
# direction in Blender 2.83 than in other versions.
|
||||||
|
if float(bpy.app.version_string[0:4]) == 2.83:
|
||||||
|
angle_to_rotate = -angle_to_rotate
|
||||||
|
|
||||||
bpy.ops.uv.select_linked()
|
bpy.ops.uv.select_linked()
|
||||||
|
|
||||||
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
|
bpy.context.tool_settings.transform_pivot_point = 'CURSOR'
|
||||||
bpy.ops.uv.cursor_set(location=uv_vert0 + diff/2)
|
bpy.ops.uv.cursor_set(location=uv_vert0 + diff/2)
|
||||||
|
|
||||||
if angle >= (math.pi/4):
|
bpy.ops.transform.rotate(value=angle_to_rotate, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
|
||||||
angle = angle - (math.pi/2)
|
|
||||||
|
|
||||||
bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(
|
|
||||||
False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
|
|
||||||
|
|
||||||
|
|
||||||
bpy.utils.register_class(op)
|
bpy.utils.register_class(op)
|
||||||
|
@ -6,17 +6,23 @@ import operator
|
|||||||
|
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from itertools import chain # 'flattens' collection of iterables
|
from itertools import chain # 'flattens' collection of iterables
|
||||||
|
|
||||||
from . import utilities_uv
|
from . import utilities_uv
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class op(bpy.types.Operator):
|
class op(bpy.types.Operator):
|
||||||
bl_idname = "uv.textools_island_align_world"
|
bl_idname = "uv.textools_island_align_world"
|
||||||
bl_label = "Align World"
|
bl_label = "Align World"
|
||||||
bl_description = "Align selected UV islands to world / gravity directions"
|
bl_description = "Align selected UV islands to world / gravity directions"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
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(
|
# is_global = bpy.props.BoolProperty(
|
||||||
# name = "Global Axis",
|
# name = "Global Axis",
|
||||||
# description = "Global or local object axis alignment",
|
# description = "Global or local object axis alignment",
|
||||||
@ -32,245 +38,243 @@ class op(bpy.types.Operator):
|
|||||||
if not bpy.context.active_object:
|
if not bpy.context.active_object:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Only in Edit mode
|
#Only in Edit mode
|
||||||
if bpy.context.active_object.mode != 'EDIT':
|
if bpy.context.active_object.mode != 'EDIT':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Requires UV map
|
#Requires UV map
|
||||||
if not bpy.context.object.data.uv_layers:
|
if not bpy.context.object.data.uv_layers:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if bpy.context.scene.tool_settings.use_uv_select_sync:
|
if bpy.context.scene.tool_settings.use_uv_select_sync:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Only in UV editor mode
|
#Only in UV editor mode
|
||||||
if bpy.context.area.type != 'IMAGE_EDITOR':
|
if bpy.context.area.type != 'IMAGE_EDITOR':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
main(self)
|
main(self, context)
|
||||||
return {'FINISHED'}
|
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________________________\nis_global")
|
print("\n________________________\nis_global")
|
||||||
|
|
||||||
# Store selection
|
#Store selection
|
||||||
utilities_uv.selection_store()
|
utilities_uv.selection_store()
|
||||||
|
|
||||||
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
|
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
|
||||||
uv_layers = bm.loops.layers.uv.verify()
|
uv_layers = bm.loops.layers.uv.verify()
|
||||||
|
|
||||||
# Only in Face or Island mode
|
#Only in Face or Island mode
|
||||||
if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
|
if bpy.context.scene.tool_settings.uv_select_mode is not 'FACE' or 'ISLAND':
|
||||||
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
|
bpy.context.scene.tool_settings.uv_select_mode = 'FACE'
|
||||||
|
|
||||||
obj = bpy.context.object
|
obj = bpy.context.object
|
||||||
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
|
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
|
||||||
uv_layers = bm.loops.layers.uv.verify()
|
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:
|
for faces in islands:
|
||||||
# Get average viewport normal of UV island
|
avg_normal = Vector((0,0,0))
|
||||||
avg_normal = Vector((0, 0, 0))
|
if self.bool_face:
|
||||||
for face in faces:
|
avg_normal = faces[0].normal
|
||||||
avg_normal += face.normal
|
else:
|
||||||
avg_normal /= len(faces)
|
# Get average viewport normal of UV island
|
||||||
|
for face in faces:
|
||||||
# avg_normal = (obj.matrix_world*avg_normal).normalized()
|
avg_normal+=face.normal
|
||||||
|
avg_normal/=len(faces)
|
||||||
|
|
||||||
# Which Side
|
# Which Side
|
||||||
x = 0
|
x = 0
|
||||||
y = 1
|
y = 1
|
||||||
z = 2
|
z = 2
|
||||||
max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z))
|
max_size = max(abs(avg_normal.x), abs(avg_normal.y), abs(avg_normal.z))
|
||||||
|
|
||||||
# Use multiple steps
|
for i in range(self.steps): # Use multiple steps
|
||||||
for i in range(3):
|
|
||||||
if(abs(avg_normal.x) == max_size):
|
if(abs(avg_normal.x) == max_size):
|
||||||
print("x normal")
|
print("x normal")
|
||||||
align_island(obj, bm, uv_layers, faces, y,
|
if self.bool_simple:
|
||||||
z, avg_normal.x < 0, False)
|
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):
|
elif(abs(avg_normal.y) == max_size):
|
||||||
print("y normal")
|
print("y normal")
|
||||||
align_island(obj, bm, uv_layers, faces, x,
|
if self.bool_simple:
|
||||||
z, avg_normal.y > 0, False)
|
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):
|
elif(abs(avg_normal.z) == max_size):
|
||||||
print("z normal")
|
print("z normal")
|
||||||
align_island(obj, bm, uv_layers, faces, x,
|
if self.bool_simple:
|
||||||
y, False, avg_normal.z < 0)
|
align_island_simple(obj, bm, uv_layers, faces, x, y, False, avg_normal.z < 0)
|
||||||
|
else:
|
||||||
|
align_island(obj, bm, uv_layers, faces, x, y, False, avg_normal.z < 0)
|
||||||
|
|
||||||
print("align island: faces {}x n:{}, max:{}".format(
|
print("align island: faces {}x n:{}, max:{}".format(len(faces), avg_normal, max_size))
|
||||||
len(faces), avg_normal, max_size))
|
|
||||||
|
#Restore selection
|
||||||
# Restore selection
|
|
||||||
utilities_uv.selection_restore()
|
utilities_uv.selection_restore()
|
||||||
|
|
||||||
|
|
||||||
def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False):
|
def align_island(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False):
|
||||||
|
|
||||||
# Find lowest and highest verts
|
# Find lowest and highest verts
|
||||||
minmax_val = [0, 0]
|
minmax_val = [0,0]
|
||||||
minmax_vert = [None, None]
|
minmax_vert = [None, None]
|
||||||
|
|
||||||
axis_names = ['x', 'y', 'z']
|
axis_names = ['x', 'y', 'z']
|
||||||
print("Align shell {}x at {},{} flip {},{}".format(
|
print("Align shell {}x at {},{} flip {},{}".format(len(faces), axis_names[x], axis_names[y], flip_x, flip_y))
|
||||||
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] ))
|
|
||||||
|
|
||||||
|
# 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
|
# Collect UV to Vert
|
||||||
vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)
|
vert_to_uv = {}
|
||||||
uv_to_vert = utilities_uv.get_uv_to_vert(bm, uv_layers)
|
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 = []
|
processed_edges = []
|
||||||
edges = []
|
n_edges = 0
|
||||||
|
avg_angle = 0
|
||||||
for face in faces:
|
for face in faces:
|
||||||
for edge in face.edges:
|
for edge in face.edges:
|
||||||
if edge not in processed_edges:
|
if edge not in processed_edges:
|
||||||
processed_edges.append(edge)
|
processed_edges.append(edge)
|
||||||
delta = edge.verts[0].co - edge.verts[1].co
|
delta = edge.verts[0].co -edge.verts[1].co
|
||||||
max_side = max(abs(delta.x), abs(delta.y), abs(delta.z))
|
max_side = max(abs(delta.x), abs(delta.y), abs(delta.z))
|
||||||
|
|
||||||
# Check edges dominant in active axis
|
# Check edges dominant in active axis
|
||||||
if(abs(delta[x]) == max_side or abs(delta[y]) == max_side):
|
if( abs(delta[x]) == max_side or abs(delta[y]) == max_side):
|
||||||
# if( abs(delta[y]) == max_side):
|
n_edges += 1
|
||||||
edges.append(edge)
|
uv0 = vert_to_uv[ edge.verts[0] ][0]
|
||||||
|
uv1 = vert_to_uv[ edge.verts[1] ][0]
|
||||||
|
|
||||||
print("Edges {}x".format(len(edges)))
|
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
|
||||||
|
))
|
||||||
|
|
||||||
avg_angle = 0
|
a0 = math.atan2(delta_verts.y, delta_verts.x) #- math.pi/2
|
||||||
for edge in edges:
|
a1 = math.atan2(delta_uvs.y, delta_uvs.x) #- math.pi/2
|
||||||
uv0 = vert_to_uv[edge.verts[0]][0]
|
|
||||||
uv1 = vert_to_uv[edge.verts[1]][0]
|
a_delta = math.atan2(math.sin(a0-a1), math.cos(a0-a1))
|
||||||
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:
|
# 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)
|
||||||
delta_verts.x = -edge.verts[1].co[x] + edge.verts[0].co[x]
|
if n_edges > 1:
|
||||||
if flip_y:
|
if abs((avg_angle / (n_edges-1)) - a_delta) > 2.8:
|
||||||
delta_verts.y = -edge.verts[1].co[y] + edge.verts[0].co[y]
|
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
|
||||||
|
|
||||||
# delta_verts.y = edge.verts[0].co[y] - edge.verts[1].co[y]
|
avg_angle /= n_edges
|
||||||
|
|
||||||
delta_uvs = Vector((
|
# For some reason, bpy.ops.transform.rotate rotates in the opposite direction in Blender 2.83 compared to other versions.
|
||||||
uv1.uv.x - uv0.uv.x,
|
if float(bpy.app.version_string[0:4]) == 2.83:
|
||||||
uv1.uv.y - uv0.uv.y
|
avg_angle = -avg_angle
|
||||||
))
|
|
||||||
a0 = math.atan2(delta_verts.y, delta_verts.x) - math.pi/2
|
print("Edges {}x".format(n_edges))
|
||||||
a1 = math.atan2(delta_uvs.y, delta_uvs.x) - math.pi/2
|
|
||||||
|
|
||||||
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))
|
print("Turn {:.1f}".format(avg_angle * 180/math.pi))
|
||||||
|
|
||||||
bpy.ops.uv.select_all(action='DESELECT')
|
bpy.ops.uv.select_all(action='DESELECT')
|
||||||
for face in faces:
|
for face in faces:
|
||||||
for loop in face.loops:
|
for loop in face.loops:
|
||||||
loop[uv_layers].select = True
|
loop[uv_layers].select = True
|
||||||
|
|
||||||
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
|
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
|
||||||
bpy.ops.transform.rotate(value=avg_angle, orient_axis='Z')
|
bpy.ops.transform.rotate(value=-avg_angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
|
||||||
# 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:
|
def align_island_simple(obj, bm, uv_layers, faces, x=0, y=1, flip_x=False, flip_y=False):
|
||||||
if vert not in processed:
|
|
||||||
processed.append(vert)
|
|
||||||
|
|
||||||
vert_y = (vert.co)[y] #obj.matrix_world *
|
# Find lowest and highest verts
|
||||||
|
minmax_val = [0,0]
|
||||||
|
minmax_vert = [None, None]
|
||||||
|
|
||||||
print("idx {} = {}".format(vert.index, vert_y))
|
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
|
||||||
|
|
||||||
if not minmax_vert[0] or not minmax_vert[1]:
|
edge = faces[0].edges[0]
|
||||||
minmax_vert[0] = vert
|
delta = edge.verts[0].co -edge.verts[1].co
|
||||||
minmax_vert[1] = vert
|
max_side = max(abs(delta.x), abs(delta.y), abs(delta.z))
|
||||||
minmax_val[0] = vert_y
|
a_delta = 0
|
||||||
minmax_val[1] = vert_y
|
|
||||||
continue
|
|
||||||
|
|
||||||
if vert_y < minmax_val[0]:
|
# Check edges dominant in active axis
|
||||||
# Not yet defined or smaller
|
if abs(delta[x]) == max_side or abs(delta[y]) == max_side :
|
||||||
minmax_vert[0] = vert
|
uv0 = vert_to_uv[ edge.verts[0] ][0]
|
||||||
minmax_val[0] = vert_y
|
uv1 = vert_to_uv[ edge.verts[1] ][0]
|
||||||
|
|
||||||
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((
|
delta_verts = Vector((
|
||||||
vert_B.co[x] - vert_A.co[x],
|
edge.verts[1].co[x] - edge.verts[0].co[x],
|
||||||
vert_B.co[y] - vert_A.co[y]
|
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((
|
delta_uvs = Vector((
|
||||||
uv_B.uv.x - uv_A.uv.x,
|
uv1.uv.x - uv0.uv.x,
|
||||||
uv_B.uv.y - uv_A.uv.y,
|
uv1.uv.y - uv0.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))
|
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))
|
||||||
|
|
||||||
print(" Angles {:.2f} | {:.2f}".format(angle_vert*180/math.pi, angle_uv*180/math.pi))
|
# For some reason, bpy.ops.transform.rotate rotates in the opposite direction in Blender 2.83 compared to other versions.
|
||||||
print(" Angle Diff {:.2f}".format(angle_delta*180/math.pi))
|
if float(bpy.app.version_string[0:4]) == 2.83:
|
||||||
|
a_delta = -a_delta
|
||||||
|
|
||||||
|
print("Turn {:.1f}".format(a_delta * 180/math.pi))
|
||||||
|
|
||||||
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
|
bpy.ops.uv.select_all(action='DESELECT')
|
||||||
bpy.ops.transform.rotate(value=angle_delta, axis='Z')
|
for face in faces:
|
||||||
# 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)
|
for loop in face.loops:
|
||||||
|
loop[uv_layers].select = True
|
||||||
|
|
||||||
# bpy.ops.mesh.select_all(action='DESELECT')
|
bpy.context.tool_settings.transform_pivot_point = 'MEDIAN_POINT'
|
||||||
# vert_A.select = True
|
bpy.ops.transform.rotate(value=-a_delta, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)
|
||||||
# vert_B.select = True
|
|
||||||
|
|
||||||
# return
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
bpy.utils.register_class(op)
|
bpy.utils.register_class(op)
|
||||||
|
@ -4,6 +4,7 @@ import operator
|
|||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from math import pi
|
from math import pi
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from math import radians, hypot
|
from math import radians, hypot
|
||||||
|
|
||||||
@ -37,6 +38,11 @@ class op(bpy.types.Operator):
|
|||||||
rectify(self, context)
|
rectify(self, context)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def time_clock():
|
||||||
|
if sys.version_info >= (3, 3):
|
||||||
|
return time.process_time()
|
||||||
|
else:
|
||||||
|
return time.clock()
|
||||||
|
|
||||||
precision = 3
|
precision = 3
|
||||||
|
|
||||||
@ -58,7 +64,7 @@ def rectify(self, context):
|
|||||||
|
|
||||||
def main(square=False, snapToClosest=False):
|
def main(square=False, snapToClosest=False):
|
||||||
|
|
||||||
startTime = time.clock()
|
startTime = time_clock()
|
||||||
obj = bpy.context.active_object
|
obj = bpy.context.active_object
|
||||||
me = obj.data
|
me = obj.data
|
||||||
bm = bmesh.from_edit_mesh(me)
|
bm = bmesh.from_edit_mesh(me)
|
||||||
@ -303,7 +309,7 @@ def SuccessFinished(me, startTime):
|
|||||||
# use for backtrack of steps
|
# use for backtrack of steps
|
||||||
# bpy.ops.ed.undo_push()
|
# bpy.ops.ed.undo_push()
|
||||||
bmesh.update_edit_mesh(me)
|
bmesh.update_edit_mesh(me)
|
||||||
#elapsed = round(time.clock()-startTime, 2)
|
#elapsed = round(time_clock()-startTime, 2)
|
||||||
#if (elapsed >= 0.05): operator.report({'INFO'}, "UvSquares finished, elapsed:", elapsed, "s.")
|
#if (elapsed >= 0.05): operator.report({'INFO'}, "UvSquares finished, elapsed:", elapsed, "s.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from . import utilities_ui
|
|||||||
|
|
||||||
class op(bpy.types.Operator):
|
class op(bpy.types.Operator):
|
||||||
bl_idname = "uv.textools_select_islands_outline"
|
bl_idname = "uv.textools_select_islands_outline"
|
||||||
bl_label = "Select Overlap"
|
bl_label = "Select Island outline"
|
||||||
bl_description = "Select island edge bounds"
|
bl_description = "Select island edge bounds"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
@ -24,7 +24,11 @@ class op(bpy.types.Operator):
|
|||||||
|
|
||||||
#Requires UV map
|
#Requires UV map
|
||||||
if not bpy.context.object.data.uv_layers:
|
if not bpy.context.object.data.uv_layers:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# #requires UV_sync
|
||||||
|
# if not bpy.context.scene.tool_settings.use_uv_select_sync:
|
||||||
|
# return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -40,12 +44,23 @@ def select_outline(context):
|
|||||||
if bpy.context.active_object.mode != 'EDIT':
|
if bpy.context.active_object.mode != 'EDIT':
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
|
||||||
bpy.context.scene.tool_settings.use_uv_select_sync = False
|
|
||||||
|
|
||||||
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
|
bm = bmesh.from_edit_mesh(bpy.context.active_object.data);
|
||||||
uv_layers = bm.loops.layers.uv.verify();
|
uv_layers = bm.loops.layers.uv.verify();
|
||||||
|
|
||||||
|
pre_sync = bpy.context.scene.tool_settings.use_uv_select_sync
|
||||||
|
if bpy.context.scene.tool_settings.use_uv_select_sync:
|
||||||
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
|
||||||
|
bpy.ops.uv.select_linked()
|
||||||
|
bpy.context.scene.tool_settings.use_uv_select_sync = False
|
||||||
|
bpy.ops.uv.select_all(action='SELECT')
|
||||||
|
else:
|
||||||
|
current_edit = tuple(bpy.context.tool_settings.mesh_select_mode)
|
||||||
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
|
||||||
|
current_select = [f for f in bm.faces if f.select]
|
||||||
|
|
||||||
|
islands = utilities_uv.getSelectionIslands()
|
||||||
|
faces_islands = [face for island in islands for face in island]
|
||||||
|
edges_islands = [edge for island in islands for face in island for edge in face.edges]
|
||||||
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
|
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')
|
||||||
bpy.ops.mesh.select_all(action='DESELECT')
|
bpy.ops.mesh.select_all(action='DESELECT')
|
||||||
|
|
||||||
@ -60,19 +75,40 @@ def select_outline(context):
|
|||||||
|
|
||||||
# Create seams from islands
|
# Create seams from islands
|
||||||
bpy.ops.uv.seams_from_islands(contextViewUV)
|
bpy.ops.uv.seams_from_islands(contextViewUV)
|
||||||
edges_islands = [edge for edge in bm.edges if edge.seam]
|
edges_seams_from_islands = [edge for edge in bm.edges if edge.seam]
|
||||||
|
|
||||||
# Clear seams
|
# Clear seams
|
||||||
for edge in edges_islands:
|
for edge in edges_seams_from_islands:
|
||||||
edge.seam = False
|
edge.seam = False
|
||||||
|
|
||||||
# Select island edges
|
if pre_sync:
|
||||||
bpy.ops.mesh.select_all(action='DESELECT')
|
# Select seams from islands edges and edge boundaries
|
||||||
for edge in edges_islands:
|
for edge in edges_islands:
|
||||||
edge.select = True
|
if edge.is_boundary or edge in edges_seams_from_islands:
|
||||||
|
edge.select = True
|
||||||
|
else:
|
||||||
|
for face in current_select:
|
||||||
|
face.select = True
|
||||||
|
bpy.context.tool_settings.mesh_select_mode = current_edit
|
||||||
|
bpy.ops.uv.select_all(action='DESELECT')
|
||||||
|
edges = []
|
||||||
|
for edge in edges_islands:
|
||||||
|
if edge.is_boundary or edge in edges_seams_from_islands:
|
||||||
|
edges.extend([e for e in edge.verts[0].link_loops])
|
||||||
|
edges.extend([e for e in edge.verts[1].link_loops])
|
||||||
|
#edges.append(edge)
|
||||||
|
|
||||||
|
bpy.context.scene.tool_settings.uv_select_mode = 'EDGE'
|
||||||
|
for face in faces_islands:
|
||||||
|
for loop in face.loops:
|
||||||
|
if loop in edges:
|
||||||
|
loop[uv_layers].select = True
|
||||||
|
|
||||||
|
|
||||||
# Restore seam selection
|
# Restore seam selection
|
||||||
for edge in edges_seam:
|
for edge in edges_seam:
|
||||||
edge.seam = True
|
edge.seam = True
|
||||||
|
|
||||||
|
bpy.context.scene.tool_settings.use_uv_select_sync = pre_sync
|
||||||
|
|
||||||
bpy.utils.register_class(op)
|
bpy.utils.register_class(op)
|
@ -110,8 +110,8 @@ class op_popup(bpy.types.Operator):
|
|||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
wm = context.window_manager
|
wm = context.window_manager
|
||||||
return wm.invoke_popup(self, width=200, height=200)
|
return wm.invoke_popup(self, width=200)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
self.layout.label(text=self.message)
|
self.layout.label(text=self.message)
|
||||||
|
|
||||||
@ -155,15 +155,17 @@ def unregister():
|
|||||||
from bpy.types import WindowManager
|
from bpy.types import WindowManager
|
||||||
for preview_collection in preview_collections.values():
|
for preview_collection in preview_collections.values():
|
||||||
bpy.utils.previews.remove(preview_collection)
|
bpy.utils.previews.remove(preview_collection)
|
||||||
preview_collections.clear()
|
preview_collection.clear()
|
||||||
|
|
||||||
|
|
||||||
# Unregister icons
|
# Unregister icons
|
||||||
# global preview_icons
|
# global preview_icons
|
||||||
bpy.utils.previews.remove(preview_icons)
|
# bpy.utils.previews.remove(preview_icons)
|
||||||
|
preview_icons.clear()
|
||||||
|
|
||||||
|
|
||||||
del bpy.types.Scene.TT_bake_mode
|
del bpy.types.Scene.TT_bake_mode
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
register()
|
register()
|
||||||
bpy.utils.register_class(op_popup)
|
bpy.utils.register_class(op_popup)
|
||||||
|
Loading…
Reference in New Issue
Block a user