Skip to content

Instantly share code, notes, and snippets.

@aobond2
Created January 28, 2026 15:52
Show Gist options
  • Select an option

  • Save aobond2/144859b00503f4862314d2424d575503 to your computer and use it in GitHub Desktop.

Select an option

Save aobond2/144859b00503f4862314d2424d575503 to your computer and use it in GitHub Desktop.
Blender plugin to make vertex colour gradient on selected objects, from top to bottom. Work on multiple objects.
import bpy
import mathutils
class VCH_OT_VertexColourByHeight(bpy.types.Operator):
"""Vertex colour based on world space Z position"""
bl_idname = "paint.colour_by_height"
bl_label = "Vertex colour by world height"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return context.selected_objects and any(obj.type == 'MESH' for obj in context.selected_objects)
def execute(self, context):
# Get the meshes
selectedMeshes = [obj for obj in context.selected_objects if obj.type == 'MESH']
if not selectedMeshes:
self.report({'ERROR'}, "Select mesh to work on")
return {'CANCELLED'}
# Get bounds of selected objects
# Initialize min max
globalMinZ = float('inf')
globalMaxZ = float('-inf')
hasVertices = False
# Cache vertex Z coords per object, like this {objName: zCoord_0, zCoord_1, ...}
objZCache = {}
for obj in selectedMeshes:
mesh = obj.data
if len(mesh.vertices) == 0:
continue
hasVertices = True
worldMatrix = obj.matrix_world
# Calculate all z coords of vertices in the object
# Do obj.matrix_world multiplication to convert from local space to world space
zValues = [(worldMatrix @ v.co).z for v in mesh.vertices]
# Update Z min max
currentMin = min(zValues)
currentMax = max(zValues)
if currentMin < globalMinZ:
globalMinZ = currentMin
if currentMax > globalMaxZ:
globalMaxZ = currentMax
# filling the set
objZCache[obj.name] = zValues
if not hasVertices:
self.report({'WARNING'}, "Selected meshes have no vertices")
return {'CANCELLED'}
zRange = globalMaxZ - globalMinZ
# Setting this value to not be 0 (if it's 0), so later we don't divide by zero.
# If object is thin and flat this value can be 0
if zRange == 0:
zRange = 1.0
# Step 2 colouring the vertices
# Iterate on each object
for obj in selectedMeshes:
if obj.name not in objZCache:
continue
mesh = obj.data
zValues = objZCache[obj.name]
# Make or get vertex colour
if not mesh.vertex_colors:
VCLayer = mesh.vertex_colors.new(name="ZHeightColours")
else:
VCLayer = mesh.vertex_colors.active
data = VCLayer.data
# Iterate on loops of the mesh where Blender store VC data(corner of triangle, that can be shared by vertex)
for i, loop in enumerate(mesh.loops):
vIndex = loop.vertex_index
z = zValues[vIndex]
# Get difference/distance of current Z with overall Min Z, then normalize it to get ratio.
# Then clamp again just in case.
ratio = (z - globalMinZ) / zRange
ratio = max(0.0, min(1.0, ratio))
# Colouring Red(low) Blue(high)
r = 1.0 - ratio
g = 0.0
b = ratio
a = 1.0
data[i].color = (r, g, b, a)
return {'FINISHED'}
# GUI
class VCH_PT_VertexColourByHeight(bpy.types.Panel):
bl_label = "Vertex Colour Tools"
bl_idname = "VIEW3D_PT_colour_by_height"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Vertex Colour'
def draw(self, context):
layout = self.layout
row = layout.row()
row.scale_y = 1.5
row.operator("paint.colour_by_height", icon='BRUSH_DATA')
# Class registration
classes = (
VCH_OT_VertexColourByHeight,
VCH_PT_VertexColourByHeight
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment