Fix #124263: Generate unique names during Alembic and USD export
While object names in Blender are already unique, the names themselves may be "unsafe" for use in the various file formats. During processing we make the names "safe". However, we did not guarantee that these new safe names were themselves unique wrt each other. Consider object names "Test 1" and "Test-1" which both become "Test_1" after being made safe. These will collide during export; only 1 object would be exported and it's undefined which object's data would "win". To rectify this we add another name map to the hierarchy iterator which is then used to handle collisions as they happen. The map is per- hierarchy meaning that a name can appear more than once as long as its under a different hierarchy. E.g. - `/root/A/X` and another `/root/B/X` is OK - `/root/A/X` and another `/root/A/X` is NOT OK Pull Request: https://projects.blender.org/blender/blender/pulls/135418
This commit is contained in:
committed by
Jesse Yurkovich
parent
321dbb0115
commit
01ddb320dd
@@ -1258,15 +1258,11 @@ class USDExportTest(AbstractUSDTest):
|
||||
("/root/Camera/Camera", "Camera"),
|
||||
("/root/env_light", "DomeLight")
|
||||
)
|
||||
|
||||
def key(el):
|
||||
return el[0]
|
||||
|
||||
expected = tuple(sorted(expected, key=key))
|
||||
expected = tuple(sorted(expected, key=lambda pair: pair[0]))
|
||||
|
||||
stage = Usd.Stage.Open(str(test_path))
|
||||
actual = ((str(p.GetPath()), p.GetTypeName()) for p in stage.Traverse())
|
||||
actual = tuple(sorted(actual, key=key))
|
||||
actual = tuple(sorted(actual, key=lambda pair: pair[0]))
|
||||
|
||||
self.assertTupleEqual(expected, actual)
|
||||
|
||||
@@ -1311,14 +1307,11 @@ class USDExportTest(AbstractUSDTest):
|
||||
("/root/env_light", "DomeLight")
|
||||
)
|
||||
|
||||
def key(el):
|
||||
return el[0]
|
||||
|
||||
expected = tuple(sorted(expected, key=key))
|
||||
expected = tuple(sorted(expected, key=lambda pair: pair[0]))
|
||||
|
||||
stage = Usd.Stage.Open(str(test_path))
|
||||
actual = ((str(p.GetPath()), p.GetTypeName()) for p in stage.Traverse())
|
||||
actual = tuple(sorted(actual, key=key))
|
||||
actual = tuple(sorted(actual, key=lambda pair: pair[0]))
|
||||
|
||||
self.assertTupleEqual(expected, actual)
|
||||
|
||||
@@ -1490,6 +1483,95 @@ class USDExportTest(AbstractUSDTest):
|
||||
self.assertTrue(tex_path.exists(),
|
||||
f"Exported texture {tex_path} doesn't exist")
|
||||
|
||||
def test_naming_collision_hierarchy(self):
|
||||
"""Validate that naming collisions during export are handled correctly"""
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_hierarchy_collision.blend"))
|
||||
export_path = self.tempdir / "usd_hierarchy_collision.usda"
|
||||
self.export_and_validate(filepath=str(export_path))
|
||||
|
||||
expected = (
|
||||
('/root', 'Xform'),
|
||||
('/root/Empty', 'Xform'),
|
||||
('/root/Empty/Par_002', 'Xform'),
|
||||
('/root/Empty/Par_002/Par_1', 'Mesh'),
|
||||
('/root/Empty/Par_003', 'Xform'),
|
||||
('/root/Empty/Par_003/Par_1', 'Mesh'),
|
||||
('/root/Empty/Par_004', 'Xform'),
|
||||
('/root/Empty/Par_004/Par_002', 'Mesh'),
|
||||
('/root/Empty/Par_1', 'Xform'),
|
||||
('/root/Empty/Par_1/Par_1', 'Mesh'),
|
||||
('/root/Level1', 'Xform'),
|
||||
('/root/Level1/Level2', 'Xform'),
|
||||
('/root/Level1/Level2/Par2_002', 'Xform'),
|
||||
('/root/Level1/Level2/Par2_002/Par2_002', 'Mesh'),
|
||||
('/root/Level1/Level2/Par2_1', 'Xform'),
|
||||
('/root/Level1/Level2/Par2_1/Par2_1', 'Mesh'),
|
||||
('/root/Level1/Par2_002', 'Xform'),
|
||||
('/root/Level1/Par2_002/Par2_1', 'Mesh'),
|
||||
('/root/Level1/Par2_1', 'Xform'),
|
||||
('/root/Level1/Par2_1/Par2_1', 'Mesh'),
|
||||
('/root/Test_002', 'Xform'),
|
||||
('/root/Test_002/Test_1', 'Mesh'),
|
||||
('/root/Test_003', 'Xform'),
|
||||
('/root/Test_003/Test_1', 'Mesh'),
|
||||
('/root/Test_004', 'Xform'),
|
||||
('/root/Test_004/Test_002', 'Mesh'),
|
||||
('/root/Test_1', 'Xform'),
|
||||
('/root/Test_1/Test_1', 'Mesh'),
|
||||
('/root/env_light', 'DomeLight'),
|
||||
('/root/xSource_002', 'Xform'),
|
||||
('/root/xSource_002/Dup_002', 'Xform'),
|
||||
('/root/xSource_002/Dup_002/Dup_002', 'Mesh'),
|
||||
('/root/xSource_002/Dup_002_0', 'Xform'),
|
||||
('/root/xSource_002/Dup_002_0/Dup_002', 'Mesh'),
|
||||
('/root/xSource_002/Dup_002_1', 'Xform'),
|
||||
('/root/xSource_002/Dup_002_1/Dup_002', 'Mesh'),
|
||||
('/root/xSource_002/Dup_002_2', 'Xform'),
|
||||
('/root/xSource_002/Dup_002_2/Dup_002', 'Mesh'),
|
||||
('/root/xSource_002/Dup_002_3', 'Xform'),
|
||||
('/root/xSource_002/Dup_002_3/Dup_002', 'Mesh'),
|
||||
('/root/xSource_002/Dup_1', 'Xform'),
|
||||
('/root/xSource_002/Dup_1/Dup_1', 'Mesh'),
|
||||
('/root/xSource_002/Dup_1_0', 'Xform'),
|
||||
('/root/xSource_002/Dup_1_0/Dup_1', 'Mesh'),
|
||||
('/root/xSource_002/Dup_1_1', 'Xform'),
|
||||
('/root/xSource_002/Dup_1_1/Dup_1', 'Mesh'),
|
||||
('/root/xSource_002/Dup_1_2', 'Xform'),
|
||||
('/root/xSource_002/Dup_1_2/Dup_1', 'Mesh'),
|
||||
('/root/xSource_002/Dup_1_3', 'Xform'),
|
||||
('/root/xSource_002/Dup_1_3/Dup_1', 'Mesh'),
|
||||
('/root/xSource_002/xSource_1', 'Mesh'),
|
||||
('/root/xSource_1', 'Xform'),
|
||||
('/root/xSource_1/Dup_002', 'Xform'),
|
||||
('/root/xSource_1/Dup_002/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1', 'Xform'),
|
||||
('/root/xSource_1/Dup_1/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_0', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_0/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_001', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_001/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_002', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_002/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_003', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_003/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_004', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_004/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_1', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_1/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_2', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_2/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/Dup_1_3', 'Xform'),
|
||||
('/root/xSource_1/Dup_1_3/Dup_1', 'Mesh'),
|
||||
('/root/xSource_1/xSource_1', 'Mesh')
|
||||
)
|
||||
expected = tuple(sorted(expected, key=lambda pair: pair[0]))
|
||||
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
actual = ((str(p.GetPath()), p.GetTypeName()) for p in stage.Traverse())
|
||||
actual = tuple(sorted(actual, key=lambda pair: pair[0]))
|
||||
|
||||
self.assertTupleEqual(expected, actual)
|
||||
|
||||
|
||||
class USDHookBase:
|
||||
instructions = {}
|
||||
|
||||
Reference in New Issue
Block a user