
The Alembic exporter has been restructured by leverages the `AbstractHierarchyIterator` introduced by the USD exporter. The produced Alembic files have not changed much (details below), as the Alembic writing code has simply been moved from the old exporter to the new. How the export hierarchy is handled changed a lot, though, and also the way in which transforms are computed. As a result, T71395 is fixed. Differences between the old and new exporter, in terms of the produced Alembic file: - Duplicated objects now have a unique numerical suffix. - Matrices are computed differently, namely by simply computing the evaluated transform of the object relative to the evaluated transform of its export-parent. This fixes {T71395}, but otherwise should produce the same result as before (but with simpler code). Compared to the old Alembic exporter, Subdivision modifiers are now disabled in a cleaner, more efficient way (they are disabled when exporting with the "Apply Subdivisions" option is unchecked). Previously the exporter would move to a new frame, disable the modifier, evaluate the object, and enable the modifier again. This is now done before exporting starts, and modifiers are only restored when exporting ends. Some issues with the old Alembic exporter that have NOT been fixed in this patch: - Exporting NURBS patches and curves (see T49114 for example). - Exporting flattened hierarchy in combination with dupli-objects. This seems to be broken in the old Alembic exporter as well, but nobody reported this yet. Differential Revision: https://developer.blender.org/D7664 Reviewed By: Sergey
480 lines
20 KiB
Python
480 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
import argparse
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
|
|
from modules.test_utils import (
|
|
with_tempdir,
|
|
AbstractBlenderRunnerTest,
|
|
)
|
|
|
|
|
|
class AbcPropError(Exception):
|
|
"""Raised when AbstractAlembicTest.abcprop() finds an error."""
|
|
|
|
|
|
class AbstractAlembicTest(AbstractBlenderRunnerTest):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
import re
|
|
|
|
cls.blender = args.blender
|
|
cls.testdir = pathlib.Path(args.testdir)
|
|
cls.alembic_root = pathlib.Path(args.alembic_root)
|
|
|
|
# 'abcls' outputs ANSI colour codes, even when stdout is not a terminal.
|
|
# See https://github.com/alembic/alembic/issues/120
|
|
cls.ansi_remove_re = re.compile(rb'\x1b[^m]*m')
|
|
|
|
# 'abcls' array notation, like "name[16]"
|
|
cls.abcls_array = re.compile(r'^(?P<name>[^\[]+)(\[(?P<arraysize>\d+)\])?$')
|
|
|
|
def abcprop(self, filepath: pathlib.Path, proppath: str) -> dict:
|
|
"""Uses abcls to obtain compound property values from an Alembic object.
|
|
|
|
A dict of subproperties is returned, where the values are Python values.
|
|
|
|
The Python bindings for Alembic are old, and only compatible with Python 2.x,
|
|
so that's why we can't use them here, and have to rely on other tooling.
|
|
"""
|
|
import collections
|
|
|
|
abcls = self.alembic_root / 'bin' / 'abcls'
|
|
|
|
command = (str(abcls), '-vl', '%s%s' % (filepath, proppath))
|
|
proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
timeout=30)
|
|
|
|
coloured_output = proc.stdout
|
|
output = self.ansi_remove_re.sub(b'', coloured_output).decode('utf8')
|
|
|
|
# Because of the ANSI colour codes, we need to remove those first before
|
|
# decoding to text. This means that we cannot use the universal_newlines
|
|
# parameter to subprocess.run(), and have to do the conversion ourselves
|
|
output = output.replace('\r\n', '\n').replace('\r', '\n')
|
|
|
|
if proc.returncode:
|
|
raise AbcPropError('Error %d running %s:\n%s' % (proc.returncode, ' '.join(command), output))
|
|
|
|
# Mapping from value type to callable that can convert a string to Python values.
|
|
converters = {
|
|
'bool_t': int,
|
|
'uint8_t': int,
|
|
'int16_t': int,
|
|
'int32_t': int,
|
|
'uint32_t': int,
|
|
'uint64_t': int,
|
|
'float64_t': float,
|
|
'float32_t': float,
|
|
}
|
|
|
|
result = {}
|
|
|
|
# Ideally we'd get abcls to output JSON, see https://github.com/alembic/alembic/issues/121
|
|
lines = collections.deque(output.split('\n'))
|
|
while lines:
|
|
info = lines.popleft()
|
|
if not info:
|
|
continue
|
|
parts = info.split()
|
|
proptype = parts[0]
|
|
|
|
if proptype == 'CompoundProperty':
|
|
# To read those, call self.abcprop() on it.
|
|
continue
|
|
if len(parts) < 2:
|
|
raise ValueError('Error parsing result from abcprop: %s', info.strip())
|
|
valtype_and_arrsize, name_and_extent = parts[1:]
|
|
|
|
# Parse name and extent
|
|
m = self.abcls_array.match(name_and_extent)
|
|
if not m:
|
|
self.fail('Unparsable name/extent from abcls: %s' % name_and_extent)
|
|
name, extent = m.group('name'), m.group('arraysize')
|
|
|
|
if extent != '1':
|
|
self.fail('Unsupported extent %s for property %s/%s' % (extent, proppath, name))
|
|
|
|
# Parse type
|
|
m = self.abcls_array.match(valtype_and_arrsize)
|
|
if not m:
|
|
self.fail('Unparsable value type from abcls: %s' % valtype_and_arrsize)
|
|
valtype, scalarsize = m.group('name'), m.group('arraysize')
|
|
|
|
# Convert values
|
|
try:
|
|
conv = converters[valtype]
|
|
except KeyError:
|
|
self.fail('Unsupported type %s for property %s/%s' % (valtype, proppath, name))
|
|
|
|
def convert_single_line(linevalue):
|
|
try:
|
|
if scalarsize is None:
|
|
return conv(linevalue)
|
|
else:
|
|
return [conv(v.strip()) for v in linevalue.split(',')]
|
|
except ValueError as ex:
|
|
return str(ex)
|
|
|
|
if proptype == 'ScalarProperty':
|
|
value = lines.popleft()
|
|
result[name] = convert_single_line(value)
|
|
elif proptype == 'ArrayProperty':
|
|
arrayvalue = []
|
|
# Arrays consist of a variable number of items, and end in a blank line.
|
|
while True:
|
|
linevalue = lines.popleft()
|
|
if not linevalue:
|
|
break
|
|
arrayvalue.append(convert_single_line(linevalue))
|
|
result[name] = arrayvalue
|
|
else:
|
|
self.fail('Unsupported type %s for property %s/%s' % (proptype, proppath, name))
|
|
|
|
return result
|
|
|
|
def assertAlmostEqualFloatArray(self, actual, expect, places=6, delta=None):
|
|
"""Asserts that the arrays of floats are almost equal."""
|
|
|
|
self.assertEqual(len(actual), len(expect),
|
|
'Actual array has %d items, expected %d' % (len(actual), len(expect)))
|
|
|
|
for idx, (act, exp) in enumerate(zip(actual, expect)):
|
|
self.assertAlmostEqual(act, exp, places=places, delta=delta,
|
|
msg='%f != %f at index %d' % (act, exp, idx))
|
|
|
|
|
|
class HierarchicalAndFlatExportTest(AbstractAlembicTest):
|
|
@with_tempdir
|
|
def test_hierarchical_export(self, tempdir: pathlib.Path):
|
|
abc = tempdir / 'cubes_hierarchical.abc'
|
|
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
|
|
"renderable_only=True, visible_objects_only=True, flatten=False)" % abc.as_posix()
|
|
self.run_blender('cubes-hierarchy.blend', script)
|
|
|
|
# Now check the resulting Alembic file.
|
|
xform = self.abcprop(abc, '/Cube/Cube_002/Cube_012/.xform')
|
|
self.assertEqual(1, xform['.inherits'])
|
|
self.assertAlmostEqualFloatArray(
|
|
xform['.vals'],
|
|
[1.0, 0.0, 0.0, 0.0,
|
|
0.0, 1.0, 0.0, 0.0,
|
|
0.0, 0.0, 1.0, 0.0,
|
|
3.07484, -2.92265, 0.0586434, 1.0]
|
|
)
|
|
|
|
@with_tempdir
|
|
def test_flat_export(self, tempdir: pathlib.Path):
|
|
abc = tempdir / 'cubes_flat.abc'
|
|
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
|
|
"renderable_only=True, visible_objects_only=True, flatten=True)" % abc.as_posix()
|
|
self.run_blender('cubes-hierarchy.blend', script)
|
|
|
|
# Now check the resulting Alembic file.
|
|
xform = self.abcprop(abc, '/Cube_012/.xform')
|
|
self.assertEqual(1, xform['.inherits'], "Blender transforms always inherit")
|
|
|
|
self.assertAlmostEqualFloatArray(
|
|
xform['.vals'],
|
|
[0.343134, 0.485243, 0.804238, 0,
|
|
0.0, 0.856222, -0.516608, 0,
|
|
-0.939287, 0.177266, 0.293799, 0,
|
|
1, 3, 4, 1],
|
|
)
|
|
|
|
|
|
class DupliGroupExportTest(AbstractAlembicTest):
|
|
@with_tempdir
|
|
def test_hierarchical_export(self, tempdir: pathlib.Path):
|
|
abc = tempdir / 'dupligroup_hierarchical.abc'
|
|
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
|
|
"renderable_only=True, visible_objects_only=True, flatten=False)" % abc.as_posix()
|
|
self.run_blender('dupligroup-scene.blend', script)
|
|
|
|
# Now check the resulting Alembic file.
|
|
xform = self.abcprop(abc, '/Real_Cube/Linked_Suzanne/Cylinder-0/Suzanne-1/.xform')
|
|
self.assertEqual(1, xform['.inherits'])
|
|
self.assertAlmostEqualFloatArray(
|
|
xform['.vals'],
|
|
[1.0, 0.0, 0.0, 0.0,
|
|
0.0, 1.0, 0.0, 0.0,
|
|
0.0, 0.0, 1.0, 0.0,
|
|
0.0, 2.0, 0.0, 1.0]
|
|
)
|
|
|
|
@with_tempdir
|
|
def test_flat_export(self, tempdir: pathlib.Path):
|
|
abc = tempdir / 'dupligroup_hierarchical.abc'
|
|
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
|
|
"renderable_only=True, visible_objects_only=True, flatten=True)" % abc.as_posix()
|
|
self.run_blender('dupligroup-scene.blend', script)
|
|
|
|
# Now check the resulting Alembic file.
|
|
xform = self.abcprop(abc, '/Suzanne-1/.xform')
|
|
self.assertEqual(1, xform['.inherits'])
|
|
|
|
self.assertAlmostEqualFloatArray(
|
|
xform['.vals'],
|
|
[1.5, 0.0, 0.0, 0.0,
|
|
0.0, 1.5, 0.0, 0.0,
|
|
0.0, 0.0, 1.5, 0.0,
|
|
2.0, 3.0, 0.0, 1.0]
|
|
)
|
|
|
|
|
|
class CurveExportTest(AbstractAlembicTest):
|
|
@with_tempdir
|
|
def test_export_single_curve(self, tempdir: pathlib.Path):
|
|
abc = tempdir / 'single-curve.abc'
|
|
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
|
|
"renderable_only=True, visible_objects_only=True, flatten=False)" % abc.as_posix()
|
|
self.run_blender('single-curve.blend', script)
|
|
|
|
# Now check the resulting Alembic file.
|
|
abcprop = self.abcprop(abc, '/NurbsCurve/CurveData/.geom')
|
|
self.assertEqual(abcprop['.orders'], [4])
|
|
|
|
abcprop = self.abcprop(abc, '/NurbsCurve/CurveData/.geom/.userProperties')
|
|
self.assertEqual(abcprop['blender:resolution'], 10)
|
|
|
|
|
|
class HairParticlesExportTest(AbstractAlembicTest):
|
|
"""Tests exporting with/without hair/particles.
|
|
|
|
Just a basic test to ensure that the enabling/disabling works, and that export
|
|
works at all. NOT testing the quality/contents of the exported file.
|
|
"""
|
|
|
|
def _do_test(self, tempdir: pathlib.Path, export_hair: bool, export_particles: bool) -> pathlib.Path:
|
|
abc = tempdir / 'hair-particles.abc'
|
|
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
|
|
"renderable_only=True, visible_objects_only=True, flatten=False, " \
|
|
"export_hair=%r, export_particles=%r, as_background_job=False)" \
|
|
% (abc.as_posix(), export_hair, export_particles)
|
|
self.run_blender('hair-particles.blend', script)
|
|
return abc
|
|
|
|
@with_tempdir
|
|
def test_with_both(self, tempdir: pathlib.Path):
|
|
abc = self._do_test(tempdir, True, True)
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/Hair_system/.geom')
|
|
self.assertIn('nVertices', abcprop)
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/Non-hair_particle_system/.geom')
|
|
self.assertIn('.velocities', abcprop)
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/MonkeyMesh/.geom')
|
|
self.assertIn('.faceIndices', abcprop)
|
|
|
|
@with_tempdir
|
|
def test_with_hair_only(self, tempdir: pathlib.Path):
|
|
abc = self._do_test(tempdir, True, False)
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/Hair_system/.geom')
|
|
self.assertIn('nVertices', abcprop)
|
|
|
|
self.assertRaises(AbcPropError, self.abcprop, abc,
|
|
'/Suzanne/Non-hair_particle_system/.geom')
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/MonkeyMesh/.geom')
|
|
self.assertIn('.faceIndices', abcprop)
|
|
|
|
@with_tempdir
|
|
def test_with_particles_only(self, tempdir: pathlib.Path):
|
|
abc = self._do_test(tempdir, False, True)
|
|
|
|
self.assertRaises(AbcPropError, self.abcprop, abc, '/Suzanne/Hair_system/.geom')
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/Non-hair_particle_system/.geom')
|
|
self.assertIn('.velocities', abcprop)
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/MonkeyMesh/.geom')
|
|
self.assertIn('.faceIndices', abcprop)
|
|
|
|
@with_tempdir
|
|
def test_with_neither(self, tempdir: pathlib.Path):
|
|
abc = self._do_test(tempdir, False, False)
|
|
|
|
self.assertRaises(AbcPropError, self.abcprop, abc, '/Suzanne/Hair_system/.geom')
|
|
self.assertRaises(AbcPropError, self.abcprop, abc,
|
|
'/Suzanne/Non-hair_particle_system/.geom')
|
|
|
|
abcprop = self.abcprop(abc, '/Suzanne/MonkeyMesh/.geom')
|
|
self.assertIn('.faceIndices', abcprop)
|
|
|
|
|
|
class UVMapExportTest(AbstractAlembicTest):
|
|
@with_tempdir
|
|
def test_uvmap_export(self, tempdir: pathlib.Path):
|
|
"""Minimal test for exporting multiple UV maps on an animated mesh.
|
|
|
|
This covers the issue reported in T77021.
|
|
"""
|
|
basename = 'T77021-multiple-uvmaps-animated-mesh'
|
|
abc = tempdir / f'{basename}.abc'
|
|
script = f"import bpy; bpy.ops.wm.alembic_export(filepath='{abc.as_posix()}', start=1, end=1, " \
|
|
f"renderable_only=True, visible_objects_only=True, flatten=False)"
|
|
self.run_blender(f'{basename}.blend', script)
|
|
|
|
self.maxDiff = 1000
|
|
|
|
# The main UV map should be written to .geom
|
|
abcprop = self.abcprop(abc, '/Cube/Cube/.geom/uv')
|
|
self.assertEqual(abcprop['.vals'], [
|
|
[0.625, 0.75],
|
|
[0.875, 0.75],
|
|
[0.875, 0.5],
|
|
[0.625, 0.5],
|
|
[0.375, 1.0],
|
|
[0.625, 1.0],
|
|
[0.375, 0.75],
|
|
[0.375, 0.25],
|
|
[0.625, 0.25],
|
|
[0.625, 0.0],
|
|
[0.375, 0.0],
|
|
[0.125, 0.75],
|
|
[0.375, 0.5],
|
|
[0.125, 0.5],
|
|
])
|
|
|
|
# The second UV map should be written to .arbGeomParams
|
|
abcprop = self.abcprop(abc, '/Cube/Cube/.geom/.arbGeomParams/Secondary')
|
|
self.assertEqual(abcprop['.vals'], [
|
|
[0.75, 0.375],
|
|
[0.75, 0.125],
|
|
[0.5, 0.125],
|
|
[0.5, 0.375],
|
|
[1.0, 0.625],
|
|
[1.0, 0.375],
|
|
[0.75, 0.625],
|
|
[0.25, 0.625],
|
|
[0.25, 0.375],
|
|
[0.0, 0.375],
|
|
[0.0, 0.625],
|
|
[0.75, 0.875],
|
|
[0.5, 0.625],
|
|
[0.5, 0.875],
|
|
])
|
|
|
|
|
|
class LongNamesExportTest(AbstractAlembicTest):
|
|
@with_tempdir
|
|
def test_export_long_names(self, tempdir: pathlib.Path):
|
|
abc = tempdir / 'long-names.abc'
|
|
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \
|
|
"renderable_only=False, visible_objects_only=False, flatten=False)" % abc.as_posix()
|
|
self.run_blender('long-names.blend', script)
|
|
|
|
name_parts = [
|
|
'foG9aeLahgoh5goacee1dah6Hethaghohjaich5pasizairuWigee1ahPeekiGh',
|
|
'yoNgoisheedah2ua0eigh2AeCaiTee5bo0uphoo7Aixephah9racahvaingeeH4',
|
|
'zuthohnoi1thooS3eezoo8seuph2Boo5aefacaethuvee1aequoonoox1sookie',
|
|
'wugh4ciTh3dipiepeequait5uug7thiseek5ca7Eijei5ietaizokohhaecieto',
|
|
'up9aeheenein9oteiX6fohP3thiez6Ahvah0oohah1ep2Eesho4Beboechaipoh',
|
|
'coh4aehiacheTh0ue0eegho9oku1lohl4loht9ohPoongoow7dasiego6yimuis',
|
|
'lohtho8eigahfeipohviepajaix4it2peeQu6Iefee1nevihaes4cee2soh4noy',
|
|
'kaht9ahv0ieXaiyih7ohxe8bah7eeyicahjoa2ohbu7Choxua7oongah6sei4bu',
|
|
'deif0iPaechohkee5nahx6oi2uJeeN7ze3seunohJibe4shai0mah5Iesh3Quai',
|
|
'ChohDahshooNee0NeNohthah0eiDeese3Vu6ohShil1Iey9ja0uebi2quiShae6',
|
|
'Dee1kai7eiph2ahh2nufah3zai3eexeengohQue1caj0eeW0xeghi3eshuadoot',
|
|
'aeshiup3aengajoog0AhCoo5tiu3ieghaeGhie4Tu1ohh1thee8aepheingah1E',
|
|
'ooRa6ahciolohshaifoopeo9ZeiGhae2aech4raisheiWah9AaNga0uas9ahquo',
|
|
'thaepheip2aip6shief4EaXopei8ohPo0ighuiXah2ashowai9nohp4uach6Mei',
|
|
'ohph4yaev3quieji3phophiem3OoNuisheepahng4waithae3Naichai7aw3noo',
|
|
'aibeawaneBahmieyuph8ieng8iopheereeD2uu9Uyee5bei2phahXeir8eeJ8oo',
|
|
'ooshahphei2hoh3uth5chaen7ohsai6uutiesucheichai8ungah9Gie1Aiphie',
|
|
'eiwohchoo7ere2iebohn4Aapheichaelooriiyaoxaik7ooqua7aezahx0aeJei',
|
|
'Vah0ohgohphiefohTheshieghichaichahch5moshoo0zai5eeva7eisi4yae8T',
|
|
'EibeeN0fee0Gohnguz8iec6yeigh7shuNg4eingu3siph9joucahpeidoom4ree',
|
|
'iejiu3shohheeZahHusheimeefaihoh5eecachu5eeZie9ceisugu9taidohT3U',
|
|
'eex6dilakaix5Eetai7xiCh5Jaa8aiD4Ag3tuij1aijohv5fo0heevah8hohs3m',
|
|
'ohqueeNgahraew6uraemohtoo5qua3oojiex6ohqu6Aideibaithaiphuriquie',
|
|
'cei0eiN4Shiey7Aeluy3unohboo5choiphahc2mahbei5paephaiKeso1thoog1',
|
|
'ieghif4ohKequ7ong0jah5ooBah0eiGh1caechahnahThae9Shoo0phopashoo4',
|
|
'roh9er3thohwi5am8iequeequuSh3aic0voocai3ihi5nie2abahphupiegh7vu',
|
|
'uv3Quei7wujoo5beingei2aish5op4VaiX0aebai7iwoaPee5pei8ko9IepaPig',
|
|
'co7aegh5beitheesi9lu7jeeQu3johgeiphee9cheichi8aithuDehu2gaeNein',
|
|
'thai3Tiewoo4nuir1ohy4aithiuZ7shae1luuwei5phibohriepe2paeci1Ach8',
|
|
'phoi3ribah7ufuvoh8eigh1oB6deeBaiPohphaghiPieshahfah5EiCi3toogoo',
|
|
'aiM8geil7ooreinee4Cheiwea4yeec8eeshi7Sei4Shoo3wu6ohkaNgooQu1mai',
|
|
'agoo3faciewah9ZeesiXeereek7am0eigaeShie3Tisu8haReeNgoo0ci2Hae5u',
|
|
'Aesatheewiedohshaephaenohbooshee8eu7EiJ8isal1laech2eiHo0noaV3ta',
|
|
'liunguep3ooChoo4eir8ahSie8eenee0oo1TooXu8Cais8Aimo4eir6Phoo3xei',
|
|
'toe9heepeobein3teequachemei0Cejoomef9ujie3ohwae9AiNgiephi3ep0de',
|
|
'ua6xooY9uzaeB3of6sheiyaedohoiS5Eev0Aequ9ahm1zoa5Aegh3ooz9ChahDa',
|
|
'eevasah6Bu9wi7EiwiequumahkaeCheegh6lui8xoh4eeY4ieneavah8phaibun',
|
|
'AhNgei2sioZeeng6phaecheemeehiShie5eFeiTh6ooV8iiphabud0die4siep4',
|
|
'kushe6Xieg6ahQuoo9aex3aipheefiec1esa7OhBuG0ueziep9phai5eegh1vie',
|
|
'Jie5yu8aafuQuoh9shaep3moboh3Pooy7och8oC6obeik6jaew2aiLooweib3ch',
|
|
'ohohjajaivaiRail3odaimei6aekohVaicheip2wu7phieg5Gohsaing2ahxaiy',
|
|
'hahzaht6yaiYu9re9jah9loisiit4ahtoh2quoh9xohishioz4oo4phofu3ogha',
|
|
'pu4oorea0uh2tahB8aiZoonge1aophaes6ogaiK9ailaigeej4zoVou8ielotee',
|
|
'cae2thei3Luphuqu0zeeG8leeZuchahxaicai4ui4Eedohte9uW6gae8Geeh0ea',
|
|
'air7tuy7ohw5sho2Tahpai8aep4so5ria7eaShus5weaqu0Naquei2xaeyoo2ae',
|
|
'vohge4aeCh7ahwoo7Jaex6sohl0Koong4Iejisei8Coir0iemeiz9uru9Iebaep',
|
|
'aepeidie8aiw6waish9gie4Woolae2thuj5phae4phexux7gishaeph4Deu7ooS',
|
|
'vahc5ia0xohHooViT0uyuxookiaquu2ogueth0ahquoudeefohshai8aeThahba',
|
|
'mun3oagah2eequaenohfoo8DaigeghoozaV2eiveeQuee7kah0quaa6tiesheet',
|
|
'ooSet4IdieC4ugow3za0die4ohGoh1oopoh6luaPhaeng4Eechea1hae0eimie5',
|
|
'iedeimadaefu2NeiPaey2jooloov5iehiegeakoo4ueso7aeK9ahqu2Thahkaes',
|
|
'nahquah9Quuu2uuf0aJah7eishi2siegh8ue5eiJa2EeVu8ebohkepoh4dahNgo',
|
|
'io1bie7chioPiej5ae2oohe2fee6ooP2thaeJohjohb9Se8tang3eipaifeimai',
|
|
'oungoqu6dieneejiechez1xeD2Zi9iox2Ahchaiy9ithah3ohVoolu2euQuuawo',
|
|
'thaew0veigei4neishohd8mecaixuqu7eeshiex1chaigohmoThoghoitoTa0Eo',
|
|
'ahroob2phohvaiz0Ohteik2ohtakie6Iu1vitho8IyiyeeleeShae9defaiw9ki',
|
|
'DohHoothohzeaxolai3Toh5eJie7ahlah9reF0ohn1chaipoogain2aibahw4no',
|
|
'aif8lo5she4aich5cho2rie8ieJaujeem2Joongeedae4vie3tah1Leequaix1O',
|
|
'Aang0Shaih6chahthie1ahZ7aewei9thiethee7iuThah3yoongi8ahngiobaa5',
|
|
'iephoBuayoothah0Ru6aichai4aiw8deg1umongauvaixai3ohy6oowohlee8ei',
|
|
'ohn5shigoameer0aejohgoh8oChohlaecho9jie6shu0ahg9Bohngau6paevei9',
|
|
'edahghaishak0paigh1eecuich3aad7yeB0ieD6akeeliem2beifufaekee6eat',
|
|
'hiechahgheloh2zo7Ieghaiph0phahhu8aeyuiKie1xeipheech9zai4aeme0ee',
|
|
'Cube'
|
|
]
|
|
name = '/' + '/'.join(name_parts)
|
|
|
|
# Now check the resulting Alembic file.
|
|
abcprop = self.abcprop(abc, '%s/.xform' % name)
|
|
self.assertEqual(abcprop['.vals'], [
|
|
1.0, 0.0, 0.0, 0.0,
|
|
0.0, 1.0, 0.0, 0.0,
|
|
0.0, 0.0, 1.0, 0.0,
|
|
0.0, 3.0, 0.0, 1.0,
|
|
])
|
|
|
|
abcprop = self.abcprop(abc, '%s/Cube/.geom' % name)
|
|
self.assertIn('.faceCounts', abcprop)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--blender', required=True)
|
|
parser.add_argument('--testdir', required=True)
|
|
parser.add_argument('--alembic-root', required=True)
|
|
args, remaining = parser.parse_known_args()
|
|
|
|
unittest.main(argv=sys.argv[0:1] + remaining)
|