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.
5 Answers
Following the several comments, I've thought an addon can be useful to do that and may have a general interest.
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()
2.80 Version.
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.
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()
-
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$– p2orMay 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$ May 3, 2021 at 14:57
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!
-
$\begingroup$ nasty..this was the easiest and worked like a charm..THANK YOU $\endgroup$ Mar 25, 2021 at 15:48
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.
-
$\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$ Mar 27, 2017 at 18:50
-
$\begingroup$ @JtheNinja Sounds good! Please write an answer. $\endgroup$ Mar 28, 2017 at 0:06
Here is the pure Blender GUI way:
- Add a new shape key (basis) and then another, call it 'merged'. Set merged to value 1.
- Change pivot point to individual origins. Select all verts.
- S, 0 Enter. All loose parts are merged into a single point in space.
- Unwrap the mesh, sphere projection. each of these loose parts has all vertices in a tiny tiny area in uv space.
- disable the shape key merged, or set to 0, or delete.
- 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.