Fix T75936: Alembic, allow exporting of invisible objects

Add a new depsgraph builder class that includes invisible objects and
use that in the Alembic exporter.

Alembic supports three options for visibility, "visible", "inherited",
and "hidden". This means that parents can be hidden and still have
visible children (contrary to USD, where invisibility is used to prune
an entire scene graph subtree). Because of this, the visibility is
stored on the transform node, as that represents the Object in Blender
and thus keeps the Alembic file as close to Blender's own structure as
possible.

Reviewed By: Sergey

Differential Revision: https://developer.blender.org/D8595
This commit is contained in:
Sybren A. Stüvel
2020-08-17 16:58:09 +02:00
parent fd3086833a
commit a95f863596
12 changed files with 239 additions and 17 deletions

View File

@@ -32,6 +32,7 @@ import pathlib
import subprocess
import sys
import unittest
from typing import Tuple
from modules.test_utils import (
with_tempdir,
@@ -59,19 +60,13 @@ class AbstractAlembicTest(AbstractBlenderRunnerTest):
# '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.
def abcls(self, *arguments) -> Tuple[int, str]:
"""Uses abcls and return its output.
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.
:return: tuple (process exit status code, stdout)
"""
import collections
abcls = self.alembic_root / 'bin' / 'abcls'
command = (str(abcls), '-vl', '%s%s' % (filepath, proppath))
command = (self.alembic_root / 'bin' / 'abcls', *arguments)
proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
timeout=30)
@@ -84,7 +79,25 @@ class AbstractAlembicTest(AbstractBlenderRunnerTest):
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))
str_command = " ".join(str(c) for c in command)
print(f'command {str_command} failed with status {proc.returncode}')
return (proc.returncode, output)
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
command = ('-vl', '%s%s' % (filepath, proppath))
returncode, output = self.abcls(*command)
if returncode:
raise AbcPropError('Error %d running abcls:\n%s' % (returncode, output))
# Mapping from value type to callable that can convert a string to Python values.
converters = {
@@ -536,6 +549,41 @@ class LongNamesExportTest(AbstractAlembicTest):
self.assertIn('.faceCounts', abcprop)
class InvisibleObjectExportTest(AbstractAlembicTest):
"""Export an object which is invisible.
This test only tests a small subset of the functionality that is required to
export invisible objects. It just tests that the visibility property is
written, and that it has the correct initial value. This is a limitation
caused by these tests relying on `abcls`.
"""
@with_tempdir
def test_hierarchical_export(self, tempdir: pathlib.Path):
abc = tempdir / 'visibility.abc'
script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=2, " \
"renderable_only=False, visible_objects_only=False)" % abc.as_posix()
self.run_blender('visibility.blend', script)
def test(cube_name: str, expect_visible: bool):
returncode, output = self.abcls('-va', f'{abc}/{cube_name}')
if returncode:
self.fail(f"abcls failed: {output}")
output = output.strip()
self.assertEqual(f'Cube .xform visible {int(expect_visible)}', output)
# This cube is always visible.
test('VisibleCube', True)
# This cube is never visible, and thus will not be pulled into the
# depsgraph by the standard builder, only by the all-objects builder.
test('InvisibleCube', False)
# This cube has animated visibility, and thus will be pulled into the
# depsgraph by the standard builder as well as the all-objects builder.
test('InvisibleAnimatedCube', False)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--blender', required=True)