2025-07-01
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import math
|
||||
import random
|
||||
import numpy as np
|
||||
|
||||
from mathutils import Vector
|
||||
from . import utilities_uv
|
||||
|
||||
|
||||
|
||||
class op(bpy.types.Operator):
|
||||
bl_idname = "uv.textools_randomize"
|
||||
bl_label = "Randomize Position"
|
||||
bl_description = "Randomize selected UV faces position and/or rotation"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
bool_face : bpy.props.BoolProperty(name="Per Face", default=False)
|
||||
intmode : bpy.props.BoolProperty(name="Int mode", default=False)
|
||||
strengh_U : bpy.props.FloatProperty(name="U Strengh", default=1, min=-10, max=10, soft_min=0, soft_max=1)
|
||||
strengh_V : bpy.props.FloatProperty(name="V Strengh", default=1, min=-10, max=10, soft_min=0, soft_max=1)
|
||||
rotation : bpy.props.FloatProperty(name="Rotation Strengh", default=0, min=-10, max=10, soft_min=0, soft_max=1)
|
||||
bool_precenter : bpy.props.BoolProperty(name="Pre Center Faces/Islands", default=False, description="Collect all faces/islands around the center of the UV space.")
|
||||
bool_bounds : bpy.props.BoolProperty(name="Within Image Bounds", default=False, description="Keep the UV faces/islands within the 0-1 UV domain.")
|
||||
rand_seed : bpy.props.IntProperty(name="Seed", default=0)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if bpy.context.area.ui_type != 'UV':
|
||||
return False
|
||||
if not bpy.context.active_object:
|
||||
return False
|
||||
if bpy.context.active_object.type != 'MESH':
|
||||
return False
|
||||
if bpy.context.active_object.mode != 'EDIT':
|
||||
return False
|
||||
if not bpy.context.object.data.uv_layers:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
if self.bool_bounds or self.bool_precenter:
|
||||
udim_tile, column, row = utilities_uv.get_UDIM_tile_coords(bpy.context.active_object)
|
||||
else:
|
||||
udim_tile = 1001
|
||||
column = row = 0
|
||||
utilities_uv.multi_object_loop(main, self, context, udim_tile=udim_tile, column=column, row=row, ob_num=0)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
|
||||
def main(self, context, udim_tile=1001, column=0, row=0, ob_num=0):
|
||||
selection_mode = bpy.context.scene.tool_settings.uv_select_mode
|
||||
me = bpy.context.active_object.data
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
uv_layers = bm.loops.layers.uv.verify()
|
||||
sync = bpy.context.scene.tool_settings.use_uv_select_sync
|
||||
|
||||
if sync:
|
||||
pregroup = {f for f in bm.faces if f.select}
|
||||
else:
|
||||
pregroup = {f for f in bm.faces if all([l[uv_layers].select for l in f.loops]) and f.select}
|
||||
if not pregroup:
|
||||
return
|
||||
|
||||
random.seed(self.rand_seed + ob_num)
|
||||
|
||||
if not self.bool_face:
|
||||
group = utilities_uv.get_selected_islands(bm, uv_layers)
|
||||
else:
|
||||
group = pregroup
|
||||
|
||||
|
||||
for f in group:
|
||||
rand_v = Vector(( 2*(random.random()-0.5), 2*(random.random()-0.5) ))
|
||||
rand_3 = 2*(random.random()-0.5)
|
||||
|
||||
|
||||
if self.bool_bounds or self.bool_precenter or self.rotation:
|
||||
if self.rotation:
|
||||
theta = self.rotation*rand_3*math.pi
|
||||
matrix = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
|
||||
else:
|
||||
matrix = np.array([[1, 0], [0, 1]])
|
||||
|
||||
if not self.bool_face:
|
||||
vec_origin = utilities_uv.get_center(f, bm, uv_layers)
|
||||
if self.bool_bounds or self.bool_precenter:
|
||||
for i in f:
|
||||
for loop in i.loops:
|
||||
uvs0 = loop[uv_layers].uv - vec_origin
|
||||
loop[uv_layers].uv = (0.5 + matrix[0][0]*uvs0.x + matrix[0][1]*uvs0.y, 0.5 + matrix[1][0]*uvs0.x + matrix[1][1]*uvs0.y)
|
||||
else:
|
||||
for i in f:
|
||||
for loop in i.loops:
|
||||
uvs0 = loop[uv_layers].uv - vec_origin
|
||||
loop[uv_layers].uv = (vec_origin.x + matrix[0][0]*uvs0.x + matrix[0][1]*uvs0.y, vec_origin.y + matrix[1][0]*uvs0.x + matrix[1][1]*uvs0.y)
|
||||
else:
|
||||
vec_origin = utilities_uv.get_center(f.loops, bm, uv_layers, are_loops=True)
|
||||
if self.bool_bounds or self.bool_precenter:
|
||||
for loop in f.loops:
|
||||
uvs0 = loop[uv_layers].uv - vec_origin
|
||||
loop[uv_layers].uv = (0.5 + matrix[0][0]*uvs0.x + matrix[0][1]*uvs0.y, 0.5 + matrix[1][0]*uvs0.x + matrix[1][1]*uvs0.y)
|
||||
else:
|
||||
for loop in f.loops:
|
||||
uvs0 = loop[uv_layers].uv - vec_origin
|
||||
loop[uv_layers].uv = (vec_origin.x + matrix[0][0]*uvs0.x + matrix[0][1]*uvs0.y, vec_origin.y + matrix[1][0]*uvs0.x + matrix[1][1]*uvs0.y)
|
||||
|
||||
bmesh.update_edit_mesh(me, loop_triangles=False)
|
||||
|
||||
|
||||
if self.bool_bounds:
|
||||
boundsMin = Vector((math.inf, math.inf))
|
||||
boundsMax = Vector((-math.inf, -math.inf))
|
||||
|
||||
if not self.bool_face:
|
||||
for i in f:
|
||||
for loop in i.loops:
|
||||
uv = loop[uv_layers].uv
|
||||
boundsMin = Vector(( max( min(boundsMin.x, uv.x), 0 ), max( min(boundsMin.y, uv.y), 0 ) ))
|
||||
boundsMax = Vector(( min( max(boundsMax.x, uv.x), 1 ), min( max(boundsMax.y, uv.y), 1 ) ))
|
||||
else:
|
||||
for loop in f.loops:
|
||||
uv = loop[uv_layers].uv
|
||||
boundsMin = Vector(( max( min(boundsMin.x, uv.x), 0 ), max( min(boundsMin.y, uv.y), 0 ) ))
|
||||
boundsMax = Vector(( min( max(boundsMax.x, uv.x), 1 ), min( max(boundsMax.y, uv.y), 1 ) ))
|
||||
|
||||
move = Vector(( min(boundsMin.x, abs(1-boundsMax.x)) * max( min(self.strengh_U, 1), -1 ), min(boundsMin.y, abs(1-boundsMax.y)) * max( min(self.strengh_V, 1), -1 ) ))
|
||||
else:
|
||||
move = Vector((self.strengh_U, self.strengh_V))
|
||||
|
||||
|
||||
randmove = rand_v * move
|
||||
if self.intmode:
|
||||
randmove = Vector(np.around(randmove))
|
||||
|
||||
if (not self.bool_bounds and not self.bool_precenter) or udim_tile == 1001:
|
||||
if move.x or move.y:
|
||||
if not self.bool_face:
|
||||
for i in f:
|
||||
for loop in i.loops:
|
||||
loop[uv_layers].uv += randmove
|
||||
else:
|
||||
for loop in f.loops:
|
||||
loop[uv_layers].uv += randmove
|
||||
else:
|
||||
if move.x or move.y:
|
||||
if not self.bool_face:
|
||||
for i in f:
|
||||
for loop in i.loops:
|
||||
loop[uv_layers].uv += randmove + Vector((column, row))
|
||||
else:
|
||||
for loop in f.loops:
|
||||
loop[uv_layers].uv += randmove + Vector((column, row))
|
||||
|
||||
bmesh.update_edit_mesh(me, loop_triangles=False)
|
||||
|
||||
# Workaround for selection not flushing properly from loops to EDGE Selection Mode, apparently since UV edge selection support was added to the UV space
|
||||
if not sync:
|
||||
bpy.ops.uv.select_mode(type='VERTEX')
|
||||
bpy.context.scene.tool_settings.uv_select_mode = selection_mode
|
||||
|
||||
|
||||
bpy.utils.register_class(op)
|
||||
Reference in New Issue
Block a user