This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/release/scripts/startup/bl_operators/vertexpaint_dirt.py
Hans Goudey 2eba15d3e8 Fix T98975: Broken vertex paint mode operators
All of the operators in vertex paint mode didn't work properly with
the new color attribute system. They only worked on byte color type
attributes on the face corner domain.

Since there are four possible combinations of domains and types now,
it mostly ended up being simpler to convert the code to C++ and use
the geometry component API for retrieving attributes, interpolating
between domains, etc. The code changes ended up being fairly large,
but the result should be simpler now.

Differential Revision: https://developer.blender.org/D15261
2022-06-23 11:33:11 -05:00

198 lines
5.7 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright Campbell Barton.
def ensure_active_color_attribute(me):
if me.attributes.active_color:
return me.attributes.active_color
return me.color_attributes.new("Color", 'BYTE_COLOR', 'FACE_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 T43345
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,
)