USDZ not including textures using typical export workflow

USDZ not including textures using typical export workflow

ian3GB9G
Explorer Explorer
1,797 Views
7 Replies
Message 1 of 8

USDZ not including textures using typical export workflow

ian3GB9G
Explorer
Explorer

I can't seem to get textures to go along with my USDZ exports now. I've done this a dozen times in 3dsmax 2023 with the previous USDZ exporter, but with 2024 and the lastest USD plugin, I just can't get textures on my objects to save. I opened an older scene that has exported fine in the past and re-exported with the new setup and textures didn't show. Does anybody know what I'm doing wrong? I have an image attached of a setup that doesn't export properly. I brought the USDZ into Blender to test it too (thinking maybe it was just my phone being the issue) but Blender showed no texture on it too. 

0 Likes
Accepted solutions (1)
1,798 Views
7 Replies
Replies (7)
Message 2 of 8

Max.macmillan
Autodesk
Autodesk

Hello,

 

Could you enable Logging in the USD Exporter. And set the Output to Error and warnings and share the output?


Max MacMillan
QA Analyst, 3ds Max, Maya
Entertainment Creation Products, DCP
0 Likes
Message 3 of 8

RobH2
Advisor
Advisor

Unfortunately USD in Max is not as robust and viable as we would all like it to be. Each time I dive in and decide to learn USD, NVidia Composer, etc., I’m met with all kinds of functionality issues. In Max 2023 I can’t export a Tycache but in Max 2025 I can. USD is awesome, but from my perspective, it’s still incomplete and Beta. I waste a couple of days every month trying to get it to work well and am so far always disappointed and just drop it till the next month. I hope that a year from now I’ll be cranking a long with no issues. 

 

So it may not be you, it just may be that USD is just not up to speed yet. 


Rob Holmes

EESignature

------------------------------------------------------------------------------------------------------------------------------------------
3ds Max (2023-2025), V-Ray 6.2, Ryzen 9 3950-X Processor, DDR 4 128MB, Gigabyte Aorus X570 Master motherboard, Sabrent Rocket NVMe 4.0 M.2 drives, NVidia RTX 4090, Space Pilot Pro, Windows 11 Pro x64, Tri-Monitor, Cintiq 13HD, Windows 11 x64
------------------------------------------------------------------------------------------------------------------------------------------
0 Likes
Message 4 of 8

julien.deboise
Autodesk
Autodesk
Accepted solution

The export for the last few versions uses USD Shade NodeGraphs (https://openusd.org/dev/api/class_usd_shade_node_graph.html), so to comply with UsdShade connection rules. I dont think this is well supported in the iOS viewers - which unless I am mistaken, support the "arkit" spec. "arkit"  only supports a subset of USD features. When we export to USDZ, we do not comply with this subset of the USD spec.

 

Unreal similarly lacks support for USD NodeGraphs, and I had written a little chaser plugin for someone else to bypass the usage of NodeGraphs (see bellow). 

 

After running this script, you will have the option to enable this chasers in the exporter UI like so : 

juliendeboise_0-1728504317260.png

 

What this chaser (a chaser is just some code that runs after the export to post-process the resulting usd stage) does is bypass the nodegraphs by connecting the materials directly onto the texture nodes. This violates some usd shade connection rules - but these are very rarely enforced and it might work for you (disclaimer : have only tried this for unreal, only suspect the issue is the same).

 

As I said though, the ARKIT USD spec is a small subset of what is supported in USD and so many other things may not work.

 

 

 

 

import maxUsd
from pxr import Usd
from pxr import UsdShade
from pymxs import runtime as mxs

import os, sys, glob

class matFixChaser(maxUsd.ExportChaser):

    def __init__(self, factoryContext, *args, **kwargs):
        super(matFixChaser, self).__init__(factoryContext, *args, **kwargs)
        self.stage = factoryContext.GetStage()

    def isId(self, id , shader):
        info_id_attr = shader.GetIdAttr()
        if info_id_attr:
            id_value = info_id_attr.Get()
            return id_value == id
        return False

    def PostExport(self):
        try:
            print("Running Unreal UsdPreviewSurface fix chaser...")
            
            all_shaders = [x for x in self.stage.Traverse() if UsdShade.Shader(x)]
            for prim in all_shaders:
                
                shader = UsdShade.Shader(prim)                
                if self.isId('UsdPreviewSurface', shader):
                    
                     # Iterate over the shader's inputs
                    for input in shader.GetInputs():
                        # Check if the input is connected
                        if input.HasConnectedSource():
                            # Get the connected path
                            source = input.GetConnectedSource()
                            source_prim = source[0].GetPrim()
                            
                            # The node graph connection...
                            connection_path = source_prim.GetPath()
                            # print(connection_path)
                            
                            # Instead connect to the USD texture 2d that lives inside the node graph.
                            new_connection = connection_path.AppendChild(source_prim.GetName())
                            # print(new_connection)
                            
                            texture = UsdShade.Shader(self.stage.GetPrimAtPath(new_connection))
                            input.ConnectToSource(texture.ConnectableAPI(), 'rgb')
                            
                            
                    continue
                if self.isId('UsdPrimvarReader_float2', shader):                    
                    varname_input = shader.GetInput("varname")
                    varname_value_attributes = varname_input.GetValueProducingAttributes()
                    primvar_name = varname_value_attributes[0].Get()
                    # Punch in value directly, not from nodegraph...
                    varname_input.Set(primvar_name)
                                        
                            
        except Exception as e:
            print('Chaser ERROR : %s' % str(e))
            print(traceback.format_exc())
        
        return True

        
maxUsd.ExportChaser.Register(matFixChaser, "unrealMatFixer", "Unreal UsdPreviewSurface fix chaser", "Connects straight to textures, bypassing nodegraphs.")


def unrealMatFixContext():
    extraArgs = {}    
    extraArgs['chaser']  = ['unrealMatFixer']
    extraArgs['chaserNames']  = ['unrealMatFixer']
    return extraArgs

registeredContexts = maxUsd.JobContextRegistry.ListJobContexts()
if 'unrealMatFixContext' not in registeredContexts:
    maxUsd.JobContextRegistry.RegisterExportJobContext("unrealMatFixContext", "Fix USD Preview Surface for Unreal", "Fixes USD preview surface materials for usage in Unreal, avoiding NodeGraphs.", unrealMatFixContext)

print("Registered unreal material fixer")

 

 

 

 

 

Message 5 of 8

ian3GB9G
Explorer
Explorer

Thank you! I will try the code this week and let you know how it goes.

0 Likes
Message 6 of 8

ian3GB9G
Explorer
Explorer

I'm not the best with scripting. I copy/pasted and saved as a .ms in the max script startup folder. When I manually ran it I got an error. How should I proceed? 

0 Likes
Message 7 of 8

julien.deboise
Autodesk
Autodesk

It is a python script, not maxscript - you need to save it as a .py file instead so that 3dsmax knows to run it as Python before running it manually. I dont think startup scripts can be python - but you can do this : https://help.autodesk.com/view/MAXDEV/2023/ENU/?guid=MAXDEV_Python_using_pymxs_pymxs_differences_pym... 

0 Likes
Message 8 of 8

ian3GB9G
Explorer
Explorer

Thank you so much! It worked perfectly.