Sdf.CopySpec Python API merge into existing prims instead of replace

I’m trying to use the Sdf.CopySpec function to copy some specs around but “merge them into existing” prims instead of completely obliterating child prims or properties that already exist but are not in the copied data.

As far as I understand I should be able to do so by providing it the should copy value and should copy children functions so I can return what it should do.

However, I’m not sure in the should_copy_children_fn how to get the children_field from the Sdf.PrimSpec. It seems I can only access that metadata on a composed Usd.Prim?

Here’s a quick reproducable:

from pxr import Usd, Sdf
import pprint
import logging

stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/A", "Xform")
prim = stage.DefinePrim("/A/A", "Xform")
prim = stage.DefinePrim("/B", "Xform")
prim = stage.DefinePrim("/B/B", "Xform")

layer = stage.GetRootLayer()


def report_errors(fn):
    """Decorator that logs any errors raised by the function.

    This can be useful for functions that are connected to e.g. USD's
    callbacks that do not output the full error if any occurs.
    
    """
    def wrap(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except Exception as exc:
            logging.error(exc, exc_info=sys.exc_info())
            raise RuntimeError(f"Error from {fn}") from exc

    return wrap


@report_errors
def should_copy_value_fn(
    spec_type: Sdf.SpecType, 
    field: str, 
    src_layer: Sdf.Layer, 
    src_path: Sdf.Path, 
    field_in_src: bool, 
    dest_layer: Sdf.Layer, 
    dest_path: Sdf.Path,
    field_in_dest: bool
):
    print("\nshould_copy_value_fn()")
    pprint.pprint(locals())
    return True


@report_errors
def should_copy_children_fn(
    children_field: str,
    src_layer: Sdf.Layer, 
    src_path: Sdf.Path, 
    field_in_src: bool, 
    dest_layer: Sdf.Layer, 
    dest_path: Sdf.Path,
    field_in_dest: bool):
    print("\should_copy_children_fn()")
    pprint.pprint(locals())

    if field_in_dest and not field_in_src:
        # Keep dest
        return False
    if field_in_dest and field_in_src:
        # Merge values, append copied after source values

        # QUESTION: How do I do this without a `Usd.Stage. but using only the `Sdf.PrimSpec`?
        src_stage = Usd.Stage.Open(src_layer)
        prim = src_stage.GetPrimAtPath(src_path)
        src_children = prim.GetMetadata(children_field)
        
        dest_stage = Usd.Stage.Open(dest_layer)
        prim = dest_stage.GetPrimAtPath(dest_path)
        dest_children = prim.GetMetadata(children_field)
        return True, src_children, src_children + dest_children
    return True

Sdf.CopySpec(layer, Sdf.Path("/A"), layer, Sdf.Path("/B"), should_copy_value_fn, should_copy_children_fn)

# These should all exist
# B/A should be newly created -> copied over
print(stage.GetPrimAtPath("/A"))
print(stage.GetPrimAtPath("/A/A"))
print(stage.GetPrimAtPath("/B/A"))
print(stage.GetPrimAtPath("/B/B"))

Note that it has quite a few prints to for debugging purposes for the behavior of these calls.

Apparently the method on the Sdf.Spec to retrieve the metadata field is GetInfo.

Was able to get this work, e.g. in code used here.

It boils down to: src_layer.GetObjectAtPath(src_path).GetInfo(children_field)

2 Likes