blender/scripts/startup/bl_operators/vertexpaint_dirt.py

199 lines
5.7 KiB
Python

# SPDX-FileCopyrightText: 2009 Campbell Barton
#
# SPDX-License-Identifier: GPL-2.0-or-later
def ensure_active_color_attribute(me):
if me.attributes.active_color:
return me.attributes.active_color
return me.color_attributes.new("Color", 'BYTE_COLOR', 'CORNER')
def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean, dirt_only, normalize):
from mathutils import Vector
from math import acos
import array
# We simulate the accumulation of dirt in the creases of geometric surfaces
# by comparing the vertex normal to the average direction of all vertices
# connected to that vertex. We can also simulate surfaces being buffed or
# worn by testing protruding surfaces.
#
# So if the angle between the normal and geometric direction is:
# < 90 - dirt has accumulated in the crease
# > 90 - surface has been worn or buffed
# ~ 90 - surface is flat and is generally unworn and clean
#
# This method is limited by the complexity or lack there of in the geometry.
#
# Original code and method by Keith "Wahooney" Boshoff.
vert_tone = array.array("f", [0.0]) * len(me.vertices)
# create lookup table for each vertex's connected vertices (via edges)
con = [[] for i in range(len(me.vertices))]
# add connected verts
for e in me.edges:
con[e.vertices[0]].append(e.vertices[1])
con[e.vertices[1]].append(e.vertices[0])
for i, v in enumerate(me.vertices):
vec = Vector()
no = v.normal
co = v.co
# get the direction of the vectors between the vertex and it's connected vertices
for c in con[i]:
vec += (me.vertices[c].co - co).normalized()
# average the vector by dividing by the number of connected verts
tot_con = len(con[i])
if tot_con == 0:
ang = pi / 2.0 # assume 90°, i. e. flat
else:
vec /= tot_con
# angle is the acos() of the dot product between normal and connected verts.
# > 90 degrees: convex
# < 90 degrees: concave
ang = acos(no.dot(vec))
# enforce min/max
ang = max(clamp_dirt, ang)
if not dirt_only:
ang = min(clamp_clean, ang)
vert_tone[i] = ang
# blur tones
for i in range(blur_iterations):
# backup the original tones
orig_vert_tone = vert_tone[:]
# use connected verts look up for blurring
for j, c in enumerate(con):
for v in c:
vert_tone[j] += blur_strength * orig_vert_tone[v]
vert_tone[j] /= len(c) * blur_strength + 1
del orig_vert_tone
if normalize:
min_tone = min(vert_tone)
max_tone = max(vert_tone)
else:
min_tone = clamp_dirt
max_tone = clamp_clean
tone_range = max_tone - min_tone
if tone_range < 0.0001:
# weak, don't cancel, see #43345
tone_range = 0.0
else:
tone_range = 1.0 / tone_range
active_color_attribute = ensure_active_color_attribute(me)
if not active_color_attribute:
return {'CANCELLED'}
point_domain = active_color_attribute.domain == 'POINT'
attribute_data = active_color_attribute.data
use_paint_mask = me.use_paint_mask
for i, p in enumerate(me.polygons):
if not use_paint_mask or p.select:
for loop_index in p.loop_indices:
loop = me.loops[loop_index]
v = loop.vertex_index
col = attribute_data[v if point_domain else loop_index].color
tone = vert_tone[v]
tone = (tone - min_tone) * tone_range
if dirt_only:
tone = min(tone, 0.5) * 2.0
col[0] = tone * col[0]
col[1] = tone * col[1]
col[2] = tone * col[2]
me.update()
return {'FINISHED'}
from bpy.types import Operator
from bpy.props import FloatProperty, IntProperty, BoolProperty
from math import pi
class VertexPaintDirt(Operator):
"""Generate a dirt map gradient based on cavity"""
bl_idname = "paint.vertex_color_dirt"
bl_label = "Dirty Vertex Colors"
bl_options = {'REGISTER', 'UNDO'}
blur_strength: FloatProperty(
name="Blur Strength",
description="Blur strength per iteration",
min=0.01, max=1.0,
default=1.0,
)
blur_iterations: IntProperty(
name="Blur Iterations",
description="Number of times to blur the colors (higher blurs more)",
min=0, max=40,
default=1,
)
clean_angle: FloatProperty(
name="Highlight Angle",
description="Less than 90 limits the angle used in the tonal range",
min=0.0, max=pi,
default=pi,
unit='ROTATION',
)
dirt_angle: FloatProperty(
name="Dirt Angle",
description="Less than 90 limits the angle used in the tonal range",
min=0.0, max=pi,
default=0.0,
unit='ROTATION',
)
dirt_only: BoolProperty(
name="Dirt Only",
description="Don't calculate cleans for convex areas",
default=False,
)
normalize: BoolProperty(
name="Normalize",
description="Normalize the colors, increasing the contrast",
default=True,
)
@classmethod
def poll(cls, context):
obj = context.object
return (obj and obj.type == 'MESH')
def execute(self, context):
obj = context.object
mesh = obj.data
ret = applyVertexDirt(
mesh,
self.blur_iterations,
self.blur_strength,
self.dirt_angle,
self.clean_angle,
self.dirt_only,
self.normalize,
)
return ret
classes = (
VertexPaintDirt,
)