13
$\begingroup$

I am trying to get color variation and to bake it in a single mesh but the Object Info Random only works if i separate the meshes. Is there an alternative way to get the same result with the meshes combined, they aren't attached between them.

enter image description here

$\endgroup$
1
  • $\begingroup$ I don't know of any way to currently do this in Cycles, though I would also like to know if anyone has figured out any way to achieve this effect. $\endgroup$ Mar 27, 2017 at 16:38

5 Answers 5

17
$\begingroup$

Following the several comments, I've thought an addon can be useful to do that and may have a general interest.

enter image description here

What does it do:

  • Identify loose parts of the object (or all selected objects)
  • Generate a random color for each loose part
  • Follow the mesh loops to assign the vertex colors

(can be installed as an addon)

The code is commented below (please comment if needed):

bl_info = {
    "name": "Assign random vertex color to each loose part of a mesh",
    "author": "lemon",
    "version": (1, 0),
    "blender": (2, 78, 0),
    "location": "Properties Editor > Object data",
    "description": "",
    "warning": "",
    "wiki_url": "",
    "category": "Mesh"}

import bpy
import time
import random
from mathutils import Color

def LoosePartsMakeVertPaths( verts, edges ):
    #Initialize the path with all vertices indexes
    result = {v.index: set() for v in verts}
    #Add the possible paths via edges
    for e in edges:
        result[e.vertices[0]].add(e.vertices[1])
        result[e.vertices[1]].add(e.vertices[0])
    return result

def LoosePartsFollowEdges( startingIndex, paths ):
    current = [startingIndex]
    result = set( current )

    follow = True
    while follow:
        #Get indexes that are still in the paths
        eligible = set( [ind for ind in current if ind in paths] )
        if len( eligible ) == 0:
            follow = False #Stops is no more
        else:
            #Get the corresponding links
            next = [paths.pop( i ) for i in eligible]
            #Get the new links as new inputs
            current = set( [ind for sub in next for ind in sub] )
            result = result.union( current )

    return result

def LoosePartsCountIslands( obj ):
    #Prepare the paths/links from each vertex to others
    paths = LoosePartsMakeVertPaths( obj.data.vertices, obj.data.edges )
    found = True
    n = 0
    while found:
        try:
            #Get one input as long there is one
            startingIndex = next( iter( paths.keys() ) )
            n = n + 1
            #Deplete the paths dictionary following this starting index
            LoosePartsFollowEdges( startingIndex, paths )               
        except:
            found = False
    return n

def LoosePartGetIslands( obj ):
    paths = LoosePartsMakeVertPaths( obj.data.vertices, obj.data.edges )

    result = []

    found = True
    while found:
        try:
            #Get one input as long there is one
            startingIndex = next( iter( paths.keys() ) )
            #Deplete the paths dictionary following this starting index and append the result
            result.append( LoosePartsFollowEdges( startingIndex, paths ) )
        except:
            found = False

    return result

def RandomColor( rnd ):
    return Color( (rnd.random(), rnd.random(), rnd.random()) )

def RandomColors( rnd, count ):
    return [RandomColor( rnd ) for i in range( count )]

def ColorFromVertexIndex( vertexIndex, looseParts, colors ):
    for c, lp in zip( colors, looseParts ):
        if vertexIndex in lp:
            return c
    return Color()

def RandomVertexColorsByLooseParts( obj, vcolName, useSeed, seed ):

    rnd = random.Random()
    if useSeed:
        rnd.seed( seed )

    mesh = obj.data

    #Create vertex colors if it does not already exist    
    if vcolName not in mesh.vertex_colors:
        mesh.vertex_colors.new( vcolName )
    #Update it so that it is available below
    mesh.vertex_colors.update()
    #Get the vertex colors
    vertexColors = mesh.vertex_colors[vcolName]

    #Get loose parts
    looseParts = LoosePartGetIslands( obj )
    #Generate random colors
    colors = RandomColors( rnd, len(looseParts) )

    mesh.loops.update()

    #Follow the loops    
    for i, vertexIndex in enumerate( [loop.vertex_index for loop in mesh.loops] ):
        #Assign vertex colors from loop index and vertex index
        vertexColors.data[i].color = ColorFromVertexIndex( vertexIndex, looseParts, colors )

def InitProperties():
    bpy.types.Scene.VertexColorsName = bpy.props.StringProperty( name = "Vertex colors", description = "Vertex colors to use or create", default = "Col" )
    bpy.types.Scene.VertexColorsUseSeed = bpy.props.BoolProperty( name = "Use seed", description = "Use fixed seed to generate random values" ) 
    bpy.types.Scene.VertexColorsSeed = bpy.props.IntProperty( name = "Seed", description = "Seed for random values" ) 
    bpy.types.Scene.VertexColorsAllSelected = bpy.props.BoolProperty( name = "All selected objects", description = "Apply to all selected object or only active one" ) 

class RndVColToLoosePartsPanel(bpy.types.Panel):
    bl_label = "Random vcol to loose parts"
    bl_space_type = "PROPERTIES";
    bl_region_type = "WINDOW";
    bl_context = ""

    @classmethod
    def poll(self, context):
        return context.object and context.scene

    def draw(self,context):
        layout = self.layout
        scn = context.scene

        layout.prop( scn, 'VertexColorsName', icon='GROUP_VCOL' )
        layout.prop( scn, 'VertexColorsUseSeed' )
        layout.prop( scn, 'VertexColorsSeed' )
        layout.operator( "rndvcoltolooseparts.ope")
        layout.prop( scn, 'VertexColorsAllSelected' )

class RndVColToLooseParts(bpy.types.Operator):
    bl_idname="rndvcoltolooseparts.ope"
    bl_label="Generate random vcol to each loose parts"

    def execute(self, context):

        scene = context.scene
        obj = context.object

        if scene and obj and obj.type == 'MESH':

            prevMode = obj.mode
            bpy.ops.object.mode_set( mode='OBJECT', toggle=False )

            if scene.VertexColorsAllSelected:
                for obj in [obj for obj in context.selected_objects if obj.type == 'MESH']:
                    RandomVertexColorsByLooseParts( obj, scene.VertexColorsName, scene.VertexColorsUseSeed, scene.VertexColorsSeed )
            else:
                RandomVertexColorsByLooseParts( obj, scene.VertexColorsName, scene.VertexColorsUseSeed, scene.VertexColorsSeed )

            bpy.ops.object.mode_set( mode=prevMode, toggle=False )

        return {'FINISHED'}     


def register():
    InitProperties()
    bpy.utils.register_class(RndVColToLooseParts)
    bpy.utils.register_class(RndVColToLoosePartsPanel)


def unregister():
    bpy.utils.unregister_class(RndVColToLooseParts)
    bpy.utils.unregister_class(RndVColToLoosePartsPanel)


if __name__ == "__main__":
    register()

$\endgroup$
10
$\begingroup$

2.80 Version.

enter image description here

  • Creates a random grey-scale color and assigns to vertex color for each mesh loose part. Each component of the rgba vector is the same. Can be plugged into factor sockets individually.

  • Sets the face material color of each part to the active material index. enter image description here Example material used in image above, for the greyscale plug the color attribute node directly into shader.

Addon script.

bl_info = {
    "name": "Mesh Islands Random Vertex Color",
    "author": "batFINGER",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "F3 Search",
    "description": "Assign random greyscale vertex color to mesh islands",
    "warning": "",
    "wiki_url": "",
    "category": "Mesh"}

import bpy
import bmesh
from random import random
from bpy.props import StringProperty, BoolProperty
from mathutils import Vector


def walk_island(vert):
    ''' walk all un-tagged linked verts '''
    vert.tag = True
    yield (vert)
    linked_verts = [
        e.other_vert(vert) for e in vert.link_edges
        if not e.other_vert(vert).tag
    ]
    for v in linked_verts:
        if v.tag:
            continue
        yield from walk_island(v)


def get_islands(bm, verts=[]):
    def tag(verts, switch):
        for v in verts:
            v.tag = switch

    tag(bm.verts, True)
    tag(verts, False)
    ret = {"islands": []}
    verts = set(verts)
    while verts:
        v = verts.pop()
        verts.add(v)
        island = set(walk_island(v))
        faces = set(
            f for x in island for f in x.link_faces
            if all(v.tag for v in f.verts))
        edges = set(
            e for x in island for e in x.link_edges
            if all(v.tag for v in e.verts))
        ret["islands"].append(island.union(edges).union(faces))
        tag(island, False)  # remove tag = True
        verts -= island
    return ret


class BMIslands(list):
    def __init__(self, bm):
        self.extend(BMeshIsland(i, island) for i, island in enumerate(get_islands(bm, verts=bm.verts)["islands"]))
        self.sort(key=lambda i: (i.co.x, i.co.y))
        for i, island in enumerate(self):
            island.index = i


class BMeshIsland:
    def __init__(self, index, geom):
        self.index = index
        self.verts = [e for e in geom if isinstance(e, bmesh.types.BMVert)]
        self.co = sum([v.co for v in self.verts], Vector()) / len(self.verts)
        self.faces = [e for e in geom if isinstance(e, bmesh.types.BMFace)]


class MESH_OT_random_island_vertex_color(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "mesh.random_island_vert_color"
    bl_label = "Random Island Vert Color"
    bl_options = {'REGISTER', 'UNDO'}
    islands = None
    bm = None

    refresh: BoolProperty(name="Refresh")
    color_map: StringProperty(
        name="Color Map", default="vertcol")

    @classmethod
    def poll(cls, context):
        return(context.object
               and context.object.mode == 'OBJECT'
               and context.object.type == 'MESH')

    def main(self, ob):
        me = ob.data

        bm = bmesh.new()
        bm.from_mesh(me)
        self.bm = bm

        self.islands = BMIslands(bm)

    def randomize(self, ob):
        me = ob.data
        clayers = self.bm.loops.layers.color
        color_layer = clayers.get(self.color_map) or clayers.new(self.color_map)

        ami = ob.active_material_index
        for island in self.islands:
            r = random()
            for f in island.faces:
                f.material_index = ami
                for l in f.loops:
                    #l[color_layer] = (r, r, r, 1)
                    l[color_layer] = (r, ) * 4

        self.bm.to_mesh(me)
        me.update()

    def invoke(self, context, event):
        ob = context.object
        self.main(ob)
        return self.execute(context)

    def execute(self, context):
        self.randomize(context.object)
        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.prop(self, "color_map")

        row.prop(self, "refresh", text="", icon='FILE_REFRESH')
        layout.operator(self.__class__.bl_idname)


def register():
    bpy.utils.register_class(MESH_OT_random_island_vertex_color)


def unregister():
    bpy.utils.unregister_class(MESH_OT_random_island_vertex_color)


if __name__ == "__main__":
    register()
$\endgroup$
2
  • 2
    $\begingroup$ Great! Many thanks for your bmesh magic. Had to find an automatic workflow to bake id maps based on random vertex colors per island in painter. Created a hsv version based on your code which allows to set the colors in vertex paint mode as well: gist.github.com/p2or/ab314836285579cdfdb5d7848af8ee5c Also fixed a bug when assigning a new name if there is more than one layer present. Anyway, this script is super quick and pretty useful. Can we have a repo for this? $\endgroup$
    – p2or
    May 2, 2021 at 19:34
  • $\begingroup$ @p2or Cheers. Update looks good. Thanks for the support. Not too many too quickly, lol (based on time matching comment above) $\endgroup$
    – batFINGER
    May 3, 2021 at 14:57
6
$\begingroup$

In Blender 2.8, there is an attribute "Random Per Island" in the geometry input. It only works with Cycles though (not Eevee nor the material preview). It does the job! screenshot of geometry input node

$\endgroup$
1
  • $\begingroup$ nasty..this was the easiest and worked like a charm..THANK YOU $\endgroup$
    – Derek Eden
    Mar 25, 2021 at 15:48
4
$\begingroup$

You may use a Color Ramp node to do it. Add a Separate XYZ node (Add-->Converter-->SeparateXYZ) and plug its Z output to the Fac input of the ColorRamp node. Set its interpolation type to Constant. Use '+' sign to add colors and set the sliders to fit the mesh's segments. enter image description here enter image description here

$\endgroup$
3
  • $\begingroup$ The problem is that it wont give a good result if i combine multiple assets with different shapes. $\endgroup$ Mar 27, 2017 at 17:16
  • $\begingroup$ You could use vertex colors to flag different regions, instead of using locations within the generated coords box. $\endgroup$
    – JtheNinja
    Mar 27, 2017 at 18:50
  • $\begingroup$ @JtheNinja Sounds good! Please write an answer. $\endgroup$
    – Paul Gonet
    Mar 28, 2017 at 0:06
1
$\begingroup$

Here is the pure Blender GUI way:

  1. Add a new shape key (basis) and then another, call it 'merged'. Set merged to value 1.
  2. Change pivot point to individual origins. Select all verts.
  3. S, 0 Enter. All loose parts are merged into a single point in space.
  4. Unwrap the mesh, sphere projection. each of these loose parts has all vertices in a tiny tiny area in uv space.
  5. disable the shape key merged, or set to 0, or delete.
  6. I can post a node setup, but you probably get it. By using a procedural texture in uv space, you get that loose part randomness you wanted.
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .