3ds Max USD API Reference
Loading...
Searching...
No Matches
3ds Max USD SDK

Welcome to the 3ds Max USD SDK. This package enables customization of the 3ds Max USD component, specifically the ability to customize import and export operations. It is important to use the SDK version that targets your version of 3ds Max (2021, 2022, 2023, or 2024). Extensions created using this SDK are extensions to the 3ds Max USD component, not stand-alone 3ds Max plug-ins, and so must be registered accordingly (see below).

Purpose

Using this SDK, the 3ds Max USD export can be extended to support 3rd party objects and materials, and to add custom data that is not currently exported in the default 3ds Max USD system.

For example, the 3ds Max USD plugin currently exports standard lights, cameras, shapes, helpers and mesh nodes. You might have defined a 3ds Max node which has its own USD schema or extended type. Using this SDK, you can properly handle the export rather than relying on the default fallback mesh export method.

The initial focus is on extending the default 3ds Max USD export implementation.

SDK Contents

The 3ds Max USD SDK contains:

  • /maxusd - The main SDK, containing headers and libraries
  • /maxsdk - The 3ds Max C++ SDK, for the target 3ds Max version. The APIs in this SDK provide access to 3ds Max itself, for example scene data, which might be needed by the 3ds Max USD extension.
  • /Pixar_USD - Pixar USD headers and libraries, including those to support HDF5 and MaterialX.
  • /Python - A Python environment.
  • /samples - A set of C++ and Python sample projects that illustrate how to use the APIs in this SDK. See Samples below.
  • /spdlog - A fast, open source logging library.

Requirements

The requirements for using this SDK are the same as the 3ds Max SDK.

Samples

The SDK contains a set of sample projects to illustrate how to implement the available APIs. The samples are available in C++ and Python. They are constructed to be easily added individually as application plugins to 3ds Max. Remember however that those extensions are not 3ds Max plugins but are plugins to the 3ds Max USD plugin, and are registered in a different way.

  • glTFMaterialWriterPlugin - an example of how to implement a ShaderWriter that exports glTFMaterial to UsdPreviewSurface. This is a minimal, non-exhaustive example.
  • SpherePrimWriterPlugin - an example of how to implement a PrimWriter that exports 3ds Max spheres to USD native spheres.
  • UserDataExportChaserPlugin - an example of how to implement an ExportChaser to expose user properties and custom attributes to a USD custom data node.

C++ Main classes

The following is an overview on the set of classes included in the SDK.

MaxUsdShaderWriter

Defines a class that translates a 3ds Max material to a UsdShade prim. The shader writer is specific to the shading schema it supports (UsdPreviewSurface, MaterialX, etc.). The shader writer is registered to a specific material type.

MaxUsdShaderWriterRegistry

Registry mapping of 3ds Max materials and the shader writers exporting those material types. Loads the registered ShaderWriter plugin required at material export.

MaxUsdShadingModeRegistry

Registry keeping a list of possible material targets (conversion to) when exporting 3ds Max materials to USD (by default, UsdPreviewSurface is the material target). The list of available shading mode exporters is also kept in the registry. It enables you to register exporters that may use another material schema (for example Renderman UsdRiMaterialAPI).

MaxUsdShadingModeExporter

Cycles through the material list extracted from the exported 3ds Max nodes and calls the proper shader writer. A shading mode exporter implements the pre, export and post methods of the material export. A default exporter is already implemented.

MaxUsdPrimWriter

Defines a class translating a 3ds Max node to a UsdGeom prim. The prim writer can be written to be type-specific or support a larger family type. It is up to the prim writer to define which nodes it exports.

MaxUsdPrimWriterRegistry

Registry keeping a list of possible prim writers available at export.

MaxUsdExportChaser

Defines a plugin that runs after the core 3ds Max USD export. This should ideally be used to make small changes or to add attributes in a non-destructive way. Chasers need to be very careful as to not modify the structure of the USD file.

MaxUsdExportChaserRegistry

Registry mapping between the ExportChaser and their 'name'.

Creating a plugin project

Plugins need to link against the maxsdk, pxrusd and the maxUsd (from the 3ds Max USD component) core.lib;maxutil.lib;maxUsd.lib and 3dsmax_<usdlibs>.lib

  • The maxsdk and pxrusd artifacts are the ones listed in the artifact file from the 3ds Max USD component.
  • The libs and includes directories can be found in the SDK package.

Registering a plugin

The PrimWriterRegistry, the ShaderWriterRegistry and the ShaderModeRegistry use the PlugRegistry concept from USD.

See the USD Registry Reference for more information.

Plugins are registered by providing a path or paths to JSON files that describe the location, structure and contents of the plugin. The standard name for these files in plugInfo.json. The Info sub-section in the plugInfo schema contains those nested properties:

  • MaxUsd : Required property identifying the 3ds Max Info block.
  • PrimWriter : Optional property stating the current plugin contains PrimWriters.
  • ShadingModePlugin : Optional property stating the current plugin contains a ShadingMode definition.
  • ShaderWriter : Optional property stating the current plugin contains ShaderWriters.
  • providesTranslator : Required property listing the translated 3ds Max material; the strings making up the array are the material non-localized names.
  • ExportChaser: Option property stating the current plugin contains an ExportChaser.

Here is a possible sample plugInfo.json file declaring a C++ plugin containing the implementation of:

  • a PrimWriter handling the translation of 3ds Max Nodes,
  • a new material target to (material conversion type) or a shader mode exporter (both part of the ShadingModeRegistry),
  • a ShaderWriter handling the translation of the 3ds Max "Physical Material" material type,
  • an ExportChaser fine-tuning the export stage.
{
"Plugins":[
{
"Info":{
"MaxUsd":{
"PrimWriter": {},
"ShadingModePlugin": {},
"ShaderWriter":{
"providesTranslator":[
"Physical Material"
]
},
"ExportChaser": {}
}
},
"Name":"sampleMaxUsdPlugin",
"Type":"library",
"LibraryPath":"SampleMaxUSDPlugin.dll"
}
]
}

In case of a Python plugin, the plugin script location must part of the Python path (this can be achieved in a startup script with the Python command sys.path.insert).

For the USD PlugRegistry to find the plugInfo.json file, the file directory must be registered.

from pxr import Plug
Plug.Registry.RegisterPlugins(<usdPluginsPath>)

Again, this can be achieved in a 3ds Max startup script for the plugin, or you can use the environment variable PXR_PLUGINPATH_NAME to define the USD plugin path (the path where the plugInfo.json file is located).

Regardless of the type of plugin, C++ or Python, to register your plugin you'll need to add a registration script to your 3ds Max plugin in the "post-start-up scripts parts", these USD plugins need to be loaded only when Max is ready to be interacted with and NOT be loaded by the 3ds Max plugin itself prior to that. You can find examples of such scripts in the SDK sample projects.

When creating a C++ plugin (.dll), it should be a separate project from your actual 3ds Max plugin. USD plugin DLLs should be loaded via the USD plugin API, USD does not expect the DLL to be already loaded when the plugin is loaded - and if it is, that can create issues.

For a C++ plugin it is also very important to set the project option "Remove unreferenced code and data" to NO. Not doing so could cause the Macro to be optimized out and the Writer to never be properly registered.

Python API

New Python module maxUsd located with component installed files in Contents/bin/python. The Python path is already added to the list of directories that should be searched for modules when using import.

To import the 3ds Max USD Python module, a user can simply use import maxUsd.

ShaderWriter

It contains the ShaderWriter base class from which material writers need to inherit from. A ShaderWriter instance is created for each material needing translation.

Two methods need to be implemented to have functional ShaderWriter:

  • CanExport(exportArgs) – a static class method which returns an enum value stating if the export context is maxUsd.ShaderWriter.ContextSupport::Supported, or Unsupported, or that the class acts as a Fallback.
  • Write()**– the write method called to properly export the material Some accessors are made available to get access to the material export context:
  • **GetStage() - get the USD stage being written to
  • GetUsdPath() - get the USD prim destination
  • SetUsdPrim() - set the USD Shade prim
  • GetUsdPrim() - get the USD prim being written to
  • GetMaterial() - get the MAXScript AnimHandle on the material being exported
  • GetFilename() - get the file name and path where the stage is written to on disk
  • GetExportArgs() - get the current global export args in effect class USDSceneBuilderOptions

To register the ShaderWriter to use for the supported materials:

maxUsd.ShaderWriter.Register(<derived_writershader_class>, "<non_localized_material_name>")

Example:

import maxUsd
from pymxs import runtime as rt
from pxr import UsdShade
import traceback
class SampleShaderWriter(maxUsd.ShaderWriter):
"""
SampleShader Writer class handling the material translation for
the 3ds Max default materials
inherits from the base class ShaderWriter from the 3ds Max USD component
"""
@classmethod
def CanExport(cls, exportArgs):
"""
Static class method required to determine if the class is handling the
translation context required by the export job. The current class only
handles converting materials to 'UsdPreviewSurface' elements.
"""
if exportArgs.GetConvertMaterialsTo == "UsdPreviewSurface":
return maxUsd.ShaderWriter.ContextSupport.Supported
else:
return maxUsd.ShaderWriter.ContextSupport.Unsupported
def Write(self):
"""Main export function that runs when the applicable material gets hit"""
try:
# the 'GetMaterial()' method returns the anim handle on the material
# need to fetch the anim on the handle to get back the pymxs.Material back
material = rt.GetAnimByHandle(self.GetMaterial())
# create the Shade prim
nodeShader = UsdShade.Shader.Define(self.GetUsdStage(), self.GetUsdPath())
nodeShader.CreateIdAttr("UsdPreviewSurface")
# assign the created Shade prim as the USD prim for the ShaderWriter
self.SetUsdPrim(nodeShader.GetPrim())
# here the actual code to export the material attributes
except Exception as e:
# Quite useful to debug errors in a Python callback
print('Write() - Error: %s' % str(e))
print(traceback.format_exc())
# register the ShaderWriter to use for the supported materials
maxUsd.ShaderWriter.Register(SampleShaderWriter, rt.PhysicalMaterial.nonLocalizedName) # or use the actual string "Physical Material"

ShadingModeRegistry

The only element exposed to Python from the shading mode registry is the ability to register conversion type (or material target).

maxUsd.ShadingModeRegistry.RegisterExportConversion(<name>,
<render context>,
<nice name>,
<description>)

The name is used directly in the render option string as one of the valid values of the "Materials export to" option of the USD export dialog.

The render context will be used to specialize the binding point. See UsdShadeMaterial documentation for details. A value of UsdShadeTokens->universalRenderContext should be used if the resulting UsdShade nodes are written using an API shared by multiple renderers, like UsdPreviewSurface. For UsdShade nodes targeting a specific rendering engine, please define a custom render context understood by the renderer.

The nice name is the name displayed in the "Materials export to" option of the USD export dialog.

The description is displayed as a tooltip in the "Materials export to" option of the USD export dialog.

PrimWriter

It contains the PrimWriter base class from which object/prim writers need to inherit from. The PrimWriter is only responsible for providing translation of the 3dsMax Object referenced by the received Node. It should therefor not attempt to handle instancing, material assignment, and the transform of the Node itself. Instancing is handled by the calling code - if an object is instanced across multiple nodes, the PrimWriter is only called once, on the first node referencing the instanced object. The required Xform prim hierarchy is already generated. Similarly, the Node's transform is applied by the calling code, on the UsdGeomXformable prim built by the PrimWriter, after it is run. If the USD prim is not a UsdGeomXformable, a warning is raised, but it doesn't prevent the export from continuing.

The instanced PrimWriters are not linked to a specific 3ds Max object. The writers will be called and evaluated for each node in the scene tree. Therefore the methods all receives a parameter nodeHandle, the MAXScript unique NodeHandle on the 3ds Max Node being exported.

The methods that may need overriding definitions:

  • Write(nodeHandle, targetPrim, applyOffsetTransform) - Responsible for writing the prim's attribute for the given context. This is where the translation from the 3ds Max object to the USD prim happens.
  • CanConvert(nodeHandle) - Returns the level of support this writer can provide for a given 3ds Max node's object (maxUsd.PrimWriter.ContextSupport::Supported, Fallback, Unsupported)
  • GetPrimType(nodeHandle) - The prim type we are writing to. For performance reasons, all prims are created ahead of time in a single SdfChangeBlock, so the prim writers are not responsible for creating the prims. The type specified here is mostly a hint for that first creation pass, if required, it can be overriden from the Write() method (by defining a prim at the same path with a different type). Unless we are always forcing the creation of an Xform prim (see RequireXformPrim()), we should return an Xformable type here, otherwise it will not be possible to apply the node's transform onto the prim later on (an error will be raised in this scenario).
  • GetPrimName(nodeHandle, suggestedName) - Returns the name that should be used for the prim. The base implementation should be sufficient in most cases, unless prim writers want to customize the prim's name. If so, it is their responsibility to ensure that the given name is unique amongst siblings. The suggestedName is what the base implementation uses, from the node's name, and uniqueness amongst siblings is ensured.
  • GetObjectPrimSuffix(nodeHandle) - In a few scenarios, we need two prims to properly represent an INode. One for it's transform, and one for the object it references (for example in case of an non-identity object offset transform, that transform must not be inherited, so we can't use a single prim for the INode). In case we do need to perform such a split, the object's prim will have the same name as the node's prim, with an added suffix - the string returned here. Defaults to "Object".
  • HandlesObjectOffsetTransform(nodeHandle) - Whether or not we want to manually handle the object offset transform in the Write().
  • RequiresXformPrim(nodeHandle) - Returns the requirement to split the object from its transform in the scene.
  • maxUsd.XformSplitRequirement.ForOffsetObjects : This should be the case for most objects, this means we require an Xform if an object offset is applied to the object. Indeed, object offset transforms should not be inherited, so we need an Xform prim to encode the node's transform (the children of the node will export to children of this prim), and another for the object itself, which will be exported to a prim under that Xform. It will contain the object offset transform, and it will not have children.
  • maxUsd.XformSplitRequirement.Always : For cases where we always need to have a separate prim for the node's transform. This could be the case if we need to add an inherent transform to the object's prim, part of the translation, and we never want that transform to be inherited.
  • maxUsd.XformSplitRequirement.Never : To be used if we know we never want to split the node's transform from its object. For example we could be baking the object offset transform into the geometry itself - in this scenario, we avoid the need of an extra Xform entirely.
  • GetExportArgs() - get the current global export args in effect
  • GetFilename() - get the file name and path where the stage is written to on disk
  • GetUsdStage() - get the USD stage being written to

To register a PrimWriter

maxUsd.PrimWriter.Register(<derived_primwriter_class>, "<primwriter_given_name>")

ExportChaser

It contains the ExportChaser base class from which export chasers need to inherit from. A ExportChaser instance is created at each export and called at the end of the export process. Two methods need to be implemented to have functional ExportChaser:

  • **__init__(factoryContext, args, kwargs)** - the constructor for the export chaser. The argument factoryContext (detailed below) contains the context details for the export.
  • PostExport() - the method being called at the end of the standard export process.

To register the ExportChaser

maxUsd.ExportChaser.Register(<derived_exportchaser_class>, "<exportchaser_given_name>")

MaxUsdExportChaserRegistry::FactoryContext

This exposes the FactoryContext, the argument received by the ExportChaser constructor.

  • GetStage() - get the USD stage being written to
  • GetPrimsToNodeHandles() - returns a dictionary mapping full USD prim paths to MAXScript NodeHandles.
  • GetJobArgs() - get the current global export args in effect class USDSceneBuilderOptions
  • GetFilename() - get the file name and path where the stage is written to on disk

Example

import maxUsd
from pymxs import runtime as rt
import traceback
class SampleExportChaser(maxUsd.ExportChaser):
"""
SampleExportChaser class to make small changes or
to add attributes in a non-destructive way in an exported stage
inherits from the base class ExportChaser from the 3ds Max USD component
"""
stage = ""
primsToNodes = {}
ChaserArgs = {}
def __init__(self, factoryContext, *args, **kwargs):
"""The constructor for the ExportChaser"""
super(SampleExportChaser, self).__init__(factoryContext, *args, **kwargs)
SampleExportChaser.stage = factoryContext.GetStage()
SampleExportChaser.primsToNodes = factoryContext.GetPrimsToNodesHandle()
SampleExportChaser.ChaserArgs = factoryContext.GetJobArgs().GetAllChaserArgs()['samplechaser']
def PostExport(self):
"""Post export function that runs after the standard export process"""
try:
print(SampleExportChaser.stage)
print(SampleExportChaser.primsToNodes)
print(SampleExportChaser.ChaserArgs)
except Exception as e:
# Quite useful to debug errors in a Python callback
print('Write() - Error: %s' % str(e))
print(traceback.format_exc())
maxUsd.ExportChaser.Register(SampleExportChaser, "samplechaser")

JobExport arguments (USDSceneBuilderOptions)

It contains the class USDSceneBuilderOptions which exposes the export arguments from the current export context:

  • GetShadingMode() - gets the shading schema (mode) to use for material export
  • GetConvertMaterialsTo() - returns a token identifier of the USD material type targeted to convert the 3ds Max materials (to which USD material are we exporting to, for example UsdPreviewSurface)
  • GetAllMaterialConversions() - gets the set of targeted materials for material conversion
  • GetChannelPrimvarName() - gets the primvar name of a given channel
  • GetTranslateMeshes() - check if 3ds Max meshes should be translated into USD meshes
  • GetTranslateShapes() - check if 3ds Max shapes should be translated into USD meshes
  • GetTranslateLights() -check if 3ds Max lights should be translated into USD lights
  • GetTranslateCameras() - check if 3ds Max cameras should be translated into USD cameras
  • GetTranslateMaterials() - check if materials should be translated
  • GetTranslateHidden() - check if hidden objects should be translated
  • GetChannelPrimvarType(channel) - gets the primvar type associated with a given max channel on export (maxUsd.MappedAttributeBuilder.Type.Color3fArray, FloatArray, Float2Array, Float3Array, TexCoord2fArray, TexCoord3fArray)
  • GetChannelPrimvarName(channel) - gets the primvar name of a given channel
  • GetChannelPrimvarAutoExpandType(channel) - gets whether to auto-expand the primvar type based on the data (for example TexCoord2F -> TexCoord3f if some UVs are using the W component)
  • GetUsdStagesAsReferences() - checks if USD Stage Objects should be exported as USD References
  • GetUseUSDVisibility() - check if we should attempt to match the Hidden state in Max with the USD visibility attribute
  • GetFileFormat() - returns the format of the file to export (maxUsd.FileFormat.Binary, ASCII, USDZ)
  • GetNormalsMode() - returns how normals should be exported (maxUsd.NormalsMode.AsPrimVar, AsAttribute, None)
  • GetMeshFormat() - returns how meshes should be exported (maxUsd.MeshFormat.FromScene, TriMesh, PolyMesh)
  • GetTimeMode() - gets the time mode to be used for export (maxUsd.TimeMode.AnimationRange, CurrentFrame, ExplicitFrame, FrameRange)
  • GetStartFrame() - gets the first frame from which to export, only used if the time mode is configured as ExplicitFrame or FrameRange
  • GetEndFrame() - gets the last frame from which to export, only used if the time mode is configured as FrameRange
  • GetSamplesPerFrame() - gets the number of samples to be exported to USD, per frame
  • GetUpAxis() - returns the "up axis" of the USD Stage produced from the translation of the 3ds Max content (maxUsd.UpAxis.Y or Z)
  • GetBakeObjectOffsetTransform() - gets whether or not the Object-offset transform should be baked into the geometry
  • GetPreserveEdgeOrientation() - gets whether or not to preserve max edge orientation
  • GetRootPrimPath() - gets the configured root prim path
  • GetLogPath() - gets the path to the log file
  • GetLogLevel() - gets the log level (maxUsd.Log.Level.Off, Error, Warn, Info)
  • GetOpenInUsdview() - check if the produced USD file should be opened in USDVIEW at the end of the export
  • GetChaserNames() - gets the list of export chasers to be called at USD export
  • GetAllChaserArgs() - gets the dictionary of export chasers with their specified arguments