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)