Fix #140225: Always ensure mesh topology is up to date during USD import

It is possible for a mesh to change topology across frames but still be
detected as not needing a topology update.

Until we can make a finer-grained check against the before and after
topology, unconditionally ensure it's updated for now.

Adds a new test that checks a few frames of changing topology that is
similar, but not the same.

Pull Request: https://projects.blender.org/blender/blender/pulls/140253
This commit is contained in:
Jesse Yurkovich
2025-06-12 19:32:43 +02:00
committed by Jesse Yurkovich
parent a25e1c9267
commit 37f8616bd5
3 changed files with 96 additions and 0 deletions

View File

@@ -324,6 +324,12 @@ bool USDMeshReader::read_faces(Mesh *mesh) const
bke::mesh_calc_edges(*mesh, false, false);
/* It's possible that the number of faces, indices, and verts remain the same but the topology
* itself is different. Until finer-grained topology detection can be implemented, always tag the
* mesh as needing updated topology mappings. Without this, a time varying mesh could trigger
* undefined behavior. */
mesh->tag_topology_changed();
return all_faces_ok;
}

View File

@@ -0,0 +1,64 @@
#usda 1.0
(
defaultPrim = "root"
doc = "Blender v4.5.0 Beta"
endTimeCode = 4
metersPerUnit = 1
startTimeCode = 1
timeCodesPerSecond = 24
upAxis = "Z"
)
def Xform "root" (
customData = {
dictionary Blender = {
bool generated = 1
}
}
)
{
def Xform "TopoTest"
{
def Mesh "TopoTest" (
active = true
)
{
float3[] extent = [(-0.5, -0.5, 0), (3.5, 0.5, 0)]
int[] faceVertexCounts = [4, 4, 4]
int[] faceVertexCounts.timeSamples = {
1: [4, 4, 4],
2: [3, 4, 5],
3: [3, 3, 6],
4: [4, 4, 4],
}
int[] faceVertexIndices = [0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 11, 10]
int[] faceVertexIndices.timeSamples = {
1: [0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 11, 10],
2: [0, 1, 2, 3, 4, 11, 5, 7, 8, 10, 6, 9],
3: [0, 1, 2, 4, 5, 6, 8, 3, 9, 11, 7, 10],
4: [0, 1, 3, 2, 4, 5, 7, 6, 8, 9, 11, 10],
}
normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1)] (
interpolation = "faceVarying"
)
point3f[] points = [(-0.5, -0.5, 0), (0.5, -0.5, 0), (-0.5, 0.5, 0), (0.5, 0.5, 0), (1, -0.5, 0), (2, -0.5, 0), (1, 0.5, 0), (2, 0.5, 0), (2.5, -0.5, 0), (3.5, -0.5, 0), (2.5, 0.5, 0), (3.5, 0.5, 0)]
point3f[] points.timeSamples = {
1: [(-0.5, -0.5, 0), (0.5, -0.5, 0), (-0.5, 0.5, 0), (0.5, 0.5, 0), (1, -0.5, 0), (2, -0.5, 0), (1, 0.5, 0), (2, 0.5, 0), (2.5, -0.5, 0), (3.5, -0.5, 0), (2.5, 0.5, 0), (3.5, 0.5, 0)],
2: [(-0.5, -0.5, 0), (0.5, -0.5, 0), (-0.5, 0.5, 0), (1, -0.5, 0), (2, -0.5, 0), (1, 0.5, 0), (3, 0.5, 0), (2.5, -0.5, 0), (3.5, -0.5, 0), (2.5, 0.5, 0), (3.5, 0.5, 0), (2, 0.5, 0)],
3: [(-0.5, -0.5, 0), (0.5, -0.5, 0), (-0.5, 0.5, 0), (3, -0.5, 0), (1, -0.5, 0), (2, -0.5, 0), (1, 0.5, 0), (3, 0.5, 0), (2.5, -0.5, 0), (3.5, -0.5, 0), (2.5, 0.5, 0), (3.5, 0.5, 0)],
4: [(-0.5, -0.5, 0), (0.5, -0.5, 0), (-0.5, 0.5, 0), (0.5, 0.5, 0), (1, -0.5, 0), (2, -0.5, 0), (1, 0.5, 0), (2, 0.5, 0), (2.5, -0.5, 0), (3.5, -0.5, 0), (2.5, 0.5, 0), (3.5, 0.5, 0)],
}
texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1)] (
interpolation = "faceVarying"
)
texCoord2f[] primvars:st.timeSamples = {
1: [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1)],
2: [(0, 0), (1, 0), (0, 1), (0, 0), (1, 0), (0.5, 0.5), (0, 1), (0, 0), (1, 0), (1, 1), (0.5, 1), (0, 1)],
3: [(0, 0), (1, 0), (0, 1), (0, 0), (1, 0), (0, 1), (0, 0), (0.5, 0), (1, 0), (1, 1), (0.5, 1), (0, 1)],
4: [(0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1), (0, 0), (1, 0), (1, 1), (0, 1)],
}
uniform token subdivisionScheme = "none"
}
}
}

View File

@@ -160,6 +160,32 @@ class USDImportTest(AbstractUSDTest):
self.assertEqual(len(mesh.vertices), 5)
self.assertEqual(len(mesh.polygons[0].vertices), 5)
def test_import_mesh_topology_change(self):
"""Test importing meshes with changing topology over time."""
infile = str(self.testdir / "usd_mesh_topology_change.usda")
res = bpy.ops.wm.usd_import(filepath=infile)
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {infile}")
# Check topology for all frames against expected vertex and face counts
expected_face_verts = [
(4, 4, 4),
(3, 4, 5),
(3, 3, 6),
(4, 4, 4),
]
for frame in range(1, 5):
bpy.context.scene.frame_set(frame)
depsgraph = bpy.context.evaluated_depsgraph_get()
mesh = depsgraph.objects["TopoTest"].data
expected = expected_face_verts[frame - 1]
self.assertEqual(len(mesh.polygons), len(expected), f"Unexpected data for {frame=}")
for face in range(0, 3):
verts = mesh.polygons[face].vertices
self.assertEqual(len(verts), expected[face], f"Unexpected data for {frame=} {face=}")
def test_import_mesh_uv_maps(self):
"""Test importing meshes with udim UVs and multiple UV sets."""