Page 1 of 1

Alembic to USD Python helper

Unread postPosted: Wed Sep 18, 2019 2:28 pm
by anemoff
Hi all,

Here is a Python helper to call the tool "abc2usd" (available since Clarisse 4.0) that converts an Alembic file to an USD file.

python code

def abc2usd(input_abc_file, output_usd_file):
Convert the input Alembic file to USD.

> Resolved path (i.e. variables are expanded) to the input Alembic file to be converted.

> Resolved path (i.e. variables are expanded) of the output USD file.
> File extension must be '.usda' or '.usdc' (recommended), or empty, in which case it will default to '.usdc'.

On success, returns the USD file path.
On failure, returns None.

print('abc2usd: converting "{}" to "{}" ...'.format(input_abc_file, output_usd_file))

bin_dir = ix.application.get_factory().get_vars().get('CLARISSE_BIN_DIR').get_string()
bin = os.path.join(bin_dir, 'abc2usd')
if platform.system() == 'Windows':
bin += '.exe'

if not os.path.isfile(bin):
ix.log_error('Error: abc2usd binary "{}" not found.'.format(bin))
return None

if not os.path.isfile(input_abc_file):
ix.log_error('Error: input Alembic file "{}" not found.'.format(input_abc_file))
return None

cmd = '{} -input {} -output {}'.format(bin, input_abc_file, output_usd_file)
output = subprocess.check_output(cmd)
return output_usd_file
except subprocess.CalledProcessError as e:
ix.log_error('abc2usd failed:\n{}'.format(e.output))
return None
except Exception as e:
ix.log_error('abc2usd failed:\n{}'.format(e))
return None

Simple usage example:

python code

abc_file = 'path/to/'

# use the same filename but change the file extension
usd_file, _ = os.path.splitext(abc_file)
usd_file += '.usdc'

# do the conversion
final_usd_file = abc2usd(abc_file, usd_file)
if os.path.isfile(final_usd_file):
ix.log_info('Alembic to USD conversion succeeded!')
# do something with the USD file ...
ix.log_error('Alembic to USD conversion failed!')

Advanced example:
In this example, we look for all Reference Contexts that reference an Alembic file, convert the file to USD, and update the reference to use the USD file.

The example doesn't update material assignments. And you must be aware that shading group names differ slightly between Alembic and USD.
Historically in Clarisse, we collapse the Alembic xforms/shapes (coming from Maya) into 1 node, and we use the shape's name.
USD does the same internally, but unfortunately, they use the name of the xform...
E.g. in Alembic, the kinematic paths will look like "root/cubeShape" and the same converted to USD "root/cube"
So if you have shading applied through a shading layer, you might need to modify some rules to accommodate this change too (but most rules should work without modifications as they usually target groups instead of individual shapes, so the names don't change).
(Thanks to Damien for the details!)

python code

def get_file_references(parent_context, file_ext, filter='*'):
Get all File Reference Contexts children of parent_context that match the path `filter`.

> Parent context where we want to search for File Reference Contexts.

> Desired file extension. Example: '.abc' to look for Alembic File References, '.project' for Projects, ...

> Path filter expression. Example: '*foo*' will only match contexts with 'foo' in their path.

A list of OfContexts.
sub_contexts = ix.api.OfContextSet()
ix.application.get_matching_contexts(sub_contexts, filter, ix.get_item(parent_context))

file_refs = []
for ctx in sub_contexts:
if ctx.is_reference():
file_attr = ctx.get_attribute('filename')
if file_attr:
file = file_attr.get_string()
if file and file.lower().endswith(file_ext):

return file_refs

# Example: convert all Alembic refs to USD refs

def convert_abc_refs_to_usd_refs(parent_context):
ix.begin_command_batch('Convert Alembic references to USD references')

# get all alembic reference contexts
abc_refs = get_file_references(parent_context, '.abc')

# create a progress bar for feedback
pbar = ix.application.create_progress_bar('Converting Alembic references to USD references...', True, True)
pbar_step = 0

# to avoid converting the same file multiple times, group contexts by referenced file
file_dict = {}
for ref_ctx in abc_refs:
file_attr = ref_ctx.get_attribute('filename')
if file_attr:
file = file_attr.get_string()
if file:
if file not in file_dict:
file_dict[file] = []

# convert and update the refs
for abc_file, contexts in file_dict.items():
# convert the file once
usd_file, _ = os.path.splitext(abc_file)
usd_file += '.usdc'
usd_file = abc2usd(abc_file, usd_file)

# update all associated refs
if os.path.isfile(usd_file):
for ref_ctx in contexts:
file_attr = ref_ctx.get_attribute('filename')
ix.cmds.SetValues([str(file_attr)], [usd_file])

pbar_step += 1



Feedback is welcome.

Re: Alembic to USD Python helper

Unread postPosted: Thu Sep 19, 2019 12:20 pm
by gerdhofer
Thanks for the script Anthony. Really helpful. Not just in terms of functionality, but also when I think of scripting in general.

Re: Alembic to USD Python helper

Unread postPosted: Thu Sep 19, 2019 12:53 pm
by anemoff
You're welcome!

Re: Alembic to USD Python helper

Unread postPosted: Sat Sep 21, 2019 12:01 am
by mati
Does abc2usd do anything more than usdcat?

Re: Alembic to USD Python helper

Unread postPosted: Mon Sep 23, 2019 9:50 am
by anemoff
Not really. Basically, it flattens an Alembic file layer in a USD stage, but it also cleanups unnecessary samples from the Alembic (this can be disabled if needed).
You could achieve the same with usdcat, minus the samples cleanup part, by writing an .usda file that references the Alembic, and then flattening it to .usdc.

Check abc2usd help with "abc2usd -help" for more details.