Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to move mtl materials into group above a sublayer or reference #3466

Open
BigRoy opened this issue Nov 15, 2023 · 4 comments
Open

Unable to move mtl materials into group above a sublayer or reference #3466

BigRoy opened this issue Nov 15, 2023 · 4 comments
Assignees
Labels
bug Something isn't working

Comments

@BigRoy
Copy link
Contributor

BigRoy commented Nov 15, 2023

Describe the bug

I'd like to set up an asset building workflow where in a layer I can apply look overrides and make sure the materials end up in my asset hierarchy /{asset}/mtl however when creating materials they default to /mtl

However, I can't seem to move those into the correct place in the hierarchy due to:

Loading this cube.usda:

#usda 1.0
(
    defaultPrim = "asset"
    metersPerUnit = 0.01
    upAxis = "Y"
)

def Xform "asset" (
    kind = "component"
)
{
    def Scope "geo"
    {
        def Mesh "cube"
        {
            uniform bool doubleSided = 1
            float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)]
            int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
            int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4]
            point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)]
        }
    }
}

As a sublayer gives me:
image

Assigning a material then gives me:
image

The material I created and assigned in my look layer that is above the loaded model:

image

I cannot seem to reparent the /mtl to /asset/mtl to move into the correct place.

However, I can create materials just fine directly in /asset or /asset/mtl if I try so manually.
Additionally - I can also reparent the /mtl/UsdPreviewSurface1 into another path that is not from a sublayer or reference but as soon as I try to put it on there Maya is not happy and gives me:

// Error: line 1: Cannot reparent [mtl]. It would orphan opinions on the layer [cube.usda].

Which seems weird since that file doesn't have opinions there - of course the material itself has over bindings to asset/geo/cube but still removing the assignment and then moving it also doesn't work.

However, if I create the asset/mtl/UsdPreviewSurface1 and then after add the model layer I can assign it fine and it's at the correct location so it is totally feasible to from that layer in USD data define opinions there. Unfortunately there's just something in Maya USD disallowing making those edits.

Steps to reproduce
Steps to reproduce the behavior:

  1. Sublayer or reference a model file into an asset layer
  2. In a new layer write materials into the asset's location (e.g. /asset/mtl) - I can't make this work

Expected behavior

It should be more trivial to allow reparenting or moving it around into the location I want it to be.
Also, bonus points if I can force by default the materials to go there for this USD Stage so I don't even need to reparent and that artists need less 'fixing' this.

Or better question; what is a good approach to load an asset.usd or model.usd and start contributing my look opinions into the default prim (e.g. /root or /{asset_name} depending on the USD asset structure) so the asset can be referenced elsewhere? (which requires referencing the (default) prim into another prim; and thus /mtl should be encapsulated within it, right?)

Attachments
If applicable, add screenshots, sample files, etc to help explain your problem.

Specs (if applicable):

  • OS & version: Windows 10
  • Maya version: Maya 2024.1
  • Maya USD release: 0.25.0

Additional context

I also tried muting the cube.usda layer and then moving the materials, but unfortunately that's not allowed either:

// Warning: line 1: The position of some objects may have moved due to non-transformable objects being reparented.
// Warning: Cannot reparent prim "/Scope1" because there is at least one muted layer.
@BigRoy BigRoy added the bug Something isn't working label Nov 15, 2023
@wallworm
Copy link
Collaborator

Thank you for this feedback. The team has been discussing the topic of restrictions and possibly relaxing some of those restrictions. The intent that the system has now is to not allow the user to create cruft (orphaned opinions). We are seeing the need to be a little less restrictive at times on this front and will continue to look into this topic.

As for the question of referencing the asset with the looks opinions: you are correct to encapsulate the mtl scope within the default prim.

@BigRoy
Copy link
Contributor Author

BigRoy commented Nov 15, 2023

Thank you for this feedback. The team has been discussing the topic of restrictions and possibly relaxing some of those restrictions. The intent that the system has now is to not allow the user to create cruft (orphaned opinions). We are seeing the need to be a little less restrictive at times on this front and will continue to look into this topic.

Is there any short-term solution, like an env var, to lift the restrictions currently?

As for the question of referencing the asset with the looks opinions: you are correct to encapsulate the mtl scope within the default prim.

Would it make sense that maybe a mayaUsdProxyShape could have an attribute available that defines e.g. the default scope for new materials, e.g. it has an attribute defaultMaterialScope or whatever?


I also tried env var MAYAUSD_MATERIALS_SCOPE_NAME to tweak the default material scope which seems to work however it does not allow a nested prim scope. MAYAUSD_MATERIALS_SCOPE_NAME=/asset/mtl creates materials in a scope named _asset_mtl and MAYAUSD_MATERIALS_SCOPE_NAME=asset/mtl creates it as asset_mtl, likely swapping the / with a valid character like _ instead of considering it to be a child prim.

(Of course that does leave to wonder; what should the parent prims be defined as if not existing? Xform? Scope? And is that Def or Over?)


Or would you maybe have any example USD API logic that for now could move the /mtl materials to /asset/mtl without the restrictions? I'd be fine if for now I just need to run a script to correct the material locations.

@BigRoy
Copy link
Contributor Author

BigRoy commented Nov 15, 2023

It seems that this works to e.g. reparent /mtl opinions in current layer elsewhere, but it does not update the material bindings:

from maya import cmds
import mayaUsd
from pxr import Sdf


def reparent_prim_spec(layer, src_prim_path, dest_parent):
    
    src_prim_path = Sdf.Path(src_prim_path)
    dest_prim_path = Sdf.Path(dest_parent)
    
    prim = layer.GetPrimAtPath(src_prim_path)
    if not prim:
        return
        
    with Sdf.ChangeBlock():
        edit = Sdf.BatchNamespaceEdit()
        edit.Add(Sdf.NamespaceEdit.Reparent(src_prim_path, dest_parent, -1))
        layer.Apply(edit)

proxies = cmds.ls(type="mayaUsdProxyShape", long=True)
for proxy in proxies:
    stage = mayaUsd.ufe.getStage("|world" + proxy)
    layers = stage.GetLayerStack()
    targets = cmds.mayaUsdEditTarget(proxy, query=True, editTarget=True)
    target = targets[0]  # edit target layer identifier
    
    layer = next(layer for layer in stage.GetLayerStack() if layer.identifier == target)
    reparent_prim_spec(layer, "/mtl", "/asset")

But it does not update the material bindings :(

Is there a better way to do this and bring along e.g. the shader assignments (material bindings on the other prims)


I suppose this code is somewhat equivalent of what I'm trying to do so might be a good reference.

And apparently edits like these are not simple/trivial currently but there is a namespace editing proposal underway, see OpenUSD-proposals: namespace editing.

Nonetheless - if there's any code that could help with my case it'd be greatly appreciated!

@BigRoy
Copy link
Contributor Author

BigRoy commented Nov 15, 2023

This appears to work:

from maya import cmds
import mayaUsd
from pxr import Sdf, Usd

LIST_ATTRS = ['addedItems', 'appendedItems', 'deletedItems', 'explicitItems',
              'orderedItems', 'prependedItems']


def repath_properties(layer, old_path, new_path):
    """Re-path property relationship targets and attribute connections.

    This will replace any relationship or connections from old path
    to new path by replacing start of any path that matches the new path.

    Args:
        layer (Sdf.Layer): Layer to move prim spec path.
        old_path (Union[Sdf.Path, str]): Source path to move from.
        new_path (Union[Sdf.Path, str]): Destination path to move to.

    Returns:
        bool: Whether any re-pathing occurred for the given paths.

    """

    old_path_str = str(old_path)
    peformed_repath = False

    def replace_in_list(spec_list):
        """Replace paths in SdfTargetProxy or SdfConnectionsProxy"""
        for attr in LIST_ATTRS:
            entries = getattr(spec_list, attr)
            for i, entry in enumerate(entries):
                entry_str = str(entry)
                if entry == old_path or entry_str.startswith(
                        old_path_str + "/"):
                    # Repath
                    entries[i] = Sdf.Path(
                        str(new_path) + entry_str[len(old_path_str):])
                    peformed_repath = True

    def repath(path):
        spec = layer.GetObjectAtPath(path)
        if isinstance(spec, Sdf.RelationshipSpec):
            replace_in_list(spec.targetPathList)
        if isinstance(spec, Sdf.AttributeSpec):
            replace_in_list(spec.connectionPathList)

    # Repath any relationship pointing to this src prim path
    layer.Traverse("/", repath)

    return peformed_repath


def move_prim_spec(layer, src_prim_path, dest_prim_path):
    """Move a PrimSpec and repath connections.

    Note that the parent path of the destination must
    exist, otherwise the namespace edit to that path
    will fail.

    Args:
        layer (Sdf.Layer): Layer to move prim spec path.
        src_prim_path (Union[Sdf.Path, str]): Source path to move from.
        dest_prim_path (Union[Sdf.Path, str]): Destination path to move to.

    Returns:
        bool: Whether the move was successful

    """

    src_prim_path = Sdf.Path(src_prim_path)
    dest_prim_path = Sdf.Path(dest_prim_path)
    dest_parent = dest_prim_path.GetParentPath()
    dest_name = dest_prim_path.name
    layer.GetPrimAtPath(dest_prim_path)

    with Sdf.ChangeBlock():
        reparent_edit = Sdf.NamespaceEdit.ReparentAndRename(
            src_prim_path,
            dest_parent,
            dest_name,
            -1
        )

        edit = Sdf.BatchNamespaceEdit()
        edit.Add(reparent_edit)
        if not layer.Apply(edit) and layer.GetPrimAtPath(src_prim_path):
            print("Failed prim spec move")
            return False

        repath_properties(layer, src_prim_path, dest_prim_path)

    return True


proxies = cmds.ls(type="mayaUsdProxyShape", long=True)
for proxy in proxies:
    stage = mayaUsd.ufe.getStage("|world" + proxy)
    layers = stage.GetLayerStack()
    targets = cmds.mayaUsdEditTarget(proxy, query=True, editTarget=True)
    target = targets[0]  # edit target layer identifier

    layer = next(
        layer for layer in stage.GetLayerStack() if layer.identifier == target)

    # Reparent and repath connections and relationships
    move_prim_spec(layer, "/mtl", "/asset/mtl")

    # Force viewport reset because sometimes viewport doesn't recognize
    # the shader moved
    cmds.ogs(reset=True)

Snippet also posted here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Needs triage
Development

No branches or pull requests

3 participants