[Bf-blender-cvs] [de570dc87ed] master: Fix T78406: create uv randomize islands operator

Chris Blackbourn noreply at git.blender.org
Thu Aug 25 08:16:09 CEST 2022


Commit: de570dc87ed17cae2d2d1ed4347793c440266b4b
Author: Chris Blackbourn
Date:   Thu Aug 25 17:59:39 2022 +1200
Branches: master
https://developer.blender.org/rBde570dc87ed17cae2d2d1ed4347793c440266b4b

Fix T78406: create uv randomize islands operator

Implement a new operator to randomize the scale, rotation and offset
of selected UV islands.

===================================================================

A	release/scripts/modules/bpy_extras/bmesh_utils.py
M	release/scripts/startup/bl_operators/__init__.py
A	release/scripts/startup/bl_operators/uvcalc_randomize_transform.py
M	release/scripts/startup/bl_ui/space_image.py

===================================================================

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"



More information about the Bf-blender-cvs mailing list