Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Blackbourn <chrisbblend@gmail.com>2022-08-25 08:59:39 +0300
committerChris Blackbourn <chrisbblend@gmail.com>2022-08-25 09:09:32 +0300
commitde570dc87ed17cae2d2d1ed4347793c440266b4b (patch)
treefca4937a3b30e4547fd6bd59e28ab2b4446f98dd
parentf36d8d59c255e627ef981000fde197cf2be84b73 (diff)
Fix T78406: create uv randomize islands operator
Implement a new operator to randomize the scale, rotation and offset of selected UV islands.
-rw-r--r--release/scripts/modules/bpy_extras/bmesh_utils.py58
-rw-r--r--release/scripts/startup/bl_operators/__init__.py1
-rw-r--r--release/scripts/startup/bl_operators/uvcalc_randomize_transform.py210
-rw-r--r--release/scripts/startup/bl_ui/space_image.py4
4 files changed, 273 insertions, 0 deletions
diff --git a/release/scripts/modules/bpy_extras/bmesh_utils.py b/release/scripts/modules/bpy_extras/bmesh_utils.py
new file mode 100644
index 00000000000..baf1f9d863f
--- /dev/null
+++ b/release/scripts/modules/bpy_extras/bmesh_utils.py
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+__all__ = (
+ "bmesh_linked_uv_islands",
+)
+
+import bmesh
+
+def match_uv(face, vert, uv, uv_layer):
+ for loop in face.loops:
+ if loop.vert == vert:
+ return uv == loop[uv_layer].uv
+ return False
+
+
+def bmesh_linked_uv_islands(bm, uv_layer):
+ """
+ Returns lists of face indices connected by UV islands.
+
+ For `bpy.types.Mesh`, use `mesh_linked_uv_islands` instead.
+
+ :arg bm: the bmesh used to group with.
+ :type bmesh: :class: `BMesh`
+ :arg uv_layer: the UV layer to source UVs from.
+ :type bmesh: :class: `BMLayerItem`
+ :return: list of lists containing polygon indices
+ :rtype: list
+ """
+
+ result = []
+ bm.faces.ensure_lookup_table()
+
+ used = {}
+ for seed_face in bm.faces:
+ seed_index = seed_face.index
+ if used.get(seed_index):
+ continue # Face has already been processed.
+ used[seed_index] = True
+ island = [seed_index]
+ stack = [seed_face] # Faces still to consider on this island.
+ while stack:
+ current_face = stack.pop()
+ for loop in current_face.loops:
+ v = loop.vert
+ uv = loop[uv_layer].uv
+ for f in v.link_faces:
+ if used.get(f.index):
+ continue
+ if not match_uv(f, v, uv, uv_layer):
+ continue
+
+ # `f` is part of island, add to island and stack
+ used[f.index] = True
+ island.append(f.index)
+ stack.append(f)
+ result.append(island)
+
+ return result
diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py
index 14dc72336f6..6f61d7e7129 100644
--- a/release/scripts/startup/bl_operators/__init__.py
+++ b/release/scripts/startup/bl_operators/__init__.py
@@ -31,6 +31,7 @@ _modules = [
"userpref",
"uvcalc_follow_active",
"uvcalc_lightmap",
+ "uvcalc_randomize_transform",
"vertexpaint_dirt",
"view3d",
"wm",
diff --git a/release/scripts/startup/bl_operators/uvcalc_randomize_transform.py b/release/scripts/startup/bl_operators/uvcalc_randomize_transform.py
new file mode 100644
index 00000000000..22ae5ed9a6f
--- /dev/null
+++ b/release/scripts/startup/bl_operators/uvcalc_randomize_transform.py
@@ -0,0 +1,210 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from bpy.types import Operator
+from mathutils import Vector
+
+import bpy.ops
+import math
+
+
+def get_random_transform(transform_params, entropy):
+ from random import uniform
+ from random import seed as random_seed
+
+ (seed, loc, rot, scale, scale_even) = transform_params
+
+ # First, seed the RNG.
+ random_seed(seed + entropy)
+
+ # Next, call uniform a known number of times.
+ offset_u = uniform(0, 1)
+ offset_v = uniform(0, 1)
+ angle = uniform(0, 1)
+ scale_u = uniform(0, 1)
+ scale_v = uniform(0, 1)
+
+ # Apply the transform_params.
+ if loc:
+ offset_u *= loc[0]
+ offset_v *= loc[1]
+ else:
+ offset_u = 0
+ offset_v = 0
+
+ if rot:
+ angle *= rot
+ else:
+ angle = 0
+
+ if scale:
+ scale_u *= scale[0]
+ scale_v *= scale[1]
+ else:
+ scale_u = 1
+ scale_v = 1
+
+ if scale_even:
+ scale_v = scale_u
+
+ # Results in homogenous co-ordinates.
+ return [[scale_u * math.cos(angle), -scale_v * math.sin(angle), offset_u],
+ [scale_u * math.sin(angle), scale_v * math.cos(angle), offset_v]]
+
+
+def randomize_uv_transform_island(bm, uv_layer, faces, transform_params):
+ entropy = min(faces) # Ensure consistent random values for island, regardless of selection etc.
+ transform = get_random_transform(transform_params, entropy)
+
+ # Find bounding box.
+ minmax = [1e30, 1e30, -1e30, -1e30]
+ for face_index in faces:
+ face = bm.faces[face_index]
+ for loop in face.loops:
+ u, v = loop[uv_layer].uv
+ minmax[0] = min(minmax[0], u)
+ minmax[1] = min(minmax[1], v)
+ minmax[2] = max(minmax[2], u)
+ minmax[3] = max(minmax[3], v)
+
+ mid_u = (minmax[0] + minmax[2]) / 2
+ mid_v = (minmax[1] + minmax[3]) / 2
+
+ del_u = transform[0][2] + mid_u - transform[0][0] * mid_u - transform[0][1] * mid_v
+ del_v = transform[1][2] + mid_v - transform[1][0] * mid_u - transform[1][1] * mid_v
+
+ # Apply transform.
+ for face_index in faces:
+ face = bm.faces[face_index]
+ for loop in face.loops:
+ pre_uv = loop[uv_layer].uv
+ u = transform[0][0] * pre_uv[0] + transform[0][1] * pre_uv[1] + del_u
+ v = transform[1][0] * pre_uv[0] + transform[1][1] * pre_uv[1] + del_v
+ loop[uv_layer].uv = (u, v)
+
+
+def is_face_uv_selected(face, uv_layer):
+ for loop in face.loops:
+ if not loop[uv_layer].select:
+ return False
+ return True
+
+
+def is_island_uv_selected(bm, island, uv_layer):
+ for face_index in island:
+ if is_face_uv_selected(bm.faces[face_index], uv_layer):
+ return True
+ return False
+
+
+def randomize_uv_transform_bmesh(mesh, bm, transform_params):
+ import bpy_extras.bmesh_utils
+ uv_layer = bm.loops.layers.uv.verify()
+ islands = bpy_extras.bmesh_utils.bmesh_linked_uv_islands(bm, uv_layer)
+ for island in islands:
+ if is_island_uv_selected(bm, island, uv_layer):
+ randomize_uv_transform_island(bm, uv_layer, island, transform_params)
+
+
+def randomize_uv_transform(context, transform_params):
+ import bmesh
+ ob_list = context.objects_in_mode_unique_data
+ for ob in ob_list:
+ bm = bmesh.from_edit_mesh(ob.data)
+ bm.faces.ensure_lookup_table()
+ if bm.loops.layers.uv:
+ randomize_uv_transform_bmesh(ob.data, bm, transform_params)
+
+ for ob in ob_list:
+ bmesh.update_edit_mesh(ob.data)
+
+ return {'FINISHED'}
+
+
+from bpy.props import (
+ BoolProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ IntProperty,
+)
+
+
+class RandomizeUVTransform(Operator):
+ """Randomize uv island's location, rotation, and scale"""
+ bl_idname = "uv.randomize_uv_transform"
+ bl_label = "Randomize"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ random_seed: IntProperty(
+ name="Random Seed",
+ description="Seed value for the random generator",
+ min=0,
+ max=10000,
+ default=0,
+ )
+ use_loc: BoolProperty(
+ name="Randomize Location",
+ description="Randomize the location values",
+ default=True,
+ )
+ loc: FloatVectorProperty(
+ name="Location",
+ description=("Maximum distance the objects "
+ "can spread over each axis"),
+ min=-100.0,
+ max=100.0,
+ size=2,
+ subtype='TRANSLATION',
+ default=(0.0, 0.0),
+ )
+ use_rot: BoolProperty(
+ name="Randomize Rotation",
+ description="Randomize the rotation value",
+ default=True,
+ )
+ rot: FloatProperty(
+ name="Rotation",
+ description="Maximum rotation",
+ min=-2 * math.pi,
+ max=2 * math.pi,
+ subtype='ANGLE',
+ default=0.0,
+ )
+ use_scale: BoolProperty(
+ name="Randomize Scale",
+ description="Randomize the scale values",
+ default=True,
+ )
+ scale_even: BoolProperty(
+ name="Scale Even",
+ description="Use the same scale value for both axes",
+ default=False,
+ )
+
+ scale: FloatVectorProperty(
+ name="Scale",
+ description="Maximum scale randomization over each axis",
+ min=-100.0,
+ max=100.0,
+ default=(1.0, 1.0),
+ size=2,
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def execute(self, context):
+ seed = self.random_seed
+
+ loc = [0, 0] if not self.use_loc else self.loc
+ rot = 0 if not self.use_rot else self.rot
+ scale = None if not self.use_scale else self.scale
+ scale_even = self.scale_even
+
+ transformParams = [seed, loc, rot, scale, scale_even]
+ return randomize_uv_transform(context, transformParams)
+
+
+classes = (
+ RandomizeUVTransform,
+)
diff --git a/release/scripts/startup/bl_ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py
index 0f51c3830eb..4165f6ab0cf 100644
--- a/release/scripts/startup/bl_ui/space_image.py
+++ b/release/scripts/startup/bl_ui/space_image.py
@@ -292,6 +292,10 @@ class IMAGE_MT_uvs_transform(Menu):
layout.operator("transform.shear")
+ layout.separator()
+
+ layout.operator("uv.randomize_uv_transform")
+
class IMAGE_MT_uvs_snap(Menu):
bl_label = "Snap"