mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Mesh: Add validation of CD_MDISPS tangent vector data
This commit adds support for validating and correcting Multires tangent data stored in CD_MDISPS when called via `BKE_mesh_validate` when importing or on demand when users call `mesh.validate()` via the Python API. Any non-finite values are set to 0. While this will result in likely odd looking data on a multires mesh, it will prevent further propagation of bad float data and will allow users to restore their mesh to a workable state in the case of data corruption. Ref: #149205 Pull Request: https://projects.blender.org/blender/blender/pulls/149320
This commit is contained in:
parent
279a3e1821
commit
b72b204390
4 changed files with 84 additions and 2 deletions
|
|
@ -762,6 +762,49 @@ static bool validate_generic_attributes(const Mesh &mesh, const bool verbose, Me
|
|||
return all_attributes_valid;
|
||||
}
|
||||
|
||||
static bool validate_mdisps(const Mesh &mesh, const bool verbose, Mesh *mesh_mut)
|
||||
{
|
||||
const MDisps *mdisps = static_cast<const MDisps *>(
|
||||
CustomData_get_layer(&mesh.corner_data, CD_MDISPS));
|
||||
if (!mdisps) {
|
||||
return true;
|
||||
}
|
||||
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask invalid = IndexMask::from_predicate(
|
||||
IndexRange(mesh.corners_num), GrainSize(4096), memory, [&](const int i) {
|
||||
const Span<float> disps = Span(reinterpret_cast<const float3 *>(mdisps[i].disps),
|
||||
mdisps[i].totdisp)
|
||||
.cast<float>();
|
||||
return std::any_of(
|
||||
disps.begin(), disps.end(), [&](const float &v) { return !std::isfinite(v); });
|
||||
});
|
||||
|
||||
if (invalid.is_empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
print_error_with_indices(invalid, "Multires displacement has invalid values");
|
||||
}
|
||||
|
||||
if (mesh_mut) {
|
||||
if (MDisps *mdisp_mut = static_cast<MDisps *>(
|
||||
CustomData_get_layer_for_write(&mesh_mut->corner_data, CD_MDISPS, mesh.corners_num)))
|
||||
{
|
||||
invalid.foreach_index(GrainSize(512), [&](const int i) {
|
||||
MutableSpan<float> disps = MutableSpan(reinterpret_cast<float3 *>(mdisp_mut[i].disps),
|
||||
mdisp_mut[i].totdisp)
|
||||
.cast<float>();
|
||||
for (float &disp_component : disps) {
|
||||
disp_component = std::isfinite(disp_component) ? disp_component : 0.0f;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool mesh_validate_impl(const Mesh &mesh, const bool verbose, Mesh *mesh_mut)
|
||||
{
|
||||
IndexMaskMemory memory;
|
||||
|
|
@ -825,6 +868,7 @@ static bool mesh_validate_impl(const Mesh &mesh, const bool verbose, Mesh *mesh_
|
|||
valid &= validate_material_indices(mesh, true, verbose, mesh_mut);
|
||||
valid &= validate_selection_history(mesh, verbose, mesh_mut);
|
||||
valid &= validate_generic_attributes(mesh, verbose, mesh_mut);
|
||||
valid &= validate_mdisps(mesh, verbose, mesh_mut);
|
||||
|
||||
if (valid) {
|
||||
return true;
|
||||
|
|
|
|||
3
tests/files/sculpting/invalid_mdisp_cube.blend
Normal file
3
tests/files/sculpting/invalid_mdisp_cube.blend
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b399e423de04c458891984f225f22c5d9e6ae4fb74ed5f36a1379246747163eb
|
||||
size 72185
|
||||
|
|
@ -417,6 +417,8 @@ if(TEST_SRC_DIR_EXISTS)
|
|||
add_blender_test_allow_error(
|
||||
mesh_validate
|
||||
--python ${TEST_PYTHON_DIR}/mesh_validate.py
|
||||
--
|
||||
--testdir "${TEST_SRC_DIR}"
|
||||
)
|
||||
|
||||
add_blender_test(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@
|
|||
import unittest
|
||||
import bpy
|
||||
import sys
|
||||
import pathlib
|
||||
|
||||
"""
|
||||
blender -b --factory-startup --python tests/python/mesh_validate.py -- --testdir tests/files/
|
||||
"""
|
||||
|
||||
|
||||
class TestMeshValidate(unittest.TestCase):
|
||||
|
|
@ -108,6 +113,34 @@ class TestMeshValidate(unittest.TestCase):
|
|||
self.assertFalse(mesh.validate(verbose=True))
|
||||
|
||||
|
||||
args = None
|
||||
|
||||
|
||||
class TestMeshValidateExistingFile(unittest.TestCase):
|
||||
def setUp(self):
|
||||
bpy.ops.wm.open_mainfile(filepath=str(args.testdir / "sculpting/invalid_mdisp_cube.blend"), load_ui=False)
|
||||
|
||||
def test_mesh_with_bad_multires_displacements(self):
|
||||
mesh = bpy.context.active_object.data
|
||||
self.assertTrue(mesh.validate(verbose=True))
|
||||
self.assertFalse(mesh.validate(verbose=True))
|
||||
|
||||
|
||||
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__':
|
||||
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
|
||||
unittest.main()
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue