Skip to content

Instantly share code, notes, and snippets.

@zvodd
Last active February 20, 2025 23:25
Show Gist options
  • Save zvodd/2dbef2d1b470db026a90451e4cb5534c to your computer and use it in GitHub Desktop.
Save zvodd/2dbef2d1b470db026a90451e4cb5534c to your computer and use it in GitHub Desktop.
Blender Vertex Face Brush [tested in 4.2]
import bpy
import bmesh
from mathutils import Vector
from bpy.types import WorkSpaceTool
from bl_ui.space_toolsystem_common import ToolDef
from bpy_extras import view3d_utils
from bl_ui.properties_paint_common import UnifiedPaintPanel
"""
Vertex Face Paint Tool
Add a brush to vertex paint mode that paints by FACE
i.e. Paints color into the vertices of a "face loop".
"""
""" NOTES :: Data for current vertex attribute mode
context.object.data.vertex_colors.active
bpy.types.MESH_UL_color_attributes
bpy.types.MESH_UL_color_attributes_selector
"""
""" NOTES :: UnifiedPaintPanel
from bl_ui.properties_paint_common import UnifiedPaintPanel
# draw_settings() use prop - row() or split() must be called and refed as parent.
UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
# Get a ref to the settings props for the tool
brush = context.tool_settings.vertex_paint.brush
#capabilities = brush.vertex_paint_capabilities
# Current Color
context.tool_settings.vertex_paint.brush.color
# Not sure if this is meaningful
?? UnifiedPaintPanel.paint_settings(brush)
?? UnifiedPaintPanel.paint_settings(C.tool_settings.vertex_paint.brush)
"""
class CustomVertexPaintTool(WorkSpaceTool):
bl_space_type = 'VIEW_3D'
bl_context_mode = 'PAINT_VERTEX'
bl_idname = "vertex_face_paint.tool"
bl_label = "Vertex Face Paint Tool"
bl_description = "A vertex painting tool that works on mesh faces"
#bl_icon = "brush.paint_vertex.replace"
#bl_icon = "brush.gpencil_draw.draw"
bl_icon = "brush.sculpt.paint"
bl_widget = None
bl_keymap = (
("vertex_face_paint.operator", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
)
def draw_settings(context, layout, tool):
props = tool.operator_properties("vertex_face_paint.operator")
# TODO Use the builtin vertex paint tool props
brush = context.tool_settings.vertex_paint.brush
row = layout.row()
#split = layout.split(factor=0.1)
UnifiedPaintPanel.prop_unified_color(row, context, brush, "color", text="")
#layout.prop(props, "brush_color")
layout.prop(props, "brush_alpha")
layout.prop(props, "apply_alpha")
class CustomVertexPaintOperator(bpy.types.Operator):
bl_idname = "vertex_face_paint.operator"
bl_label = "Vertex Face Paint Operator"
bl_options = {'REGISTER', 'UNDO'}
brush_color: bpy.props.FloatVectorProperty(
name="Brush Color",
subtype='COLOR',
default=(1.0, 0.0, 0.0),
min=0.0,
max=1.0
)
brush_alpha: bpy.props.FloatProperty(
name="Alpha",
default=(1.0),
min=0.0,
max=1.0
)
apply_alpha: bpy.props.BoolProperty(
name="Apply Alpha",
default=False,
)
_did_paint = False
def modal(self, context, event):
if event.type == 'MOUSEMOVE':
self._did_paint = True
self.paint(context, event)
elif event.type == 'LEFTMOUSE':
if event.value == 'RELEASE':
if self._did_paint:
self._did_paint = False
else:
# ensure paint on single click
self.paint(context, event)
return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
self._did_paint = False
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
# TODO: is this inline with default behaviour?
if context.object.data.vertex_colors.active is None:
self.report({'WARNING'}, "No vertex color attribute layer")
return {'CANCELLED'}
if context.object and context.object.type == 'MESH':
context.window.cursor_set('PAINT_BRUSH')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "No active mesh object")
return {'CANCELLED'}
def paint(self, context, event):
obj = context.object
vertex_color_layer = obj.data.vertex_colors.active
brush_color = context.tool_settings.vertex_paint.brush.color
# Get the ray from the viewport and mouse
region = context.region
rv3d = context.region_data
coord = event.mouse_region_x, event.mouse_region_y
view_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)
# Cast the ray
hit, location, normal, face_index, object, matrix = context.scene.ray_cast(context.view_layer.depsgraph, ray_origin, view_vector)
if hit:
face = obj.data.polygons[face_index]
for loop_index in face.loop_indices:
alpha = self.brush_alpha
if not self.apply_alpha:
alpha = Vector(vertex_color_layer.data[loop_index].color).w
new_color = Vector((brush_color.r, brush_color.g, brush_color.b, alpha))
vertex_color_layer.data[loop_index].color = new_color
# Update mesh
obj.data.update()
def register():
bpy.utils.register_class(CustomVertexPaintOperator)
bpy.utils.register_tool(CustomVertexPaintTool, separator=True, group=True)
def unregister():
bpy.utils.unregister_class(CustomVertexPaintOperator)
bpy.utils.unregister_tool(CustomVertexPaintTool)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment