Merge branch 'blender-v4.5-release'

This commit is contained in:
Sean Kim
2025-06-23 10:23:17 -07:00
5 changed files with 102 additions and 8 deletions

View File

@@ -140,7 +140,7 @@ static void apply_new_mask_mesh(const Depsgraph &depsgraph,
node_mask.foreach_index(GrainSize(1), [&](const int i, const int pos) {
const Span<int> verts = nodes[i].verts();
const Span<float> new_node_mask = new_mask.slice(node_verts[pos]);
if (array_utils::indexed_data_equal<float>(mask, verts, new_mask)) {
if (array_utils::indexed_data_equal<float>(mask, verts, new_node_mask)) {
return;
}
undo::push_node(depsgraph, object, &nodes[i], undo::Type::Mask);

View File

@@ -31,12 +31,12 @@
namespace blender::ed::sculpt_paint::smooth {
template<typename T> T calc_average(const Span<T> positions, const Span<int> indices)
template<typename T> T calc_average(const Span<T> values, const Span<int> indices)
{
const float factor = math::rcp(float(indices.size()));
const float factor = math::safe_rcp(float(indices.size()));
T result{};
for (const int i : indices) {
result += positions[i] * factor;
result += values[i] * factor;
}
return result;
}
@@ -78,7 +78,6 @@ void neighbor_data_average_mesh(const Span<T> src,
BLI_assert(vert_neighbors.size() == dst.size());
for (const int i : vert_neighbors.index_range()) {
BLI_assert(!vert_neighbors[i].is_empty());
dst[i] = calc_average(src, vert_neighbors[i]);
}
}
@@ -233,7 +232,7 @@ void average_data_grids(const SubdivCCG &subdiv_ccg,
CCG_grid_xy_to_index(key.grid_size, neighbor.x, neighbor.y);
sum += src[index];
}
dst[node_vert_index] = sum / neighbors.coords.size();
dst[node_vert_index] = math::safe_divide(sum, float(neighbors.coords.size()));
}
}
}
@@ -251,7 +250,7 @@ void average_data_bmesh(const Span<T> src, const Set<BMVert *, 0> &verts, const
for (const BMVert *neighbor : neighbors) {
sum += src[BM_elem_index_get(neighbor)];
}
dst[i] = sum / neighbors.size();
dst[i] = math::safe_divide(sum, float(neighbors.size()));
i++;
}
}
@@ -273,7 +272,7 @@ template void average_data_bmesh<float3>(Span<float3> src,
static float3 average_positions(const Span<const BMVert *> verts)
{
const float factor = math::rcp(float(verts.size()));
const float factor = math::safe_rcp(float(verts.size()));
float3 result(0);
for (const BMVert *vert : verts) {
result += float3(vert->co) * factor;

BIN
tests/files/sculpting/partially_masked_sphere.blend (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -1277,6 +1277,13 @@ if(TEST_SRC_DIR_EXISTS)
--
--testdir "${TEST_SRC_DIR}/sculpting"
)
add_blender_test(
bl_sculpt_mask_filter
--python ${CMAKE_CURRENT_LIST_DIR}/sculpt_paint/mask_filter_test.py
--
--testdir "${TEST_SRC_DIR}/sculpting"
)
endif()
if(WITH_GPU_MESH_PAINT_TESTS AND TEST_SRC_DIR_EXISTS)

View File

@@ -0,0 +1,85 @@
# SPDX-FileCopyrightText: 2025 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later */
__all__ = (
"main",
)
import math
import unittest
import sys
import pathlib
import numpy as np
import bpy
"""
blender -b --factory-startup --python tests/python/sculpt_paint/mask_filter_test.py -- --testdir tests/files/sculpting/
"""
args = None
class GrowMaskTest(unittest.TestCase):
def setUp(self):
bpy.ops.wm.open_mainfile(filepath=str(args.testdir / "partially_masked_sphere.blend"), load_ui=False)
bpy.ops.ed.undo_push()
def test_grow_increases_number_of_masked_vertices(self):
mesh = bpy.context.object.data
mask_attr = mesh.attributes['.sculpt_mask']
num_vertices = mesh.attributes.domain_size('POINT')
old_mask_data = np.zeros(num_vertices, dtype=np.float32)
mask_attr.data.foreach_get('value', old_mask_data)
bpy.ops.sculpt.mask_filter(filter_type='GROW')
new_mask_data = np.zeros(num_vertices, dtype=np.float32)
mask_attr.data.foreach_get('value', new_mask_data)
self.assertGreater(np.count_nonzero(new_mask_data), np.count_nonzero(old_mask_data))
class ShrinkMaskTest(unittest.TestCase):
def setUp(self):
bpy.ops.wm.open_mainfile(filepath=str(args.testdir / "partially_masked_sphere.blend"), load_ui=False)
bpy.ops.ed.undo_push()
def test_shrink_decreases_number_of_masked_vertices(self):
mesh = bpy.context.object.data
mask_attr = mesh.attributes['.sculpt_mask']
num_vertices = mesh.attributes.domain_size('POINT')
old_mask_data = np.zeros(num_vertices, dtype=np.float32)
mask_attr.data.foreach_get('value', old_mask_data)
bpy.ops.sculpt.mask_filter(filter_type='SHRINK')
new_mask_data = np.zeros(num_vertices, dtype=np.float32)
mask_attr.data.foreach_get('value', new_mask_data)
self.assertLess(np.count_nonzero(new_mask_data), np.count_nonzero(old_mask_data))
def main():
global args
import argparse
argv = [sys.argv[0]]
if '--' in sys.argv:
argv += sys.argv[sys.argv.index('--') + 1:]
parser = argparse.ArgumentParser()
parser.add_argument('--testdir', required=True, type=pathlib.Path)
args, remaining = parser.parse_known_args(argv)
unittest.main(argv=remaining)
if __name__ == "__main__":
main()