How to duplicate a prim?

I just want to duplicate any specific prim via C++ USD API.

In omniverse CREATE I’ve created an empty scene, dropped local asset
I’ve saved the scene as a .usda to look what I have. Verified that prim is here:

def "Cardbox_A3_01" (
        instanceable = true
        prepend payload = @file:/C:/Users/hellothere/Downloads/ov-industrial3dpack-01-100.1.3/Assets/ArchVis/Industrial/Containers/Cardboard/Cardbox_A3.usd@
    )
    {
        float3 xformOp:rotateXYZ = (0, -90, -90)
        float3 xformOp:scale = (1, 1, 1)
        double3 xformOp:translate = (60.27730765253733, -8.036520471939059e-29, -5.649315969397589)
        uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
    }

I want my app to duplicate that prim (only a name should be different), after a 3 days i finally got something very close to my goal. My edited .usda now contains extra prim:

def Xform "Cardbox_A3_09" (
        instanceable = true
        kind = "component"
    )
    {
        float3 xformOp:rotateXYZ = (0, -90, -90)
        float3 xformOp:scale = (1, 1, 1)
        double3 xformOp:translate = (60.27730765253733, -8.036520471939059e-29, -5.649315969397589)
        uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ", "xformOp:scale"]
    }

As you can notice, there is still some difference:

  1. redundant line kind = “component”
  2. there is no prepend payload = @…

What should I correct in my code to get exactly same prim (except name)?
Is there some simple way to clone a prim and add it into stage?

static void copyMeta(UsdObject origin, UsdObject target) {
    UsdMetadataValueMap mm = origin.GetAllAuthoredMetadata();

    for (auto& meta : mm) {
        target.SetMetadata(meta.first, meta.second);
    }
}

static void copyProp(UsdProperty pr, UsdPrim target) {
    UsdProperty newProp;
    if (UsdAttribute attr = pr.As<UsdAttribute>()) {
        UsdAttribute newAttr = target.CreateAttribute(attr.GetName(), attr.GetTypeName());
        VtValue val = VtValue();
        VtValue* valPtr = &val;
        if (attr.Get(valPtr)) {
            newAttr.Set(val);
        }
            
        newProp = newAttr;
        //TODO: copy animation
        //TODO: copy connections

    }
    else if (UsdRelationship rel = pr.As<UsdRelationship>()) {
        UsdRelationship newRel = target.CreateRelationship(rel.GetName(), rel.IsCustom());
        SdfPathVector pVec = SdfPathVector();
        SdfPathVector* vecPtr = &pVec;
        if (rel.GetTargets(vecPtr)) {
            newRel.SetTargets(pVec);
        }
        newProp = newRel;
        //TODO: set targets to newRel
    }
    if (newProp) {
        copyMeta(pr, newProp);
    }
    
}

static void clonePrim(UsdStageRefPtr stage, UsdPrim origin, std::string name) {
    SdfPath originPath = origin.GetPath().GetParentPath();
    //originPath = originPath;
    SdfPath newPath = SdfPath(originPath.GetAsString() + "/" + name);

    //TODO: if already exists?
    UsdPrim newPrim = stage->DefinePrim(newPath, origin.GetTypeName());

    copyMeta(origin, newPrim);

    for (UsdProperty pr : origin.GetAuthoredProperties()) {
        copyProp(pr, newPrim);
    }

    for (UsdPrim ch : origin.GetChildren()) {
        //TODO: copy child
        std::cout << "there is child" << std::endl;
    }

    //UsdPrimCompositionQuery
    UsdReferences refs = origin.GetPayloads();

}

I think Sdf.CopySpec is the closest to this. However you’d be copying the individual prim specs from one layer to another (or same target layer but different name).


I’m not entirely sure if there’s any trivial way to clone a composed primitive state (that might have been defined and composed over multiple layers) and then copy that spec as a single contribution on a single layer.

Thank you, I will investigate Sdf.CopySpec.

Maybe I should go another way for duplication of assets on a scene? The goal is to control a population of prims: quantity, coordinates of each etc.

There’s a recommendation here to maybe also look into UsdUtils.StitchLayers:

Copying specs (prim and properties) from layer to layer with Sdf.CopySpec()

The Sdf.CopySpec can copy anything that is representable via the Sdf.Path. This means we can copy prim/property/variant specs. When copying, the default is to completely replace the target spec.

We can filter this by passing in filter functions. Another option is to copy the content to a new anonymous layer and then merge it via UsdUtils.StitchLayers(<StrongLayer>, <WeakerLayer>). This is often more “user friendly” than implementing a custom merge logic, as we get the “high layer wins” logic for free and this is what we are used to when working with USD.

Probably won’t be “optimal” in performance however.

Here’s an example in Python:

from pxr import Sdf, Usd


def unique_name(stage: Usd.Stage, prim_path: Sdf.Path) -> Sdf.Path:
    """Return Sdf.Path that is unique under the current composed stage.

    Note that this technically does not ensure that the Sdf.Path does not
    exist in any of the layers, e.g. it could be defined within a currently
    unselected variant or a muted layer.

    """
    src = prim_path.pathString.rstrip("123456789")
    i = 1
    while stage.GetPrimAtPath(prim_path):
        prim_path = Sdf.Path(f"{src}{i}")
        i += 1
    return prim_path


def duplicate_prim(prim: Usd.Prim) -> Sdf.Path:
    """Duplicate prim"""
    path = prim.GetPath()
    stage = prim.GetStage()
    new_path = unique_name(stage, path)
    for spec in prim.GetPrimStack():
        layer = spec.layer
        Sdf.CopySpec(layer, path, layer, new_path)        
    return new_path

Example usage:

stage = Usd.Stage.CreateInMemory()
prim_a = stage.DefinePrim("/root/a", "Xform")
prim_b = stage.DefinePrim("/root/b", "Xform")
new_prim_a_path = duplicate_prim(prim_a)
print(new_prim_a_path)
# "/root/a1"
new_prim_b_path = duplicate_prim(prim_b)
print(new_prim_b_path)
# "/root/b1"

Note that this will clone the prim across all layers it is found currently in the composed stage and then also create the duplicated specs in each of those layers. As such, it does not rely on or target the current edit target.

How one instead would clone a “composed prim” and then create those opinions only in the current stage’s edit target I don’t know. An example of that would be welcomed! :slight_smile:

And here’s a quick prototype example code to duplicate a prim, flatten its opinions using UsdUtils stitch commands and then put that flattened prim spec into the new layer.

Prototype 1: Duplicate prim with UsdUtils.StitchInfo

In this case I use UsdUtils.StitchInfo to solely merge specs however there are some caveats to be aware of:

  • This does not stitch any childrens and their opinions, even though the initial call to CopySpec will copy over children prims from that stack. You might want to either remove all children from the copied spec or do something different there so you also transfer children and apply their stitched specs
  • Unfortunately this requires a temporary layer because the logic of the StitchInfo checks whether the spec paths are equal - which I suppose is solely a “safety check” but due to that we can’t stitch into a duplicated prim that will of course require its own unique name.
from pxr import Sdf, Usd, UsdUtils, UsdGeom


def unique_name(stage: Usd.Stage, prim_path: Sdf.Path) -> Sdf.Path:
    """Return Sdf.Path that is unique under the current composed stage.

    Note that this technically does not ensure that the Sdf.Path does not
    exist in any of the layers, e.g. it could be defined within a currently
    unselected variant or a muted layer.

    """
    src = prim_path.pathString.rstrip("123456789")
    i = 1
    while stage.GetPrimAtPath(prim_path):
        prim_path = Sdf.Path(f"{src}{i}")
        i += 1
    return prim_path



def duplicate_prim(prim: Usd.Prim) -> list[Sdf.Path]:
    """Duplicate prim flattened into current edit target"""
    path = prim.GetPath()
    stage = prim.GetStage()
    
    stack = prim.GetPrimStack()
    initial_spec = stack.pop()
    
    # Copy spec to a temp layer where we use stitch utiltiies
    # to merge the opinions of multiple layers of the original
    # prim's PrimStack so we have a 'merged opinion' on one
    # layer
    tmp_layer = Sdf.Layer.CreateAnonymous()
    Sdf.CreatePrimInLayer(tmp_layer, path.GetParentPath())  # ensure parent exists
    if not Sdf.CopySpec(initial_spec.layer, path, tmp_layer, path):
        raise RuntimeError("No spec copied")
        
    # Add the additional opinions that make the original prim stack
    new_spec = tmp_layer.GetPrimAtPath(path)
    for spec in stack:
        # Note that this does not copy over opinions to prim specs
        # nor does it copy over children. It doesn't seem UsdUtils.StitchValueFn
        # is exposed to Python so we also couldn't make it copy over new children
        # with a custom call to `Sdf.CopySpec`
        UsdUtils.StitchInfo(new_spec, spec) 
    
    # Now copy the flattened spec to the target "duplicate"
    target = stage.GetEditTarget()
    target_layer = target.GetLayer()
    new_path = unique_name(stage, path)
    new_path = target.MapToSpecPath(new_path)
    Sdf.CopySpec(tmp_layer, path, target_layer, new_path)
    
    return new_path


# Create a stage with an Xform prim
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/root/a", "Xform")


# Now add an opinion in another layer
layer = Sdf.Layer.CreateAnonymous()
prim_spec = Sdf.CreatePrimInLayer(layer, "/root/a")
attr_spec = Sdf.AttributeSpec(prim_spec, "my_custom_attr", Sdf.ValueTypeNames.Double)
attr_spec.default = 10
attr_spec.interpolation = UsdGeom.Tokens.constant

# Sublayer the path ontot the stage
root_layer = stage.GetRootLayer()
root_layer.subLayerPaths.append(layer.identifier)

# Debugging: Make sure the attribute appears on the composed stage
prim = stage.GetPrimAtPath("/root/a")
assert prim.GetAttribute("my_custom_attr").Get() == 10

# Duplicate things
new_prim_path = duplicate_prim(prim)

# Ensure the new prim is also as we want it, of Xform type with the custom attribute
# and that the opinions appear in a single prim spec (single layer)
new_prim = stage.GetPrimAtPath("/root/a1")
assert new_prim.IsValid(), "New prim must exist"
print(stage.ExportToString())
assert new_prim.GetAttribute("my_custom_attr").Get() == 10
assert new_prim.GetTypeName() == "Xform"
assert len(new_prim.GetPrimStack()) == 1, "Must be on a single layer"
assert new_prim.GetPrimStack()[0].layer == stage.GetEditTarget().GetLayer(), "New prim must be on the current edit target layer"

Prototype 2: Duplicate prim with UsdUtils.StitchLayers

And here’s an example that does clone its children by just using a temporary Sdf.Layer per spec in the prim’s stack and then doing a full stitch layers:

from pxr import Sdf, Usd, UsdUtils, UsdGeom


def unique_name(stage: Usd.Stage, prim_path: Sdf.Path) -> Sdf.Path:
    """Return Sdf.Path that is unique under the current composed stage.

    Note that this technically does not ensure that the Sdf.Path does not
    exist in any of the layers, e.g. it could be defined within a currently
    unselected variant or a muted layer.

    """
    src = prim_path.pathString.rstrip("123456789")
    i = 1
    while stage.GetPrimAtPath(prim_path):
        prim_path = Sdf.Path(f"{src}{i}")
        i += 1
    return prim_path


def duplicate_prim(prim: Usd.Prim) -> list[Sdf.Path]:
    """Duplicate prim flattened into current edit target"""
    path = prim.GetPath()
    stage = prim.GetStage()
    
    # Copy spec to a temp layer where we use stitch utiltiies
    # to merge the opinions of multiple layers of the original
    # prim's PrimStack so we have a 'merged opinion' on one
    # layer
    tmp_layers = []
    for spec in prim.GetPrimStack():
        tmp_layer = Sdf.Layer.CreateAnonymous()
        Sdf.CreatePrimInLayer(tmp_layer, path.GetParentPath())
        if not Sdf.CopySpec(spec.layer, path, tmp_layer, path):
            raise RuntimeError("No spec copied")
        
        tmp_layers.append(tmp_layer)
    
    # Add the additional opinions that make the original prim stack
    new_spec = tmp_layer.GetPrimAtPath(path)
    full_layer = Sdf.Layer.CreateAnonymous()
    for tmp_layer in tmp_layers:
        UsdUtils.StitchLayers(full_layer, tmp_layer) 
    
    # Now copy the flattened spec to the target "duplicate"
    target = stage.GetEditTarget()
    target_layer = target.GetLayer()
    new_path = unique_name(stage, path)
    new_path = target.MapToSpecPath(new_path)
    Sdf.CopySpec(full_layer, path, target_layer, new_path)
    
    return new_path


# Create a stage with an Xform prim
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/root/a", "Xform")
prim = stage.DefinePrim("/root/a/child_in_rootlayer", "Xform")

# Now add an opinion in another layer
layer = Sdf.Layer.CreateAnonymous()
prim_spec = Sdf.CreatePrimInLayer(layer, "/root/a")
attr_spec = Sdf.AttributeSpec(prim_spec, "my_custom_attr", Sdf.ValueTypeNames.Double)
attr_spec.default = 10
attr_spec.interpolation = UsdGeom.Tokens.constant
spec = Sdf.CreatePrimInLayer(layer, "/root/a/child_in_sublayer")
spec.specifier = Sdf.SpecifierDef
spec.typeName = "Xform"

# Sublayer the path ontot the stage
root_layer = stage.GetRootLayer()
root_layer.subLayerPaths.append(layer.identifier)

# Debugging: Make sure the attribute appears on the composed stage
prim = stage.GetPrimAtPath("/root/a")
assert prim.GetAttribute("my_custom_attr").Get() == 10
assert stage.GetPrimAtPath("/root/a/child_in_rootlayer")
assert stage.GetPrimAtPath("/root/a/child_in_sublayer")

# Duplicate things
new_prim_path = duplicate_prim(prim)

# Ensure the new prim is also as we want it, of Xform type with the custom attribute
# and that the opinions appear in a single prim spec (single layer)
new_prim = stage.GetPrimAtPath("/root/a1")
assert new_prim.IsValid(), "New prim must exist"
print(stage.ExportToString())
assert new_prim.GetAttribute("my_custom_attr").Get() == 10
assert new_prim.GetTypeName() == "Xform"
assert len(new_prim.GetPrimStack()) == 1, "Must be on a single layer"
assert new_prim.GetPrimStack()[0].layer == stage.GetEditTarget().GetLayer(), "New prim must be on the current edit target layer"
assert stage.GetPrimAtPath("/root/a1/child_in_rootlayer")
assert stage.GetPrimAtPath("/root/a1/child_in_sublayer")
2 Likes

HUGE thanks! Your first answer (with Sdf.CopySpec) was enough for my needs.

I hope, your detailed answer (with examples) will be useful not only for me but for all community, cause there is no beginner-friendly information about prim duplication.