Skip to content

Instantly share code, notes, and snippets.

@zvodd
Last active July 26, 2024 20:20
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 3.6]
#
# Add a tool to vertex paint mode that paints by FACE
#
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
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
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
# 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((self.brush_color.r, self.brush_color.g, self.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