From 0779aca574d95d274cd461b2c25544b2120c00ae Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Thu, 24 Oct 2019 15:12:14 -0400 Subject: [PATCH 01/15] Create ProxyShapeBase out of Pixar plugin --- CMakeLists.txt | 49 +- build.py | 124 +- cmake/defaults/CXXDefaults.cmake | 4 +- cmake/mayausd_version.info | 3 + cmake/modules/FindUSD.cmake | 14 +- cmake/utils.cmake | 31 +- lib/CMakeLists.txt | 209 ++++ lib/README.md | 4 +- lib/base/api.h | 78 ++ .../lib/usdMaya => lib/base}/debugCodes.cpp | 2 +- .../lib/usdMaya => lib/base}/debugCodes.h | 4 +- lib/base/mayaUsd.h.src | 46 + .../lib/usdMaya => lib/listeners}/notice.cpp | 2 +- .../lib/usdMaya => lib/listeners}/notice.h | 20 +- lib/listeners/proxyShapeNotice.cpp | 38 + lib/listeners/proxyShapeNotice.h | 48 + .../listeners}/stageNoticeListener.cpp | 2 +- .../listeners}/stageNoticeListener.h | 10 +- lib/nodes/proxyShapeBase.cpp | 1027 +++++++++++++++++ lib/nodes/proxyShapeBase.h | 294 +++++ lib/nodes/proxyShapePlugin.cpp | 112 ++ lib/nodes/proxyShapePlugin.h | 58 + .../lib/usdMaya => lib/nodes}/stageData.cpp | 41 +- lib/nodes/stageData.h | 138 +++ .../usdMaya => lib/nodes}/usdPrimProvider.cpp | 2 +- .../usdMaya => lib/nodes}/usdPrimProvider.h | 4 +- .../lib/usdMaya => lib/utils}/colorSpace.cpp | 2 +- .../lib/usdMaya => lib/utils}/colorSpace.h | 4 +- .../maya/lib/usdMaya => lib/utils}/query.cpp | 6 +- .../maya/lib/usdMaya => lib/utils}/query.h | 6 +- .../lib/usdMaya => lib/utils}/stageCache.cpp | 4 +- .../lib/usdMaya => lib/utils}/stageCache.h | 9 +- .../maya/lib/usdMaya => lib/utils}/util.cpp | 18 +- .../pxr/maya/lib/usdMaya => lib/utils}/util.h | 104 +- lib/utils/utilFileSystem.cpp | 99 ++ lib/utils/utilFileSystem.h | 42 + plugin/al/CMakeLists.txt | 7 +- plugin/al/translators/CMakeLists.txt | 2 +- .../ProxyShapeTranslator.cpp | 2 +- plugin/pxr/CMakeLists.txt | 4 - .../pxr/maya/lib/pxrUsdMayaGL/batchRenderer.h | 4 +- .../maya/lib/pxrUsdMayaGL/instancerImager.h | 4 +- .../pxrUsdMayaGL/instancerShapeAdapter.cpp | 2 +- .../lib/pxrUsdMayaGL/proxyDrawOverride.cpp | 6 +- .../lib/pxrUsdMayaGL/proxyShapeDelegate.cpp | 4 +- .../maya/lib/pxrUsdMayaGL/proxyShapeUI.cpp | 6 +- .../lib/pxrUsdMayaGL/usdProxyShapeAdapter.cpp | 4 +- plugin/pxr/maya/lib/usdMaya/CMakeLists.txt | 10 +- plugin/pxr/maya/lib/usdMaya/adaptor.cpp | 2 +- .../pxr/maya/lib/usdMaya/chaserRegistry.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/chaserRegistry.h | 2 +- .../maya/lib/usdMaya/diagnosticDelegate.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/editUtil.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/exportCommand.cpp | 2 +- .../pxr/maya/lib/usdMaya/hdImagingShape.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/hdImagingShape.h | 2 +- .../maya/lib/usdMaya/instancedNodeWriter.cpp | 2 +- .../maya/lib/usdMaya/instancedNodeWriter.h | 2 +- plugin/pxr/maya/lib/usdMaya/jobArgs.h | 2 +- .../lib/usdMaya/pointBasedDeformerNode.cpp | 8 +- .../maya/lib/usdMaya/primReaderRegistry.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/primWriter.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/primWriter.h | 2 +- .../maya/lib/usdMaya/primWriterRegistry.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/proxyShape.cpp | 787 +------------ plugin/pxr/maya/lib/usdMaya/proxyShape.h | 156 +-- plugin/pxr/maya/lib/usdMaya/readJob.cpp | 4 +- .../lib/usdMaya/readJob_ImportWithProxies.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/readUtil.cpp | 4 +- .../maya/lib/usdMaya/referenceAssembly.cpp | 34 +- .../pxr/maya/lib/usdMaya/referenceAssembly.h | 2 +- .../pxr/maya/lib/usdMaya/registryHelper.cpp | 2 +- .../lib/usdMaya/shadingModeDisplayColor.cpp | 2 +- .../maya/lib/usdMaya/shadingModeExporter.cpp | 2 +- .../maya/lib/usdMaya/shadingModeExporter.h | 2 +- .../usdMaya/shadingModeExporterContext.cpp | 2 +- .../lib/usdMaya/shadingModeExporterContext.h | 2 +- .../maya/lib/usdMaya/shadingModePxrRis.cpp | 2 +- .../lib/usdMaya/shadingModeUseRegistry.cpp | 2 +- .../maya/lib/usdMaya/skelBindingsProcessor.h | 2 +- plugin/pxr/maya/lib/usdMaya/stageData.h | 108 -- plugin/pxr/maya/lib/usdMaya/stageNode.cpp | 12 +- .../pxr/maya/lib/usdMaya/transformWriter.cpp | 2 +- .../pxr/maya/lib/usdMaya/translatorCamera.cpp | 2 +- .../pxr/maya/lib/usdMaya/translatorGprim.cpp | 2 +- .../maya/lib/usdMaya/translatorMaterial.cpp | 2 +- .../pxr/maya/lib/usdMaya/translatorMaterial.h | 2 +- .../pxr/maya/lib/usdMaya/translatorMesh.cpp | 2 +- .../lib/usdMaya/translatorMesh_PrimVars.cpp | 2 +- .../lib/usdMaya/translatorMesh_SubDiv.cpp | 2 +- .../lib/usdMaya/translatorModelAssembly.cpp | 4 +- .../pxr/maya/lib/usdMaya/translatorPrim.cpp | 2 +- .../maya/lib/usdMaya/translatorRfMLight.cpp | 2 +- .../pxr/maya/lib/usdMaya/translatorSkel.cpp | 2 +- .../pxr/maya/lib/usdMaya/translatorUtil.cpp | 2 +- .../maya/lib/usdMaya/userTaggedAttribute.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/wrapAdaptor.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/wrapAssembly.cpp | 2 +- .../pxr/maya/lib/usdMaya/wrapColorSpace.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/wrapEditUtil.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/wrapMeshUtil.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/wrapQuery.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/wrapReadUtil.cpp | 2 +- .../pxr/maya/lib/usdMaya/wrapStageCache.cpp | 2 +- .../lib/usdMaya/wrapUserTaggedAttribute.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/wrapWriteUtil.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/writeJob.cpp | 2 +- plugin/pxr/maya/lib/usdMaya/writeJob.h | 2 +- .../pxr/maya/lib/usdMaya/writeJobContext.cpp | 4 +- plugin/pxr/maya/lib/usdMaya/writeUtil.cpp | 2 +- plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt | 1 + plugin/pxr/maya/plugin/pxrUsd/plugin.cpp | 13 +- .../usdPreviewSurfaceWriter.cpp | 2 +- .../plugin/pxrUsdTranslators/cameraWriter.cpp | 2 +- .../pxrUsdTranslators/fileTextureWriter.cpp | 2 +- .../pxrUsdTranslators/instancerWriter.cpp | 2 +- .../plugin/pxrUsdTranslators/jointWriter.cpp | 2 +- .../plugin/pxrUsdTranslators/meshWriter.cpp | 2 +- .../pxrUsdTranslators/meshWriter_Primvars.cpp | 4 +- 119 files changed, 2643 insertions(+), 1325 deletions(-) create mode 100644 cmake/mayausd_version.info create mode 100644 lib/CMakeLists.txt create mode 100644 lib/base/api.h rename {plugin/pxr/maya/lib/usdMaya => lib/base}/debugCodes.cpp (96%) rename {plugin/pxr/maya/lib/usdMaya => lib/base}/debugCodes.h (90%) create mode 100644 lib/base/mayaUsd.h.src rename {plugin/pxr/maya/lib/usdMaya => lib/listeners}/notice.cpp (99%) rename {plugin/pxr/maya/lib/usdMaya => lib/listeners}/notice.h (91%) create mode 100644 lib/listeners/proxyShapeNotice.cpp create mode 100644 lib/listeners/proxyShapeNotice.h rename {plugin/pxr/maya/lib/usdMaya => lib/listeners}/stageNoticeListener.cpp (98%) rename {plugin/pxr/maya/lib/usdMaya => lib/listeners}/stageNoticeListener.h (94%) create mode 100644 lib/nodes/proxyShapeBase.cpp create mode 100644 lib/nodes/proxyShapeBase.h create mode 100644 lib/nodes/proxyShapePlugin.cpp create mode 100644 lib/nodes/proxyShapePlugin.h rename {plugin/pxr/maya/lib/usdMaya => lib/nodes}/stageData.cpp (66%) create mode 100644 lib/nodes/stageData.h rename {plugin/pxr/maya/lib/usdMaya => lib/nodes}/usdPrimProvider.cpp (95%) rename {plugin/pxr/maya/lib/usdMaya => lib/nodes}/usdPrimProvider.h (95%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/colorSpace.cpp (97%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/colorSpace.h (98%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/query.cpp (95%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/query.h (93%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/stageCache.cpp (98%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/stageCache.h (94%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/util.cpp (99%) rename {plugin/pxr/maya/lib/usdMaya => lib/utils}/util.h (94%) create mode 100644 lib/utils/utilFileSystem.cpp create mode 100644 lib/utils/utilFileSystem.h delete mode 100644 plugin/pxr/maya/lib/usdMaya/stageData.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d9fcca67e9..2e1d36fd9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,29 +1,35 @@ -#- +# # ======================================================================= -# Copyright 2019 Autodesk, Inc. All rights reserved. +# Copyright 2019 Autodesk # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at - +# # http://www.apache.org/licenses/LICENSE-2.0 - +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ======================================================================= -#+ +# cmake_minimum_required(VERSION 3.1.1) +project(maya-usd) + #============================================================================== # Define common build variables #============================================================================== -set(BUILD_PXR_PLUGIN ON CACHE BOOL "Build the Pixar USD plugin and libraries.") -set(BUILD_AL_PLUGIN ON CACHE BOOL "Build the Animal Logic USD plugin and libraries.") +option(BUILD_MAYAUSD_LIBRARY "Build Core USD libraries." ON) +option(BUILD_PXR_PLUGIN "Build the Pixar USD plugin and libraries." ON) +option(BUILD_AL_PLUGIN "Build the Animal Logic USD plugin and libraries." ON) +option(WANT_USD_RELATIVE_PATH "Use relative rpaths for USD libraries" OFF) +# Avoid noisy install messages +set(CMAKE_INSTALL_MESSAGE "NEVER") #============================================================================== # Modules and Definitions #============================================================================== @@ -32,17 +38,34 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/defaults ) +if (BUILD_MAYAUSD_LIBRARY) + include(cmake/mayausd_version.info) + set(MAYAUSD_VERSION "${MAYAUSD_MAJOR_VERSION}.${MAYAUSD_MINOR_VERSION}.${MAYAUSD_PATCH_LEVEL}") +endif() + if (APPLE) set(OSX_ARCHITECTURES "x86_64") add_definitions(-DOSMac_ -DMAC_PLUGIN) add_definitions(-D_BOOL -DREQUIRE_IOSTREAM) endif() -include(cmake/python.cmake) +if (DEFINED PYTHON_INCLUDE_DIR AND DEFINED PYTHON_LIBRARIES AND DEFINED PYTHON_EXECUTABLE) + SET(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") + SET(PYTHONLIBS_FOUND TRUE) + find_package(PythonInterp 2.7 REQUIRED) + if(NOT PYTHONINTERP_FOUND) + set(PYTHONLIBS_FOUND FALSE) + endif() +endif() +if (NOT PYTHONLIBS_FOUND) + include(cmake/python.cmake) +endif() include(cmake/utils.cmake) +find_package(Maya REQUIRED) find_package(USD REQUIRED) include(cmake/usd.cmake) +include(${USD_CONFIG_FILE}) #============================================================================== # Compiler @@ -51,7 +74,7 @@ include(cmake/usd.cmake) # Consume them here. This is an effort to keep the most common # build files readable. include(CXXDefaults) -add_definitions(${_PXR_CXX_DEFINITIONS}) +add_definitions(${_PXR_CXX_DEFINITIONS} -DBOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE) set(CMAKE_CXX_FLAGS "${_PXR_CXX_FLAGS} ${CMAKE_CXX_FLAGS}") if(NOT WIN32) @@ -63,6 +86,13 @@ endif() string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +#============================================================================== +# CORE +#============================================================================== +if (BUILD_MAYAUSD_LIBRARY) + add_subdirectory(lib) +endif() + #============================================================================== # PLUGINS #============================================================================== @@ -90,3 +120,4 @@ configure_file(mayaUSD.mod.template ${PROJECT_BINARY_DIR}/mayaUSD.mod) install(FILES ${PROJECT_BINARY_DIR}/mayaUSD.mod DESTINATION ${CMAKE_INSTALL_PREFIX} ) + diff --git a/build.py b/build.py index fa73bf20e5..97a36f9667 100644 --- a/build.py +++ b/build.py @@ -12,6 +12,7 @@ import shutil import subprocess import sys +import distutils.util ############################################################ # Helpers for printing output @@ -153,7 +154,7 @@ def GetCPUCount(): except NotImplementedError: return 1 -def Run(cmd, logCommandOutput=True): +def Run(context, cmd): """Run the specified command in a subprocess.""" PrintInfo('Running "{cmd}"'.format(cmd=cmd)) @@ -165,7 +166,7 @@ def Run(cmd, logCommandOutput=True): # Let exceptions escape from subprocess calls -- higher level # code will handle them. - if logCommandOutput: + if context.redirectOutstreamFile: p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: @@ -207,6 +208,18 @@ def FormatMultiProcs(numJobs, generator): return "{tag}{procs}".format(tag=tag, procs=numJobs) +def onerror(func, path, exc_info): + """ + If the error is due to an access error (read only file) + add write permission and then retries. + If the error is for another reason it re-raises the error. + """ + import stat + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWUSR) + func(path) + else: + raise ############################################################ # contextmanager @contextlib.contextmanager @@ -231,7 +244,7 @@ def RunCMake(context, extraArgs=None, stages=None): buildDir = context.buildDir if 'clean' in stages and os.path.isdir(buildDir): - shutil.rmtree(buildDir) + shutil.rmtree(buildDir, onerror=onerror) if 'clean' in stages and os.path.isdir(instDir): shutil.rmtree(instDir) @@ -267,7 +280,8 @@ def RunCMake(context, extraArgs=None, stages=None): os.remove(context.logFileLocation) if 'configure' in stages: - Run('cmake ' + Run(context, + 'cmake ' '-DCMAKE_INSTALL_PREFIX="{instDir}" ' '-DCMAKE_BUILD_TYPE={variant} ' '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON' @@ -287,14 +301,12 @@ def RunCMake(context, extraArgs=None, stages=None): installArg = "--target install" if 'build' in stages or 'install' in stages: - Run("cmake --build . --config {variant} {installArg} -- {multiproc}" + Run(context, "cmake --build . --config {variant} {installArg} -- {multiproc}" .format(variant=variant, installArg=installArg, multiproc=FormatMultiProcs(context.numJobs, generator))) -############################################################ -# Maya USD -def InstallMayaUSD(context, buildArgs, stages): +def BuildAndInstall(context, buildArgs, stages): with CurrentWorkingDirectory(context.mayaUsdSrcDir): extraArgs = [] stagesArgs = [] @@ -310,17 +322,29 @@ def InstallMayaUSD(context, buildArgs, stages): extraArgs.append('-DMAYA_DEVKIT_LOCATION="{devkitLocation}"' .format(devkitLocation=context.devkitLocation)) - # Add on any user-specified extra arguments. extraArgs += buildArgs - - # Add on any user-specified stages arguments. stagesArgs += stages RunCMake(context, extraArgs, stagesArgs) + # Ensure directory structure is created and is writable. + for dir in [context.workspaceDir, context.buildDir, context.instDir]: + try: + if os.path.isdir(dir): + testFile = os.path.join(dir, "canwrite") + open(testFile, "w").close() + os.remove(testFile) + else: + os.makedirs(dir) + except Exception as e: + PrintError("Could not write to directory {dir}. Change permissions " + "or choose a different location to install to." + .format(dir=dir)) + sys.exit(1) + Print("""Success MayaUSD build and install !!!!""") + ############################################################ # ArgumentParser - parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter) @@ -374,6 +398,9 @@ def InstallMayaUSD(context, buildArgs, stages): "(default: # of processors [{0}])" .format(GetCPUCount()))) +parser.add_argument("--redirect-outstream-file", type=distutils.util.strtobool, dest="redirect_outstream_file", default=True, + help="Redirect output stream to a file. Set this flag to false to redirect output stream to console instead.") + args = parser.parse_args() verbosity = args.verbosity @@ -438,32 +465,39 @@ def __init__(self, args): for argList in args.stages: for arg in argList.split(","): self.stagesArgs.append(arg) + + # Redirect output stream to file + self.redirectOutstreamFile = args.redirect_outstream_file try: context = InstallContext(args) except Exception as e: PrintError(str(e)) sys.exit(1) -# Summarize -summaryMsg = """ -Building with settings: - Source directory {mayaUsdSrcDir} - Workspace directory {workspaceDir} - Build directory {buildDir} - Install directory {instDir} - Variant {buildVariant} - CMake generator {cmakeGenerator} - Build Log {logFileLocation}""" - -if context.buildArgs: - summaryMsg += """ - Extra Build arguments {buildArgs}""" - -if context.stagesArgs: - summaryMsg += """ - Stages arguments {stagesArgs}""" - -summaryMsg = summaryMsg.format( +if __name__ == "__main__": + # Summarize + summaryMsg = """ + Building with settings: + Source directory {mayaUsdSrcDir} + Workspace directory {workspaceDir} + Build directory {buildDir} + Install directory {instDir} + Variant {buildVariant} + CMake generator {cmakeGenerator}""" + + if context.redirectOutstreamFile: + summaryMsg += """ + Build Log {logFileLocation}""" + + if context.buildArgs: + summaryMsg += """ + Build arguments {buildArgs}""" + + if context.stagesArgs: + summaryMsg += """ + Stages arguments {stagesArgs}""" + + summaryMsg = summaryMsg.format( mayaUsdSrcDir=context.mayaUsdSrcDir, workspaceDir=context.workspaceDir, buildDir=context.buildDir, @@ -474,26 +508,10 @@ def __init__(self, args): buildVariant=BuildVariant(context), cmakeGenerator=("Default" if not context.cmakeGenerator else context.cmakeGenerator) -) - -Print(summaryMsg) + ) -# Install MayaUSD -InstallMayaUSD(context, context.buildArgs, context.stagesArgs) + Print(summaryMsg) -# Ensure directory structure is created and is writable. -for dir in [context.workspaceDir, context.buildDir, context.instDir]: - try: - if os.path.isdir(dir): - testFile = os.path.join(dir, "canwrite") - open(testFile, "w").close() - os.remove(testFile) - else: - os.makedirs(dir) - except Exception as e: - PrintError("Could not write to directory {dir}. Change permissions " - "or choose a different location to install to." - .format(dir=dir)) - sys.exit(1) - -Print("""Success Maya USD build and install !!!!""") + # BuildAndInstall + if any(stage in ['clean', 'configure', 'build', 'install'] for stage in context.stagesArgs): + BuildAndInstall(context, context.buildArgs, context.stagesArgs) diff --git a/cmake/defaults/CXXDefaults.cmake b/cmake/defaults/CXXDefaults.cmake index 4d293a0513..755d729407 100644 --- a/cmake/defaults/CXXDefaults.cmake +++ b/cmake/defaults/CXXDefaults.cmake @@ -19,7 +19,7 @@ include(Options) if (CMAKE_COMPILER_IS_GNUCXX) include(gccdefaults) -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") +elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") include(clangdefaults) elseif(MSVC) include(msvcdefaults) @@ -32,7 +32,7 @@ _add_define(GLX_GLXEXT_PROTOTYPES) _add_define(BOOST_PYTHON_NO_PY_SIGNATURES) # Maya seems to require this -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") +if (IS_LINUX) _add_define(LINUX) endif() diff --git a/cmake/mayausd_version.info b/cmake/mayausd_version.info new file mode 100644 index 0000000000..bb330b08ef --- /dev/null +++ b/cmake/mayausd_version.info @@ -0,0 +1,3 @@ +set(MAYAUSD_MAJOR_VERSION 0) +set(MAYAUSD_MINOR_VERSION 1) +set(MAYAUSD_PATCH_LEVEL 0) diff --git a/cmake/modules/FindUSD.cmake b/cmake/modules/FindUSD.cmake index adee9bd56f..e0ca1e21b7 100644 --- a/cmake/modules/FindUSD.cmake +++ b/cmake/modules/FindUSD.cmake @@ -80,15 +80,19 @@ if(USD_INCLUDE_DIR AND EXISTS "${USD_INCLUDE_DIR}/pxr/pxr.h") set(USD_VERSION ${USD_MAJOR_VERSION}.${USD_MINOR_VERSION}.${USD_PATCH_VERSION}) endif() +message(STATUS "USD include dir: ${USD_INCLUDE_DIR}") +message(STATUS "USD library dir: ${USD_LIBRARY_DIR}") +message(STATUS "USD version: ${USD_VERSION}") + include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - USD -REQUIRED_VARS +find_package_handle_standard_args(USD + REQUIRED_VARS PXR_USD_LOCATION USD_INCLUDE_DIR USD_LIBRARY_DIR USD_GENSCHEMA USD_CONFIG_FILE -VERSION_VAR - USD_VERSION) + VERSION_VAR + USD_VERSION +) diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 9e7f506e77..09a2dcc5b4 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -1,6 +1,6 @@ # # ======================================================================= -# Copyright 2019 Autodesk, Inc. All rights reserved. +# Copyright 2019 Autodesk # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -61,3 +61,32 @@ function(find_python_module module) endif(NOT _${module}_status) endif(NOT ${module_found}) endfunction(find_python_module) + +function(mayaUsd_promoteMayaUsdHeader) + set(srcFile ${CMAKE_CURRENT_SOURCE_DIR}/base/mayaUsd.h.src) + set(dstFile ${CMAKE_BINARY_DIR}/include/mayaUsd/mayaUsd.h) + if (NOT EXISTS ${dstFile}) + message(STATUS "promoting: " ${srcFile}) + endif() + configure_file(${srcFile} ${dstFile}) +endfunction() + +function(mayaUsd_promoteHeaderList) + foreach(header ${ARGV}) + set(srcFile ${CMAKE_CURRENT_SOURCE_DIR}/${header}) + set(dstFile ${CMAKE_BINARY_DIR}/include/mayaUsd/${header}) + + set(content "#pragma once\n#include \"${srcFile}\"\n") + + if (NOT EXISTS ${dstFile}) + message(STATUS "promoting: " ${srcFile}) + file(WRITE ${dstFile} "${content}") + else() + file(READ ${dstFile} oldContent) + if (NOT "${content}" STREQUAL "${oldContent}") + message(STATUS "Promoting ${srcfile}") + file(WRITE ${dstFile} "${content}") + endif() + endif() + endforeach() +endfunction() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000000..54d36b56da --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,209 @@ +# +# ======================================================================= +# Copyright 2019 Autodesk +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ======================================================================= +# + +cmake_minimum_required(VERSION 3.1.1) +project(mayaUsd) + +if (POLICY CMP0074) + cmake_policy(SET CMP0074 OLD) +endif() + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +#============================================================================== +# Packages +#============================================================================== +set(BOOST_ROOT ${PXR_USD_LOCATION}) +find_package(Boost COMPONENTS + filesystem + REQUIRED +) + +#============================================================================== +# Library +#============================================================================== + +set(LIBRARY_NAME mayaUsd) + +list(APPEND mayaUsd_src + # + base/debugCodes.cpp + # + utils/colorSpace.cpp + utils/query.cpp + utils/util.cpp + utils/utilFileSystem.cpp + utils/stageCache.cpp + # + nodes/usdPrimProvider.cpp + nodes/proxyShapeBase.cpp + nodes/proxyShapePlugin.cpp + nodes/stageData.cpp + # + listeners/stageNoticeListener.cpp + listeners/notice.cpp + listeners/proxyShapeNotice.cpp +) + +list(APPEND mayaUsdBase_headers + base/api.h + base/debugCodes.h +) + +list(APPEND mayaUsdUtils_headers + utils/colorSpace.h + utils/query.h + utils/util.h + utils/utilFileSystem.h + utils/stageCache.h + utils/query.h +) + +list(APPEND mayaUsdNodes_headers + nodes/usdPrimProvider.h + nodes/proxyShapeBase.h + nodes/proxyShapePlugin.h + nodes/stageData.h +) + +list(APPEND mayaUsdListeners_headers + listeners/stageNoticeListener.h + listeners/notice.h + listeners/proxyShapeNotice.h +) + +add_library(${LIBRARY_NAME} SHARED + ${mayaUsd_src} +) + +set(compile_definitions_list + MAYAUSD_MACROS_EXPORT + MAYAUSD_CORE_EXPORT + MFB_PACKAGE_NAME="${LIBRARY_NAME}" + MFB_ALT_PACKAGE_NAME="${LIBRARY_NAME}" + MFB_PACKAGE_MODULE=mayaUsd +) + +if(IS_MACOSX) + list(APPEND compile_definitions_list OSMac_) +endif() + +target_compile_definitions(${LIBRARY_NAME} PRIVATE + ${compile_definitions_list} +) + +set(include_directories_list + ${MAYA_INCLUDE_DIRS} + ${PXR_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR}/include +) + +target_include_directories( ${LIBRARY_NAME} PUBLIC + ${include_directories_list} +) + +target_link_libraries(${LIBRARY_NAME} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${MAYA_Foundation_LIBRARY} + ${MAYA_OpenMayaAnim_LIBRARY} + ${MAYA_OpenMaya_LIBRARY} + ${MAYA_OpenMayaRender_LIBRARY} + ${MAYA_OpenMayaUI_LIBRARY} + ar + gf + hd + hdx + js + kind + plug + sdf + tf + usd + usdGeom + usdImaging + usdImagingGL + usdLux + usdRi + usdShade + usdSkel + usdUtils + vt +) + +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "plugin") + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + mayaUsd_install_rpath(rpath ${LIBRARY_NAME}) +endif() + +if (IS_LINUX) + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../USD/lib64") + endif() +endif() + +install(TARGETS ${LIBRARY_NAME} + LIBRARY + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib + ARCHIVE + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib + RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib +) +if(IS_WINDOWS) + install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib OPTIONAL) +endif() + +set_property(GLOBAL PROPERTY GLOBAL_LIBRARY_LOCATION ${CMAKE_INSTALL_PREFIX}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# promote headers +mayaUsd_promoteMayaUsdHeader() +mayaUsd_promoteHeaderList(${mayaUsdBase_headers}) +mayaUsd_promoteHeaderList(${mayaUsdUtils_headers}) +mayaUsd_promoteHeaderList(${mayaUsdNodes_headers}) +mayaUsd_promoteHeaderList(${mayaUsdListeners_headers}) + +# install public headers +install(FILES ${CMAKE_BINARY_DIR}/include/mayaUsd/mayaUsd.h + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/ +) + +install(FILES ${mayaUsdBase_headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/base/ +) + +install(FILES ${mayaUsdUtils_headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/utils/ +) + +install(FILES ${mayaUsdNodes_headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/nodes/ +) + +install(FILES ${mayaUsdListeners_headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/listeners/ +) + +if(IS_MACOSX) + set(_macDef OSMac_) +endif() diff --git a/lib/README.md b/lib/README.md index 39f3a912b6..2859cc8769 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,3 +1 @@ -## NOTE - -This folder holds the libraries that all other plugins depend on. It will contain library of building blocks. +# Core library diff --git a/lib/base/api.h b/lib/base/api.h new file mode 100644 index 0000000000..1b7d90198d --- /dev/null +++ b/lib/base/api.h @@ -0,0 +1,78 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "pxr/base/arch/export.h" + +#if defined _WIN32 || defined __CYGWIN__ + + // The main export symbol used for the core library. + #ifdef MAYAUSD_CORE_EXPORT + #ifdef __GNUC__ + #define MAYAUSD_CORE_PUBLIC __attribute__ ((dllexport)) + #else + #define MAYAUSD_CORE_PUBLIC __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define MAYAUSD_CORE_PUBLIC __attribute__ ((dllimport)) + #else + #define MAYAUSD_CORE_PUBLIC __declspec(dllimport) + #endif + #endif + #define MAYAUSD_CORE_LOCAL + + // We have a separate export symbol used in the helper macros. + #ifdef MAYAUSD_MACROS_EXPORT + #ifdef __GNUC__ + #define MAYAUSD_MACROS_PUBLIC __attribute__ ((dllexport)) + #else + #define MAYAUSD_MACROS_PUBLIC __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define MAYAUSD_MACROS_PUBLIC __attribute__ ((dllimport)) + #else + #define MAYAUSD_MACROS_PUBLIC __declspec(dllimport) + #endif + #endif + #define MAYAUSD_MACROS_LOCAL +#else + #if __GNUC__ >= 4 + #define MAYAUSD_CORE_PUBLIC __attribute__ ((visibility ("default"))) + #define MAYAUSD_CORE_LOCAL __attribute__ ((visibility ("hidden"))) + + #define MAYAUSD_MACROS_PUBLIC __attribute__ ((visibility ("default"))) + #define MAYAUSD_MACROS_LOCAL __attribute__ ((visibility ("hidden"))) +#else + #define MAYAUSD_CORE_PUBLIC + #define MAYAUSD_CORE_LOCAL + + #define MAYAUSD_MACROS_PUBLIC + #define MAYAUSD_MACROS_LOCAL +#endif +#endif + +#ifdef MAYAUSD_CORE_EXPORT + #define MAYAUSD_TEMPLATE_CLASS(...) ARCH_EXPORT_TEMPLATE(class, __VA_ARGS__) + #define MAYAUSD_TEMPLATE_STRUCT(...) ARCH_EXPORT_TEMPLATE(struct, __VA_ARGS__) +#else + #define MAYAUSD_TEMPLATE_CLASS(...) ARCH_IMPORT_TEMPLATE(class, __VA_ARGS__) + #define MAYAUSD_TEMPLATE_STRUCT(...) ARCH_IMPORT_TEMPLATE(struct, __VA_ARGS__) +#endif + +// Convenience symbol versioning include: because api.h is widely +// included, this reduces the need to explicitly include mayaUsd.h. +#include "mayaUsd/mayaUsd.h" diff --git a/plugin/pxr/maya/lib/usdMaya/debugCodes.cpp b/lib/base/debugCodes.cpp similarity index 96% rename from plugin/pxr/maya/lib/usdMaya/debugCodes.cpp rename to lib/base/debugCodes.cpp index ba9d0c57a4..78c67aad67 100644 --- a/plugin/pxr/maya/lib/usdMaya/debugCodes.cpp +++ b/lib/base/debugCodes.cpp @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/debugCodes.h" +#include "base/debugCodes.h" #include "pxr/base/tf/registryManager.h" diff --git a/plugin/pxr/maya/lib/usdMaya/debugCodes.h b/lib/base/debugCodes.h similarity index 90% rename from plugin/pxr/maya/lib/usdMaya/debugCodes.h rename to lib/base/debugCodes.h index 66570694bd..12f737713e 100644 --- a/plugin/pxr/maya/lib/usdMaya/debugCodes.h +++ b/lib/base/debugCodes.h @@ -24,7 +24,9 @@ PXR_NAMESPACE_OPEN_SCOPE TF_DEBUG_CODES( PXRUSDMAYA_REGISTRY, - PXRUSDMAYA_DIAGNOSTICS + PXRUSDMAYA_DIAGNOSTICS, + PXRUSDMAYA_TRANSLATORS, + USDMAYA_PROXYSHAPEBASE ); diff --git a/lib/base/mayaUsd.h.src b/lib/base/mayaUsd.h.src new file mode 100644 index 0000000000..5bb026b7a1 --- /dev/null +++ b/lib/base/mayaUsd.h.src @@ -0,0 +1,46 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#pragma once + +#define MAYAUSD_MAJOR_VERSION ${MAYAUSD_MAJOR_VERSION} +#define MAYAUSD_MINOR_VERSION ${MAYAUSD_MINOR_VERSION} +#define MAYAUSD_PATCH_LEVEL ${MAYAUSD_PATCH_LEVEL} + +// MayaUsd public namespace string will never change. +#define MAYAUSD_NS MayaUsd +// C preprocessor trickery to expand arguments. +#define MAYAUSD_CONCAT(A, B) MAYAUSD_CONCAT_IMPL(A, B) +#define MAYAUSD_CONCAT_IMPL(A, B) A##B +// Versioned namespace includes the major version number. +#define MAYAUSD_VERSIONED_NS MAYAUSD_CONCAT(MAYAUSD_NS, _v${MAYAUSD_MAJOR_VERSION}) + +namespace MAYAUSD_VERSIONED_NS {} + +// With a using namespace declaration, pull in the versioned namespace into the +// MayaUsd public namespace, to allow client code to use the plain MayaUsd +// namespace, e.g. MayaUsd::Class. +namespace MAYAUSD_NS { + using namespace MAYAUSD_VERSIONED_NS; +} + +// Macros to place the MayaUsd symbols in the versioned namespace, which is how +// they will appear in the shared library, e.g. MayaUsd_v1::Class. +#ifdef DOXYGEN +#define MAYAUSD_NS_DEF namespace MAYAUSD_NS +#else +#define MAYAUSD_NS_DEF namespace MAYAUSD_VERSIONED_NS +#endif diff --git a/plugin/pxr/maya/lib/usdMaya/notice.cpp b/lib/listeners/notice.cpp similarity index 99% rename from plugin/pxr/maya/lib/usdMaya/notice.cpp rename to lib/listeners/notice.cpp index 0b5d946294..badd984b53 100644 --- a/plugin/pxr/maya/lib/usdMaya/notice.cpp +++ b/lib/listeners/notice.cpp @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/notice.h" +#include "notice.h" #include "pxr/base/tf/instantiateType.h" diff --git a/plugin/pxr/maya/lib/usdMaya/notice.h b/lib/listeners/notice.h similarity index 91% rename from plugin/pxr/maya/lib/usdMaya/notice.h rename to lib/listeners/notice.h index 6187941293..5fe84749bb 100644 --- a/plugin/pxr/maya/lib/usdMaya/notice.h +++ b/lib/listeners/notice.h @@ -20,7 +20,7 @@ #include "pxr/base/tf/notice.h" -#include "usdMaya/api.h" +#include "../base/api.h" #include #include @@ -34,15 +34,15 @@ PXR_NAMESPACE_OPEN_SCOPE class UsdMayaSceneResetNotice : public TfNotice { public: - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC UsdMayaSceneResetNotice(); /// Registers the proper Maya callbacks for recognizing stage resets. - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static void InstallListener(); /// Removes any Maya callbacks for recognizing stage resets. - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static void RemoveListener(); private: @@ -53,17 +53,17 @@ class UsdMayaSceneResetNotice : public TfNotice class UsdMaya_AssemblyInstancerNoticeBase : public TfNotice { public: - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC virtual ~UsdMaya_AssemblyInstancerNoticeBase() = default; - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC MObject GetAssembly() const; - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC MObject GetInstancer() const; protected: - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC UsdMaya_AssemblyInstancerNoticeBase( const MObject& assembly, const MObject& instancer); @@ -79,7 +79,7 @@ class UsdMayaAssemblyConnectedToInstancerNotice : public UsdMaya_AssemblyInstancerNoticeBase { public: - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC UsdMayaAssemblyConnectedToInstancerNotice( const MObject& assembly, const MObject& instancer); @@ -91,7 +91,7 @@ class UsdMayaAssemblyDisconnectedFromInstancerNotice : public UsdMaya_AssemblyInstancerNoticeBase { public: - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC UsdMayaAssemblyDisconnectedFromInstancerNotice( const MObject& assembly, const MObject& instancer); diff --git a/lib/listeners/proxyShapeNotice.cpp b/lib/listeners/proxyShapeNotice.cpp new file mode 100644 index 0000000000..db6caa8e20 --- /dev/null +++ b/lib/listeners/proxyShapeNotice.cpp @@ -0,0 +1,38 @@ +// +// Copyright 2018 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "proxyShapeNotice.h" + +#include "pxr/base/tf/instantiateType.h" + +PXR_NAMESPACE_OPEN_SCOPE + +TF_INSTANTIATE_TYPE(UsdMayaProxyStageSetNotice, + TfType::CONCRETE, TF_1_PARENT(TfNotice)); + +UsdMayaProxyStageSetNotice::UsdMayaProxyStageSetNotice( + const MayaUsdProxyShapeBase& proxy) + : _proxy(proxy) +{ +} + +const MayaUsdProxyShapeBase& +UsdMayaProxyStageSetNotice::GetProxyShape() const +{ + return _proxy; +} + + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/listeners/proxyShapeNotice.h b/lib/listeners/proxyShapeNotice.h new file mode 100644 index 0000000000..b5f6733c59 --- /dev/null +++ b/lib/listeners/proxyShapeNotice.h @@ -0,0 +1,48 @@ +// +// Copyright 2018 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef USDMAYA_PROXYSTAGE_NOTICE_H +#define USDMAYA_PROXYSTAGE_NOTICE_H + +/// \file usdMaya/proxyShapeNotice.h + +#include "pxr/base/tf/notice.h" + +#include "../base/api.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class MayaUsdProxyShapeBase; + +/// Notice sent when the ProxyShape loads a new stage +class UsdMayaProxyStageSetNotice : public TfNotice +{ +public: + MAYAUSD_CORE_PUBLIC + UsdMayaProxyStageSetNotice(const MayaUsdProxyShapeBase& proxy); + + /// Get proxy shape which had stage set + MAYAUSD_CORE_PUBLIC + const MayaUsdProxyShapeBase& GetProxyShape() const; + +private: + const MayaUsdProxyShapeBase& _proxy; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/plugin/pxr/maya/lib/usdMaya/stageNoticeListener.cpp b/lib/listeners/stageNoticeListener.cpp similarity index 98% rename from plugin/pxr/maya/lib/usdMaya/stageNoticeListener.cpp rename to lib/listeners/stageNoticeListener.cpp index 11b95c539e..9bbc0d841a 100644 --- a/plugin/pxr/maya/lib/usdMaya/stageNoticeListener.cpp +++ b/lib/listeners/stageNoticeListener.cpp @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/stageNoticeListener.h" +#include "stageNoticeListener.h" #include "pxr/base/tf/notice.h" #include "pxr/base/tf/weakBase.h" diff --git a/plugin/pxr/maya/lib/usdMaya/stageNoticeListener.h b/lib/listeners/stageNoticeListener.h similarity index 94% rename from plugin/pxr/maya/lib/usdMaya/stageNoticeListener.h rename to lib/listeners/stageNoticeListener.h index 071b26ed8d..6039715eb9 100644 --- a/plugin/pxr/maya/lib/usdMaya/stageNoticeListener.h +++ b/lib/listeners/stageNoticeListener.h @@ -18,7 +18,7 @@ /// \file usdMaya/stageNoticeListener.h -#include "usdMaya/api.h" +#include "../base/api.h" #include "pxr/pxr.h" @@ -43,14 +43,14 @@ PXR_NAMESPACE_OPEN_SCOPE class UsdMayaStageNoticeListener : public TfWeakBase { public: - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC UsdMayaStageNoticeListener(); - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC virtual ~UsdMayaStageNoticeListener(); /// Set the USD stage for which this instance will listen for notices. - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC void SetStage(const UsdStageWeakPtr& stage); /// Callback type for StageContentsChanged notices. @@ -59,7 +59,7 @@ class UsdMayaStageNoticeListener : public TfWeakBase /// Sets the callback to be invoked when the listener receives a /// StageContentsChanged notice. - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC void SetStageContentsChangedCallback( const StageContentsChangedCallback& callback); diff --git a/lib/nodes/proxyShapeBase.cpp b/lib/nodes/proxyShapeBase.cpp new file mode 100644 index 0000000000..194db069f2 --- /dev/null +++ b/lib/nodes/proxyShapeBase.cpp @@ -0,0 +1,1027 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "proxyShapeBase.h" + +#include "../base/debugCodes.h" +#include "../listeners/proxyShapeNotice.h" +#include "../utils/query.h" +#include "../utils/stageCache.h" +#include "../utils/utilFileSystem.h" +#include "stageData.h" + +#include "pxr/base/gf/bbox3d.h" +#include "pxr/base/gf/range3d.h" +#include "pxr/base/gf/ray.h" +#include "pxr/base/gf/vec3d.h" +#include "pxr/base/tf/envSetting.h" +#include "pxr/base/tf/fileUtils.h" +#include "pxr/base/tf/hash.h" +#include "pxr/base/tf/pathUtils.h" +#include "pxr/base/tf/staticData.h" +#include "pxr/base/tf/staticTokens.h" +#include "pxr/base/tf/stringUtils.h" +#include "pxr/base/tf/token.h" + +#include "pxr/usd/ar/resolver.h" +#include "pxr/usd/sdf/layer.h" +#include "pxr/usd/sdf/path.h" +#include "pxr/usd/usd/prim.h" +#include "pxr/usd/usd/stage.h" +#include "pxr/usd/usd/stageCacheContext.h" +#include "pxr/usd/usd/timeCode.h" +#include "pxr/usd/usdGeom/bboxCache.h" +#include "pxr/usd/usdGeom/imageable.h" +#include "pxr/usd/usdGeom/tokens.h" +#include "pxr/usd/usdUtils/stageCache.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if defined(WANT_UFE_BUILD) +#include +#endif + +PXR_NAMESPACE_OPEN_SCOPE + +TF_DEFINE_PUBLIC_TOKENS(MayaUsdProxyShapeBaseTokens, + MAYAUSD_PROXY_SHAPE_BASE_TOKENS); + +MayaUsdProxyShapeBase::ClosestPointDelegate +MayaUsdProxyShapeBase::_sharedClosestPointDelegate = nullptr; + + +// ======================================================== + +// TypeID from the MayaUsd type ID range. +const MTypeId MayaUsdProxyShapeBase::typeId(0x58000094); +const MString MayaUsdProxyShapeBase::typeName( + MayaUsdProxyShapeBaseTokens->MayaTypeName.GetText()); + +const MString MayaUsdProxyShapeBase::displayFilterName( + TfStringPrintf("%sDisplayFilter", + MayaUsdProxyShapeBaseTokens->MayaTypeName.GetText()).c_str()); +const MString MayaUsdProxyShapeBase::displayFilterLabel("USD Proxies"); + +// Attributes +MObject MayaUsdProxyShapeBase::filePathAttr; +MObject MayaUsdProxyShapeBase::primPathAttr; +MObject MayaUsdProxyShapeBase::excludePrimPathsAttr; +MObject MayaUsdProxyShapeBase::timeAttr; +MObject MayaUsdProxyShapeBase::complexityAttr; +MObject MayaUsdProxyShapeBase::inStageDataAttr; +MObject MayaUsdProxyShapeBase::inStageDataCachedAttr; +MObject MayaUsdProxyShapeBase::outStageDataAttr; +MObject MayaUsdProxyShapeBase::drawRenderPurposeAttr; +MObject MayaUsdProxyShapeBase::drawProxyPurposeAttr; +MObject MayaUsdProxyShapeBase::drawGuidePurposeAttr; + + +/* static */ +void* +MayaUsdProxyShapeBase::creator() +{ + return new MayaUsdProxyShapeBase(); +} + +/* static */ +MStatus +MayaUsdProxyShapeBase::initialize() +{ + MStatus retValue = MS::kSuccess; + + // + // create attr factories + // + MFnNumericAttribute numericAttrFn; + MFnTypedAttribute typedAttrFn; + MFnUnitAttribute unitAttrFn; + + filePathAttr = typedAttrFn.create( + "filePath", + "fp", + MFnData::kString, + MObject::kNullObj, + &retValue); + typedAttrFn.setInternal(true); + typedAttrFn.setAffectsAppearance(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(filePathAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + primPathAttr = typedAttrFn.create( + "primPath", + "pp", + MFnData::kString, + MObject::kNullObj, + &retValue); + typedAttrFn.setInternal(true); + typedAttrFn.setAffectsAppearance(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(primPathAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + excludePrimPathsAttr = typedAttrFn.create( + "excludePrimPaths", + "epp", + MFnData::kString, + MObject::kNullObj, + &retValue); + typedAttrFn.setInternal(true); + typedAttrFn.setAffectsAppearance(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(excludePrimPathsAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + timeAttr = unitAttrFn.create( + "time", + "tm", + MFnUnitAttribute::kTime, + 0.0, + &retValue); + unitAttrFn.setAffectsAppearance(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(timeAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + complexityAttr = numericAttrFn.create( + "complexity", + "cplx", + MFnNumericData::kInt, + 0, + &retValue); + numericAttrFn.setMin(0); + numericAttrFn.setSoftMax(4); + numericAttrFn.setMax(8); + numericAttrFn.setChannelBox(true); + numericAttrFn.setStorable(false); + numericAttrFn.setAffectsAppearance(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(complexityAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + inStageDataAttr = typedAttrFn.create( + "inStageData", + "id", + MayaUsdStageData::mayaTypeId, + MObject::kNullObj, + &retValue); + typedAttrFn.setReadable(false); + typedAttrFn.setStorable(false); + typedAttrFn.setDisconnectBehavior(MFnNumericAttribute::kReset); // on disconnect, reset to Null + typedAttrFn.setAffectsAppearance(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(inStageDataAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + // inStageData or filepath-> inStageDataCached -> outStageData + inStageDataCachedAttr = typedAttrFn.create( + "inStageDataCached", + "idc", + MayaUsdStageData::mayaTypeId, + MObject::kNullObj, + &retValue); + typedAttrFn.setStorable(false); + typedAttrFn.setWritable(false); + typedAttrFn.setAffectsAppearance(true); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(inStageDataCachedAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + outStageDataAttr = typedAttrFn.create( + "outStageData", + "od", + MayaUsdStageData::mayaTypeId, + MObject::kNullObj, + &retValue); + typedAttrFn.setStorable(false); + typedAttrFn.setWritable(false); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = addAttribute(outStageDataAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + drawRenderPurposeAttr = numericAttrFn.create( + "drawRenderPurpose", + "drp", + MFnNumericData::kBoolean, + 0.0, + &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + numericAttrFn.setKeyable(true); + numericAttrFn.setReadable(false); + numericAttrFn.setAffectsAppearance(true); + retValue = addAttribute(drawRenderPurposeAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + drawProxyPurposeAttr = numericAttrFn.create( + "drawProxyPurpose", + "dpp", + MFnNumericData::kBoolean, + 1.0, + &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + numericAttrFn.setKeyable(true); + numericAttrFn.setReadable(false); + numericAttrFn.setAffectsAppearance(true); + retValue = addAttribute(drawProxyPurposeAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + drawGuidePurposeAttr = numericAttrFn.create( + "drawGuidePurpose", + "dgp", + MFnNumericData::kBoolean, + 0.0, + &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + numericAttrFn.setKeyable(true); + numericAttrFn.setReadable(false); + numericAttrFn.setAffectsAppearance(true); + retValue = addAttribute(drawGuidePurposeAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + // + // add attribute dependencies + // + retValue = attributeAffects(filePathAttr, inStageDataCachedAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = attributeAffects(filePathAttr, outStageDataAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + retValue = attributeAffects(primPathAttr, inStageDataCachedAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = attributeAffects(primPathAttr, outStageDataAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + retValue = attributeAffects(inStageDataAttr, inStageDataCachedAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + retValue = attributeAffects(inStageDataAttr, outStageDataAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + retValue = attributeAffects(inStageDataCachedAttr, outStageDataAttr); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + return retValue; +} + +/* static */ +MayaUsdProxyShapeBase* +MayaUsdProxyShapeBase::GetShapeAtDagPath(const MDagPath& dagPath) +{ + MObject mObj = dagPath.node(); + if (mObj.apiType() != MFn::kPluginShape) { + TF_CODING_ERROR( + "Could not get MayaUsdProxyShapeBase for non-plugin shape node " + "at DAG path: %s (apiTypeStr = %s)", + dagPath.fullPathName().asChar(), + mObj.apiTypeStr()); + return nullptr; + } + + const MFnDependencyNode depNodeFn(mObj); + MayaUsdProxyShapeBase* pShape = + static_cast(depNodeFn.userNode()); + if (!pShape) { + TF_CODING_ERROR( + "Could not get MayaUsdProxyShapeBase for node at DAG path: %s", + dagPath.fullPathName().asChar()); + return nullptr; + } + + return pShape; +} + +/* static */ +void +MayaUsdProxyShapeBase::SetClosestPointDelegate(ClosestPointDelegate delegate) +{ + _sharedClosestPointDelegate = delegate; +} + +/* virtual */ +bool +MayaUsdProxyShapeBase::GetObjectSoftSelectEnabled() const +{ + return false; +} + +/* virtual */ +void +MayaUsdProxyShapeBase::postConstructor() +{ + setRenderable(true); +} + +/* virtual */ +MStatus +MayaUsdProxyShapeBase::compute(const MPlug& plug, MDataBlock& dataBlock) +{ + if (plug == excludePrimPathsAttr || + plug == timeAttr || + plug == complexityAttr || + plug == drawRenderPurposeAttr || + plug == drawProxyPurposeAttr || + plug == drawGuidePurposeAttr) { + // If the attribute that needs to be computed is one of these, then it + // does not affect the ouput stage data, but it *does* affect imaging + // the shape. In that case, we notify Maya that the shape needs to be + // redrawn and let it take care of computing the attribute. This covers + // the case where an attribute on the proxy shape may have an incoming + // connection from another node (e.g. "time1.outTime" being connected + // to the proxy shape's "time" attribute). In that case, + // setDependentsDirty() might not get called and only compute() might. + MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); + return MS::kUnknownParameter; + } + else if (plug == inStageDataCachedAttr) { + return computeInStageDataCached(dataBlock); + } + else if (plug == outStageDataAttr) { + return computeOutStageData(dataBlock); + } + + return MS::kUnknownParameter; +} + +/* virtual */ +SdfLayerRefPtr +MayaUsdProxyShapeBase::computeSessionLayer(MDataBlock&) +{ + return nullptr; +} + +MStatus +MayaUsdProxyShapeBase::computeInStageDataCached(MDataBlock& dataBlock) +{ + MStatus retValue = MS::kSuccess; + + MDataHandle inDataHandle = dataBlock.inputValue(inStageDataAttr, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + // If inData has an incoming connection, then use it. Otherwise generate stage from the filepath + if (!inDataHandle.data().isNull() ) { + // + // Propagate inData -> inDataCached + // + MDataHandle inDataCachedHandle = dataBlock.outputValue(inStageDataCachedAttr, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + inDataCachedHandle.copy(inDataHandle); + + inDataCachedHandle.setClean(); + return MS::kSuccess; + } + else { + // + // Calculate from USD filepath and primPath and variantKey + // + + // Get input attr values + const MString file = dataBlock.inputValue(filePathAttr, &retValue).asString(); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + // + // let the usd stage cache deal with caching the usd stage data + // + std::string fileString = TfStringTrimRight(file.asChar()); + + TF_DEBUG(USDMAYA_PROXYSHAPEBASE).Msg("ProxyShapeBase::reloadStage original USD file path is %s\n", fileString.c_str()); + + boost::filesystem::path filestringPath(fileString); + if(filestringPath.is_absolute()) + { + fileString = UsdMayaUtilFileSystem::resolvePath(fileString); + TF_DEBUG(USDMAYA_PROXYSHAPEBASE).Msg("ProxyShapeBase::reloadStage resolved the USD file path to %s\n", fileString.c_str()); + } + else + { + fileString = UsdMayaUtilFileSystem::resolveRelativePathWithinMayaContext(thisMObject(), fileString); + TF_DEBUG(USDMAYA_PROXYSHAPEBASE).Msg("ProxyShapeBase::reloadStage resolved the relative USD file path to %s\n", fileString.c_str()); + } + + // Fall back on providing the path "as is" to USD + if (fileString.empty()) + { + fileString.assign(file.asChar(), file.length()); + } + + TF_DEBUG(USDMAYA_PROXYSHAPEBASE).Msg("ProxyShapeBase::loadStage called for the usd file: %s\n", fileString.c_str()); + + // == Load the Stage + UsdStageRefPtr usdStage; + SdfPath primPath; + + if (SdfLayerRefPtr rootLayer = SdfLayer::FindOrOpen(fileString)) { + UsdStageCacheContext ctx(UsdMayaStageCache::Get()); + SdfLayerRefPtr sessionLayer = computeSessionLayer(dataBlock); + if (sessionLayer) { + usdStage = UsdStage::Open(rootLayer, + sessionLayer, + ArGetResolver().GetCurrentContext()); + } else { + usdStage = UsdStage::Open(rootLayer, + ArGetResolver().GetCurrentContext()); + } + + usdStage->SetEditTarget(usdStage->GetSessionLayer()); + } + + if (usdStage) { + primPath = usdStage->GetPseudoRoot().GetPath(); + } + + // Create the output outData ======== + MFnPluginData pluginDataFn; + MObject stageDataObj = + pluginDataFn.create(MayaUsdStageData::mayaTypeId, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + MayaUsdStageData* stageData = + reinterpret_cast(pluginDataFn.data(&retValue)); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + // Set the outUsdStageData + stageData->stage = usdStage; + stageData->primPath = primPath; + + // + // set the data on the output plug + // + MDataHandle inDataCachedHandle = + dataBlock.outputValue(inStageDataCachedAttr, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + inDataCachedHandle.set(stageData); + inDataCachedHandle.setClean(); + return MS::kSuccess; + } +} + +MStatus +MayaUsdProxyShapeBase::computeOutStageData(MDataBlock& dataBlock) +{ + MStatus retValue = MS::kSuccess; + + TfReset(_boundingBoxCache); + + // Reset the stage listener until we determine that everything is valid. + _stageNoticeListener.SetStage(UsdStageWeakPtr()); + _stageNoticeListener.SetStageContentsChangedCallback(nullptr); + + MDataHandle inDataCachedHandle = + dataBlock.inputValue(inStageDataCachedAttr, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + UsdStageRefPtr usdStage; + + MayaUsdStageData* inData = + dynamic_cast(inDataCachedHandle.asPluginData()); + if(inData) + { + usdStage = inData->stage; + } + + // If failed to get a valid stage, then + // Propagate inDataCached -> outData + // and return + if (!usdStage) { + MDataHandle outDataHandle = dataBlock.outputValue(outStageDataAttr, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + outDataHandle.copy(inDataCachedHandle); + return MS::kSuccess; + } + + // Get the primPath + const MString primPath = dataBlock.inputValue(primPathAttr, &retValue).asString(); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + // Get the prim + // If no primPath string specified, then use the pseudo-root. + UsdPrim usdPrim; + std::string primPathStr = primPath.asChar(); + if ( !primPathStr.empty() ) { + SdfPath primPath(primPathStr); + + // Validate assumption: primPath is descendent of passed-in stage primPath + // Make sure that the primPath is a child of the passed in stage's primpath + if ( primPath.HasPrefix(inData->primPath) ) { + usdPrim = usdStage->GetPrimAtPath( primPath ); + } + else { + TF_WARN("%s: Shape primPath <%s> is not a descendant of input " + "stage primPath <%s>", + MPxSurfaceShape::name().asChar(), + primPath.GetText(), + inData->primPath.GetText()); + } + } else { + usdPrim = usdStage->GetPseudoRoot(); + } + + // Create the output outData + MFnPluginData pluginDataFn; + MObject stageDataObj = + pluginDataFn.create(MayaUsdStageData::mayaTypeId, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + MayaUsdStageData* stageData = + reinterpret_cast(pluginDataFn.data(&retValue)); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + // Set the outUsdStageData + stageData->stage = usdStage; + stageData->primPath = usdPrim ? usdPrim.GetPath() : + usdStage->GetPseudoRoot().GetPath(); + + // + // set the data on the output plug + // + MDataHandle outDataHandle = + dataBlock.outputValue(outStageDataAttr, &retValue); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + outDataHandle.set(stageData); + outDataHandle.setClean(); + + // Start listening for notices for the USD stage. + _stageNoticeListener.SetStage(usdStage); + _stageNoticeListener.SetStageContentsChangedCallback( + std::bind(&MayaUsdProxyShapeBase::_OnStageContentsChanged, + this, + std::placeholders::_1)); + + UsdMayaProxyStageSetNotice(*this).Send(); + + return MS::kSuccess; +} + +/* virtual */ +bool +MayaUsdProxyShapeBase::isBounded() const +{ + return isStageValid(); +} + +/* virtual */ +void +MayaUsdProxyShapeBase::CacheEmptyBoundingBox(MBoundingBox&) +{} + +/* virtual */ +UsdTimeCode +MayaUsdProxyShapeBase::GetOutputTime(MDataBlock dataBlock) const +{ + return _GetTime(dataBlock); +} + +/* virtual */ +MBoundingBox +MayaUsdProxyShapeBase::boundingBox() const +{ + MStatus status; + + // Make sure outStage is up to date + MayaUsdProxyShapeBase* nonConstThis = const_cast(this); + MDataBlock dataBlock = nonConstThis->forceCache(); + dataBlock.inputValue(outStageDataAttr, &status); + CHECK_MSTATUS_AND_RETURN(status, MBoundingBox()); + + // XXX: + // If we could cheaply determine whether a stage only has static geometry, + // we could make this value a constant one for that case, avoiding the + // memory overhead of a cache entry per frame + UsdTimeCode currTime = GetOutputTime(dataBlock); + + std::map::const_iterator cacheLookup = + _boundingBoxCache.find(currTime); + + if (cacheLookup != _boundingBoxCache.end()) { + return cacheLookup->second; + } + + UsdPrim prim = _GetUsdPrim(dataBlock); + if (!prim) { + return MBoundingBox(); + } + + const UsdGeomImageable imageablePrim(prim); + + bool drawRenderPurpose = false; + bool drawProxyPurpose = true; + bool drawGuidePurpose = false; + _GetDrawPurposeToggles( + dataBlock, + &drawRenderPurpose, + &drawProxyPurpose, + &drawGuidePurpose); + + const TfToken purpose1 = UsdGeomTokens->default_; + const TfToken purpose2 = + drawRenderPurpose ? UsdGeomTokens->render : TfToken(); + const TfToken purpose3 = + drawProxyPurpose ? UsdGeomTokens->proxy : TfToken(); + const TfToken purpose4 = + drawGuidePurpose ? UsdGeomTokens->guide : TfToken(); + + const GfBBox3d allBox = imageablePrim.ComputeUntransformedBound( + currTime, + purpose1, + purpose2, + purpose3, + purpose4); + + MBoundingBox &retval = nonConstThis->_boundingBoxCache[currTime]; + + const GfRange3d boxRange = allBox.ComputeAlignedBox(); + + // Convert to GfRange3d to MBoundingBox + if ( !boxRange.IsEmpty() ) { + const GfVec3d boxMin = boxRange.GetMin(); + const GfVec3d boxMax = boxRange.GetMax(); + retval = MBoundingBox( + MPoint(boxMin[0], boxMin[1], boxMin[2]), + MPoint(boxMax[0], boxMax[1], boxMax[2])); + } + else { + nonConstThis->CacheEmptyBoundingBox(retval); + } + + return retval; +} + +void +MayaUsdProxyShapeBase::clearBoundingBoxCache() +{ + _boundingBoxCache.clear(); +} + +bool +MayaUsdProxyShapeBase::isStageValid() const +{ + MStatus localStatus; + MayaUsdProxyShapeBase* nonConstThis = const_cast(this); + MDataBlock dataBlock = nonConstThis->forceCache(); + + MDataHandle outDataHandle = dataBlock.inputValue(outStageDataAttr, &localStatus); + CHECK_MSTATUS_AND_RETURN(localStatus, false); + + MayaUsdStageData* outData = + dynamic_cast(outDataHandle.asPluginData()); + if(!outData || !outData->stage) { + return false; + } + + return true; +} + +/* virtual */ +MStatus +MayaUsdProxyShapeBase::setDependentsDirty(const MPlug& plug, MPlugArray& plugArray) +{ + // If/when the MPxDrawOverride for the proxy shape specifies + // isAlwaysDirty=false to improve performance, we must be sure to notify + // the Maya renderer that the geometry is dirty and needs to be redrawn + // when any plug on the proxy shape is dirtied. + MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); + return MPxSurfaceShape::setDependentsDirty(plug, plugArray); +} + +/* virtual */ +bool +MayaUsdProxyShapeBase::setInternalValue(const MPlug& plug, const MDataHandle& dataHandle) +{ + if (plug == excludePrimPathsAttr) { + _IncreaseExcludePrimPathsVersion(); + } + return MPxSurfaceShape::setInternalValue(plug, dataHandle); +} + +UsdPrim +MayaUsdProxyShapeBase::_GetUsdPrim(MDataBlock dataBlock) const +{ + MStatus localStatus; + UsdPrim usdPrim; + + MDataHandle outDataHandle = + dataBlock.inputValue(outStageDataAttr, &localStatus); + CHECK_MSTATUS_AND_RETURN(localStatus, usdPrim); + + MayaUsdStageData* outData = dynamic_cast(outDataHandle.asPluginData()); + if(!outData) { + return usdPrim; // empty UsdPrim + } + + if(!outData->stage) { + return usdPrim; // empty UsdPrim + } + + usdPrim = (outData->primPath.IsEmpty()) ? + outData->stage->GetPseudoRoot() : + outData->stage->GetPrimAtPath(outData->primPath); + + return usdPrim; +} + +int +MayaUsdProxyShapeBase::getComplexity() const +{ + return _GetComplexity( const_cast(this)->forceCache() ); +} + +int +MayaUsdProxyShapeBase::_GetComplexity(MDataBlock dataBlock) const +{ + int complexity = 0; + MStatus status; + + complexity = dataBlock.inputValue(complexityAttr, &status).asInt(); + + return complexity; +} + +UsdTimeCode +MayaUsdProxyShapeBase::getTime() const +{ + return _GetTime( const_cast(this)->forceCache() ); +} + +UsdTimeCode +MayaUsdProxyShapeBase::_GetTime(MDataBlock dataBlock) const +{ + MStatus status; + + return UsdTimeCode(dataBlock.inputValue(timeAttr, &status).asTime().value()); +} + +UsdStageRefPtr +MayaUsdProxyShapeBase::getUsdStage() const +{ + MStatus localStatus; + MayaUsdProxyShapeBase* nonConstThis = const_cast(this); + MDataBlock dataBlock = nonConstThis->forceCache(); + + MDataHandle outDataHandle = dataBlock.inputValue(outStageDataAttr, &localStatus); + CHECK_MSTATUS_AND_RETURN(localStatus, UsdStageRefPtr()); + + MayaUsdStageData* outData = + dynamic_cast(outDataHandle.asPluginData()); + + if (outData && outData->stage) + return outData->stage; + else + return {}; + +} + +SdfPathVector +MayaUsdProxyShapeBase::getExcludePrimPaths() const +{ + return _GetExcludePrimPaths( const_cast(this)->forceCache() ); +} + +size_t +MayaUsdProxyShapeBase::getExcludePrimPathsVersion() const { + return _excludePrimPathsVersion; +} + +SdfPathVector +MayaUsdProxyShapeBase::_GetExcludePrimPaths(MDataBlock dataBlock) const +{ + SdfPathVector ret; + + const MString excludePrimPathsStr = + dataBlock.inputValue(excludePrimPathsAttr).asString(); + std::vector excludePrimPaths = + TfStringTokenize(excludePrimPathsStr.asChar(), ","); + ret.resize(excludePrimPaths.size()); + for (size_t i = 0; i < excludePrimPaths.size(); ++i) { + ret[i] = SdfPath(TfStringTrim(excludePrimPaths[i])); + } + + return ret; +} + +bool +MayaUsdProxyShapeBase::_GetDrawPurposeToggles( + MDataBlock dataBlock, + bool* drawRenderPurpose, + bool* drawProxyPurpose, + bool* drawGuidePurpose) const +{ + MStatus status; + + MDataHandle drawRenderPurposeHandle = + dataBlock.inputValue(drawRenderPurposeAttr, &status); + CHECK_MSTATUS_AND_RETURN(status, false); + + MDataHandle drawProxyPurposeHandle = + dataBlock.inputValue(drawProxyPurposeAttr, &status); + CHECK_MSTATUS_AND_RETURN(status, false); + + MDataHandle drawGuidePurposeHandle = + dataBlock.inputValue(drawGuidePurposeAttr, &status); + CHECK_MSTATUS_AND_RETURN(status, false); + + if (drawRenderPurpose) { + *drawRenderPurpose = drawRenderPurposeHandle.asBool(); + } + if (drawProxyPurpose) { + *drawProxyPurpose = drawProxyPurposeHandle.asBool(); + } + if (drawGuidePurpose) { + *drawGuidePurpose = drawGuidePurposeHandle.asBool(); + } + + return true; +} + +bool +MayaUsdProxyShapeBase::GetAllRenderAttributes( + UsdPrim* usdPrimOut, + SdfPathVector* excludePrimPathsOut, + int* complexityOut, + UsdTimeCode* timeOut, + bool* drawRenderPurpose, + bool* drawProxyPurpose, + bool* drawGuidePurpose) +{ + MDataBlock dataBlock = forceCache(); + + *usdPrimOut = _GetUsdPrim(dataBlock); + if (!usdPrimOut->IsValid()) { + return false; + } + + *excludePrimPathsOut = _GetExcludePrimPaths(dataBlock); + *complexityOut = _GetComplexity(dataBlock); + *timeOut = _GetTime(dataBlock); + + _GetDrawPurposeToggles( + dataBlock, + drawRenderPurpose, + drawProxyPurpose, + drawGuidePurpose); + + return true; +} + +/* virtual */ +UsdPrim +MayaUsdProxyShapeBase::usdPrim() const +{ + return _GetUsdPrim( const_cast(this)->forceCache() ); +} + +MDagPath +MayaUsdProxyShapeBase::parentTransform() +{ + MFnDagNode fn(thisMObject()); + MDagPath proxyTransformPath; + fn.getPath(proxyTransformPath); + proxyTransformPath.pop(); + return proxyTransformPath; +} + +MayaUsdProxyShapeBase::MayaUsdProxyShapeBase() : + MPxSurfaceShape() +{ + TfRegistryManager::GetInstance().SubscribeTo(); +} + +/* virtual */ +MayaUsdProxyShapeBase::~MayaUsdProxyShapeBase() +{ + // + // empty + // +} + +MSelectionMask +MayaUsdProxyShapeBase::getShapeSelectionMask() const +{ + // The intent of this function is to control whether this object is + // selectable at all in VP2 + + // However, due to a bug / quirk, it could be used to specifically control + // whether the object was SOFT-selectable if you were using + // MAYA_VP2_USE_VP1_SELECTON; in this mode, this setting is NOT querierd + // when doing "normal" selection, but IS queried when doing soft + // selection. + + // Unfortunately, it is queried for both "normal" selection AND soft + // selection if you are using "true" VP2 selection. So in order to + // control soft selection, in both modes, we keep track of whether + // we currently have object soft-select enabled, and then return an empty + // selection mask if it is, but this object is set to be non-soft-selectable + + static const MSelectionMask emptyMask; + static const MSelectionMask normalMask(MSelectionMask::kSelectMeshes); + + if (GetObjectSoftSelectEnabled() && !canBeSoftSelected()) { + // Disable selection, to disable soft-selection + return emptyMask; + } + return normalMask; +} + +bool +MayaUsdProxyShapeBase::canBeSoftSelected() const +{ + return false; +} + +void +MayaUsdProxyShapeBase::_OnStageContentsChanged( + const UsdNotice::StageContentsChanged& notice) +{ + // If the USD stage this proxy represents changes without Maya's knowledge, + // we need to inform Maya that the shape is dirty and needs to be redrawn. + MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); +} + +bool +MayaUsdProxyShapeBase::closestPoint( + const MPoint& raySource, + const MVector& rayDirection, + MPoint& theClosestPoint, + MVector& theClosestNormal, + bool /*findClosestOnMiss*/, + double /*tolerance*/) +{ + if (_sharedClosestPointDelegate) { + GfRay ray( + GfVec3d(raySource.x, raySource.y, raySource.z), + GfVec3d(rayDirection.x, rayDirection.y, rayDirection.z)); + GfVec3d hitPoint; + GfVec3d hitNorm; + if (_sharedClosestPointDelegate(*this, ray, &hitPoint, &hitNorm)) { + theClosestPoint = MPoint(hitPoint[0], hitPoint[1], hitPoint[2]); + theClosestNormal = MVector(hitNorm[0], hitNorm[1], hitNorm[2]); + return true; + } + } + + return false; +} + +bool MayaUsdProxyShapeBase::canMakeLive() const { + return (bool) _sharedClosestPointDelegate; +} + +#if defined(WANT_UFE_BUILD) +Ufe::Path MayaUsdProxyShapeBase::ufePath() const +{ + // Build a path segment to proxyShape + MDagPath thisPath; + MDagPath::getAPathTo(thisMObject(), thisPath); + + // MDagPath does not include |world to its full path name + MString fullpath = "|world" + thisPath.fullPathName(); + + return Ufe::Path(Ufe::PathSegment(fullpath.asChar(), MAYA_UFE_RUNTIME_ID, MAYA_UFE_SEPARATOR)); +} +#endif + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/nodes/proxyShapeBase.h b/lib/nodes/proxyShapeBase.h new file mode 100644 index 0000000000..5013af5418 --- /dev/null +++ b/lib/nodes/proxyShapeBase.h @@ -0,0 +1,294 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef PXRUSDMAYA_PROXY_SHAPE_BASE_H +#define PXRUSDMAYA_PROXY_SHAPE_BASE_H + +/// \file usdMaya/proxyShapeBase.h + +#include "../base/api.h" +#include "../listeners/stageNoticeListener.h" +#include "usdPrimProvider.h" + +#include "pxr/pxr.h" + +#include "pxr/base/gf/ray.h" +#include "pxr/base/gf/vec3d.h" +#include "pxr/base/tf/staticTokens.h" + +#include "pxr/usd/sdf/path.h" +#include "pxr/usd/usd/notice.h" +#include "pxr/usd/usd/prim.h" +#include "pxr/usd/usd/timeCode.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(WANT_UFE_BUILD) +#include + +UFE_NS_DEF { + class Path; +} + +constexpr int MAYA_UFE_RUNTIME_ID = 1; +constexpr char MAYA_UFE_SEPARATOR = '|'; +constexpr int USD_UFE_RUNTIME_ID = 2; +constexpr char USD_UFE_SEPARATOR = '/'; +#endif + + +PXR_NAMESPACE_OPEN_SCOPE + + +#define MAYAUSD_PROXY_SHAPE_BASE_TOKENS \ + ((MayaTypeName, "mayaUsdProxyShapeBase")) + +TF_DECLARE_PUBLIC_TOKENS(MayaUsdProxyShapeBaseTokens, + MAYAUSD_CORE_PUBLIC, + MAYAUSD_PROXY_SHAPE_BASE_TOKENS); + + +class MayaUsdProxyShapeBase : public MPxSurfaceShape, + public UsdMayaUsdPrimProvider +{ + public: + typedef MayaUsdProxyShapeBase ThisClass; + + MAYAUSD_CORE_PUBLIC + static const MTypeId typeId; + MAYAUSD_CORE_PUBLIC + static const MString typeName; + + MAYAUSD_CORE_PUBLIC + static const MString displayFilterName; + MAYAUSD_CORE_PUBLIC + static const MString displayFilterLabel; + + // Attributes + MAYAUSD_CORE_PUBLIC + static MObject filePathAttr; + MAYAUSD_CORE_PUBLIC + static MObject primPathAttr; + MAYAUSD_CORE_PUBLIC + static MObject excludePrimPathsAttr; + MAYAUSD_CORE_PUBLIC + static MObject timeAttr; + MAYAUSD_CORE_PUBLIC + static MObject complexityAttr; + MAYAUSD_CORE_PUBLIC + static MObject inStageDataAttr; + MAYAUSD_CORE_PUBLIC + static MObject inStageDataCachedAttr; + MAYAUSD_CORE_PUBLIC + static MObject outStageDataAttr; + MAYAUSD_CORE_PUBLIC + static MObject drawRenderPurposeAttr; + MAYAUSD_CORE_PUBLIC + static MObject drawProxyPurposeAttr; + MAYAUSD_CORE_PUBLIC + static MObject drawGuidePurposeAttr; + + /// Delegate function for computing the closest point and surface normal + /// on the proxy shape to a given ray. + /// The input ray, output point, and output normal should be in the + /// proxy shape's local space. + /// Should return true if a point was found, and false otherwise. + /// (You could just treat this as a ray intersection and return true + /// if intersected, false if missed.) + typedef std::function ClosestPointDelegate; + + MAYAUSD_CORE_PUBLIC + static void* creator(); + + MAYAUSD_CORE_PUBLIC + static MStatus initialize(); + + MAYAUSD_CORE_PUBLIC + static MayaUsdProxyShapeBase* GetShapeAtDagPath(const MDagPath& dagPath); + + MAYAUSD_CORE_PUBLIC + static void SetClosestPointDelegate(ClosestPointDelegate delegate); + + // UsdMayaUsdPrimProvider overrides: + /** + * accessor to get the usdprim + * + * This method pulls the usdstage data from outData, and will evaluate + * the dependencies necessary to do so. It should be called instead of + * pulling on the data directly. + */ + MAYAUSD_CORE_PUBLIC + UsdPrim usdPrim() const override; + + // Virtual function overrides + + MAYAUSD_CORE_PUBLIC + void postConstructor() override; + MAYAUSD_CORE_PUBLIC + MStatus compute( + const MPlug& plug, + MDataBlock& dataBlock) override; + MAYAUSD_CORE_PUBLIC + bool isBounded() const override; + MAYAUSD_CORE_PUBLIC + MBoundingBox boundingBox() const override; + MAYAUSD_CORE_PUBLIC + MSelectionMask getShapeSelectionMask() const override; + + MAYAUSD_CORE_PUBLIC + bool closestPoint( + const MPoint& raySource, + const MVector& rayDirection, + MPoint& theClosestPoint, + MVector& theClosestNormal, + bool findClosestOnMiss, + double tolerance) override; + + MAYAUSD_CORE_PUBLIC + bool canMakeLive() const override; + + // Public functions + MAYAUSD_CORE_PUBLIC + virtual SdfPathVector getExcludePrimPaths() const; + MAYAUSD_CORE_PUBLIC + size_t getExcludePrimPathsVersion() const; + + MAYAUSD_CORE_PUBLIC + int getComplexity() const; + MAYAUSD_CORE_PUBLIC + virtual UsdTimeCode getTime() const; + MAYAUSD_CORE_PUBLIC + virtual UsdStageRefPtr getUsdStage() const; + + MAYAUSD_CORE_PUBLIC + bool GetAllRenderAttributes( + UsdPrim* usdPrimOut, + SdfPathVector* excludePrimPathsOut, + int* complexityOut, + UsdTimeCode* timeOut, + bool* drawRenderPurpose, + bool* drawProxyPurpose, + bool* drawGuidePurpose); + + MAYAUSD_CORE_PUBLIC + MStatus setDependentsDirty( + const MPlug& plug, + MPlugArray& plugArray) override; + + /// \brief Clears the bounding box cache of the shape + MAYAUSD_CORE_PUBLIC + void clearBoundingBoxCache(); + + // returns the shape's parent transform + MAYAUSD_CORE_PUBLIC + MDagPath parentTransform(); + + // Is this required if there is parentTransform? +#if defined(WANT_UFE_BUILD) + MAYAUSD_CORE_PUBLIC + Ufe::Path ufePath() const; +#endif + + protected: + MAYAUSD_CORE_PUBLIC + MayaUsdProxyShapeBase(); + + MAYAUSD_CORE_PUBLIC + ~MayaUsdProxyShapeBase() override; + + MAYAUSD_CORE_PUBLIC + bool isStageValid() const; + + // Hook method for derived classes. This class returns a nullptr. + MAYAUSD_CORE_PUBLIC + virtual SdfLayerRefPtr computeSessionLayer(MDataBlock&); + + // Hook method for derived classes: can this object be soft selected? + // This class returns false. + MAYAUSD_CORE_PUBLIC + virtual bool canBeSoftSelected() const; + + // Hook method for derived classes: is soft select enabled? This + // class returns false. + MAYAUSD_CORE_PUBLIC + virtual bool GetObjectSoftSelectEnabled() const; + + MAYAUSD_CORE_PUBLIC + UsdPrim _GetUsdPrim(MDataBlock dataBlock) const; + + // Hook method for derived classes: cache an empty computed bounding + // box. This class does nothing. + MAYAUSD_CORE_PUBLIC + virtual void CacheEmptyBoundingBox(MBoundingBox&); + + // Return the output time. This class returns the value of the + // input time attribute. + MAYAUSD_CORE_PUBLIC + virtual UsdTimeCode GetOutputTime(MDataBlock) const; + + MAYAUSD_CORE_PUBLIC + void _IncreaseExcludePrimPathsVersion() { _excludePrimPathsVersion++; } + + MAYAUSD_CORE_PUBLIC + bool setInternalValue(const MPlug& plug, const MDataHandle& dataHandle) override; + + private: + MayaUsdProxyShapeBase(const MayaUsdProxyShapeBase&); + MayaUsdProxyShapeBase& operator=(const MayaUsdProxyShapeBase&); + + MStatus computeInStageDataCached(MDataBlock& dataBlock); + MStatus computeOutStageData(MDataBlock& dataBlock); + + SdfPathVector _GetExcludePrimPaths(MDataBlock dataBlock) const; + int _GetComplexity(MDataBlock dataBlock) const; + UsdTimeCode _GetTime(MDataBlock dataBlock) const; + + bool _GetDrawPurposeToggles( + MDataBlock dataBlock, + bool* drawRenderPurpose, + bool* drawProxyPurpose, + bool* drawGuidePurpose) const; + + void _OnStageContentsChanged( + const UsdNotice::StageContentsChanged& notice); + + UsdMayaStageNoticeListener _stageNoticeListener; + + std::map _boundingBoxCache; + size_t _excludePrimPathsVersion{ 1 }; + + static ClosestPointDelegate _sharedClosestPointDelegate; +}; + + +PXR_NAMESPACE_CLOSE_SCOPE + + +#endif diff --git a/lib/nodes/proxyShapePlugin.cpp b/lib/nodes/proxyShapePlugin.cpp new file mode 100644 index 0000000000..d7f00384c0 --- /dev/null +++ b/lib/nodes/proxyShapePlugin.cpp @@ -0,0 +1,112 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "proxyShapePlugin.h" + +#include "stageData.h" +#include "proxyShapeBase.h" + +#include "pxr/base/tf/envSetting.h" + +#include +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace { +const MString _RegistrantId("mayaUsd"); +int _registrationCount = 0; + +// Name of the plugin registering the proxy shape base class. +MString _registrantPluginName; +} + +PXR_NAMESPACE_OPEN_SCOPE + +/* static */ +MStatus +MayaUsdProxyShapePlugin::initialize(MFnPlugin& plugin) +{ + // If we're already registered, do nothing. + if (_registrationCount++ > 0) { + return MS::kSuccess; + } + + _registrantPluginName = plugin.name(); + + MStatus status; + + // Proxy shape initialization. + status = plugin.registerData( + MayaUsdStageData::typeName, + MayaUsdStageData::mayaTypeId, + MayaUsdStageData::creator); + CHECK_MSTATUS(status); + + status = plugin.registerShape( + MayaUsdProxyShapeBase::typeName, + MayaUsdProxyShapeBase::typeId, + MayaUsdProxyShapeBase::creator, + MayaUsdProxyShapeBase::initialize, + nullptr, + getProxyShapeClassification()); + CHECK_MSTATUS(status); + + return status; +} + +/* static */ +MStatus +MayaUsdProxyShapePlugin::finalize(MFnPlugin& plugin) +{ + // If more than one plugin still has us registered, do nothing. + if (_registrationCount-- > 1) { + return MS::kSuccess; + } + + // Maya requires deregistration to be done by the same plugin that + // performed the registration. If this isn't possible, warn and don't + // deregister. + if (plugin.name() != _registrantPluginName) { + MGlobal::displayWarning( + "USD proxy shape base cannot be deregistered, registering plugin " + + _registrantPluginName + " is unloaded."); + return MS::kSuccess; + } + + MStatus status; + + status = plugin.deregisterNode(MayaUsdProxyShapeBase::typeId); + CHECK_MSTATUS(status); + + status = plugin.deregisterData(MayaUsdStageData::mayaTypeId); + CHECK_MSTATUS(status); + + return status; +} + +const MString* MayaUsdProxyShapePlugin::getProxyShapeClassification() +{ + return nullptr; +} + +bool MayaUsdProxyShapePlugin::useVP2_NativeUSD_Rendering() +{ + return false; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/nodes/proxyShapePlugin.h b/lib/nodes/proxyShapePlugin.h new file mode 100644 index 0000000000..42e7f2cc5c --- /dev/null +++ b/lib/nodes/proxyShapePlugin.h @@ -0,0 +1,58 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef PXRUSDMAYA_PROXY_SHAPE_PLUGIN_H +#define PXRUSDMAYA_PROXY_SHAPE_PLUGIN_H + +/// \file usdMaya/proxyShapePlugin.h + +#include "../base/api.h" + +#include "pxr/pxr.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/// \class MayaUsdProxyShapePlugin +/// \brief Encapsulates plugin registration and deregistration of proxy shape classes. +/// +/// Proxy shape support requires plugin registration of node classes, node +/// data, and draw support. This class provides this service, including if +/// multiple plugins that use proxy shapes are loaded: using reference +/// counting, only the first registration and the last deregistration will +/// be performed. Note that because of Maya architecture requirements, +/// deregistration will only be done if the deregistering plugin is the same as +/// the registering plugin. Otherwise, a warning is shown. + +class MayaUsdProxyShapePlugin +{ + public: + MAYAUSD_CORE_PUBLIC + static MStatus initialize(MFnPlugin&); + + MAYAUSD_CORE_PUBLIC + static MStatus finalize(MFnPlugin&); + + MAYAUSD_CORE_PUBLIC + static const MString* getProxyShapeClassification(); + + MAYAUSD_CORE_PUBLIC + static bool useVP2_NativeUSD_Rendering(); +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/plugin/pxr/maya/lib/usdMaya/stageData.cpp b/lib/nodes/stageData.cpp similarity index 66% rename from plugin/pxr/maya/lib/usdMaya/stageData.cpp rename to lib/nodes/stageData.cpp index 782c48cf74..caf689113d 100644 --- a/plugin/pxr/maya/lib/usdMaya/stageData.cpp +++ b/lib/nodes/stageData.cpp @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/stageData.h" +#include "stageData.h" #include "pxr/base/gf/bbox3d.h" #include "pxr/base/tf/staticTokens.h" @@ -30,43 +30,44 @@ PXR_NAMESPACE_OPEN_SCOPE -TF_DEFINE_PUBLIC_TOKENS(UsdMayaStageDataTokens, - PXRUSDMAYA_STAGE_DATA_TOKENS); +TF_DEFINE_PUBLIC_TOKENS(MayaUsdStageDataTokens, + PXRMAYAUSD_STAGE_DATA_TOKENS); -const MTypeId UsdMayaStageData::mayaTypeId(0x0010A257); -const MString UsdMayaStageData::typeName( - UsdMayaStageDataTokens->MayaTypeName.GetText()); +const MTypeId MayaUsdStageData::mayaTypeId(0x0010A257); +const MString MayaUsdStageData::typeName( + MayaUsdStageDataTokens->MayaTypeName.GetText()); /* This exists solely to make sure that the usdStage instance - * gets discarded when Maya exits, so that an temporary files + * gets discarded when Maya exits, so that any temporary files * that might have been created are unlinked. */ -static +namespace { void _cleanUp(void *gdPtr) { - UsdMayaStageData *gd = (UsdMayaStageData *)gdPtr; + MayaUsdStageData *gd = (MayaUsdStageData *)gdPtr; gd->unregisterExitCallback(); gd->stage = UsdStageRefPtr(); } +} /* static */ void* -UsdMayaStageData::creator() +MayaUsdStageData::creator() { - return new UsdMayaStageData(); + return new MayaUsdStageData(); } /* virtual */ void -UsdMayaStageData::copy(const MPxData& src) +MayaUsdStageData::copy(const MPxData& src) { - const UsdMayaStageData* stageData = - dynamic_cast(&src); + const MayaUsdStageData* stageData = + dynamic_cast(&src); if (stageData) { stage = stageData->stage; @@ -76,25 +77,25 @@ UsdMayaStageData::copy(const MPxData& src) /* virtual */ MTypeId -UsdMayaStageData::typeId() const +MayaUsdStageData::typeId() const { return mayaTypeId; } /* virtual */ MString -UsdMayaStageData::name() const +MayaUsdStageData::name() const { return typeName; } -UsdMayaStageData::UsdMayaStageData() : MPxGeometryData() +MayaUsdStageData::MayaUsdStageData() : MPxGeometryData() { registerExitCallback(); } void -UsdMayaStageData::registerExitCallback() +MayaUsdStageData::registerExitCallback() { _exitCallbackId = MSceneMessage::addCallback(MSceneMessage::kMayaExiting, _cleanUp, @@ -102,13 +103,13 @@ UsdMayaStageData::registerExitCallback() } void -UsdMayaStageData::unregisterExitCallback() +MayaUsdStageData::unregisterExitCallback() { MSceneMessage::removeCallback(_exitCallbackId); } /* virtual */ -UsdMayaStageData::~UsdMayaStageData() { +MayaUsdStageData::~MayaUsdStageData() { unregisterExitCallback(); } diff --git a/lib/nodes/stageData.h b/lib/nodes/stageData.h new file mode 100644 index 0000000000..6891021fd2 --- /dev/null +++ b/lib/nodes/stageData.h @@ -0,0 +1,138 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef PXRUSDMAYA_STAGE_DATA_H +#define PXRUSDMAYA_STAGE_DATA_H + +/// \file usdMaya/stageData.h + +#include "../base/api.h" + +#include "pxr/pxr.h" + +#include "pxr/base/tf/staticTokens.h" + +#include "pxr/usd/sdf/path.h" +#include "pxr/usd/usd/stage.h" + +#include +#include +#include +#include +#include + + +PXR_NAMESPACE_OPEN_SCOPE + + +#define PXRMAYAUSD_STAGE_DATA_TOKENS \ + ((MayaTypeName, "pxrUsdStageData")) + +TF_DECLARE_PUBLIC_TOKENS(MayaUsdStageDataTokens, + MAYAUSD_CORE_PUBLIC, + PXRMAYAUSD_STAGE_DATA_TOKENS); + + +class MayaUsdStageData : public MPxGeometryData +{ + public: + /// Unlike other Maya node types, MPxData/MPxGeometryData declare + /// typeId() as a pure virtual method that must be overridden in + /// derived classes, so we have to call this static member "mayaTypeId" + /// instead of just "typeId" as we usually would. + MAYAUSD_CORE_PUBLIC + static const MTypeId mayaTypeId; + MAYAUSD_CORE_PUBLIC + static const MString typeName; + + MAYAUSD_CORE_PUBLIC + static void* creator(); + + /** + * \name MPxGeometryData overrides + */ + //@{ + + MAYAUSD_CORE_PUBLIC + void copy(const MPxData& src) override; + + MAYAUSD_CORE_PUBLIC + MTypeId typeId() const override; + + MAYAUSD_CORE_PUBLIC + MString name() const override; + //@} + + void unregisterExitCallback(); + + /** + * \name data + */ + //@{ + + // The original Pixar code was the following: + // + // UsdStageRefPtr stage; + // + // Now using a weak pointer instead of a referencing pointer. The AL + // plugin originally used a referencing pointer, but ran into problems. + // From a Rob Bateman @ AL e-mail, 7-Apr-2019: + // + // "The reason for the weak pointer was that we found that Maya seemed + // to have a memory leak (as in, the MPxData derived objects were being + // created, but never deleted when we expected them to be - possibly by + // design?). We had originally used a direct mirror of the Pixar data + // object, but because the data objects were never freed (even after + // file new), the original stage would be retained. This meant the + // internal SdfLayerCache in USD would keep hold of the previously + // loaded layers. So we found we had this problem: + // + // 1. Import some.usda file into a proxy shape. + // 2. Make some modifications + // 3. File New + // 4. Import the same "some.usda" file into a proxy shape. + // + // At this point, USD would essentially hand you back the stage + // composed of the modified layers, rather than a clean stage composed + // from the files on disk. Switching the type from a shared_ptr to a + // weak_ptr worked around this issue. Basically if we use a shared + // pointer here, the only way to reload a scene, would be to restart + // Maya." + + UsdStageWeakPtr stage; + SdfPath primPath; + + //@} + + protected: + MAYAUSD_CORE_PUBLIC + MayaUsdStageData(); + MAYAUSD_CORE_PUBLIC + ~MayaUsdStageData() override; + + private: + MayaUsdStageData(const MayaUsdStageData&); + MayaUsdStageData& operator=(const MayaUsdStageData&); + + void registerExitCallback(); + + MCallbackId _exitCallbackId; +}; + + +PXR_NAMESPACE_CLOSE_SCOPE + + +#endif diff --git a/plugin/pxr/maya/lib/usdMaya/usdPrimProvider.cpp b/lib/nodes/usdPrimProvider.cpp similarity index 95% rename from plugin/pxr/maya/lib/usdMaya/usdPrimProvider.cpp rename to lib/nodes/usdPrimProvider.cpp index 1ec79b3a36..479b6b4f58 100644 --- a/plugin/pxr/maya/lib/usdMaya/usdPrimProvider.cpp +++ b/lib/nodes/usdPrimProvider.cpp @@ -14,7 +14,7 @@ // limitations under the License. // #include "pxr/pxr.h" -#include "usdMaya/usdPrimProvider.h" +#include "usdPrimProvider.h" PXR_NAMESPACE_OPEN_SCOPE diff --git a/plugin/pxr/maya/lib/usdMaya/usdPrimProvider.h b/lib/nodes/usdPrimProvider.h similarity index 95% rename from plugin/pxr/maya/lib/usdMaya/usdPrimProvider.h rename to lib/nodes/usdPrimProvider.h index f8efe46386..2dab51d245 100644 --- a/plugin/pxr/maya/lib/usdMaya/usdPrimProvider.h +++ b/lib/nodes/usdPrimProvider.h @@ -17,7 +17,7 @@ #define PXRUSDMAYA_USD_PRIM_PROVIDER_H #include "pxr/pxr.h" -#include "usdMaya/api.h" +#include "../base/api.h" #include "pxr/usd/usd/prim.h" PXR_NAMESPACE_OPEN_SCOPE @@ -30,7 +30,7 @@ class UsdMayaUsdPrimProvider // returns the prim that this node is holding virtual UsdPrim usdPrim() const = 0; - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC virtual ~UsdMayaUsdPrimProvider(); }; diff --git a/plugin/pxr/maya/lib/usdMaya/colorSpace.cpp b/lib/utils/colorSpace.cpp similarity index 97% rename from plugin/pxr/maya/lib/usdMaya/colorSpace.cpp rename to lib/utils/colorSpace.cpp index 3cd18de21c..4d19f915a6 100644 --- a/plugin/pxr/maya/lib/usdMaya/colorSpace.cpp +++ b/lib/utils/colorSpace.cpp @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/colorSpace.h" +#include "colorSpace.h" #include "pxr/base/tf/envSetting.h" diff --git a/plugin/pxr/maya/lib/usdMaya/colorSpace.h b/lib/utils/colorSpace.h similarity index 98% rename from plugin/pxr/maya/lib/usdMaya/colorSpace.h rename to lib/utils/colorSpace.h index c582d29dab..eb3b6f9772 100644 --- a/plugin/pxr/maya/lib/usdMaya/colorSpace.h +++ b/lib/utils/colorSpace.h @@ -18,7 +18,7 @@ /// \file usdMaya/colorSpace.h -#include "usdMaya/api.h" +#include "../base/api.h" #include "pxr/pxr.h" @@ -50,7 +50,7 @@ namespace UsdMayaColorSpace /// You should only be setting that if you've more or less fully switched to /// Viewport 2.0 (as proper color management is only supported there). /// -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool IsColorManaged(); diff --git a/plugin/pxr/maya/lib/usdMaya/query.cpp b/lib/utils/query.cpp similarity index 95% rename from plugin/pxr/maya/lib/usdMaya/query.cpp rename to lib/utils/query.cpp index 452f330d05..2afefa934e 100644 --- a/plugin/pxr/maya/lib/usdMaya/query.cpp +++ b/lib/utils/query.cpp @@ -13,10 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/query.h" +#include "query.h" -#include "usdMaya/usdPrimProvider.h" -#include "usdMaya/util.h" +#include "../nodes/usdPrimProvider.h" +#include "util.h" #include "pxr/base/arch/systemInfo.h" diff --git a/plugin/pxr/maya/lib/usdMaya/query.h b/lib/utils/query.h similarity index 93% rename from plugin/pxr/maya/lib/usdMaya/query.h rename to lib/utils/query.h index 47f8cc4b70..6916cf65a0 100644 --- a/plugin/pxr/maya/lib/usdMaya/query.h +++ b/lib/utils/query.h @@ -18,7 +18,7 @@ /// \file usdMaya/query.h -#include "usdMaya/api.h" +#include "../base/api.h" #include "pxr/pxr.h" @@ -35,9 +35,9 @@ struct UsdMayaQuery { /*! \brief converts a dagPath of a usdStageShapeNode into a usdprim */ - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static UsdPrim GetPrim(const std::string& shapeName); - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static void ReloadStage(const std::string& shapeName); }; diff --git a/plugin/pxr/maya/lib/usdMaya/stageCache.cpp b/lib/utils/stageCache.cpp similarity index 98% rename from plugin/pxr/maya/lib/usdMaya/stageCache.cpp rename to lib/utils/stageCache.cpp index a41c39a379..f0b13c07b6 100644 --- a/plugin/pxr/maya/lib/usdMaya/stageCache.cpp +++ b/lib/utils/stageCache.cpp @@ -13,9 +13,9 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/stageCache.h" +#include "stageCache.h" -#include "usdMaya/notice.h" +#include "../listeners/notice.h" #include "pxr/usd/sdf/attributeSpec.h" #include "pxr/usd/sdf/layer.h" diff --git a/plugin/pxr/maya/lib/usdMaya/stageCache.h b/lib/utils/stageCache.h similarity index 94% rename from plugin/pxr/maya/lib/usdMaya/stageCache.h rename to lib/utils/stageCache.h index 99cd23e0f3..61d59141d6 100644 --- a/plugin/pxr/maya/lib/usdMaya/stageCache.h +++ b/lib/utils/stageCache.h @@ -18,7 +18,7 @@ /// \file usdMaya/stageCache.h -#include "usdMaya/api.h" +#include "../base/api.h" #include "pxr/pxr.h" @@ -38,24 +38,25 @@ class UsdMayaStageCache /// Return the singleton stage cache for use by all USD clients within Maya. /// 2 stage caches are maintained; 1 for stages that have been /// force-populated, and 1 for stages that have not been force-populated. - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static UsdStageCache& Get(const bool forcePopulate=true); /// Clear the cache - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static void Clear(); /// Erase all stages from the stage caches whose root layer path is /// \p layerPath. /// /// The number of stages erased from the caches is returned. - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static size_t EraseAllStagesWithRootLayerPath( const std::string& layerPath); /// Gets (or creates) a shared session layer tied with the given variant /// selections and draw mode on the given root path. /// The stage is cached for the lifetime of the current Maya scene. + MAYAUSD_CORE_PUBLIC static SdfLayerRefPtr GetSharedSessionLayer( const SdfPath& rootPath, const std::map& variantSelections, diff --git a/plugin/pxr/maya/lib/usdMaya/util.cpp b/lib/utils/util.cpp similarity index 99% rename from plugin/pxr/maya/lib/usdMaya/util.cpp rename to lib/utils/util.cpp index 450ad4e3c5..7e3fd68c7b 100644 --- a/plugin/pxr/maya/lib/usdMaya/util.cpp +++ b/lib/utils/util.cpp @@ -13,10 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "pxr/pxr.h" -#include "usdMaya/colorSpace.h" -#include "usdMaya/util.h" +#include "util.h" + +#include "colorSpace.h" #include "pxr/base/gf/gamma.h" #include "pxr/base/gf/vec2f.h" @@ -738,7 +738,7 @@ _getAttachedMayaShaderObjects( for (unsigned int i=0; i < setObjs.length(); ++i) { // Get associated Set and Shading Group MFnSet setFn(setObjs[i], &status); - MPlug seSurfaceShaderPlg = setFn.findPlug("surfaceShader", &status); + MPlug seSurfaceShaderPlg = setFn.findPlug("surfaceShader", true, &status); // Find connection shader->shadingGroup MPlugArray plgCons; @@ -811,11 +811,11 @@ _GetColorAndTransparencyFromDepNode( { MStatus status; MFnDependencyNode d(shaderObj); - MPlug colorPlug = d.findPlug("color", &status); + MPlug colorPlug = d.findPlug("color", true, &status); if (!status) { return false; } - MPlug transparencyPlug = d.findPlug("transparency", &status); + MPlug transparencyPlug = d.findPlug("transparency", true, &status); if (!status) { return false; } @@ -1358,12 +1358,12 @@ UsdMayaUtil::getPlugMatrix( MMatrix* outVal) { MStatus status; - MPlug plug = depNode.findPlug(attr, &status); + MPlug plug = depNode.findPlug(attr, true, &status); if (!status) { return false; } - MObject plugObj = plug.asMObject(MDGContext::fsNormal, &status); + MObject plugObj = plug.asMObject(&status); if (!status) { return false; } @@ -1384,7 +1384,7 @@ UsdMayaUtil::setPlugMatrix( const GfMatrix4d& mx) { MStatus status; - MPlug plug = depNode.findPlug(attr, &status); + MPlug plug = depNode.findPlug(attr, true, &status); CHECK_MSTATUS_AND_RETURN(status, false); return setPlugMatrix(mx, plug); } diff --git a/plugin/pxr/maya/lib/usdMaya/util.h b/lib/utils/util.h similarity index 94% rename from plugin/pxr/maya/lib/usdMaya/util.h rename to lib/utils/util.h index 8c13e58030..961c81fdb2 100644 --- a/plugin/pxr/maya/lib/usdMaya/util.h +++ b/lib/utils/util.h @@ -19,7 +19,7 @@ /// \file usdMaya/util.h #include "pxr/pxr.h" -#include "usdMaya/api.h" +#include "../base/api.h" #include "pxr/base/gf/vec2f.h" #include "pxr/base/gf/vec3f.h" @@ -113,9 +113,9 @@ class MDataHandleHolder : public TfRefBase MDataHandle _dataHandle; public: - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC static TfRefPtr New(const MPlug& plug); - PXRUSDMAYA_API + MAYAUSD_CORE_PUBLIC MDataHandle GetDataHandle() { return _dataHandle; } private: @@ -165,13 +165,13 @@ ConvertCMToMM(const double cm) /// Converts the given value \p mdistance in Maya's MDistance units to the /// equivalent value in USD's metersPerUnit. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC double ConvertMDistanceUnitToUsdGeomLinearUnit( const MDistance::Unit mdistanceUnit); /// Coverts the given value \p linearUnit in USD's metersPerUnit to the /// equivalent value in Maya's MDistance units. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MDistance::Unit ConvertUsdGeomLinearUnitToMDistanceUnit( const double linearUnit); @@ -185,21 +185,21 @@ MDistance::Unit ConvertUsdGeomLinearUnitToMDistanceUnit( /// /// If \p mayaNode is not one of these or if an error is encountered, an /// empty string will be returned. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC std::string GetMayaNodeName(const MObject& mayaNode); /// Gets the Maya MObject for the node named \p nodeName. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MStatus GetMObjectByName(const std::string& nodeName, MObject& mObj); /// Gets the Maya MDagPath for the node named \p nodeName. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MStatus GetDagPathByName(const std::string& nodeName, MDagPath& dagPath); /// Gets the Maya MPlug for the given \p attrPath. /// The attribute path should be specified as "nodeName.attrName" (the format /// used by MEL). -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MStatus GetPlugByName(const std::string& attrPath, MPlug& plug); /// Get the MPlug for the output time attribute of Maya's global time object @@ -214,36 +214,36 @@ MStatus GetPlugByName(const std::string& attrPath, MPlug& plug); /// all MFn::kTime function set objects in the scene and returning the one whose /// outTime attribute matches the current time. If no such object can be found, /// an invalid plug is returned. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MPlug GetMayaTimePlug(); /// Get the MPlug for the shaders attribute of Maya's defaultShaderList /// /// This is an accessor for the "defaultShaderList1.shaders" plug. Similar to /// GetMayaTimePlug(), it will traverse through MFn::kShaderList objects. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MPlug GetMayaShaderListPlug(); /// Get the MObject for the DefaultLightSet, which should add any light nodes /// as members for them to take effect in the scene -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MObject GetDefaultLightSetObject(); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool isAncestorDescendentRelationship( const MDagPath& path1, const MDagPath& path2); // returns 0 if static, 1 if sampled, and 2 if a curve -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC int getSampledType(const MPlug& iPlug, const bool includeConnectedChildren); /// Determine if the Maya object \p mayaObject is animated or not -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool isAnimated(const MObject& mayaObject, const bool checkParent = false); // Determine if a specific Maya plug is animated or not. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool isPlugAnimated(const MPlug& plug); /// Determine if a Maya object is an intermediate object. @@ -251,11 +251,11 @@ bool isPlugAnimated(const MPlug& plug); /// Only objects with the MFnDagNode function set can be intermediate objects. /// Objects whose intermediate object status cannot be determined are assumed /// not to be intermediate objects. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool isIntermediate(const MObject& object); // returns true for visible and lod invisible and not templated objects -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool isRenderable(const MObject& object); /// Determine whether a Maya object can be saved to or exported from the Maya @@ -263,7 +263,7 @@ bool isRenderable(const MObject& object); /// /// Objects whose "default node" or "do not write" status cannot be determined /// using the MFnDependencyNode function set are assumed to be writable. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool isWritable(const MObject& object); /// This is the delimiter that Maya uses to identify levels of hierarchy in the @@ -279,16 +279,16 @@ const std::string MayaNamespaceDelimiter(":"); /// This will turn "taco:foo:bar" into "foo:bar" for \p nsDepth == 1, or /// "taco:foo:bar" into "bar" for \p nsDepth > 1. /// If \p nsDepth is -1, all namespaces are stripped. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC std::string stripNamespaces( const std::string& nodeName, const int nsDepth = -1); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC std::string SanitizeName(const std::string& name); // This to allow various pipeline to sanitize the colorset name for output -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC std::string SanitizeColorSetName(const std::string& name); /// Get the base colors and opacities from the shader(s) bound to \p node. @@ -297,7 +297,7 @@ std::string SanitizeColorSetName(const std::string& name); /// A single value for each of color and alpha will be returned, /// interpolation will be constant, and assignmentIndices will be empty. /// -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool GetLinearShaderColor( const MFnDagNode& node, PXR_NS::VtVec3fArray* RGBData, @@ -318,7 +318,7 @@ bool GetLinearShaderColor( /// representing per-face assignments. Faces with no assigned shader will have /// a value of -1 in \p assignmentIndices. \p interpolation will be uniform. /// -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool GetLinearShaderColor( const MFnMesh& mesh, PXR_NS::VtVec3fArray* RGBData, @@ -328,28 +328,28 @@ bool GetLinearShaderColor( /// Combine distinct indices that point to the same values to all point to the /// same index for that value. This will potentially shrink the data array. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC void MergeEquivalentIndexedValues( PXR_NS::VtFloatArray* valueData, PXR_NS::VtIntArray* assignmentIndices); /// Combine distinct indices that point to the same values to all point to the /// same index for that value. This will potentially shrink the data array. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC void MergeEquivalentIndexedValues( PXR_NS::VtVec2fArray* valueData, PXR_NS::VtIntArray* assignmentIndices); /// Combine distinct indices that point to the same values to all point to the /// same index for that value. This will potentially shrink the data array. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC void MergeEquivalentIndexedValues( PXR_NS::VtVec3fArray* valueData, PXR_NS::VtIntArray* assignmentIndices); /// Combine distinct indices that point to the same values to all point to the /// same index for that value. This will potentially shrink the data array. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC void MergeEquivalentIndexedValues( PXR_NS::VtVec4fArray* valueData, PXR_NS::VtIntArray* assignmentIndices); @@ -358,7 +358,7 @@ void MergeEquivalentIndexedValues( /// constant interpolation if possible. This will potentially shrink the /// indices array and will update the interpolation if any compression was /// possible. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC void CompressFaceVaryingPrimvarIndices( const MFnMesh& mesh, PXR_NS::TfToken* interpolation, @@ -370,20 +370,20 @@ void CompressFaceVaryingPrimvarIndices( /// default (or since being brought in from a reference for plugs on nodes from /// referenced files), or if the plug is the destination of a connection. /// Otherwise, it is considered unauthored. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool IsAuthored(const MPlug& plug); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MPlug GetConnected(const MPlug& plug); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC void Connect( const MPlug& srcPlug, const MPlug& dstPlug, const bool clearDstPlug); /// Get a named child plug of \p plug by name. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MPlug FindChildPlugByName(const MPlug& plug, const MString& name); /// Converts the given Maya node name \p nodeName into an SdfPath. @@ -391,8 +391,8 @@ MPlug FindChildPlugByName(const MPlug& plug, const MString& name); /// Elements of the path will be sanitized such that it is a valid SdfPath. /// This means it will replace Maya's namespace delimiter (':') with /// underscores ('_'). -PXRUSDMAYA_API -PXR_NS::SdfPath MayaNodeNameToSdfPath( +MAYAUSD_CORE_PUBLIC +SdfPath MayaNodeNameToSdfPath( const std::string& nodeName, const bool stripNamespaces); @@ -405,14 +405,14 @@ PXR_NS::SdfPath MayaNodeNameToSdfPath( /// Elements of the path will be sanitized such that it is a valid SdfPath. /// This means it will replace Maya's namespace delimiter (':') with /// underscores ('_'). -PXRUSDMAYA_API -PXR_NS::SdfPath MDagPathToUsdPath( +MAYAUSD_CORE_PUBLIC +SdfPath MDagPathToUsdPath( const MDagPath& dagPath, const bool mergeTransformAndShape, const bool stripNamespaces); /// Convenience function to retrieve custom data -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool GetBoolCustomData( const PXR_NS::UsdAttribute& obj, const PXR_NS::TfToken& key, @@ -439,13 +439,13 @@ bool getPlugValue( } /// Convert a Gf matrix to an MMatrix. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MMatrix GfMatrixToMMatrix(const GfMatrix4d& mx); // Like getPlugValue, but gets the matrix stored inside the MFnMatrixData on a // plug. // Returns true upon success, placing the matrix in the outVal parameter. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool getPlugMatrix( const MFnDependencyNode& depNode, const MString& attr, @@ -453,13 +453,13 @@ bool getPlugMatrix( /// Set a matrix value on plug name \p attr, of \p depNode. /// Returns true if the value was set on the plug successfully, false otherwise. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool setPlugMatrix( const MFnDependencyNode& depNode, const MString& attr, const GfMatrix4d& mx); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool setPlugMatrix(const GfMatrix4d& mx, MPlug& plug); /// Given an \p usdAttr , extract the value at the default timecode and write @@ -467,7 +467,7 @@ bool setPlugMatrix(const GfMatrix4d& mx, MPlug& plug); /// This will make sure that color values (which are linear in usd) get /// gamma corrected (display in maya). /// Returns true if the value was set on the plug successfully, false otherwise. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool setPlugValue(const PXR_NS::UsdAttribute& attr, MPlug& attrPlug); /// Given an \p usdAttr , extract the value at timecode \p time and write it @@ -475,7 +475,7 @@ bool setPlugValue(const PXR_NS::UsdAttribute& attr, MPlug& attrPlug); /// This will make sure that color values (which are linear in usd) get /// gamma corrected (display in maya). /// Returns true if the value was set on the plug successfully, false otherwise. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool setPlugValue( const PXR_NS::UsdAttribute& attr, const PXR_NS::UsdTimeCode time, @@ -501,20 +501,20 @@ bool setPlugValue( /// plug. When the helper object goes out of scope, the data handle will be /// destructed. /// If the plug's data handle could not be obtained, returns nullptr. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC TfRefPtr GetPlugDataHandle(const MPlug& plug); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool SetNotes(MFnDependencyNode& depNode, const std::string& notes); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool SetHiddenInOutliner(MFnDependencyNode& depNode, const bool hidden); /// Reads values from the given \p argData into a VtDictionary, using the /// \p guideDict to figure out which keys and what type of values should be read /// from \p argData. /// Mainly useful for parsing arguments in commands all at once. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC VtDictionary GetDictionaryFromArgDatabase( const MArgDatabase& argData, const VtDictionary& guideDict); @@ -525,7 +525,7 @@ VtDictionary GetDictionaryFromArgDatabase( /// Mainly useful for parsing arguments one-by-one in translators' option /// strings. If you have an MArgList/MArgParser/MArgDatabase, it's going to be /// way simpler to use GetDictionaryFromArgDatabase() instead. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC VtValue ParseArgumentValue( const std::string& key, const std::string& value, @@ -537,18 +537,18 @@ VtValue ParseArgumentValue( /// The returned list is sorted from furthest to closest ancestor. The returned /// list will always have the given type \p ty as the last item. /// Note that this calls out to MEL. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC std::vector GetAllAncestorMayaNodeTypes(const std::string& ty); /// If dagPath is a scene assembly node or is the descendant of one, populates /// the \p *assemblyPath with the assembly path and returns \c true. /// Otherwise, returns \c false. -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC bool FindAncestorSceneAssembly( const MDagPath& dagPath, MDagPath* assemblyPath = nullptr); -PXRUSDMAYA_API +MAYAUSD_CORE_PUBLIC MBoundingBox GetInfiniteBoundingBox(); } // namespace UsdMayaUtil diff --git a/lib/utils/utilFileSystem.cpp b/lib/utils/utilFileSystem.cpp new file mode 100644 index 0000000000..57be2519c6 --- /dev/null +++ b/lib/utils/utilFileSystem.cpp @@ -0,0 +1,99 @@ + +#include "utilFileSystem.h" +#include "../base/debugCodes.h" + +#include +#include +#include + +#include "pxr/usd/ar/resolver.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +std::string +UsdMayaUtilFileSystem::resolvePath(const std::string& filePath) +{ + ArResolver& resolver = ArGetResolver(); + return resolver.Resolve(filePath); +} + +std::string +UsdMayaUtilFileSystem::getDir(const std::string &fullFilePath) +{ + return boost::filesystem::path(fullFilePath).parent_path().string(); +} + +std::string +UsdMayaUtilFileSystem::getMayaReferencedFileDir(const MObject &proxyShapeNode) +{ + // Can not use MFnDependencyNode(proxyShapeNode).isFromReferencedFile() to test if it is reference node or not, + // which always return false even the proxyShape node is referenced... + + MStatus stat; + MFnReference refFn; + MItDependencyNodes dgIter(MFn::kReference, &stat); + for (; !dgIter.isDone(); dgIter.next()) + { + MObject cRefNode = dgIter.thisNode(); + refFn.setObject(cRefNode); + if(refFn.containsNodeExactly(proxyShapeNode, &stat)) + { + // According to Maya API document, the second argument is 'includePath' and set it to true to include the file path. + // However, I have to set it to false to return the full file path otherwise I get a file name only... + MString refFilePath = refFn.fileName(true, false, false, &stat); + if(!refFilePath.length()) + return std::string(); + + std::string referencedFilePath = refFilePath.asChar(); + TF_DEBUG(USDMAYA_PROXYSHAPEBASE).Msg("getMayaReferencedFileDir: The reference file that contains the proxyShape node is : %s\n", referencedFilePath.c_str()); + + return getDir(referencedFilePath); + } + } + + return std::string(); +} + +std::string +UsdMayaUtilFileSystem::getMayaSceneFileDir() +{ + std::string currentFile = std::string(MFileIO::currentFile().asChar(), MFileIO::currentFile().length()); + size_t filePathSize = currentFile.size(); + if(filePathSize < 4) + return std::string(); + + // If scene is untitled, the maya file will be MayaWorkspaceDir/untitled : + constexpr char ma_ext[] = ".ma"; + constexpr char mb_ext[] = ".mb"; + auto ext_start = currentFile.end() - 3; + if(std::equal(ma_ext, ma_ext + 3, ext_start) || std::equal(mb_ext, mb_ext + 3, ext_start)) + return getDir(currentFile); + + return std::string(); +} + +std::string +UsdMayaUtilFileSystem::resolveRelativePathWithinMayaContext(const MObject &proxyShape, const std::string& relativeFilePath) +{ + if (relativeFilePath.length() < 3) + return relativeFilePath; + + std::string currentFileDir = getMayaReferencedFileDir(proxyShape); + + if(currentFileDir.empty()) + currentFileDir = getMayaSceneFileDir(); + + if(currentFileDir.empty()) + return relativeFilePath; + + boost::system::error_code errorCode; + auto path = boost::filesystem::canonical(relativeFilePath, currentFileDir, errorCode); + if (errorCode) + { + // file does not exist + return std::string(); + } + + return path.string(); +} + diff --git a/lib/utils/utilFileSystem.h b/lib/utils/utilFileSystem.h new file mode 100644 index 0000000000..9241c1c224 --- /dev/null +++ b/lib/utils/utilFileSystem.h @@ -0,0 +1,42 @@ +#include "pxr/pxr.h" + +#include "../base/api.h" + +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace UsdMayaUtilFileSystem +{ + /*! \brief returns the resolved filesystem path for the file identified by the given path + */ + MAYAUSD_CORE_PUBLIC + std::string resolvePath(const std::string& filePath); + + /*! \brief returns the path to the + */ + MAYAUSD_CORE_PUBLIC + std::string getDir(const std::string &fullFilePath); + + /*! \brief returns parent directory of a maya scene file opened by reference + */ + MAYAUSD_CORE_PUBLIC + std::string getMayaReferencedFileDir(const MObject& proxyShapeNode); + + /*! \brief returns parent directory of opened maya scene file + */ + MAYAUSD_CORE_PUBLIC + std::string getMayaSceneFileDir(); + + /*! \brief returns the aboluste path relative to the maya file + */ + MAYAUSD_CORE_PUBLIC + std::string resolveRelativePathWithinMayaContext(const MObject& proxyShape, + const std::string& relativeFilePath); + +} // namespace UsdMayaUtilFileSystem + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/plugin/al/CMakeLists.txt b/plugin/al/CMakeLists.txt index 00c9b05ef8..87d783a0a3 100644 --- a/plugin/al/CMakeLists.txt +++ b/plugin/al/CMakeLists.txt @@ -23,15 +23,10 @@ else() set(CMAKE_WANT_UFE_BUILD ON) endif() -include(${USD_CONFIG_FILE}) - -find_package(Maya REQUIRED) # to get PYTHON_EXECUTABLE find_package(PythonInterp) -# find_package(MayaUSD REQUIRED) - if(CMAKE_WANT_UFE_BUILD) add_definitions(-DWANT_UFE_BUILD) @@ -188,4 +183,4 @@ get_property(PYTHON_LIBRARY_LOCATION GLOBAL PROPERTY GLOBAL_PYTHON_LIBRARY_LOCAT configure_file(ALUsdMayaConfig.cmake.in ${PROJECT_BINARY_DIR}/ALUsdMayaConfig.cmake @ONLY) install(CODE "message(STATUS \"POST INSTALL: Compiling python/pyc for ${CMAKE_INSTALL_PREFIX} ... \")") -install(CODE "execute_process(COMMAND python -m compileall ${CMAKE_INSTALL_PREFIX} )") +install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -m compileall ${CMAKE_INSTALL_PREFIX} )") diff --git a/plugin/al/translators/CMakeLists.txt b/plugin/al/translators/CMakeLists.txt index ec5b998a16..bef6881145 100644 --- a/plugin/al/translators/CMakeLists.txt +++ b/plugin/al/translators/CMakeLists.txt @@ -1,4 +1,4 @@ -list(APPEND DEPENDANT_LIBRARIES AL_USDMaya AL_USDMayaSchemas gf plug tf) +list(APPEND DEPENDANT_LIBRARIES AL_USDMaya AL_USDMayaSchemas mayaUsd gf plug tf) set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) diff --git a/plugin/al/translators/pxrUsdTranslators/ProxyShapeTranslator.cpp b/plugin/al/translators/pxrUsdTranslators/ProxyShapeTranslator.cpp index f2d6712ea2..16daf0bd8e 100644 --- a/plugin/al/translators/pxrUsdTranslators/ProxyShapeTranslator.cpp +++ b/plugin/al/translators/pxrUsdTranslators/ProxyShapeTranslator.cpp @@ -27,7 +27,7 @@ #include "usdMaya/primWriterArgs.h" #include "usdMaya/primWriterContext.h" #include "usdMaya/primWriterRegistry.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/token.h" #include "pxr/usd/kind/registry.h" diff --git a/plugin/pxr/CMakeLists.txt b/plugin/pxr/CMakeLists.txt index 948aacd7f0..df21e3ec03 100644 --- a/plugin/pxr/CMakeLists.txt +++ b/plugin/pxr/CMakeLists.txt @@ -18,10 +18,6 @@ pxr_setup_python() #============================================================================== # Packages #============================================================================== -find_package(Maya REQUIRED) - -include(${USD_CONFIG_FILE}) - pxr_toplevel_prologue() add_subdirectory(maya) pxr_toplevel_epilogue() diff --git a/plugin/pxr/maya/lib/pxrUsdMayaGL/batchRenderer.h b/plugin/pxr/maya/lib/pxrUsdMayaGL/batchRenderer.h index 09470e09c1..2dd285241b 100644 --- a/plugin/pxr/maya/lib/pxrUsdMayaGL/batchRenderer.h +++ b/plugin/pxr/maya/lib/pxrUsdMayaGL/batchRenderer.h @@ -25,8 +25,8 @@ #include "pxrUsdMayaGL/shapeAdapter.h" #include "pxrUsdMayaGL/softSelectHelper.h" #include "usdMaya/diagnosticDelegate.h" -#include "usdMaya/notice.h" -#include "usdMaya/util.h" +#include +#include #include "pxr/base/gf/matrix4d.h" #include "pxr/base/gf/vec2i.h" diff --git a/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerImager.h b/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerImager.h index 908d705533..d244179240 100644 --- a/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerImager.h +++ b/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerImager.h @@ -24,8 +24,8 @@ #include "pxr/base/tf/singleton.h" #include "pxr/base/tf/weakBase.h" -#include "usdMaya/notice.h" -#include "usdMaya/util.h" +#include +#include #include #include diff --git a/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerShapeAdapter.cpp b/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerShapeAdapter.cpp index 6f1db1008b..c9592508df 100644 --- a/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerShapeAdapter.cpp +++ b/plugin/pxr/maya/lib/pxrUsdMayaGL/instancerShapeAdapter.cpp @@ -21,7 +21,7 @@ #include "pxrUsdMayaGL/shapeAdapter.h" #include "usdMaya/referenceAssembly.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeUtil.h" #include "pxr/base/gf/vec4f.h" diff --git a/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyDrawOverride.cpp b/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyDrawOverride.cpp index dce98e79e1..4238986b21 100644 --- a/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyDrawOverride.cpp +++ b/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyDrawOverride.cpp @@ -122,7 +122,7 @@ UsdMayaProxyDrawOverride::boundingBox( return MBoundingBox(); } - UsdMayaProxyShape* pShape = UsdMayaProxyShape::GetShapeAtDagPath(objPath); + MayaUsdProxyShapeBase* pShape = MayaUsdProxyShapeBase::GetShapeAtDagPath(objPath); if (!pShape) { return MBoundingBox(); } @@ -156,7 +156,7 @@ UsdMayaProxyDrawOverride::isBounded( // remove PIXMAYA_ENABLE_BOUNDING_BOX_MODE. return true; - // UsdMayaProxyShape* pShape = UsdMayaProxyShape::GetShapeAtDagPath(objPath); + // MayaUsdProxyShapeBase* pShape = MayaUsdProxyShapeBase::GetShapeAtDagPath(objPath); // if (!pShape) { // return false; // } @@ -190,7 +190,7 @@ UsdMayaProxyDrawOverride::prepareForDraw( return nullptr; } - UsdMayaProxyShape* shape = UsdMayaProxyShape::GetShapeAtDagPath(objPath); + MayaUsdProxyShapeBase* shape = MayaUsdProxyShapeBase::GetShapeAtDagPath(objPath); if (!shape) { return nullptr; } diff --git a/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeDelegate.cpp b/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeDelegate.cpp index 24a4f50330..e7ed73052a 100644 --- a/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeDelegate.cpp +++ b/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeDelegate.cpp @@ -48,7 +48,7 @@ static PxrMayaHdPrimFilter _sharedPrimFilter = { /// rendering using Hydra via the UsdMayaGLBatchRenderer. bool UsdMayaGL_ClosestPointOnProxyShape( - const UsdMayaProxyShape& shape, + const MayaUsdProxyShapeBase& shape, const GfRay& ray, GfVec3d* outClosestPoint, GfVec3d* outClosestNormal) @@ -137,7 +137,7 @@ UsdMayaGL_ObjectSoftSelectEnabled() TF_REGISTRY_FUNCTION(UsdMayaProxyShape) { - UsdMayaProxyShape::SetClosestPointDelegate( + MayaUsdProxyShapeBase::SetClosestPointDelegate( UsdMayaGL_ClosestPointOnProxyShape); UsdMayaProxyShape::SetObjectSoftSelectEnabledDelegate( UsdMayaGL_ObjectSoftSelectEnabled); diff --git a/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeUI.cpp b/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeUI.cpp index 03932d1268..840f0442a0 100644 --- a/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeUI.cpp +++ b/plugin/pxr/maya/lib/pxrUsdMayaGL/proxyShapeUI.cpp @@ -64,8 +64,8 @@ UsdMayaProxyShapeUI::getDrawRequests( MDrawRequestQueue& requests) { const MDagPath shapeDagPath = drawInfo.multiPath(); - UsdMayaProxyShape* shape = - UsdMayaProxyShape::GetShapeAtDagPath(shapeDagPath); + MayaUsdProxyShapeBase* shape = + MayaUsdProxyShapeBase::GetShapeAtDagPath(shapeDagPath); if (!shape) { return; } @@ -142,7 +142,7 @@ UsdMayaProxyShapeUI::select( return false; } - // Note that we cannot use UsdMayaProxyShape::GetShapeAtDagPath() here. + // Note that we cannot use MayaUsdProxyShapeBase::GetShapeAtDagPath() here. // selectInfo.selectPath() returns the dag path to the assembly node, not // the shape node, so we don't have the shape node's path readily available. UsdMayaProxyShape* shape = static_cast(surfaceShape()); diff --git a/plugin/pxr/maya/lib/pxrUsdMayaGL/usdProxyShapeAdapter.cpp b/plugin/pxr/maya/lib/pxrUsdMayaGL/usdProxyShapeAdapter.cpp index 75f5ce09e8..318122cb0e 100644 --- a/plugin/pxr/maya/lib/pxrUsdMayaGL/usdProxyShapeAdapter.cpp +++ b/plugin/pxr/maya/lib/pxrUsdMayaGL/usdProxyShapeAdapter.cpp @@ -123,8 +123,8 @@ PxrMayaHdUsdProxyShapeAdapter::_Sync( const unsigned int displayStyle, const MHWRender::DisplayStatus displayStatus) { - UsdMayaProxyShape* usdProxyShape = - UsdMayaProxyShape::GetShapeAtDagPath(shapeDagPath); + MayaUsdProxyShapeBase* usdProxyShape = + MayaUsdProxyShapeBase::GetShapeAtDagPath(shapeDagPath); if (!usdProxyShape) { TF_DEBUG(PXRUSDMAYAGL_SHAPE_ADAPTER_LIFECYCLE).Msg( "Failed to get UsdMayaProxyShape for '%s'\n", diff --git a/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt b/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt index 03b080e32f..e849c03cad 100644 --- a/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt +++ b/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt @@ -22,6 +22,7 @@ pxr_shared_library(${PXR_PACKAGE} ${MAYA_OpenMaya_LIBRARY} ${MAYA_OpenMayaAnim_LIBRARY} ${MAYA_OpenMayaRender_LIBRARY} + mayaUsd INCLUDE_DIRS ${Boost_INCLUDE_DIRS} @@ -33,13 +34,11 @@ pxr_shared_library(${PXR_PACKAGE} PUBLIC_CLASSES adaptor blockSceneModificationContext - colorSpace diagnosticDelegate editUtil hdImagingShape jobArgs meshUtil - notice pointBasedDeformerNode primReader primReaderArgs @@ -49,7 +48,6 @@ pxr_shared_library(${PXR_PACKAGE} primWriterArgs primWriterContext primWriterRegistry - query readUtil roundTripUtil shaderWriter @@ -58,10 +56,7 @@ pxr_shared_library(${PXR_PACKAGE} shadingModeImporter shadingModeRegistry shadingUtil - stageCache - stageData stageNode - stageNoticeListener transformWriter translatorCamera translatorCurves @@ -75,8 +70,6 @@ pxr_shared_library(${PXR_PACKAGE} translatorSkel translatorUtil translatorXformable - util - usdPrimProvider writeJobContext writeUtil xformStack @@ -96,7 +89,6 @@ pxr_shared_library(${PXR_PACKAGE} userTaggedAttribute PRIVATE_CLASSES - debugCodes fallbackPrimReader functorPrimReader functorPrimWriter diff --git a/plugin/pxr/maya/lib/usdMaya/adaptor.cpp b/plugin/pxr/maya/lib/usdMaya/adaptor.cpp index 2836269e0c..f7e42c8d1b 100644 --- a/plugin/pxr/maya/lib/usdMaya/adaptor.cpp +++ b/plugin/pxr/maya/lib/usdMaya/adaptor.cpp @@ -18,7 +18,7 @@ #include "usdMaya/primWriterRegistry.h" #include "usdMaya/readUtil.h" #include "usdMaya/registryHelper.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeUtil.h" #include "pxr/usd/sdf/schema.h" diff --git a/plugin/pxr/maya/lib/usdMaya/chaserRegistry.cpp b/plugin/pxr/maya/lib/usdMaya/chaserRegistry.cpp index f43da67ce4..b9714cce00 100644 --- a/plugin/pxr/maya/lib/usdMaya/chaserRegistry.cpp +++ b/plugin/pxr/maya/lib/usdMaya/chaserRegistry.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/chaserRegistry.h" -#include "usdMaya/debugCodes.h" +#include #include "usdMaya/registryHelper.h" #include "pxr/base/tf/instantiateSingleton.h" diff --git a/plugin/pxr/maya/lib/usdMaya/chaserRegistry.h b/plugin/pxr/maya/lib/usdMaya/chaserRegistry.h index 593f3eaa52..1819cfaeff 100644 --- a/plugin/pxr/maya/lib/usdMaya/chaserRegistry.h +++ b/plugin/pxr/maya/lib/usdMaya/chaserRegistry.h @@ -21,7 +21,7 @@ #include "usdMaya/api.h" #include "usdMaya/chaser.h" #include "usdMaya/jobArgs.h" -#include "usdMaya/util.h" +#include #include "pxr/pxr.h" diff --git a/plugin/pxr/maya/lib/usdMaya/diagnosticDelegate.cpp b/plugin/pxr/maya/lib/usdMaya/diagnosticDelegate.cpp index 49b4b71ed5..f659071454 100644 --- a/plugin/pxr/maya/lib/usdMaya/diagnosticDelegate.cpp +++ b/plugin/pxr/maya/lib/usdMaya/diagnosticDelegate.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/diagnosticDelegate.h" -#include "usdMaya/debugCodes.h" +#include #include "pxr/base/arch/threads.h" #include "pxr/base/tf/envSetting.h" diff --git a/plugin/pxr/maya/lib/usdMaya/editUtil.cpp b/plugin/pxr/maya/lib/usdMaya/editUtil.cpp index 6dbb114dbb..2710aec5c8 100644 --- a/plugin/pxr/maya/lib/usdMaya/editUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/editUtil.cpp @@ -16,7 +16,7 @@ #include "usdMaya/editUtil.h" #include "usdMaya/referenceAssembly.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/stringUtils.h" diff --git a/plugin/pxr/maya/lib/usdMaya/exportCommand.cpp b/plugin/pxr/maya/lib/usdMaya/exportCommand.cpp index c4ebf0c2ba..8f9d50f6fd 100644 --- a/plugin/pxr/maya/lib/usdMaya/exportCommand.cpp +++ b/plugin/pxr/maya/lib/usdMaya/exportCommand.cpp @@ -16,7 +16,7 @@ #include "usdMaya/exportCommand.h" #include "usdMaya/shadingModeRegistry.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJob.h" #include "usdMaya/writeUtil.h" diff --git a/plugin/pxr/maya/lib/usdMaya/hdImagingShape.cpp b/plugin/pxr/maya/lib/usdMaya/hdImagingShape.cpp index a2179efa08..c4100f1dd6 100644 --- a/plugin/pxr/maya/lib/usdMaya/hdImagingShape.cpp +++ b/plugin/pxr/maya/lib/usdMaya/hdImagingShape.cpp @@ -18,7 +18,7 @@ #include "usdMaya/blockSceneModificationContext.h" #include "usdMaya/translatorUtil.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/diagnostic.h" #include "pxr/base/tf/envSetting.h" diff --git a/plugin/pxr/maya/lib/usdMaya/hdImagingShape.h b/plugin/pxr/maya/lib/usdMaya/hdImagingShape.h index dc407cd668..bf1da84820 100644 --- a/plugin/pxr/maya/lib/usdMaya/hdImagingShape.h +++ b/plugin/pxr/maya/lib/usdMaya/hdImagingShape.h @@ -21,7 +21,7 @@ #include "pxr/pxr.h" #include "usdMaya/api.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/staticTokens.h" diff --git a/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.cpp b/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.cpp index 3b6a8617b2..32830a7077 100644 --- a/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.cpp +++ b/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.cpp @@ -17,7 +17,7 @@ #include "usdMaya/instancedNodeWriter.h" #include "usdMaya/primWriter.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/usd/sdf/path.h" diff --git a/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.h b/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.h index b5e84ee275..e42b43f339 100644 --- a/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.h +++ b/plugin/pxr/maya/lib/usdMaya/instancedNodeWriter.h @@ -21,7 +21,7 @@ #include "pxr/pxr.h" #include "usdMaya/primWriter.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/usd/sdf/path.h" diff --git a/plugin/pxr/maya/lib/usdMaya/jobArgs.h b/plugin/pxr/maya/lib/usdMaya/jobArgs.h index 4688726b62..f50bbc3e33 100644 --- a/plugin/pxr/maya/lib/usdMaya/jobArgs.h +++ b/plugin/pxr/maya/lib/usdMaya/jobArgs.h @@ -19,7 +19,7 @@ /// \file usdMaya/jobArgs.h #include "usdMaya/api.h" -#include "usdMaya/util.h" +#include #include "pxr/pxr.h" diff --git a/plugin/pxr/maya/lib/usdMaya/pointBasedDeformerNode.cpp b/plugin/pxr/maya/lib/usdMaya/pointBasedDeformerNode.cpp index c96b50a08b..f1c5e40b57 100644 --- a/plugin/pxr/maya/lib/usdMaya/pointBasedDeformerNode.cpp +++ b/plugin/pxr/maya/lib/usdMaya/pointBasedDeformerNode.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/pointBasedDeformerNode.h" -#include "usdMaya/stageData.h" +#include #include "pxr/base/gf/math.h" #include "pxr/base/gf/vec3f.h" @@ -88,7 +88,7 @@ UsdMayaPointBasedDeformerNode::initialize() inUsdStageAttr = typedAttrFn.create("inUsdStage", "is", - UsdMayaStageData::mayaTypeId, + MayaUsdStageData::mayaTypeId, MObject::kNullObj, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -149,8 +149,8 @@ UsdMayaPointBasedDeformerNode::deform( block.inputValue(inUsdStageAttr, &status); CHECK_MSTATUS_AND_RETURN_IT(status); - UsdMayaStageData* stageData = - dynamic_cast(inUsdStageHandle.asPluginData()); + MayaUsdStageData* stageData = + dynamic_cast(inUsdStageHandle.asPluginData()); if (!stageData || !stageData->stage) { return MS::kFailure; } diff --git a/plugin/pxr/maya/lib/usdMaya/primReaderRegistry.cpp b/plugin/pxr/maya/lib/usdMaya/primReaderRegistry.cpp index 1e6e216247..343591e2f1 100644 --- a/plugin/pxr/maya/lib/usdMaya/primReaderRegistry.cpp +++ b/plugin/pxr/maya/lib/usdMaya/primReaderRegistry.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/primReaderRegistry.h" -#include "usdMaya/debugCodes.h" +#include #include "usdMaya/fallbackPrimReader.h" #include "usdMaya/functorPrimReader.h" #include "usdMaya/registryHelper.h" diff --git a/plugin/pxr/maya/lib/usdMaya/primWriter.cpp b/plugin/pxr/maya/lib/usdMaya/primWriter.cpp index da1e12ae09..818855db96 100644 --- a/plugin/pxr/maya/lib/usdMaya/primWriter.cpp +++ b/plugin/pxr/maya/lib/usdMaya/primWriter.cpp @@ -19,7 +19,7 @@ #include "usdMaya/adaptor.h" #include "usdMaya/jobArgs.h" #include "usdMaya/translatorGprim.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "usdMaya/writeUtil.h" diff --git a/plugin/pxr/maya/lib/usdMaya/primWriter.h b/plugin/pxr/maya/lib/usdMaya/primWriter.h index 37a573f936..8fdc9ba893 100644 --- a/plugin/pxr/maya/lib/usdMaya/primWriter.h +++ b/plugin/pxr/maya/lib/usdMaya/primWriter.h @@ -22,7 +22,7 @@ #include "usdMaya/api.h" #include "usdMaya/jobArgs.h" -#include "usdMaya/util.h" +#include #include "pxr/base/vt/value.h" #include "pxr/usd/sdf/path.h" diff --git a/plugin/pxr/maya/lib/usdMaya/primWriterRegistry.cpp b/plugin/pxr/maya/lib/usdMaya/primWriterRegistry.cpp index fc0b8a1a8d..e2fd8c52ef 100644 --- a/plugin/pxr/maya/lib/usdMaya/primWriterRegistry.cpp +++ b/plugin/pxr/maya/lib/usdMaya/primWriterRegistry.cpp @@ -16,7 +16,7 @@ #include "pxr/pxr.h" #include "usdMaya/primWriterRegistry.h" -#include "usdMaya/debugCodes.h" +#include #include "usdMaya/functorPrimWriter.h" #include "usdMaya/registryHelper.h" diff --git a/plugin/pxr/maya/lib/usdMaya/proxyShape.cpp b/plugin/pxr/maya/lib/usdMaya/proxyShape.cpp index 788697248c..b6f19e5b75 100644 --- a/plugin/pxr/maya/lib/usdMaya/proxyShape.cpp +++ b/plugin/pxr/maya/lib/usdMaya/proxyShape.cpp @@ -16,10 +16,11 @@ #include "usdMaya/proxyShape.h" #include "usdMaya/hdImagingShape.h" -#include "usdMaya/query.h" -#include "usdMaya/stageCache.h" -#include "usdMaya/stageData.h" -#include "usdMaya/util.h" +#include +#include +#include +#include +#include #include "pxr/base/gf/bbox3d.h" #include "pxr/base/gf/range3d.h" @@ -76,7 +77,6 @@ #include #include - PXR_NAMESPACE_OPEN_SCOPE @@ -91,11 +91,8 @@ TF_DEFINE_PUBLIC_TOKENS(UsdMayaProxyShapeTokens, TF_DEFINE_ENV_SETTING(PIXMAYA_ENABLE_BOUNDING_BOX_MODE, false, "Enable bounding box rendering (slows refresh rate)"); -UsdMayaProxyShape::ClosestPointDelegate -UsdMayaProxyShape::_sharedClosestPointDelegate = nullptr; - -UsdMayaProxyShape::ObjectSoftSelectEnabledDelgate -UsdMayaProxyShape::_sharedObjectSoftSelectEnabledDelgate = nullptr; +UsdMayaProxyShape::ObjectSoftSelectEnabledDelegate +UsdMayaProxyShape::_sharedObjectSoftSelectEnabledDelegate = nullptr; // ======================================================== @@ -104,25 +101,9 @@ const MTypeId UsdMayaProxyShape::typeId(0x0010A259); const MString UsdMayaProxyShape::typeName( UsdMayaProxyShapeTokens->MayaTypeName.GetText()); -const MString UsdMayaProxyShape::displayFilterName( - TfStringPrintf("%sDisplayFilter", - UsdMayaProxyShapeTokens->MayaTypeName.GetText()).c_str()); -const MString UsdMayaProxyShape::displayFilterLabel("USD Proxies"); - // Attributes -MObject UsdMayaProxyShape::filePathAttr; -MObject UsdMayaProxyShape::primPathAttr; -MObject UsdMayaProxyShape::excludePrimPathsAttr; -MObject UsdMayaProxyShape::timeAttr; MObject UsdMayaProxyShape::variantKeyAttr; -MObject UsdMayaProxyShape::complexityAttr; -MObject UsdMayaProxyShape::inStageDataAttr; -MObject UsdMayaProxyShape::inStageDataCachedAttr; MObject UsdMayaProxyShape::fastPlaybackAttr; -MObject UsdMayaProxyShape::outStageDataAttr; -MObject UsdMayaProxyShape::drawRenderPurposeAttr; -MObject UsdMayaProxyShape::drawProxyPurposeAttr; -MObject UsdMayaProxyShape::drawGuidePurposeAttr; MObject UsdMayaProxyShape::softSelectableAttr; @@ -137,61 +118,14 @@ UsdMayaProxyShape::creator() MStatus UsdMayaProxyShape::initialize() { - MStatus retValue = MS::kSuccess; + MStatus retValue = inheritAttributesFrom(MayaUsdProxyShapeBase::typeName); + CHECK_MSTATUS_AND_RETURN_IT(retValue); // // create attr factories // - MFnCompoundAttribute compoundAttrFn; - MFnEnumAttribute enumAttrFn; MFnNumericAttribute numericAttrFn; MFnTypedAttribute typedAttrFn; - MFnUnitAttribute unitAttrFn; - - filePathAttr = typedAttrFn.create( - "filePath", - "fp", - MFnData::kString, - MObject::kNullObj, - &retValue); - typedAttrFn.setReadable(false); - typedAttrFn.setAffectsAppearance(true); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(filePathAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - primPathAttr = typedAttrFn.create( - "primPath", - "pp", - MFnData::kString, - MObject::kNullObj, - &retValue); - typedAttrFn.setAffectsAppearance(true); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(primPathAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - excludePrimPathsAttr = typedAttrFn.create( - "excludePrimPaths", - "epp", - MFnData::kString, - MObject::kNullObj, - &retValue); - typedAttrFn.setAffectsAppearance(true); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(excludePrimPathsAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - timeAttr = unitAttrFn.create( - "time", - "tm", - MFnUnitAttribute::kTime, - 0.0, - &retValue); - unitAttrFn.setAffectsAppearance(true); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(timeAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); variantKeyAttr = typedAttrFn.create( "variantKey", @@ -205,50 +139,6 @@ UsdMayaProxyShape::initialize() retValue = addAttribute(variantKeyAttr); CHECK_MSTATUS_AND_RETURN_IT(retValue); - complexityAttr = numericAttrFn.create( - "complexity", - "cplx", - MFnNumericData::kInt, - 0, - &retValue); - numericAttrFn.setMin(0); - numericAttrFn.setSoftMax(4); - numericAttrFn.setMax(8); - numericAttrFn.setChannelBox(true); - numericAttrFn.setStorable(false); - numericAttrFn.setAffectsAppearance(true); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(complexityAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - inStageDataAttr = typedAttrFn.create( - "inStageData", - "id", - UsdMayaStageData::mayaTypeId, - MObject::kNullObj, - &retValue); - typedAttrFn.setReadable(false); - typedAttrFn.setStorable(false); - typedAttrFn.setDisconnectBehavior(MFnNumericAttribute::kReset); // on disconnect, reset to Null - typedAttrFn.setAffectsAppearance(true); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(inStageDataAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - // inStageData or filepath-> inStageDataCached -> outStageData - inStageDataCachedAttr = typedAttrFn.create( - "inStageDataCached", - "idc", - UsdMayaStageData::mayaTypeId, - MObject::kNullObj, - &retValue); - typedAttrFn.setStorable(false); - typedAttrFn.setWritable(false); - typedAttrFn.setAffectsAppearance(true); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(inStageDataCachedAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - fastPlaybackAttr = numericAttrFn.create( "fastPlayback", "fs", @@ -261,57 +151,6 @@ UsdMayaProxyShape::initialize() retValue = addAttribute(fastPlaybackAttr); CHECK_MSTATUS_AND_RETURN_IT(retValue); - outStageDataAttr = typedAttrFn.create( - "outStageData", - "od", - UsdMayaStageData::mayaTypeId, - MObject::kNullObj, - &retValue); - typedAttrFn.setStorable(false); - typedAttrFn.setWritable(false); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - retValue = addAttribute(outStageDataAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - drawRenderPurposeAttr = numericAttrFn.create( - "drawRenderPurpose", - "drp", - MFnNumericData::kBoolean, - 0.0, - &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - numericAttrFn.setKeyable(true); - numericAttrFn.setReadable(false); - numericAttrFn.setAffectsAppearance(true); - retValue = addAttribute(drawRenderPurposeAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - drawProxyPurposeAttr = numericAttrFn.create( - "drawProxyPurpose", - "dpp", - MFnNumericData::kBoolean, - 1.0, - &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - numericAttrFn.setKeyable(true); - numericAttrFn.setReadable(false); - numericAttrFn.setAffectsAppearance(true); - retValue = addAttribute(drawProxyPurposeAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - drawGuidePurposeAttr = numericAttrFn.create( - "drawGuidePurpose", - "dgp", - MFnNumericData::kBoolean, - 0.0, - &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - numericAttrFn.setKeyable(true); - numericAttrFn.setReadable(false); - numericAttrFn.setAffectsAppearance(true); - retValue = addAttribute(drawGuidePurposeAttr); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - softSelectableAttr = numericAttrFn.create( "softSelectable", "softSelectable", @@ -326,162 +165,43 @@ UsdMayaProxyShape::initialize() // // add attribute dependencies // - retValue = attributeAffects(filePathAttr, inStageDataCachedAttr); - retValue = attributeAffects(filePathAttr, outStageDataAttr); - - retValue = attributeAffects(primPathAttr, inStageDataCachedAttr); - retValue = attributeAffects(primPathAttr, outStageDataAttr); - retValue = attributeAffects(variantKeyAttr, inStageDataCachedAttr); retValue = attributeAffects(variantKeyAttr, outStageDataAttr); - retValue = attributeAffects(inStageDataAttr, inStageDataCachedAttr); - retValue = attributeAffects(inStageDataAttr, outStageDataAttr); - - retValue = attributeAffects(inStageDataCachedAttr, outStageDataAttr); - return retValue; } -/* static */ -UsdMayaProxyShape* -UsdMayaProxyShape::GetShapeAtDagPath(const MDagPath& dagPath) -{ - MObject mObj = dagPath.node(); - if (mObj.apiType() != MFn::kPluginShape) { - TF_CODING_ERROR( - "Could not get UsdMayaProxyShape for non-plugin shape node " - "at DAG path: %s (apiTypeStr = %s)", - dagPath.fullPathName().asChar(), - mObj.apiTypeStr()); - return nullptr; - } - - const MFnDependencyNode depNodeFn(mObj); - UsdMayaProxyShape* pShape = - static_cast(depNodeFn.userNode()); - if (!pShape) { - TF_CODING_ERROR( - "Could not get UsdMayaProxyShape for node at DAG path: %s", - dagPath.fullPathName().asChar()); - return nullptr; - } - - return pShape; -} - -/* static */ -void -UsdMayaProxyShape::SetClosestPointDelegate(ClosestPointDelegate delegate) -{ - _sharedClosestPointDelegate = delegate; -} - /* static */ void UsdMayaProxyShape::SetObjectSoftSelectEnabledDelegate( - ObjectSoftSelectEnabledDelgate delegate) + ObjectSoftSelectEnabledDelegate delegate) { - _sharedObjectSoftSelectEnabledDelgate = delegate; + _sharedObjectSoftSelectEnabledDelegate = delegate; } -/* static */ +/* virtual */ bool -UsdMayaProxyShape::GetObjectSoftSelectEnabled() +UsdMayaProxyShape::GetObjectSoftSelectEnabled() const { // If the delegate isn't set, we just assume soft select isn't currently // enabled - this will mean that the object is selectable in VP2, by default - if (!_sharedObjectSoftSelectEnabledDelgate) { + if (!_sharedObjectSoftSelectEnabledDelegate) { return false; } - return _sharedObjectSoftSelectEnabledDelgate(); -} - -/* virtual */ -void -UsdMayaProxyShape::postConstructor() -{ - setRenderable(true); - - // This shape uses Hydra for imaging, so make sure that the - // pxrHdImagingShape is setup. - PxrMayaHdImagingShape::GetOrCreateInstance(); + return _sharedObjectSoftSelectEnabledDelegate(); } -/* virtual */ -MStatus -UsdMayaProxyShape::compute(const MPlug& plug, MDataBlock& dataBlock) -{ - if (plug == excludePrimPathsAttr || - plug == timeAttr || - plug == complexityAttr || - plug == drawRenderPurposeAttr || - plug == drawProxyPurposeAttr || - plug == drawGuidePurposeAttr) { - // If the attribute that needs to be computed is one of these, then it - // does not affect the ouput stage data, but it *does* affect imaging - // the shape. In that case, we notify Maya that the shape needs to be - // redrawn and let it take care of computing the attribute. This covers - // the case where an attribute on the proxy shape may have an incoming - // connection from another node (e.g. "time1.outTime" being connected - // to the proxy shape's "time" attribute). In that case, - // setDependentsDirty() might not get called and only compute() might. - MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); - return MS::kUnknownParameter; - } - else if (plug == inStageDataCachedAttr) { - return computeInStageDataCached(dataBlock); - } - else if (plug == outStageDataAttr) { - return computeOutStageData(dataBlock); - } - - return MS::kUnknownParameter; -} - -MStatus -UsdMayaProxyShape::computeInStageDataCached(MDataBlock& dataBlock) +SdfLayerRefPtr +UsdMayaProxyShape::computeSessionLayer(MDataBlock& dataBlock) { MStatus retValue = MS::kSuccess; - MDataHandle inDataHandle = dataBlock.inputValue(inStageDataAttr, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - // If inData has an incoming connection, then use it. Otherwise generate stage from the filepath - if (!inDataHandle.data().isNull() ) { - // - // Propagate inData -> inDataCached - // - MDataHandle inDataCachedHandle = dataBlock.outputValue(inStageDataCachedAttr, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - inDataCachedHandle.copy(inDataHandle); - - inDataCachedHandle.setClean(); - return MS::kSuccess; - } - else { - // - // Calculate from USD filepath and primPath and variantKey - // - - // Get input attr values - const MString file = dataBlock.inputValue(filePathAttr, &retValue).asString(); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - // - // let the usd stage cache deal with caching the usd stage data - // - std::string fileString = TfStringTrimRight(file.asChar()); - - // == Load the Stage - UsdStageRefPtr usdStage; - SdfPath primPath; - // get the variantKey MDataHandle variantKeyHandle = dataBlock.inputValue(variantKeyAttr, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); + if (retValue != MS::kSuccess) { + return nullptr; + } const MString variantKey = variantKeyHandle.asString(); SdfLayerRefPtr sessionLayer; @@ -494,7 +214,9 @@ UsdMayaProxyShape::computeInStageDataCached(MDataBlock& dataBlock) // Get the primPath const MString primPathMString = dataBlock.inputValue(primPathAttr, &retValue).asString(); - CHECK_MSTATUS_AND_RETURN_IT(retValue); + if (retValue != MS::kSuccess) { + return nullptr; + } std::vector primPathEltStrs = TfStringTokenize(primPathMString.asChar(),"/"); @@ -505,145 +227,7 @@ UsdMayaProxyShape::computeInStageDataCached(MDataBlock& dataBlock) } } - if (SdfLayerRefPtr rootLayer = SdfLayer::FindOrOpen(fileString)) { - UsdStageCacheContext ctx(UsdMayaStageCache::Get()); - if (sessionLayer) { - usdStage = UsdStage::Open(rootLayer, - sessionLayer, - ArGetResolver().GetCurrentContext()); - } else { - usdStage = UsdStage::Open(rootLayer, - ArGetResolver().GetCurrentContext()); - } - - usdStage->SetEditTarget(usdStage->GetSessionLayer()); - } - - if (usdStage) { - primPath = usdStage->GetPseudoRoot().GetPath(); - } - - // Create the output outData ======== - MFnPluginData pluginDataFn; - MObject stageDataObj = - pluginDataFn.create(UsdMayaStageData::mayaTypeId, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - UsdMayaStageData* stageData = - reinterpret_cast(pluginDataFn.data(&retValue)); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - // Set the outUsdStageData - stageData->stage = usdStage; - stageData->primPath = primPath; - - // - // set the data on the output plug - // - MDataHandle inDataCachedHandle = - dataBlock.outputValue(inStageDataCachedAttr, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - inDataCachedHandle.set(stageData); - inDataCachedHandle.setClean(); - return MS::kSuccess; - } -} - -MStatus -UsdMayaProxyShape::computeOutStageData(MDataBlock& dataBlock) -{ - MStatus retValue = MS::kSuccess; - - TfReset(_boundingBoxCache); - - // Reset the stage listener until we determine that everything is valid. - _stageNoticeListener.SetStage(UsdStageWeakPtr()); - _stageNoticeListener.SetStageContentsChangedCallback(nullptr); - - MDataHandle inDataCachedHandle = - dataBlock.inputValue(inStageDataCachedAttr, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - UsdStageRefPtr usdStage; - - UsdMayaStageData* inData = - dynamic_cast(inDataCachedHandle.asPluginData()); - if(inData) - { - usdStage = inData->stage; - } - - // If failed to get a valid stage, then - // Propagate inDataCached -> outData - // and return - if (!usdStage) { - MDataHandle outDataHandle = dataBlock.outputValue(outStageDataAttr, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - outDataHandle.copy(inDataCachedHandle); - return MS::kSuccess; - } - - // Get the primPath - const MString primPath = dataBlock.inputValue(primPathAttr, &retValue).asString(); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - // Get the prim - // If no primPath string specified, then use the pseudo-root. - UsdPrim usdPrim; - std::string primPathStr = primPath.asChar(); - if ( !primPathStr.empty() ) { - SdfPath primPath(primPathStr); - - // Validate assumption: primPath is descendent of passed-in stage primPath - // Make sure that the primPath is a child of the passed in stage's primpath - if ( primPath.HasPrefix(inData->primPath) ) { - usdPrim = usdStage->GetPrimAtPath( primPath ); - } - else { - TF_WARN("%s: Shape primPath <%s> is not a descendant of input " - "stage primPath <%s>", - MPxSurfaceShape::name().asChar(), - primPath.GetText(), - inData->primPath.GetText()); - } - } else { - usdPrim = usdStage->GetPseudoRoot(); - } - - // Create the output outData - MFnPluginData pluginDataFn; - MObject stageDataObj = - pluginDataFn.create(UsdMayaStageData::mayaTypeId, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - UsdMayaStageData* stageData = - reinterpret_cast(pluginDataFn.data(&retValue)); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - // Set the outUsdStageData - stageData->stage = usdStage; - stageData->primPath = usdPrim ? usdPrim.GetPath() : - usdStage->GetPseudoRoot().GetPath(); - - // - // set the data on the output plug - // - MDataHandle outDataHandle = - dataBlock.outputValue(outStageDataAttr, &retValue); - CHECK_MSTATUS_AND_RETURN_IT(retValue); - - outDataHandle.set(stageData); - outDataHandle.setClean(); - - // Start listening for notices for the USD stage. - _stageNoticeListener.SetStage(usdStage); - _stageNoticeListener.SetStageContentsChangedCallback( - std::bind(&UsdMayaProxyShape::_OnStageContentsChanged, - this, - std::placeholders::_1)); - - return MS::kSuccess; + return sessionLayer; } /* virtual */ @@ -651,7 +235,8 @@ bool UsdMayaProxyShape::isBounded() const { return !_useFastPlayback && - TfGetEnvSetting(PIXMAYA_ENABLE_BOUNDING_BOX_MODE)&& isStageValid(); + TfGetEnvSetting(PIXMAYA_ENABLE_BOUNDING_BOX_MODE) && + ParentClass::isBounded(); } /* virtual */ @@ -662,102 +247,7 @@ UsdMayaProxyShape::boundingBox() const return UsdMayaUtil::GetInfiniteBoundingBox(); } - MStatus status; - - // Make sure outStage is up to date - UsdMayaProxyShape* nonConstThis = const_cast(this); - MDataBlock dataBlock = nonConstThis->forceCache(); - dataBlock.inputValue(outStageDataAttr, &status); - CHECK_MSTATUS_AND_RETURN(status, MBoundingBox()); - - // XXX: - // If we could cheaply determine whether a stage only has static geometry, - // we could make this value a constant one for that case, avoiding the - // memory overhead of a cache entry per frame - MDataHandle timeHandle = dataBlock.inputValue(timeAttr, &status); - UsdTimeCode currTime = UsdTimeCode(timeHandle.asTime().value()); - - std::map::const_iterator cacheLookup = - _boundingBoxCache.find(currTime); - - if (cacheLookup != _boundingBoxCache.end()) { - return cacheLookup->second; - } - - UsdPrim prim = usdPrim(); - if (!prim) { - return MBoundingBox(); - } - - const UsdGeomImageable imageablePrim(prim); - - bool drawRenderPurpose = false; - bool drawProxyPurpose = true; - bool drawGuidePurpose = false; - _GetDrawPurposeToggles( - dataBlock, - &drawRenderPurpose, - &drawProxyPurpose, - &drawGuidePurpose); - - const TfToken purpose1 = UsdGeomTokens->default_; - const TfToken purpose2 = - drawRenderPurpose ? UsdGeomTokens->render : TfToken(); - const TfToken purpose3 = - drawProxyPurpose ? UsdGeomTokens->proxy : TfToken(); - const TfToken purpose4 = - drawGuidePurpose ? UsdGeomTokens->guide : TfToken(); - - const GfBBox3d allBox = imageablePrim.ComputeUntransformedBound( - currTime, - purpose1, - purpose2, - purpose3, - purpose4); - - MBoundingBox& retval = nonConstThis->_boundingBoxCache[currTime]; - - const GfRange3d boxRange = allBox.ComputeAlignedBox(); - if (!boxRange.IsEmpty()) { - const GfVec3d boxMin = boxRange.GetMin(); - const GfVec3d boxMax = boxRange.GetMax(); - retval = MBoundingBox( - MPoint(boxMin[0], boxMin[1], boxMin[2]), - MPoint(boxMax[0], boxMax[1], boxMax[2])); - } - - return retval; -} - -bool -UsdMayaProxyShape::isStageValid() const -{ - MStatus localStatus; - UsdMayaProxyShape* nonConstThis = const_cast(this); - MDataBlock dataBlock = nonConstThis->forceCache(); - - MDataHandle outDataHandle = dataBlock.inputValue(outStageDataAttr, &localStatus); - CHECK_MSTATUS_AND_RETURN(localStatus, false); - - UsdMayaStageData* outData = - dynamic_cast(outDataHandle.asPluginData()); - if(!outData || !outData->stage) { - return false; - } - - return true; -} - -/* virtual */ -MStatus -UsdMayaProxyShape::setDependentsDirty(const MPlug& plug, MPlugArray& plugArray) -{ - // If/when the MPxDrawOverride for the proxy shape specifies - // isAlwaysDirty=false to improve performance, we must be sure to notify - // the Maya renderer that the geometry is dirty and needs to be redrawn - // when any plug on the proxy shape is dirtied. - MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); - return MPxSurfaceShape::setDependentsDirty(plug, plugArray); + return ParentClass::boundingBox(); } /* virtual */ @@ -790,159 +280,8 @@ UsdMayaProxyShape::getInternalValueInContext( return MPxSurfaceShape::getInternalValueInContext(plug, dataHandle, ctx); } -/* virtual */ -UsdPrim -UsdMayaProxyShape::usdPrim() const -{ - return _GetUsdPrim( const_cast(this)->forceCache() ); -} - -UsdPrim -UsdMayaProxyShape::_GetUsdPrim(MDataBlock dataBlock) const -{ - MStatus localStatus; - UsdPrim usdPrim; - - MDataHandle outDataHandle = - dataBlock.inputValue(outStageDataAttr, &localStatus); - CHECK_MSTATUS_AND_RETURN(localStatus, usdPrim); - - UsdMayaStageData* outData = dynamic_cast(outDataHandle.asPluginData()); - if(!outData) { - return usdPrim; // empty UsdPrim - } - - if(!outData->stage) { - return usdPrim; // empty UsdPrim - } - - usdPrim = (outData->primPath.IsEmpty()) ? - outData->stage->GetPseudoRoot() : - outData->stage->GetPrimAtPath(outData->primPath); - - return usdPrim; -} - -int -UsdMayaProxyShape::getComplexity() const -{ - return _GetComplexity( const_cast(this)->forceCache() ); -} - -int -UsdMayaProxyShape::_GetComplexity(MDataBlock dataBlock) const -{ - int complexity = 0; - MStatus status; - - complexity = dataBlock.inputValue(complexityAttr, &status).asInt(); - - return complexity; -} - -UsdTimeCode -UsdMayaProxyShape::getTime() const -{ - return _GetTime( const_cast(this)->forceCache() ); -} - -UsdTimeCode -UsdMayaProxyShape::_GetTime(MDataBlock dataBlock) const -{ - MStatus status; - - return UsdTimeCode(dataBlock.inputValue(timeAttr, &status).asTime().value()); -} - -SdfPathVector -UsdMayaProxyShape::getExcludePrimPaths() const -{ - return _GetExcludePrimPaths( const_cast(this)->forceCache() ); -} - -SdfPathVector -UsdMayaProxyShape::_GetExcludePrimPaths(MDataBlock dataBlock) const -{ - SdfPathVector ret; - - const MString excludePrimPathsStr = - dataBlock.inputValue(excludePrimPathsAttr).asString(); - std::vector excludePrimPaths = - TfStringTokenize(excludePrimPathsStr.asChar(), ","); - ret.resize(excludePrimPaths.size()); - for (size_t i = 0; i < excludePrimPaths.size(); ++i) { - ret[i] = SdfPath(TfStringTrim(excludePrimPaths[i])); - } - - return ret; -} - -bool -UsdMayaProxyShape::_GetDrawPurposeToggles( - MDataBlock dataBlock, - bool* drawRenderPurpose, - bool* drawProxyPurpose, - bool* drawGuidePurpose) const -{ - MStatus status; - - MDataHandle drawRenderPurposeHandle = - dataBlock.inputValue(drawRenderPurposeAttr, &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - MDataHandle drawProxyPurposeHandle = - dataBlock.inputValue(drawProxyPurposeAttr, &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - MDataHandle drawGuidePurposeHandle = - dataBlock.inputValue(drawGuidePurposeAttr, &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - if (drawRenderPurpose) { - *drawRenderPurpose = drawRenderPurposeHandle.asBool(); - } - if (drawProxyPurpose) { - *drawProxyPurpose = drawProxyPurposeHandle.asBool(); - } - if (drawGuidePurpose) { - *drawGuidePurpose = drawGuidePurposeHandle.asBool(); - } - - return true; -} - -bool -UsdMayaProxyShape::GetAllRenderAttributes( - UsdPrim* usdPrimOut, - SdfPathVector* excludePrimPathsOut, - int* complexityOut, - UsdTimeCode* timeOut, - bool* drawRenderPurpose, - bool* drawProxyPurpose, - bool* drawGuidePurpose) -{ - MDataBlock dataBlock = forceCache(); - - *usdPrimOut = _GetUsdPrim(dataBlock); - if (!usdPrimOut->IsValid()) { - return false; - } - - *excludePrimPathsOut = _GetExcludePrimPaths(dataBlock); - *complexityOut = _GetComplexity(dataBlock); - *timeOut = _GetTime(dataBlock); - - _GetDrawPurposeToggles( - dataBlock, - drawRenderPurpose, - drawProxyPurpose, - drawGuidePurpose); - - return true; -} - UsdMayaProxyShape::UsdMayaProxyShape() : - MPxSurfaceShape(), + MayaUsdProxyShapeBase(), _useFastPlayback(false) { TfRegistryManager::GetInstance().SubscribeTo(); @@ -956,36 +295,8 @@ UsdMayaProxyShape::~UsdMayaProxyShape() // } -MSelectionMask -UsdMayaProxyShape::getShapeSelectionMask() const -{ - // The intent of this function is to control whether this object is - // selectable at all in VP2 - - // However, due to a bug / quirk, it could be used to specifically control - // whether the object was SOFT-selectable if you were using - // MAYA_VP2_USE_VP1_SELECTON; in this mode, this setting is NOT querierd - // when doing "normal" selection, but IS queried when doing soft - // selection. - - // Unfortunately, it is queried for both "normal" selection AND soft - // selection if you are using "true" VP2 selection. So in order to - // control soft selection, in both modes, we keep track of whether - // we currently have object soft-select enabled, and then return an empty - // selection mask if it is, but this object is set to be non-soft-selectable - - static const MSelectionMask emptyMask; - static const MSelectionMask normalMask(MSelectionMask::kSelectMeshes); - - if (GetObjectSoftSelectEnabled() && !_CanBeSoftSelected()) { - // Disable selection, to disable soft-selection - return emptyMask; - } - return normalMask; -} - bool -UsdMayaProxyShape::_CanBeSoftSelected() const +UsdMayaProxyShape::canBeSoftSelected() const { UsdMayaProxyShape* nonConstThis = const_cast(this); MDataBlock dataBlock = nonConstThis->forceCache(); @@ -999,42 +310,16 @@ UsdMayaProxyShape::_CanBeSoftSelected() const return softSelHandle.asBool(); } -void -UsdMayaProxyShape::_OnStageContentsChanged( - const UsdNotice::StageContentsChanged& notice) +void UsdMayaProxyShape::postConstructor() { - // If the USD stage this proxy represents changes without Maya's knowledge, - // we need to inform Maya that the shape is dirty and needs to be redrawn. - MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); -} + ParentClass::postConstructor(); -bool -UsdMayaProxyShape::closestPoint( - const MPoint& raySource, - const MVector& rayDirection, - MPoint& theClosestPoint, - MVector& theClosestNormal, - bool /*findClosestOnMiss*/, - double /*tolerance*/) -{ - if (_sharedClosestPointDelegate) { - GfRay ray( - GfVec3d(raySource.x, raySource.y, raySource.z), - GfVec3d(rayDirection.x, rayDirection.y, rayDirection.z)); - GfVec3d hitPoint; - GfVec3d hitNorm; - if (_sharedClosestPointDelegate(*this, ray, &hitPoint, &hitNorm)) { - theClosestPoint = MPoint(hitPoint[0], hitPoint[1], hitPoint[2]); - theClosestNormal = MVector(hitNorm[0], hitNorm[1], hitNorm[2]); - return true; - } + if (!MayaUsdProxyShapePlugin::useVP2_NativeUSD_Rendering()) + { + // This shape uses Hydra for imaging, so make sure that the + // pxrHdImagingShape is setup. + PxrMayaHdImagingShape::GetOrCreateInstance(); } - - return false; -} - -bool UsdMayaProxyShape::canMakeLive() const { - return (bool) _sharedClosestPointDelegate; } diff --git a/plugin/pxr/maya/lib/usdMaya/proxyShape.h b/plugin/pxr/maya/lib/usdMaya/proxyShape.h index 47f0142001..27910b4eab 100644 --- a/plugin/pxr/maya/lib/usdMaya/proxyShape.h +++ b/plugin/pxr/maya/lib/usdMaya/proxyShape.h @@ -19,12 +19,10 @@ /// \file usdMaya/proxyShape.h #include "usdMaya/api.h" -#include "usdMaya/stageNoticeListener.h" -#include "usdMaya/usdPrimProvider.h" +#include #include "pxr/pxr.h" -#include "pxr/base/gf/ray.h" #include "pxr/base/gf/vec3d.h" #include "pxr/base/tf/staticTokens.h" @@ -61,64 +59,26 @@ TF_DECLARE_PUBLIC_TOKENS(UsdMayaProxyShapeTokens, PXRUSDMAYA_PROXY_SHAPE_TOKENS); -class UsdMayaProxyShape : public MPxSurfaceShape, - public UsdMayaUsdPrimProvider +class UsdMayaProxyShape : public MayaUsdProxyShapeBase { public: + typedef MayaUsdProxyShapeBase ParentClass; + PXRUSDMAYA_API static const MTypeId typeId; PXRUSDMAYA_API static const MString typeName; - PXRUSDMAYA_API - static const MString displayFilterName; - PXRUSDMAYA_API - static const MString displayFilterLabel; - - // Attributes - PXRUSDMAYA_API - static MObject filePathAttr; - PXRUSDMAYA_API - static MObject primPathAttr; - PXRUSDMAYA_API - static MObject excludePrimPathsAttr; - PXRUSDMAYA_API - static MObject timeAttr; PXRUSDMAYA_API static MObject variantKeyAttr; PXRUSDMAYA_API - static MObject complexityAttr; - PXRUSDMAYA_API - static MObject inStageDataAttr; - PXRUSDMAYA_API - static MObject inStageDataCachedAttr; - PXRUSDMAYA_API static MObject fastPlaybackAttr; - PXRUSDMAYA_API - static MObject outStageDataAttr; - PXRUSDMAYA_API - static MObject drawRenderPurposeAttr; - PXRUSDMAYA_API - static MObject drawProxyPurposeAttr; - PXRUSDMAYA_API - static MObject drawGuidePurposeAttr; - PXRUSDMAYA_API static MObject softSelectableAttr; - /// Delegate function for computing the closest point and surface normal - /// on the proxy shape to a given ray. - /// The input ray, output point, and output normal should be in the - /// proxy shape's local space. - /// Should return true if a point was found, and false otherwise. - /// (You could just treat this as a ray intersection and return true - /// if intersected, false if missed.) - typedef std::function ClosestPointDelegate; - /// Delegate function for returning whether object soft select mode is /// currently on - typedef std::function ObjectSoftSelectEnabledDelgate; + typedef std::function ObjectSoftSelectEnabledDelegate; PXRUSDMAYA_API static void* creator(); @@ -126,81 +86,18 @@ class UsdMayaProxyShape : public MPxSurfaceShape, PXRUSDMAYA_API static MStatus initialize(); - PXRUSDMAYA_API - static UsdMayaProxyShape* GetShapeAtDagPath(const MDagPath& dagPath); - - PXRUSDMAYA_API - static void SetClosestPointDelegate(ClosestPointDelegate delegate); - PXRUSDMAYA_API static void SetObjectSoftSelectEnabledDelegate( - ObjectSoftSelectEnabledDelgate delegate); - - PXRUSDMAYA_API - static bool GetObjectSoftSelectEnabled(); + ObjectSoftSelectEnabledDelegate delegate); // Virtual function overrides - PXRUSDMAYA_API - void postConstructor() override; - PXRUSDMAYA_API - MStatus compute( - const MPlug& plug, - MDataBlock& dataBlock) override; + PXRUSDMAYA_API bool isBounded() const override; PXRUSDMAYA_API MBoundingBox boundingBox() const override; - PXRUSDMAYA_API - MSelectionMask getShapeSelectionMask() const override; - - PXRUSDMAYA_API - bool closestPoint( - const MPoint& raySource, - const MVector& rayDirection, - MPoint& theClosestPoint, - MVector& theClosestNormal, - bool findClosestOnMiss, - double tolerance) override; - - PXRUSDMAYA_API - bool canMakeLive() const override; - - // UsdMayaUsdPrimProvider overrides: - /** - * accessor to get the usdprim - * - * This method pulls the usdstage data from outData, and will evaluate - * the dependencies necessary to do so. It should be called instead of - * pulling on the data directly. - */ - PXRUSDMAYA_API - UsdPrim usdPrim() const override; // Public functions - PXRUSDMAYA_API - SdfPathVector getExcludePrimPaths() const; - - PXRUSDMAYA_API - int getComplexity() const; - - PXRUSDMAYA_API - UsdTimeCode getTime() const; - - PXRUSDMAYA_API - bool GetAllRenderAttributes( - UsdPrim* usdPrimOut, - SdfPathVector* excludePrimPathsOut, - int* complexityOut, - UsdTimeCode* timeOut, - bool* drawRenderPurpose, - bool* drawProxyPurpose, - bool* drawGuidePurpose); - - PXRUSDMAYA_API - MStatus setDependentsDirty( - const MPlug& plug, - MPlugArray& plugArray) override; - PXRUSDMAYA_API bool setInternalValueInContext( const MPlug& plug, @@ -213,9 +110,18 @@ class UsdMayaProxyShape : public MPxSurfaceShape, MDataHandle& dataHandle, MDGContext& ctx) override; + void postConstructor() override; + protected: + // Use the variant key to get the session layer from the prim path. + PXRUSDMAYA_API + SdfLayerRefPtr computeSessionLayer(MDataBlock&) override; + + PXRUSDMAYA_API + bool canBeSoftSelected() const override; + PXRUSDMAYA_API - bool isStageValid() const; + bool GetObjectSoftSelectEnabled() const override; private: UsdMayaProxyShape(); @@ -224,34 +130,10 @@ class UsdMayaProxyShape : public MPxSurfaceShape, ~UsdMayaProxyShape() override; UsdMayaProxyShape& operator=(const UsdMayaProxyShape&); - MStatus computeInStageDataCached(MDataBlock& dataBlock); - MStatus computeOutStageData(MDataBlock& dataBlock); - - UsdPrim _GetUsdPrim(MDataBlock dataBlock) const; - SdfPathVector _GetExcludePrimPaths(MDataBlock dataBlock) const; - int _GetComplexity(MDataBlock dataBlock) const; - UsdTimeCode _GetTime(MDataBlock dataBlock) const; - - bool _GetDrawPurposeToggles( - MDataBlock dataBlock, - bool* drawRenderPurpose, - bool* drawProxyPurpose, - bool* drawGuidePurpose) const; - - bool _CanBeSoftSelected() const; - - void _OnStageContentsChanged( - const UsdNotice::StageContentsChanged& notice); - - UsdMayaStageNoticeListener _stageNoticeListener; - - std::map _boundingBoxCache; - bool _useFastPlayback; - static ClosestPointDelegate _sharedClosestPointDelegate; - static ObjectSoftSelectEnabledDelgate - _sharedObjectSoftSelectEnabledDelgate; + static ObjectSoftSelectEnabledDelegate + _sharedObjectSoftSelectEnabledDelegate; }; diff --git a/plugin/pxr/maya/lib/usdMaya/readJob.cpp b/plugin/pxr/maya/lib/usdMaya/readJob.cpp index e4734178cc..3504267a70 100644 --- a/plugin/pxr/maya/lib/usdMaya/readJob.cpp +++ b/plugin/pxr/maya/lib/usdMaya/readJob.cpp @@ -17,12 +17,12 @@ #include "usdMaya/primReaderRegistry.h" #include "usdMaya/shadingModeRegistry.h" -#include "usdMaya/stageCache.h" +#include #include "usdMaya/stageNode.h" #include "usdMaya/translatorMaterial.h" #include "usdMaya/translatorModelAssembly.h" #include "usdMaya/translatorXformable.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/token.h" diff --git a/plugin/pxr/maya/lib/usdMaya/readJob_ImportWithProxies.cpp b/plugin/pxr/maya/lib/usdMaya/readJob_ImportWithProxies.cpp index 81c3df2699..fbe3ab1185 100644 --- a/plugin/pxr/maya/lib/usdMaya/readJob_ImportWithProxies.cpp +++ b/plugin/pxr/maya/lib/usdMaya/readJob_ImportWithProxies.cpp @@ -18,7 +18,7 @@ #include "usdMaya/primReaderArgs.h" #include "usdMaya/primReaderContext.h" #include "usdMaya/primReaderRegistry.h" -#include "usdMaya/stageCache.h" +#include #include "usdMaya/translatorModelAssembly.h" #include "usdMaya/translatorUtil.h" diff --git a/plugin/pxr/maya/lib/usdMaya/readUtil.cpp b/plugin/pxr/maya/lib/usdMaya/readUtil.cpp index 67159e968e..fcb879e85f 100644 --- a/plugin/pxr/maya/lib/usdMaya/readUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/readUtil.cpp @@ -16,8 +16,8 @@ #include "usdMaya/readUtil.h" #include "usdMaya/adaptor.h" -#include "usdMaya/colorSpace.h" -#include "usdMaya/util.h" +#include +#include #include "pxr/base/gf/gamma.h" #include "pxr/base/gf/matrix4d.h" diff --git a/plugin/pxr/maya/lib/usdMaya/referenceAssembly.cpp b/plugin/pxr/maya/lib/usdMaya/referenceAssembly.cpp index 07de263900..950e686f50 100644 --- a/plugin/pxr/maya/lib/usdMaya/referenceAssembly.cpp +++ b/plugin/pxr/maya/lib/usdMaya/referenceAssembly.cpp @@ -17,12 +17,12 @@ #include "usdMaya/editUtil.h" #include "usdMaya/jobArgs.h" -#include "usdMaya/notice.h" +#include #include "usdMaya/proxyShape.h" -#include "usdMaya/query.h" +#include #include "usdMaya/readJob.h" -#include "usdMaya/stageCache.h" -#include "usdMaya/stageData.h" +#include +#include #include "pxr/base/tf/fileUtils.h" #include "pxr/base/tf/registryManager.h" @@ -176,7 +176,7 @@ UsdMayaReferenceAssembly::initialize() inStageDataAttr = typedAttrFn.create( "inStageData", "id", - UsdMayaStageData::mayaTypeId, + MayaUsdStageData::mayaTypeId, MObject::kNullObj, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -220,7 +220,7 @@ UsdMayaReferenceAssembly::initialize() inStageDataCachedAttr = typedAttrFn.create( "inStageDataCached", "idc", - UsdMayaStageData::mayaTypeId, + MayaUsdStageData::mayaTypeId, MObject::kNullObj, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -232,7 +232,7 @@ UsdMayaReferenceAssembly::initialize() outStageDataAttr = typedAttrFn.create( "outStageData", "od", - UsdMayaStageData::mayaTypeId, + MayaUsdStageData::mayaTypeId, MObject::kNullObj, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -749,7 +749,7 @@ UsdMayaReferenceAssembly::computeInStageDataCached(MDataBlock& dataBlock) // issue an error. (If fileString is empty, it just means that the // reference assembly hasn't been set up yet.) // We'll still return a success code from this function because we can - // provide Maya with a sane result (an empty UsdMayaStageData). + // provide Maya with a sane result (an empty MayaUsdStageData). if (!fileString.empty() && !usdStage) { TF_RUNTIME_ERROR( "Could not open USD stage with root layer '%s' for assembly %s", @@ -760,11 +760,11 @@ UsdMayaReferenceAssembly::computeInStageDataCached(MDataBlock& dataBlock) // Create the output outData ======== MFnPluginData pluginDataFn; MObject stageDataObj = - pluginDataFn.create(UsdMayaStageData::mayaTypeId, &retValue); + pluginDataFn.create(MayaUsdStageData::mayaTypeId, &retValue); CHECK_MSTATUS_AND_RETURN_IT(retValue); - UsdMayaStageData* stageData = - reinterpret_cast(pluginDataFn.data(&retValue)); + MayaUsdStageData* stageData = + reinterpret_cast(pluginDataFn.data(&retValue)); CHECK_MSTATUS_AND_RETURN_IT(retValue); // Set the outUsdStageData @@ -795,8 +795,8 @@ UsdMayaReferenceAssembly::computeOutStageData(MDataBlock& dataBlock) UsdStageRefPtr usdStage; - UsdMayaStageData* inData = - dynamic_cast(inDataCachedHandle.asPluginData()); + MayaUsdStageData* inData = + dynamic_cast(inDataCachedHandle.asPluginData()); if(inData) { usdStage = inData->stage; @@ -908,11 +908,11 @@ UsdMayaReferenceAssembly::computeOutStageData(MDataBlock& dataBlock) // Create the output outData MFnPluginData pluginDataFn; MObject stageDataObj = - pluginDataFn.create(UsdMayaStageData::mayaTypeId, &retValue); + pluginDataFn.create(MayaUsdStageData::mayaTypeId, &retValue); CHECK_MSTATUS_AND_RETURN_IT(retValue); - UsdMayaStageData* stageData = - reinterpret_cast(pluginDataFn.data(&retValue)); + MayaUsdStageData* stageData = + reinterpret_cast(pluginDataFn.data(&retValue)); CHECK_MSTATUS_AND_RETURN_IT(retValue); // Set the outUsdStageData @@ -1066,7 +1066,7 @@ UsdPrim UsdMayaReferenceAssembly::usdPrim() const MDataHandle outDataHandle = dataBlock.inputValue(outStageDataAttr, &status); CHECK_MSTATUS_AND_RETURN(status, usdPrim); - UsdMayaStageData* outData = dynamic_cast(outDataHandle.asPluginData()); + MayaUsdStageData* outData = dynamic_cast(outDataHandle.asPluginData()); if(!outData) { return usdPrim; // empty UsdPrim } diff --git a/plugin/pxr/maya/lib/usdMaya/referenceAssembly.h b/plugin/pxr/maya/lib/usdMaya/referenceAssembly.h index f5264a397f..ced6e9cb87 100644 --- a/plugin/pxr/maya/lib/usdMaya/referenceAssembly.h +++ b/plugin/pxr/maya/lib/usdMaya/referenceAssembly.h @@ -20,7 +20,7 @@ #include "usdMaya/api.h" #include "usdMaya/proxyShape.h" -#include "usdMaya/usdPrimProvider.h" +#include #include "pxr/pxr.h" diff --git a/plugin/pxr/maya/lib/usdMaya/registryHelper.cpp b/plugin/pxr/maya/lib/usdMaya/registryHelper.cpp index 56e5931fdd..f5d8cf94c1 100644 --- a/plugin/pxr/maya/lib/usdMaya/registryHelper.cpp +++ b/plugin/pxr/maya/lib/usdMaya/registryHelper.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/registryHelper.h" -#include "usdMaya/debugCodes.h" +#include #include "pxr/base/js/converter.h" #include "pxr/base/plug/plugin.h" diff --git a/plugin/pxr/maya/lib/usdMaya/shadingModeDisplayColor.cpp b/plugin/pxr/maya/lib/usdMaya/shadingModeDisplayColor.cpp index 601c777086..9c79682f9a 100644 --- a/plugin/pxr/maya/lib/usdMaya/shadingModeDisplayColor.cpp +++ b/plugin/pxr/maya/lib/usdMaya/shadingModeDisplayColor.cpp @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. // -#include "usdMaya/colorSpace.h" +#include #include "usdMaya/shadingModeExporter.h" #include "usdMaya/shadingModeExporterContext.h" #include "usdMaya/shadingModeRegistry.h" diff --git a/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.cpp b/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.cpp index 4e74e49bf0..0cb637b07f 100644 --- a/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.cpp +++ b/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.cpp @@ -17,7 +17,7 @@ #include "usdMaya/jobArgs.h" #include "usdMaya/shadingModeExporterContext.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/tf/diagnostic.h" diff --git a/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.h b/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.h index 823fda83d0..3c902d8a29 100644 --- a/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.h +++ b/plugin/pxr/maya/lib/usdMaya/shadingModeExporter.h @@ -21,7 +21,7 @@ #include "pxr/pxr.h" #include "usdMaya/api.h" #include "usdMaya/shadingModeExporterContext.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/usd/sdf/path.h" diff --git a/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.cpp b/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.cpp index 696f165a3c..f94302fccb 100644 --- a/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.cpp +++ b/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.cpp @@ -16,7 +16,7 @@ #include "usdMaya/shadingModeExporterContext.h" #include "usdMaya/jobArgs.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/tf/diagnostic.h" diff --git a/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.h b/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.h index bb59ba8f33..f2e47cdaa9 100644 --- a/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.h +++ b/plugin/pxr/maya/lib/usdMaya/shadingModeExporterContext.h @@ -21,7 +21,7 @@ #include "pxr/pxr.h" #include "usdMaya/api.h" #include "usdMaya/jobArgs.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/tf/token.h" diff --git a/plugin/pxr/maya/lib/usdMaya/shadingModePxrRis.cpp b/plugin/pxr/maya/lib/usdMaya/shadingModePxrRis.cpp index 92e6d122d1..a9fd84583c 100644 --- a/plugin/pxr/maya/lib/usdMaya/shadingModePxrRis.cpp +++ b/plugin/pxr/maya/lib/usdMaya/shadingModePxrRis.cpp @@ -21,7 +21,7 @@ #include "usdMaya/shadingModePxrRis_rfm_map.h" #include "usdMaya/shadingModeRegistry.h" #include "usdMaya/translatorUtil.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeUtil.h" #include "pxr/pxr.h" diff --git a/plugin/pxr/maya/lib/usdMaya/shadingModeUseRegistry.cpp b/plugin/pxr/maya/lib/usdMaya/shadingModeUseRegistry.cpp index 5b6ca45fd3..7dce3c4a0a 100644 --- a/plugin/pxr/maya/lib/usdMaya/shadingModeUseRegistry.cpp +++ b/plugin/pxr/maya/lib/usdMaya/shadingModeUseRegistry.cpp @@ -20,7 +20,7 @@ #include "usdMaya/shadingModeExporterContext.h" #include "usdMaya/shadingModeRegistry.h" #include "usdMaya/shadingUtil.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/diagnostic.h" #include "pxr/base/tf/token.h" diff --git a/plugin/pxr/maya/lib/usdMaya/skelBindingsProcessor.h b/plugin/pxr/maya/lib/usdMaya/skelBindingsProcessor.h index de787b3cf1..58897426e4 100644 --- a/plugin/pxr/maya/lib/usdMaya/skelBindingsProcessor.h +++ b/plugin/pxr/maya/lib/usdMaya/skelBindingsProcessor.h @@ -19,7 +19,7 @@ /// \file usdMaya/skelBindingsProcessor.h #include "usdMaya/api.h" -#include "usdMaya/util.h" +#include #include "pxr/pxr.h" #include "pxr/usd/sdf/path.h" diff --git a/plugin/pxr/maya/lib/usdMaya/stageData.h b/plugin/pxr/maya/lib/usdMaya/stageData.h deleted file mode 100644 index ac1a388614..0000000000 --- a/plugin/pxr/maya/lib/usdMaya/stageData.h +++ /dev/null @@ -1,108 +0,0 @@ -// -// Copyright 2016 Pixar -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#ifndef PXRUSDMAYA_STAGE_DATA_H -#define PXRUSDMAYA_STAGE_DATA_H - -/// \file usdMaya/stageData.h - -#include "usdMaya/api.h" - -#include "pxr/pxr.h" - -#include "pxr/base/tf/staticTokens.h" - -#include "pxr/usd/sdf/path.h" -#include "pxr/usd/usd/stage.h" - -#include -#include -#include -#include -#include - - -PXR_NAMESPACE_OPEN_SCOPE - - -#define PXRUSDMAYA_STAGE_DATA_TOKENS \ - ((MayaTypeName, "pxrUsdStageData")) - -TF_DECLARE_PUBLIC_TOKENS(UsdMayaStageDataTokens, - PXRUSDMAYA_API, - PXRUSDMAYA_STAGE_DATA_TOKENS); - - -class UsdMayaStageData : public MPxGeometryData -{ - public: - /// Unlike other Maya node types, MPxData/MPxGeometryData declare - /// typeId() as a pure virtual method that must be overridden in - /// derived classes, so we have to call this static member "mayaTypeId" - /// instead of just "typeId" as we usually would. - PXRUSDMAYA_API - static const MTypeId mayaTypeId; - PXRUSDMAYA_API - static const MString typeName; - - PXRUSDMAYA_API - static void* creator(); - - /** - * \name MPxGeometryData overrides - */ - //@{ - - PXRUSDMAYA_API - void copy(const MPxData& src) override; - - PXRUSDMAYA_API - MTypeId typeId() const override; - - PXRUSDMAYA_API - MString name() const override; - //@} - - PXRUSDMAYA_API - void registerExitCallback(); - - PXRUSDMAYA_API - void unregisterExitCallback(); - - /** - * \name data - */ - //@{ - - UsdStageRefPtr stage; - SdfPath primPath; - - //@} - - private: - UsdMayaStageData(); - ~UsdMayaStageData() override; - - UsdMayaStageData(const UsdMayaStageData&); - UsdMayaStageData& operator=(const UsdMayaStageData&); - - MCallbackId _exitCallbackId; -}; - - -PXR_NAMESPACE_CLOSE_SCOPE - - -#endif diff --git a/plugin/pxr/maya/lib/usdMaya/stageNode.cpp b/plugin/pxr/maya/lib/usdMaya/stageNode.cpp index 2c81da23e6..9c544eb492 100644 --- a/plugin/pxr/maya/lib/usdMaya/stageNode.cpp +++ b/plugin/pxr/maya/lib/usdMaya/stageNode.cpp @@ -15,8 +15,8 @@ // #include "usdMaya/stageNode.h" -#include "usdMaya/stageCache.h" -#include "usdMaya/stageData.h" +#include +#include #include "pxr/base/tf/staticTokens.h" #include "pxr/base/tf/stringUtils.h" @@ -92,7 +92,7 @@ UsdMayaStageNode::initialize() outUsdStageAttr = typedAttrFn.create("outUsdStage", "os", - UsdMayaStageData::mayaTypeId, + MayaUsdStageData::mayaTypeId, MObject::kNullObj, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -141,11 +141,11 @@ UsdMayaStageNode::compute(const MPlug& plug, MDataBlock& dataBlock) // Create the output stage data object. MFnPluginData pluginDataFn; MObject stageDataObj = - pluginDataFn.create(UsdMayaStageData::mayaTypeId, &status); + pluginDataFn.create(MayaUsdStageData::mayaTypeId, &status); CHECK_MSTATUS_AND_RETURN_IT(status); - UsdMayaStageData* stageData = - reinterpret_cast(pluginDataFn.data(&status)); + MayaUsdStageData* stageData = + reinterpret_cast(pluginDataFn.data(&status)); CHECK_MSTATUS_AND_RETURN_IT(status); stageData->stage = usdStage; diff --git a/plugin/pxr/maya/lib/usdMaya/transformWriter.cpp b/plugin/pxr/maya/lib/usdMaya/transformWriter.cpp index ff404b0223..a772d962e8 100644 --- a/plugin/pxr/maya/lib/usdMaya/transformWriter.cpp +++ b/plugin/pxr/maya/lib/usdMaya/transformWriter.cpp @@ -18,7 +18,7 @@ #include "usdMaya/adaptor.h" #include "usdMaya/primWriterRegistry.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "usdMaya/xformStack.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorCamera.cpp b/plugin/pxr/maya/lib/usdMaya/translatorCamera.cpp index 91d3b9115f..8cb633bd09 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorCamera.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorCamera.cpp @@ -19,7 +19,7 @@ #include "usdMaya/primReaderArgs.h" #include "usdMaya/primReaderContext.h" #include "usdMaya/translatorUtil.h" -#include "usdMaya/util.h" +#include #include "pxr/base/gf/vec2f.h" #include "pxr/base/tf/token.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorGprim.cpp b/plugin/pxr/maya/lib/usdMaya/translatorGprim.cpp index 7cad7e1d7e..f2b0a67741 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorGprim.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorGprim.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/translatorGprim.h" -#include "usdMaya/util.h" +#include #include diff --git a/plugin/pxr/maya/lib/usdMaya/translatorMaterial.cpp b/plugin/pxr/maya/lib/usdMaya/translatorMaterial.cpp index 058be888cb..7a887c5036 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorMaterial.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorMaterial.cpp @@ -19,7 +19,7 @@ #include "usdMaya/shadingModeExporter.h" #include "usdMaya/shadingModeImporter.h" #include "usdMaya/shadingModeRegistry.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/tf/diagnostic.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorMaterial.h b/plugin/pxr/maya/lib/usdMaya/translatorMaterial.h index 381860bc59..b5f6c4bfb1 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorMaterial.h +++ b/plugin/pxr/maya/lib/usdMaya/translatorMaterial.h @@ -21,7 +21,7 @@ #include "pxr/pxr.h" #include "usdMaya/api.h" #include "usdMaya/primReaderContext.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/tf/token.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorMesh.cpp b/plugin/pxr/maya/lib/usdMaya/translatorMesh.cpp index 53aa93cda1..ff48b3c3d0 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorMesh.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorMesh.cpp @@ -25,7 +25,7 @@ #include "usdMaya/translatorGprim.h" #include "usdMaya/translatorMaterial.h" #include "usdMaya/translatorUtil.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/diagnostic.h" #include "pxr/base/tf/stringUtils.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorMesh_PrimVars.cpp b/plugin/pxr/maya/lib/usdMaya/translatorMesh_PrimVars.cpp index 4d342478f5..9c5e7b0911 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorMesh_PrimVars.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorMesh_PrimVars.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/translatorMesh.h" -#include "usdMaya/colorSpace.h" +#include #include "usdMaya/meshUtil.h" #include "usdMaya/readUtil.h" #include "usdMaya/roundTripUtil.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorMesh_SubDiv.cpp b/plugin/pxr/maya/lib/usdMaya/translatorMesh_SubDiv.cpp index 21399f6651..149a048b9a 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorMesh_SubDiv.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorMesh_SubDiv.cpp @@ -15,7 +15,7 @@ // #include "usdMaya/translatorMesh.h" -#include "usdMaya/util.h" +#include #include "pxr/usd/usdGeom/mesh.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorModelAssembly.cpp b/plugin/pxr/maya/lib/usdMaya/translatorModelAssembly.cpp index c3bb1b40e6..ae1d738a84 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorModelAssembly.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorModelAssembly.cpp @@ -22,10 +22,10 @@ #include "usdMaya/primWriterArgs.h" #include "usdMaya/primWriterContext.h" #include "usdMaya/referenceAssembly.h" -#include "usdMaya/stageCache.h" +#include #include "usdMaya/translatorUtil.h" #include "usdMaya/translatorXformable.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/diagnostic.h" #include "pxr/base/tf/stringUtils.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorPrim.cpp b/plugin/pxr/maya/lib/usdMaya/translatorPrim.cpp index ed8b908822..df1a97b792 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorPrim.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorPrim.cpp @@ -17,7 +17,7 @@ #include "usdMaya/readUtil.h" #include "usdMaya/translatorUtil.h" -#include "usdMaya/util.h" +#include #include "pxr/usd/usdGeom/imageable.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorRfMLight.cpp b/plugin/pxr/maya/lib/usdMaya/translatorRfMLight.cpp index 3adff021f3..7dee04e982 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorRfMLight.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorRfMLight.cpp @@ -23,7 +23,7 @@ #include "usdMaya/primWriterRegistry.h" #include "usdMaya/translatorUtil.h" #include "usdMaya/translatorXformable.h" -#include "usdMaya/util.h" +#include #include "pxr/base/gf/vec3f.h" #include "pxr/base/tf/staticTokens.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorSkel.cpp b/plugin/pxr/maya/lib/usdMaya/translatorSkel.cpp index c8e99f84e2..270fa40cca 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorSkel.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorSkel.cpp @@ -17,7 +17,7 @@ #include "usdMaya/translatorUtil.h" #include "usdMaya/translatorXformable.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/staticData.h" #include "pxr/base/tf/staticTokens.h" diff --git a/plugin/pxr/maya/lib/usdMaya/translatorUtil.cpp b/plugin/pxr/maya/lib/usdMaya/translatorUtil.cpp index 9356a6851c..3c6fbe2cbd 100644 --- a/plugin/pxr/maya/lib/usdMaya/translatorUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/translatorUtil.cpp @@ -20,7 +20,7 @@ #include "usdMaya/primReaderArgs.h" #include "usdMaya/primReaderContext.h" #include "usdMaya/translatorXformable.h" -#include "usdMaya/util.h" +#include #include "usdMaya/xformStack.h" #include "pxr/usd/sdf/schema.h" diff --git a/plugin/pxr/maya/lib/usdMaya/userTaggedAttribute.cpp b/plugin/pxr/maya/lib/usdMaya/userTaggedAttribute.cpp index 72013e5330..49f1bc70fa 100644 --- a/plugin/pxr/maya/lib/usdMaya/userTaggedAttribute.cpp +++ b/plugin/pxr/maya/lib/usdMaya/userTaggedAttribute.cpp @@ -16,7 +16,7 @@ #include "pxr/pxr.h" #include "usdMaya/userTaggedAttribute.h" -#include "usdMaya/util.h" +#include #include "pxr/base/js/json.h" #include "pxr/base/js/value.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapAdaptor.cpp b/plugin/pxr/maya/lib/usdMaya/wrapAdaptor.cpp index 35de1a1157..a7aa6f51ec 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapAdaptor.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapAdaptor.cpp @@ -17,7 +17,7 @@ #include "pxr/pxr.h" #include "usdMaya/adaptor.h" #include "usdMaya/undoHelperCommand.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/pyResultConversions.h" #include "pxr/usd/usdGeom/primvar.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapAssembly.cpp b/plugin/pxr/maya/lib/usdMaya/wrapAssembly.cpp index 040af63096..bcdddb41d1 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapAssembly.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapAssembly.cpp @@ -16,7 +16,7 @@ #include "pxr/pxr.h" #include "usdMaya/referenceAssembly.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/pyContainerConversions.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapColorSpace.cpp b/plugin/pxr/maya/lib/usdMaya/wrapColorSpace.cpp index 39effabfed..5641339c5e 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapColorSpace.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapColorSpace.cpp @@ -14,7 +14,7 @@ // limitations under the License. // #include "pxr/pxr.h" -#include "usdMaya/colorSpace.h" +#include #include "pxr/base/gf/vec3f.h" #include "pxr/base/gf/vec3d.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapEditUtil.cpp b/plugin/pxr/maya/lib/usdMaya/wrapEditUtil.cpp index 2a35fcd294..ece8f5d89f 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapEditUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapEditUtil.cpp @@ -16,7 +16,7 @@ #include "pxr/pxr.h" #include "usdMaya/editUtil.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/pyResultConversions.h" #include "pxr/base/tf/pyEnum.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapMeshUtil.cpp b/plugin/pxr/maya/lib/usdMaya/wrapMeshUtil.cpp index e5c2cacbc4..af6a8f7049 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapMeshUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapMeshUtil.cpp @@ -16,7 +16,7 @@ #include "pxr/pxr.h" #include "usdMaya/meshUtil.h" -#include "usdMaya/util.h" +#include #include "pxr/base/gf/vec3f.h" #include "pxr/base/tf/pyResultConversions.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapQuery.cpp b/plugin/pxr/maya/lib/usdMaya/wrapQuery.cpp index 9d861752e4..3fb69cc4ba 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapQuery.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapQuery.cpp @@ -14,7 +14,7 @@ // limitations under the License. // #include "pxr/pxr.h" -#include "usdMaya/query.h" +#include #include #include diff --git a/plugin/pxr/maya/lib/usdMaya/wrapReadUtil.cpp b/plugin/pxr/maya/lib/usdMaya/wrapReadUtil.cpp index b7442faf41..00575a19aa 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapReadUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapReadUtil.cpp @@ -15,7 +15,7 @@ // #include "pxr/pxr.h" -#include "usdMaya/util.h" +#include #include "usdMaya/readUtil.h" #include "pxr/base/tf/pyResultConversions.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapStageCache.cpp b/plugin/pxr/maya/lib/usdMaya/wrapStageCache.cpp index 3198977432..5ced7aac53 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapStageCache.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapStageCache.cpp @@ -14,7 +14,7 @@ // limitations under the License. // #include "pxr/pxr.h" -#include "usdMaya/stageCache.h" +#include #include #include diff --git a/plugin/pxr/maya/lib/usdMaya/wrapUserTaggedAttribute.cpp b/plugin/pxr/maya/lib/usdMaya/wrapUserTaggedAttribute.cpp index 7e8fd95708..f46f32fef2 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapUserTaggedAttribute.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapUserTaggedAttribute.cpp @@ -16,7 +16,7 @@ #include "pxr/pxr.h" #include "usdMaya/userTaggedAttribute.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/pyContainerConversions.h" #include "pxr/base/tf/pyResultConversions.h" diff --git a/plugin/pxr/maya/lib/usdMaya/wrapWriteUtil.cpp b/plugin/pxr/maya/lib/usdMaya/wrapWriteUtil.cpp index 9d4afe3208..b188719206 100644 --- a/plugin/pxr/maya/lib/usdMaya/wrapWriteUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/wrapWriteUtil.cpp @@ -15,7 +15,7 @@ // #include "pxr/pxr.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeUtil.h" #include "pxr/base/tf/pyResultConversions.h" diff --git a/plugin/pxr/maya/lib/usdMaya/writeJob.cpp b/plugin/pxr/maya/lib/usdMaya/writeJob.cpp index ee5c528543..1181bf9dbb 100644 --- a/plugin/pxr/maya/lib/usdMaya/writeJob.cpp +++ b/plugin/pxr/maya/lib/usdMaya/writeJob.cpp @@ -23,7 +23,7 @@ #include "usdMaya/shadingModeExporterContext.h" #include "usdMaya/transformWriter.h" #include "usdMaya/translatorMaterial.h" -#include "usdMaya/util.h" +#include #include "usdMaya/chaser.h" #include "usdMaya/chaserRegistry.h" diff --git a/plugin/pxr/maya/lib/usdMaya/writeJob.h b/plugin/pxr/maya/lib/usdMaya/writeJob.h index 70bf92bbda..b8399267de 100644 --- a/plugin/pxr/maya/lib/usdMaya/writeJob.h +++ b/plugin/pxr/maya/lib/usdMaya/writeJob.h @@ -19,7 +19,7 @@ /// \file usdMaya/writeJob.h #include "usdMaya/chaser.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/pxr.h" diff --git a/plugin/pxr/maya/lib/usdMaya/writeJobContext.cpp b/plugin/pxr/maya/lib/usdMaya/writeJobContext.cpp index a261258cdf..77364392cd 100644 --- a/plugin/pxr/maya/lib/usdMaya/writeJobContext.cpp +++ b/plugin/pxr/maya/lib/usdMaya/writeJobContext.cpp @@ -21,9 +21,9 @@ #include "usdMaya/primWriter.h" #include "usdMaya/primWriterRegistry.h" #include "usdMaya/skelBindingsProcessor.h" -#include "usdMaya/stageCache.h" +#include #include "usdMaya/transformWriter.h" -#include "usdMaya/util.h" +#include #include "pxr/base/tf/staticTokens.h" #include "pxr/base/tf/stringUtils.h" diff --git a/plugin/pxr/maya/lib/usdMaya/writeUtil.cpp b/plugin/pxr/maya/lib/usdMaya/writeUtil.cpp index d67de34fb2..c6a974c512 100644 --- a/plugin/pxr/maya/lib/usdMaya/writeUtil.cpp +++ b/plugin/pxr/maya/lib/usdMaya/writeUtil.cpp @@ -17,7 +17,7 @@ #include "usdMaya/writeUtil.h" #include "usdMaya/adaptor.h" -#include "usdMaya/colorSpace.h" +#include #include "usdMaya/translatorUtil.h" #include "usdMaya/userTaggedAttribute.h" diff --git a/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt b/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt index 7194ef686e..71fb1267db 100644 --- a/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt +++ b/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt @@ -5,6 +5,7 @@ pxr_plugin(${PXR_PACKAGE} LIBRARIES pxrUsdMayaGL + mayaUsd sdf tf usd diff --git a/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp b/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp index 03b388fa0f..38e790c78d 100644 --- a/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp +++ b/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp @@ -16,6 +16,8 @@ #include "pxr/pxr.h" #include "pxrUsd/api.h" +#include + #include "pxrUsdMayaGL/hdImagingShapeDrawOverride.h" #include "pxrUsdMayaGL/hdImagingShapeUI.h" #include "pxrUsdMayaGL/proxyDrawOverride.h" @@ -28,11 +30,11 @@ #include "usdMaya/importCommand.h" #include "usdMaya/importTranslator.h" #include "usdMaya/listShadingModesCommand.h" -#include "usdMaya/notice.h" +#include #include "usdMaya/pointBasedDeformerNode.h" #include "usdMaya/proxyShape.h" #include "usdMaya/referenceAssembly.h" -#include "usdMaya/stageData.h" +#include #include "usdMaya/stageNode.h" #include "usdMaya/undoHelperCommand.h" @@ -57,10 +59,7 @@ initializePlugin(MObject obj) MStatus status; MFnPlugin plugin(obj, "Pixar", "1.0", "Any"); - status = plugin.registerData( - UsdMayaStageData::typeName, - UsdMayaStageData::mayaTypeId, - UsdMayaStageData::creator); + status = MayaUsdProxyShapePlugin::initialize(plugin); CHECK_MSTATUS(status); status = plugin.registerNode( @@ -280,7 +279,7 @@ uninitializePlugin(MObject obj) status = plugin.deregisterNode(UsdMayaStageNode::typeId); CHECK_MSTATUS(status); - status = plugin.deregisterData(UsdMayaStageData::mayaTypeId); + status = MayaUsdProxyShapePlugin::finalize(plugin); CHECK_MSTATUS(status); UsdMayaSceneResetNotice::RemoveListener(); diff --git a/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/usdPreviewSurfaceWriter.cpp b/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/usdPreviewSurfaceWriter.cpp index 19d45d713e..d8f38358d5 100644 --- a/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/usdPreviewSurfaceWriter.cpp +++ b/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/usdPreviewSurfaceWriter.cpp @@ -19,7 +19,7 @@ #include "pxrUsdPreviewSurface/usdPreviewSurface.h" #include "usdMaya/primWriterRegistry.h" #include "usdMaya/shaderWriter.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "usdMaya/writeUtil.h" diff --git a/plugin/pxr/maya/plugin/pxrUsdTranslators/cameraWriter.cpp b/plugin/pxr/maya/plugin/pxrUsdTranslators/cameraWriter.cpp index c12f337f5f..f2946ab779 100644 --- a/plugin/pxr/maya/plugin/pxrUsdTranslators/cameraWriter.cpp +++ b/plugin/pxr/maya/plugin/pxrUsdTranslators/cameraWriter.cpp @@ -19,7 +19,7 @@ #include "usdMaya/adaptor.h" #include "usdMaya/primWriter.h" #include "usdMaya/primWriterRegistry.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/gf/vec2f.h" diff --git a/plugin/pxr/maya/plugin/pxrUsdTranslators/fileTextureWriter.cpp b/plugin/pxr/maya/plugin/pxrUsdTranslators/fileTextureWriter.cpp index b3d52d91b7..9e96929455 100644 --- a/plugin/pxr/maya/plugin/pxrUsdTranslators/fileTextureWriter.cpp +++ b/plugin/pxr/maya/plugin/pxrUsdTranslators/fileTextureWriter.cpp @@ -18,7 +18,7 @@ #include "usdMaya/primWriterRegistry.h" #include "usdMaya/shaderWriter.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/gf/vec3f.h" diff --git a/plugin/pxr/maya/plugin/pxrUsdTranslators/instancerWriter.cpp b/plugin/pxr/maya/plugin/pxrUsdTranslators/instancerWriter.cpp index 44f1c78d80..5aa643e878 100644 --- a/plugin/pxr/maya/plugin/pxrUsdTranslators/instancerWriter.cpp +++ b/plugin/pxr/maya/plugin/pxrUsdTranslators/instancerWriter.cpp @@ -17,7 +17,7 @@ #include "usdMaya/adaptor.h" #include "usdMaya/primWriterRegistry.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "usdMaya/writeUtil.h" diff --git a/plugin/pxr/maya/plugin/pxrUsdTranslators/jointWriter.cpp b/plugin/pxr/maya/plugin/pxrUsdTranslators/jointWriter.cpp index 803c1b244c..2b255041b2 100644 --- a/plugin/pxr/maya/plugin/pxrUsdTranslators/jointWriter.cpp +++ b/plugin/pxr/maya/plugin/pxrUsdTranslators/jointWriter.cpp @@ -21,7 +21,7 @@ #include "usdMaya/primWriterRegistry.h" #include "usdMaya/translatorSkel.h" #include "usdMaya/translatorUtil.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeJobContext.h" #include "pxr/base/tf/staticTokens.h" diff --git a/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter.cpp b/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter.cpp index d7eca808bb..fc770902c8 100644 --- a/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter.cpp +++ b/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter.cpp @@ -20,7 +20,7 @@ #include "usdMaya/meshUtil.h" #include "usdMaya/primWriter.h" #include "usdMaya/primWriterRegistry.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeUtil.h" #include "usdMaya/writeJobContext.h" diff --git a/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter_Primvars.cpp b/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter_Primvars.cpp index aeb5ea7c33..dbdee6f9d9 100644 --- a/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter_Primvars.cpp +++ b/plugin/pxr/maya/plugin/pxrUsdTranslators/meshWriter_Primvars.cpp @@ -15,9 +15,9 @@ // #include "pxrUsdTranslators/meshWriter.h" -#include "usdMaya/colorSpace.h" +#include #include "usdMaya/roundTripUtil.h" -#include "usdMaya/util.h" +#include #include "usdMaya/writeUtil.h" #include "pxr/base/gf/gamma.h" From 70efd232c05f9d5f5d880136b56fe435e996be60 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Fri, 25 Oct 2019 08:36:21 -0400 Subject: [PATCH 02/15] Refactor AL proxy shape to use ProxyShapeBase class --- .../AL_USDMaya/AL/usdmaya/ForwardDeclares.h | 1 - .../AL_USDMaya/AL/usdmaya/PluginRegister.h | 14 +- .../AL/usdmaya/nodes/MeshAnimCreator.cpp | 5 +- .../AL/usdmaya/nodes/MeshAnimDeformer.cpp | 5 +- .../AL/usdmaya/nodes/ProxyShape.cpp | 359 +++++------------- .../AL_USDMaya/AL/usdmaya/nodes/ProxyShape.h | 55 +-- .../AL/usdmaya/nodes/ProxyShapeUI.cpp | 11 +- .../AL/usdmaya/nodes/ProxyUsdGeomCamera.cpp | 7 +- .../AL_USDMaya/AL/usdmaya/nodes/Transform.cpp | 11 +- .../AL/usdmaya/nodes/wrapProxyShape.cpp | 5 +- plugin/al/lib/AL_USDMaya/CMakeLists.txt | 17 +- .../al/mayatest/AL/maya/test/CMakeLists.txt | 2 +- plugin/al/mayautils/AL/maya/CMakeLists.txt | 7 +- .../al/mayautils/AL/maya/utils/NodeHelper.cpp | 42 +- .../al/mayautils/AL/maya/utils/NodeHelper.h | 34 ++ .../al/plugin/AL_USDMayaPlugin/CMakeLists.txt | 2 +- .../test_translators_Translator.cpp | 3 +- .../AL/usd/schemas/maya/tests/CMakeLists.txt | 1 + plugin/al/translators/CMakeLists.txt | 2 +- plugin/al/translators/Mesh.cpp | 3 +- .../pxrUsdTranslators/CMakeLists.txt | 2 +- .../AL/usdmaya/utils/CMakeLists.txt | 7 +- .../usdmayautils/AL/usdmaya/utils/Utils.cpp | 21 + .../al/usdmayautils/AL/usdmaya/utils/Utils.h | 15 + .../al/usdutils/AL/usd/utils/CMakeLists.txt | 2 +- plugin/al/utils/AL/CMakeLists.txt | 2 +- 26 files changed, 297 insertions(+), 338 deletions(-) diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/ForwardDeclares.h b/plugin/al/lib/AL_USDMaya/AL/usdmaya/ForwardDeclares.h index fbc8ee32d6..48542d3692 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/ForwardDeclares.h +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/ForwardDeclares.h @@ -24,7 +24,6 @@ namespace usdmaya { struct guid; class Global; struct MObjectMap; -class StageData; class StageCache; namespace cmds { diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h b/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h index f44e12fa62..13186e75c5 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h @@ -22,7 +22,6 @@ #include "AL/maya/utils/CommandGuiHelper.h" #include "AL/maya/utils/MenuBuilder.h" #include "AL/usdmaya/Global.h" -#include "AL/usdmaya/StageData.h" #include "AL/usdmaya/cmds/CreateUsdPrim.h" #include "AL/usdmaya/cmds/DebugCommands.h" #include "AL/usdmaya/cmds/EventCommand.h" @@ -60,6 +59,8 @@ #include "maya/MGlobal.h" #include "maya/MStatus.h" +#include + PXR_NAMESPACE_USING_DIRECTIVE namespace AL { @@ -232,7 +233,6 @@ MStatus registerPlugin(AFnPlugin& plugin) } } - AL_REGISTER_DATA(plugin, AL::usdmaya::StageData); AL_REGISTER_COMMAND(plugin, AL::maya::utils::CommandGuiListGen); AL_REGISTER_COMMAND(plugin, AL::usdmaya::cmds::CreateUsdPrim); AL_REGISTER_COMMAND(plugin, AL::usdmaya::cmds::LayerCreateLayer); @@ -272,7 +272,12 @@ MStatus registerPlugin(AFnPlugin& plugin) AL_REGISTER_TRANSLATOR(plugin, AL::usdmaya::fileio::ImportTranslator); AL_REGISTER_TRANSLATOR(plugin, AL::usdmaya::fileio::ExportTranslator); AL_REGISTER_DRAW_OVERRIDE(plugin, AL::usdmaya::nodes::ProxyDrawOverride); + + status = MayaUsdProxyShapePlugin::initialize(plugin); + CHECK_MSTATUS(status); + AL_REGISTER_SHAPE_NODE(plugin, AL::usdmaya::nodes::ProxyShape, AL::usdmaya::nodes::ProxyShapeUI, AL::usdmaya::nodes::ProxyDrawOverride); + AL_REGISTER_TRANSFORM_NODE(plugin, AL::usdmaya::nodes::Transform, AL::usdmaya::nodes::TransformationMatrix); AL_REGISTER_DEPEND_NODE(plugin, AL::usdmaya::nodes::RendererManager); AL_REGISTER_DEPEND_NODE(plugin, AL::usdmaya::nodes::Layer); @@ -395,11 +400,14 @@ MStatus unregisterPlugin(AFnPlugin& plugin) AL_UNREGISTER_NODE(plugin, AL::usdmaya::nodes::MeshAnimDeformer); AL_UNREGISTER_NODE(plugin, AL::usdmaya::nodes::MeshAnimCreator); AL_UNREGISTER_NODE(plugin, AL::usdmaya::nodes::ProxyShape); + + status = MayaUsdProxyShapePlugin::finalize(plugin); + CHECK_MSTATUS(status); + AL_UNREGISTER_NODE(plugin, AL::usdmaya::nodes::Transform); AL_UNREGISTER_NODE(plugin, AL::usdmaya::nodes::RendererManager); AL_UNREGISTER_NODE(plugin, AL::usdmaya::nodes::Layer); AL_UNREGISTER_NODE(plugin, AL::usdmaya::nodes::LayerManager); - AL_UNREGISTER_DATA(plugin, AL::usdmaya::StageData); AL::usdmaya::Global::onPluginUnload(); return status; diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimCreator.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimCreator.cpp index f68c40248a..9257a38e90 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimCreator.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimCreator.cpp @@ -19,7 +19,6 @@ #include "AL/usdmaya/DebugCodes.h" #include "AL/usdmaya/nodes/MeshAnimCreator.h" #include "AL/usdmaya/nodes/ProxyShape.h" -#include "AL/usdmaya/StageData.h" #include "AL/usdmaya/utils/Utils.h" #include "AL/usdmaya/utils/MeshUtils.h" @@ -28,6 +27,8 @@ #include "pxr/usd/usdGeom/mesh.h" +#include + namespace AL { namespace usdmaya { namespace nodes { @@ -52,7 +53,7 @@ MStatus MeshAnimCreator::initialise() // do not write these nodes to the file. They will be created automagically by the proxy shape m_primPath = addStringAttr("primPath", "pp", kReadable | kWritable); m_inTime = addTimeAttr("inTime", "it", MTime(), kReadable | kWritable | kStorable | kConnectable); - m_inStageData = addDataAttr("inStageData", "isd", StageData::kTypeId, kWritable | kStorable | kConnectable); + m_inStageData = addDataAttr("inStageData", "isd", MayaUsdStageData::mayaTypeId, kWritable | kStorable | kConnectable); m_outMesh = addMeshAttr("outMesh", "out", kReadable | kStorable | kConnectable); attributeAffects(m_primPath, m_outMesh); attributeAffects(m_inTime, m_outMesh); diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimDeformer.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimDeformer.cpp index 560167273e..dbab7178ed 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimDeformer.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/MeshAnimDeformer.cpp @@ -19,7 +19,6 @@ #include "AL/usdmaya/DebugCodes.h" #include "AL/usdmaya/nodes/MeshAnimDeformer.h" #include "AL/usdmaya/nodes/ProxyShape.h" -#include "AL/usdmaya/StageData.h" #include "AL/usdmaya/utils/Utils.h" #include "maya/MFnMesh.h" @@ -27,6 +26,8 @@ #include "pxr/usd/usdGeom/mesh.h" +#include + namespace AL { namespace usdmaya { namespace nodes { @@ -52,7 +53,7 @@ MStatus MeshAnimDeformer::initialise() // do not write these nodes to the file. They will be created automagically by the proxy shape m_primPath = addStringAttr("primPath", "pp", kReadable | kWritable); m_inTime = addTimeAttr("inTime", "it", MTime(), kReadable | kWritable | kStorable | kConnectable); - m_inStageData = addDataAttr("inStageData", "isd", StageData::kTypeId, kWritable | kStorable | kConnectable); + m_inStageData = addDataAttr("inStageData", "isd", MayaUsdStageData::mayaTypeId, kWritable | kStorable | kConnectable); m_outMesh = addMeshAttr("outMesh", "out", kReadable | kStorable | kConnectable); m_inMesh = addMeshAttr("inMesh", "in", kWritable | kStorable | kConnectable); attributeAffects(m_primPath, m_outMesh); diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp index 1f85f8bb94..00d10220fd 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp @@ -19,21 +19,7 @@ #include "pxr/usdImaging/usdImagingGL/engine.h" */ -#if (__cplusplus >= 201703L) -# include -#else -# include -#endif -namespace AL { -namespace filesystem { -#if (__cplusplus >= 201703L) -typedef std::filesystem::path path; -#else -typedef boost::filesystem::path path; -#endif -} -} #include "maya/MEvaluationNode.h" #include "maya/MEventMessage.h" #include "maya/MFileIO.h" @@ -61,7 +47,6 @@ typedef boost::filesystem::path path; #include "AL/usdmaya/nodes/Transform.h" #include "AL/usdmaya/nodes/TransformationMatrix.h" #include "AL/usdmaya/StageCache.h" -#include "AL/usdmaya/StageData.h" #include "AL/usdmaya/TypeIDs.h" #include "AL/usdmaya/Version.h" #include "AL/usdmaya/utils/Utils.h" @@ -77,6 +62,10 @@ typedef boost::filesystem::path path; #include "pxr/usd/usdUtils/stageCache.h" #include "pxr/usdImaging/usdImaging/delegate.h" +#include +#include +#include + #if defined(WANT_UFE_BUILD) #include "ufe/path.h" #endif @@ -88,15 +77,6 @@ typedef void (*proxy_function_prototype)(void* userData, AL::usdmaya::nodes::Pro const char* ProxyShape::s_selectionMaskName = "al_ProxyShape"; -MDagPath ProxyShape::parentTransform() -{ - MFnDagNode fn(thisMObject()); - MDagPath proxyTransformPath; - fn.getPath(proxyTransformPath); - proxyTransformPath.pop(); - return proxyTransformPath; -} - //---------------------------------------------------------------------------------------------------------------------- void ProxyShape::serialiseTranslatorContext() { @@ -119,104 +99,14 @@ void ProxyShape::deserialiseTranslatorContext() triggerEvent("PostDeserialiseContext"); } -//---------------------------------------------------------------------------------------------------------------------- -static std::string resolvePath(const std::string& filePath) -{ - ArResolver& resolver = ArGetResolver(); - - return resolver.Resolve(filePath); -} - -static std::string getDir(const std::string &fullFilePath) -{ - return AL::filesystem::path(fullFilePath).parent_path().string(); -} - -static std::string getMayaReferencedFileDir(const MObject &proxyShapeNode) -{ - // Can not use MFnDependencyNode(proxyShapeNode).isFromReferencedFile() to test if it is reference node or not, - // which always return false even the proxyShape node is referenced... - - MStatus stat; - MFnReference refFn; - MItDependencyNodes dgIter(MFn::kReference, &stat); - for (; !dgIter.isDone(); dgIter.next()) - { - MObject cRefNode = dgIter.thisNode(); - refFn.setObject(cRefNode); - if(refFn.containsNodeExactly(proxyShapeNode, &stat)) - { - // According to Maya API document, the second argument is 'includePath' and set it to true to include the file path. - // However, I have to set it to false to return the full file path otherwise I get a file name only... - MString refFilePath = refFn.fileName(true, false, false, &stat); - if(!refFilePath.length()) - return std::string(); - - std::string referencedFilePath = refFilePath.asChar(); - TF_DEBUG(ALUSDMAYA_TRANSLATORS).Msg("getMayaReferencedFileDir: The reference file that contains the proxyShape node is : %s\n", referencedFilePath.c_str()); - - return getDir(referencedFilePath); - } - } - - return std::string(); -} - -static std::string getMayaSceneFileDir() -{ - std::string currentFile = AL::maya::utils::convert(MFileIO::currentFile()); - size_t filePathSize = currentFile.size(); - if(filePathSize < 4) - return std::string(); - - // If scene is untitled, the maya file will be MayaWorkspaceDir/untitled : - constexpr char ma_ext[] = ".ma"; - constexpr char mb_ext[] = ".mb"; - auto ext_start = currentFile.end() - 3; - if(std::equal(ma_ext, ma_ext + 3, ext_start) || - std::equal(mb_ext, mb_ext + 3, ext_start)) - return getDir(currentFile); - - return std::string(); -} - -static std::string resolveRelativePathWithinMayaContext(const MObject &proxyShape, const std::string& relativeFilePath) -{ - if (relativeFilePath.length() < 3) - return relativeFilePath; - - std::string currentFileDir = getMayaReferencedFileDir(proxyShape); - if(currentFileDir.empty()) - currentFileDir = getMayaSceneFileDir(); - - if(currentFileDir.empty()) - return relativeFilePath; - - boost::system::error_code errorCode; - AL::filesystem::path path = boost::filesystem::canonical(relativeFilePath, currentFileDir, errorCode); - if (errorCode){ - // file does not exist - return std::string(); - } - return path.string(); -} - //---------------------------------------------------------------------------------------------------------------------- AL_MAYA_DEFINE_NODE(ProxyShape, AL_USDMAYA_PROXYSHAPE, AL_usdmaya); -MObject ProxyShape::m_filePath = MObject::kNullObj; -MObject ProxyShape::m_primPath = MObject::kNullObj; -MObject ProxyShape::m_excludePrimPaths = MObject::kNullObj; MObject ProxyShape::m_populationMaskIncludePaths = MObject::kNullObj; MObject ProxyShape::m_excludedTranslatedGeometry = MObject::kNullObj; -MObject ProxyShape::m_time = MObject::kNullObj; MObject ProxyShape::m_timeOffset = MObject::kNullObj; MObject ProxyShape::m_timeScalar = MObject::kNullObj; MObject ProxyShape::m_outTime = MObject::kNullObj; -MObject ProxyShape::m_complexity = MObject::kNullObj; -MObject ProxyShape::m_outStageData = MObject::kNullObj; -MObject ProxyShape::m_displayGuides = MObject::kNullObj; -MObject ProxyShape::m_displayRenderGuides = MObject::kNullObj; MObject ProxyShape::m_layers = MObject::kNullObj; MObject ProxyShape::m_serializedSessionLayer = MObject::kNullObj; MObject ProxyShape::m_sessionLayerName = MObject::kNullObj; @@ -245,18 +135,7 @@ int m_stageCacheId; UsdPrim ProxyShape::getUsdPrim(MDataBlock& dataBlock) const { TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("ProxyShape::getUsdPrim\n"); - UsdPrim usdPrim; - StageData* outData = inputDataValue(dataBlock, m_outStageData); - if(outData) - { - if(outData->stage) - { - usdPrim = (outData->primPath.IsEmpty()) ? - outData->stage->GetPseudoRoot() : - outData->stage->GetPrimAtPath(outData->primPath); - } - } - return usdPrim; + return _GetUsdPrim(dataBlock); } //---------------------------------------------------------------------------------------------------------------------- @@ -267,7 +146,20 @@ SdfPathVector ProxyShape::getExcludePrimPaths() const SdfPathVector paths = getPrimPathsFromCommaJoinedString(excludePrimPathsPlug().asString()); SdfPathVector temp = getPrimPathsFromCommaJoinedString(excludedTranslatedGeometryPlug().asString()); paths.insert(paths.end(), temp.begin(), temp.end()); - return paths; + + const auto& translatedGeo = m_context->excludedGeometry(); + + // combine the excluded paths + SdfPathVector excludedGeometryPaths; + excludedGeometryPaths.reserve(m_excludedTaggedGeometry.size() + paths.size() + translatedGeo.size()); + excludedGeometryPaths.assign(m_excludedTaggedGeometry.begin(), m_excludedTaggedGeometry.end()); + excludedGeometryPaths.insert(excludedGeometryPaths.end(), m_excludedGeometry.begin(), m_excludedGeometry.end()); + for (auto& it : translatedGeo) + { + excludedGeometryPaths.push_back(it.second); + } + + return excludedGeometryPaths; } //---------------------------------------------------------------------------------------------------------------------- @@ -399,7 +291,7 @@ void ProxyShape::translatePrimsIntoMaya( if(context()->isExcludedGeometryDirty()) { TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("ProxyShape:translatePrimsIntoMaya excluded geometry has been modified, reconstructing imaging engine \n"); - constructGLImagingEngine(); + constructExcludedPrims(); //if excluded prims changed, this will call constructGLImagingEngine } } //---------------------------------------------------------------------------------------------------------------------- @@ -461,19 +353,7 @@ void ProxyShape::constructGLImagingEngine() // delete previous instance destroyGLImagingEngine(); - const auto& translatedGeo = m_context->excludedGeometry(); - - // combine the excluded paths - SdfPathVector excludedGeometryPaths; - excludedGeometryPaths.reserve(m_excludedTaggedGeometry.size() + m_excludedGeometry.size() + translatedGeo.size()); - excludedGeometryPaths.assign(m_excludedTaggedGeometry.begin(), m_excludedTaggedGeometry.end()); - excludedGeometryPaths.insert(excludedGeometryPaths.end(), m_excludedGeometry.begin(), m_excludedGeometry.end()); - for(auto& it : translatedGeo) - { - excludedGeometryPaths.push_back(it.second); - } - - m_engine = new Engine(m_path, excludedGeometryPaths); + m_engine = new Engine(m_path, m_excludedGeometry); // set renderer plugin based on RendererManager setting RendererManager* manager = RendererManager::findManager(); if(manager && m_engine) @@ -520,12 +400,12 @@ MStatus ProxyShape::setDependentsDirty(const MPlug& plugBeingDirtied, MPlugArray } } - if(plugBeingDirtied == m_time || plugBeingDirtied == m_timeOffset || plugBeingDirtied == m_timeScalar) + if(plugBeingDirtied == time() || plugBeingDirtied == m_timeOffset || plugBeingDirtied == m_timeScalar) { plugs.append(outTimePlug()); return MS::kSuccess; } - if(plugBeingDirtied == m_filePath) + if(plugBeingDirtied == filePath()) { MHWRender::MRenderer::setGeometryDrawDirty(thisMObject(), true); } @@ -605,14 +485,14 @@ bool ProxyShape::getRenderAttris(UsdImagingGLRenderParams& attribs, const MHWRen const float complexities[] = {1.05f, 1.15f, 1.25f, 1.35f, 1.45f, 1.55f, 1.65f, 1.75f, 1.9f}; attribs.complexity = complexities[complexityPlug().asInt()]; - attribs.showGuides = displayGuidesPlug().asBool(); - attribs.showRender = displayRenderGuidesPlug().asBool(); + attribs.showGuides = drawGuidePurposePlug().asBool(); + attribs.showRender = drawRenderPurposePlug().asBool(); return true; } //---------------------------------------------------------------------------------------------------------------------- ProxyShape::ProxyShape() - : MPxSurfaceShape(), AL::maya::utils::NodeHelper(), AL::event::NodeEvents(&AL::event::EventScheduler::getScheduler()), + : MayaUsdProxyShapeBase(), AL::maya::utils::NodeHelper(), AL::event::NodeEvents(&AL::event::EventScheduler::getScheduler()), m_context(fileio::translators::TranslatorContext::create(this)), m_translatorManufacture(context()) { @@ -769,6 +649,9 @@ MStatus ProxyShape::initialise() { TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("ProxyShape::initialise\n"); + MStatus retValue = inheritAttributesFrom(MayaUsdProxyShapeBase::typeName); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + const char* errorString = "ProxyShape::initialize"; try { @@ -780,23 +663,22 @@ MStatus ProxyShape::initialise() //for backward compatibility (or at least to stop maya spewing out errors on scene open). This attribute was removed in 0.32.17 addStringAttr("serializedArCtx", "arcd", kReadable|kWritable|kHidden); // m_filePath / m_primPath / m_excludePrimPaths are internal just so we get notification on change - m_filePath = addFilePathAttr("filePath", "fp", kCached | kReadable | kWritable | kStorable | kAffectsAppearance | kInternal, kLoad, "USD Files (*.usd*) (*.usd*);;Alembic Files (*.abc)"); + inheritFilePathAttr("filePath", kCached | kReadable | kWritable | kStorable | kAffectsAppearance | kInternal, kLoad, "USD Files (*.usd*) (*.usd*);;Alembic Files (*.abc)"); - m_primPath = addStringAttr("primPath", "pp", kCached | kReadable | kWritable | kStorable | kAffectsAppearance | kInternal); - m_excludePrimPaths = addStringAttr("excludePrimPaths", "epp", kCached | kReadable | kWritable | kStorable | kAffectsAppearance | kInternal); + inheritStringAttr("primPath", kCached | kReadable | kWritable | kStorable | kAffectsAppearance | kInternal); + inheritStringAttr("excludePrimPaths", kCached | kReadable | kWritable | kStorable | kAffectsAppearance | kInternal); m_populationMaskIncludePaths = addStringAttr("populationMaskIncludePaths", "pmi", kCached | kReadable | kWritable | kStorable | kAffectsAppearance); m_excludedTranslatedGeometry = addStringAttr("excludedTranslatedGeometry", "etg", kCached | kReadable | kWritable | kStorable | kAffectsAppearance); - m_complexity = addInt32Attr("complexity", "cplx", 0, kCached | kConnectable | kReadable | kWritable | kAffectsAppearance | kKeyable | kStorable); - setMinMax(m_complexity, 0, 8, 0, 4); - m_outStageData = addDataAttr("outStageData", "od", StageData::kTypeId, kConnectable | kReadable | kWritable | kAffectsAppearance); - m_displayGuides = addBoolAttr("displayGuides", "dg", false, kCached | kKeyable | kWritable | kAffectsAppearance | kStorable); - m_displayRenderGuides = addBoolAttr("displayRenderGuides", "drg", false, kCached | kKeyable | kWritable | kAffectsAppearance | kStorable); + inheritInt32Attr("complexity", kCached | kConnectable | kReadable | kWritable | kAffectsAppearance | kKeyable | kStorable); + // outStageData attribute already added in base class. + inheritBoolAttr("displayGuides", kCached | kKeyable | kWritable | kAffectsAppearance | kStorable); + inheritBoolAttr("displayRenderGuides", kCached | kKeyable | kWritable | kAffectsAppearance | kStorable); m_unloaded = addBoolAttr("unloaded", "ul", false, kCached | kKeyable | kWritable | kAffectsAppearance | kStorable); m_serializedTrCtx = addStringAttr("serializedTrCtx", "srtc", kReadable|kWritable|kStorable|kHidden); addFrame("USD Timing Information"); - m_time = addTimeAttr("time", "tm", MTime(0.0), kCached | kConnectable | kReadable | kWritable | kStorable | kAffectsAppearance); + inheritTimeAttr("time", kCached | kConnectable | kReadable | kWritable | kStorable | kAffectsAppearance); m_timeOffset = addTimeAttr("timeOffset", "tmo", MTime(0.0), kCached | kConnectable | kReadable | kWritable | kStorable | kAffectsAppearance); m_timeScalar = addDoubleAttr("timeScalar", "tms", 1.0, kCached | kConnectable | kReadable | kWritable | kStorable | kAffectsAppearance); m_outTime = addTimeAttr("outTime", "otm", MTime(0.0), kCached | kConnectable | kReadable | kAffectsAppearance); @@ -830,14 +712,14 @@ MStatus ProxyShape::initialise() m_assetResolverConfig = addStringAttr("assetResolverConfig", "arc", kReadable | kWritable | kConnectable | kStorable | kAffectsAppearance | kInternal); - AL_MAYA_CHECK_ERROR(attributeAffects(m_time, m_outTime), errorString); + AL_MAYA_CHECK_ERROR(attributeAffects(time(), m_outTime), errorString); AL_MAYA_CHECK_ERROR(attributeAffects(m_timeOffset, m_outTime), errorString); AL_MAYA_CHECK_ERROR(attributeAffects(m_timeScalar, m_outTime), errorString); - AL_MAYA_CHECK_ERROR(attributeAffects(m_filePath, m_outStageData), errorString); - AL_MAYA_CHECK_ERROR(attributeAffects(m_primPath, m_outStageData), errorString); - AL_MAYA_CHECK_ERROR(attributeAffects(m_populationMaskIncludePaths, m_outStageData), errorString); - AL_MAYA_CHECK_ERROR(attributeAffects(m_stageDataDirty, m_outStageData), errorString); - AL_MAYA_CHECK_ERROR(attributeAffects(m_assetResolverConfig, m_outStageData), errorString); + // file path and prim path affects on out stage data already done in base + // class. + AL_MAYA_CHECK_ERROR(attributeAffects(m_populationMaskIncludePaths, outStageData()), errorString); + AL_MAYA_CHECK_ERROR(attributeAffects(m_stageDataDirty, outStageData()), errorString); + AL_MAYA_CHECK_ERROR(attributeAffects(m_assetResolverConfig, outStageData()), errorString); } catch (const MStatus& status) { @@ -1063,7 +945,7 @@ void ProxyShape::serializeAll() //---------------------------------------------------------------------------------------------------------------------- void ProxyShape::onObjectsChanged(UsdNotice::ObjectsChanged const& notice, UsdStageWeakPtr const& sender) { - if(MFileIO::isReadingFile()) + if(MFileIO::isReadingFile() || AL::usdmaya::utils::BlockNotifications::isBlockingNotifications()) return; if (!sender || sender != m_stage) @@ -1120,7 +1002,7 @@ void ProxyShape::onObjectsChanged(UsdNotice::ObjectsChanged const& notice, UsdSt // do we need to clear the bounding box cache? if(shouldCleanBBoxCache) { - m_boundingBoxCache.clear(); + clearBoundingBoxCache(); // Ideally we want to have a way to force maya to call ProxyShape::boundingBox() again to update the bbox attributes. // This may lead to a delay in the bbox updates (e.g. usually you need to reselect the proxy before the bounds will @@ -1426,7 +1308,7 @@ void ProxyShape::loadStage() const int stageIdVal = inputInt32Value(dataBlock, m_stageCacheId); UsdStageCache::Id stageId = UsdStageCache::Id().FromLongInt(stageIdVal); - MString file = inputStringValue(dataBlock, m_filePath); + MString file = inputStringValue(dataBlock, filePath()); if (m_stage) { @@ -1456,7 +1338,7 @@ void ProxyShape::loadStage() // Save the initial edit target and all dirty layers. trackAllDirtyLayers(); file.set(m_stage->GetRootLayer()->GetIdentifier().c_str()); - outputStringValue(dataBlock, m_filePath, file); + outputStringValue(dataBlock, filePath(), file); } else { @@ -1481,16 +1363,16 @@ void ProxyShape::loadStage() TF_DEBUG(ALUSDMAYA_TRANSLATORS).Msg("ProxyShape::reloadStage original USD file path is %s\n", fileString.c_str()); - AL::filesystem::path filestringPath(fileString); + boost::filesystem::path filestringPath(fileString); if (filestringPath.is_absolute()) { - fileString = resolvePath(fileString); + fileString = UsdMayaUtilFileSystem::resolvePath(fileString); TF_DEBUG(ALUSDMAYA_TRANSLATORS).Msg("ProxyShape::reloadStage resolved the USD file path to %s\n", fileString.c_str()); } else { - fileString = resolveRelativePathWithinMayaContext(thisMObject(), fileString); + fileString = UsdMayaUtilFileSystem::resolveRelativePathWithinMayaContext(thisMObject(), fileString); TF_DEBUG(ALUSDMAYA_TRANSLATORS).Msg("ProxyShape::reloadStage resolved the relative USD file path to %s\n", fileString.c_str()); } @@ -1605,7 +1487,7 @@ void ProxyShape::loadStage() // Get the prim // If no primPath string specified, then use the pseudo-root. const SdfPath rootPath(std::string("/")); - MString primPathStr = inputStringValue(dataBlock, m_primPath); + MString primPathStr = inputStringValue(dataBlock, primPath()); if (primPathStr.length()) { m_path = SdfPath(AL::maya::utils::convert(primPathStr)); @@ -1680,6 +1562,8 @@ void ProxyShape::constructExcludedPrims() auto excludedPaths = getExcludePrimPaths(); if (m_excludedGeometry != excludedPaths) { + _IncreaseExcludePrimPathsVersion(); + std::swap(m_excludedGeometry, excludedPaths); constructGLImagingEngine(); } @@ -1801,8 +1685,9 @@ void ProxyShape::postConstructor() { TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("ProxyShape::postConstructor\n"); + ParentClass::postConstructor(); + // Apply render defaults - setRenderable(true); MPlug(thisMObject(), m_visibleInReflections).setValue(true); MPlug(thisMObject(), m_visibleInRefractions).setValue(true); } @@ -1941,7 +1826,7 @@ MStatus ProxyShape::computeOutStageData(const MPlug& plug, MDataBlock& dataBlock { // create new stage data MObject data; - StageData* usdStageData = createData(StageData::kTypeId, data); + MayaUsdStageData* usdStageData = createData(MayaUsdStageData::mayaTypeId, data); if(!usdStageData) { return MS::kFailure; @@ -1958,11 +1843,14 @@ MStatus ProxyShape::computeOutStageData(const MPlug& plug, MDataBlock& dataBlock usdStageData->primPath = m_path; // set the cached output value, and flush - MStatus status = outputDataValue(dataBlock, m_outStageData, usdStageData); + MStatus status = outputDataValue(dataBlock, outStageData(), usdStageData); if(!status) { return MS::kFailure; } + + UsdMayaProxyStageSetNotice(*this).Send(); + return status; } @@ -1972,7 +1860,7 @@ bool ProxyShape::isStageValid() const TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("ProxyShape::isStageValid\n"); MDataBlock dataBlock = const_cast(this)->forceCache(); - StageData* outData = inputDataValue(dataBlock, m_outStageData); + MayaUsdStageData* outData = inputDataValue(dataBlock, outStageData()); if(outData && outData->stage) return true; @@ -1984,11 +1872,11 @@ UsdStageRefPtr ProxyShape::getUsdStage() const { TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("ProxyShape::getUsdStage\n"); - MPlug plug(thisMObject(), m_outStageData); + MPlug plug(thisMObject(), outStageData()); MObject data; plug.getValue(data); MFnPluginData fnData(data); - StageData* outData = static_cast(fnData.data()); + MayaUsdStageData* outData = static_cast(fnData.data()); if(outData) { return outData->stage; @@ -1996,10 +1884,15 @@ UsdStageRefPtr ProxyShape::getUsdStage() const return UsdStageRefPtr(); } +UsdTimeCode ProxyShape::getTime() const +{ + return UsdTimeCode(outTimePlug().asMTime().as(MTime::uiUnit())); +} + //---------------------------------------------------------------------------------------------------------------------- MStatus ProxyShape::computeOutputTime(const MPlug& plug, MDataBlock& dataBlock, MTime& currentTime) { - MTime inTime = inputTimeValue(dataBlock, m_time); + MTime inTime = inputTimeValue(dataBlock, time()); MTime inTimeOffset = inputTimeValue(dataBlock, m_timeOffset); double inTimeScalar = inputDoubleValue(dataBlock, m_timeScalar); currentTime.setValue((inTime.as(MTime::uiUnit()) - inTimeOffset.as(MTime::uiUnit())) * inTimeScalar); @@ -2018,11 +1911,13 @@ MStatus ProxyShape::compute(const MPlug& plug, MDataBlock& dataBlock) return computeOutputTime(plug, dataBlock, currentTime); } else - if(plug == m_outStageData) + if(plug == outStageData()) { MStatus status = computeOutputTime(MPlug(plug.node(), m_outTime), dataBlock, currentTime); return status == MS::kSuccess ? computeOutStageData(plug, dataBlock) : status; } + // Completely skip over parent class compute(), because it has inStageData + // and inStageDataCached attributes we don't use. return MPxSurfaceShape::compute(plug, dataBlock); } @@ -2036,13 +1931,14 @@ bool ProxyShape::setInternalValue(const MPlug& plug, const MDataHandle& dataHand // the datablock for us, but this would be too late for these subfunctions TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("ProxyShape::setInternalValue %s\n", plug.name().asChar()); - if(plug == m_filePath || plug == m_assetResolverConfig || plug == m_stageCacheId) + if(plug == filePath() || plug == m_assetResolverConfig || plug == m_stageCacheId) { m_filePathDirty = true; // can't use dataHandle.datablock(), as this is a temporary datahandle MDataBlock datablock = forceCache(); - if (plug == m_filePath || plug == m_assetResolverConfig) + + if (plug == filePath() || plug == m_assetResolverConfig) { AL_MAYA_CHECK_ERROR_RETURN_VAL(outputStringValue(datablock, plug, dataHandle.asString()), false, @@ -2066,11 +1962,11 @@ bool ProxyShape::setInternalValue(const MPlug& plug, const MDataHandle& dataHand return true; } else - if(plug == m_primPath) + if(plug == primPath()) { // can't use dataHandle.datablock(), as this is a temporary datahandle MDataBlock datablock = forceCache(); - AL_MAYA_CHECK_ERROR_RETURN_VAL(outputStringValue(datablock, m_primPath, dataHandle.asString()), + AL_MAYA_CHECK_ERROR_RETURN_VAL(outputStringValue(datablock, primPath(), dataHandle.asString()), false, "ProxyShape::setInternalValue - error setting primPath"); if(m_stage) @@ -2096,7 +1992,7 @@ bool ProxyShape::setInternalValue(const MPlug& plug, const MDataHandle& dataHand return true; } else - if(plug == m_excludePrimPaths || plug == m_excludedTranslatedGeometry) + if(plug == excludePrimPaths() || plug == m_excludedTranslatedGeometry) { // can't use dataHandle.datablock(), as this is a temporary datahandle MDataBlock datablock = forceCache(); @@ -2126,98 +2022,17 @@ bool ProxyShape::isBounded() const } //---------------------------------------------------------------------------------------------------------------------- -MBoundingBox ProxyShape::boundingBox() const +void ProxyShape::CacheEmptyBoundingBox(MBoundingBox& cachedBBox) { - MStatus status; - - // Make sure outStage is up to date - MDataBlock dataBlock = const_cast(this)->forceCache(); - - // This would seem to be superfluous? unless it is actually forcing a DG pull? - MDataHandle outDataHandle = dataBlock.inputValue(m_outStageData, &status); - (void)outDataHandle; - CHECK_MSTATUS_AND_RETURN(status, MBoundingBox() ); - - // XXX:aluk - // If we could cheaply determine whether a stage only has static geometry, - // we could make this value a constant one for that case, avoiding the - // memory overhead of a cache entry per frame - UsdTimeCode currTime = UsdTimeCode(inputTimeValue(dataBlock, m_outTime).as(MTime::uiUnit())); - - // RB: There must be a nicer way of doing this that avoids the map? - // The time codes are likely to be ranged, so an ordered array + binary search would surely work? - std::map::const_iterator cacheLookup = m_boundingBoxCache.find(currTime); - if (cacheLookup != m_boundingBoxCache.end()) - { - return cacheLookup->second; - } - - GfBBox3d allBox; - UsdPrim prim = getUsdPrim(dataBlock); - if (prim) - { - UsdGeomImageable imageablePrim(prim); - bool showGuides = inputBoolValue(dataBlock, m_displayGuides); - bool showRenderGuides = inputBoolValue(dataBlock, m_displayRenderGuides); - if (showGuides && showRenderGuides) - { - allBox = imageablePrim.ComputeUntransformedBound( - currTime, - UsdGeomTokens->default_, - UsdGeomTokens->proxy, - UsdGeomTokens->guide, - UsdGeomTokens->render); - } - else - if (showGuides && !showRenderGuides) - { - allBox = imageablePrim.ComputeUntransformedBound( - currTime, - UsdGeomTokens->default_, - UsdGeomTokens->proxy, - UsdGeomTokens->guide); - } - else if (!showGuides && showRenderGuides) - { - allBox = imageablePrim.ComputeUntransformedBound( - currTime, - UsdGeomTokens->default_, - UsdGeomTokens->proxy, - UsdGeomTokens->render); - } - else - { - allBox = imageablePrim.ComputeUntransformedBound( - currTime, - UsdGeomTokens->default_, - UsdGeomTokens->proxy); - } - } - else - { - return MBoundingBox(); - } - - // insert new cache entry - MBoundingBox& retval = m_boundingBoxCache[currTime]; - - // Convert to GfRange3d to MBoundingBox - GfRange3d boxRange = allBox.ComputeAlignedBox(); - if (!boxRange.IsEmpty()) - { - retval = MBoundingBox(MPoint(boxRange.GetMin()[0], - boxRange.GetMin()[1], - boxRange.GetMin()[2]), - MPoint(boxRange.GetMax()[0], - boxRange.GetMax()[1], - boxRange.GetMax()[2])); - } - else - { - retval = MBoundingBox(MPoint(-100000.0f, -100000.0f, -100000.0f), MPoint(100000.0f, 100000.0f, 100000.0f)); - } + cachedBBox = MBoundingBox( + MPoint(-100000.0f, -100000.0f, -100000.0f), + MPoint( 100000.0f, 100000.0f, 100000.0f)); +} - return retval; +//---------------------------------------------------------------------------------------------------------------------- +UsdTimeCode ProxyShape::GetOutputTime(MDataBlock dataBlock) const +{ + return UsdTimeCode(inputDoubleValue(dataBlock, m_outTime)); } //---------------------------------------------------------------------------------------------------------------------- diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.h b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.h index 105acc2ef8..a322d37405 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.h +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.h @@ -47,6 +47,8 @@ #include "pxr/usd/usd/stage.h" #include "pxr/usdImaging/usdImagingGL/renderParams.h" +#include + #if defined(WANT_UFE_BUILD) #include "ufe/ufe.h" @@ -250,7 +252,7 @@ extern AL::event::EventId kPostClearStageCache; /// \ingroup nodes //---------------------------------------------------------------------------------------------------------------------- class ProxyShape - : public MPxSurfaceShape, + : public MayaUsdProxyShapeBase, public AL::maya::utils::NodeHelper, public proxy::PrimFilterInterface, public AL::event::NodeEvents, @@ -260,10 +262,9 @@ class ProxyShape friend class ProxyShapeUI; friend class StageReloadGuard; friend class ProxyDrawOverride; -public: - // returns the shape's parent transform - MDagPath parentTransform(); + typedef MayaUsdProxyShapeBase ParentClass; +public: /// a method that registers all of the events in the ProxyShape AL_USDMAYA_PUBLIC @@ -295,17 +296,25 @@ class ProxyShape /// \name Input Attributes //-------------------------------------------------------------------------------------------------------------------- + // Convenience declarations for attributes inherited from proxy shape + // base class. +#define AL_INHERIT_ATTRIBUTE(XX) \ + AL_USDMAYA_PUBLIC \ + static const MObject& XX() { return XX##Attr; } \ + AL_USDMAYA_PUBLIC \ + MPlug XX##Plug() const { return MPlug( thisMObject(), XX##Attr ); } + /// the input USD file path for this proxy - AL_DECL_ATTRIBUTE(filePath); + AL_INHERIT_ATTRIBUTE(filePath); /// a path to a prim you want to view with this shape - AL_DECL_ATTRIBUTE(primPath); + AL_INHERIT_ATTRIBUTE(primPath); /// a comma seperated list of prims you *don't* want to see. - AL_DECL_ATTRIBUTE(excludePrimPaths); + AL_INHERIT_ATTRIBUTE(excludePrimPaths); /// the input time value (probably connected to time1.outTime) - AL_DECL_ATTRIBUTE(time); + AL_INHERIT_ATTRIBUTE(time); /// an offset, in GUI time units, where the animation should start playback AL_DECL_ATTRIBUTE(timeOffset); @@ -315,13 +324,13 @@ class ProxyShape AL_DECL_ATTRIBUTE(timeScalar); /// the subdiv complexity used - AL_DECL_ATTRIBUTE(complexity); + AL_INHERIT_ATTRIBUTE(complexity); /// display guide - sets shape to display geometry of purpose "guide". See imageable.h - AL_DECL_ATTRIBUTE(displayGuides); + AL_INHERIT_ATTRIBUTE(drawGuidePurpose); - /// display render guide - sets hape to display geometry of purpose "render". See imageable.h - AL_DECL_ATTRIBUTE(displayRenderGuides); + /// display render guide - sets shape to display geometry of purpose "render". See imageable.h + AL_INHERIT_ATTRIBUTE(drawRenderPurpose); /// Connection to any layer DG nodes AL_DECL_ATTRIBUTE(layers); @@ -381,8 +390,8 @@ class ProxyShape /// outTime = (time - timeOffset) * timeScalar AL_DECL_ATTRIBUTE(outTime); - /// inStageData ---> inStageDataCached ---> outStageData - AL_DECL_ATTRIBUTE(outStageData); + /// Inject m_stage and m_path members into DG as a data attribute. + AL_INHERIT_ATTRIBUTE(outStageData); //-------------------------------------------------------------------------------------------------------------------- @@ -393,8 +402,11 @@ class ProxyShape /// on the output stage. /// \return the proxy shape AL_USDMAYA_PUBLIC - UsdStageRefPtr getUsdStage() const; + UsdStageRefPtr getUsdStage() const override; + AL_USDMAYA_PUBLIC + UsdTimeCode getTime() const override; + /// \brief provides access to the UsdStage that this proxy shape is currently representing /// \return the proxy shape UsdStageRefPtr usdStage() const @@ -407,10 +419,6 @@ class ProxyShape /// \return true if the attribs could be retrieved (i.e. is the stage is valid) bool getRenderAttris(UsdImagingGLRenderParams& attribs, const MHWRender::MFrameContext& frameContext, const MDagPath& dagPath); - /// \brief compute bounds - AL_USDMAYA_PUBLIC - MBoundingBox boundingBox() const override; - //-------------------------------------------------------------------------------------------------------------------- /// \name AL_usdmaya_Transform utils /// \brief A set of commands to manipulate the chains of transforms that map to the usd prims found in a stage. @@ -860,10 +868,6 @@ class ProxyShape AL_USDMAYA_PUBLIC MSelectionMask getShapeSelectionMask() const override; - /// \brief Clears the bounding box cache of the shape - inline void clearBoundingBoxCache() - { m_boundingBoxCache.clear(); } - private: /// \brief constructs the USD imaging engine for this shape void constructGLImagingEngine(); @@ -999,6 +1003,8 @@ class ProxyShape MPxNode::SchedulingType schedulingType() const override { return kSerial; } #endif MStatus preEvaluation(const MDGContext & context, const MEvaluationNode& evaluationNode) override; + void CacheEmptyBoundingBox(MBoundingBox&) override; + UsdTimeCode GetOutputTime(MDataBlock) const override; //-------------------------------------------------------------------------------------------------------------------- /// \name Compute methods @@ -1014,7 +1020,7 @@ class ProxyShape //-------------------------------------------------------------------------------------------------------------------- UsdPrim getUsdPrim(MDataBlock& dataBlock) const; - SdfPathVector getExcludePrimPaths() const; + SdfPathVector getExcludePrimPaths() const override; UsdStagePopulationMask constructStagePopulationMask(const MString &paths) const; bool isStageValid() const; @@ -1072,7 +1078,6 @@ class ProxyShape TfNotice::Key m_editTargetChanged; TfNotice::Key m_transactionNoticeKey; - mutable std::map m_boundingBoxCache; MCallbackId m_onSelectionChanged = 0; SdfPathVector m_excludedGeometry; SdfPathVector m_excludedTaggedGeometry; diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShapeUI.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShapeUI.cpp index b2f05488be..c31ffa87ed 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShapeUI.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShapeUI.cpp @@ -20,6 +20,9 @@ #include "AL/usdmaya/nodes/ProxyShape.h" #include "AL/usdmaya/nodes/ProxyShapeUI.h" +#include "maya/MFnDagNode.h" +#include "maya/MMatrix.h" +#include "maya/MTime.h" #include "maya/MDrawInfo.h" #include "maya/MDrawRequest.h" #include "maya/MDrawRequestQueue.h" @@ -138,8 +141,8 @@ void ProxyShapeUI::draw(const MDrawRequest& request, M3dView& view) const auto stage = shape->getUsdStage(); UsdImagingGLRenderParams params; - params.showGuides = shape->displayGuidesPlug().asBool(); - params.showRender = shape->displayRenderGuidesPlug().asBool(); + params.showGuides = shape->drawGuidePurposePlug().asBool(); + params.showRender = shape->drawRenderPurposePlug().asBool(); params.frame = UsdTimeCode(shape->outTimePlug().asMTime().as(MTime::uiUnit())); params.complexity = 1.0f; @@ -345,8 +348,8 @@ bool ProxyShapeUI::select(MSelectInfo& selectInfo, MSelectionList& selectionList proxyShape->m_pleaseIgnoreSelection = true; UsdImagingGLRenderParams params; - params.showGuides = proxyShape->displayGuidesPlug().asBool(); - params.showRender = proxyShape->displayRenderGuidesPlug().asBool(); + params.showGuides = proxyShape->drawGuidePurposePlug().asBool(); + params.showRender = proxyShape->drawRenderPurposePlug().asBool(); UsdPrim root = proxyShape->getUsdStage()->GetPseudoRoot(); diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyUsdGeomCamera.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyUsdGeomCamera.cpp index 52b700a4e2..18afad6e0d 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyUsdGeomCamera.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/ProxyUsdGeomCamera.cpp @@ -31,9 +31,10 @@ #include #include "AL/usdmaya/TypeIDs.h" -#include #include +#include + namespace AL { namespace usdmaya { namespace nodes { @@ -81,8 +82,8 @@ UsdGeomCamera ProxyUsdGeomCamera::getCamera() const { // Pull in stage data MFnPluginData fnData(stageObject); - AL::usdmaya::StageData* stageData = static_cast(fnData.data()); + auto* stageData = static_cast(fnData.data()); if (stageData != nullptr) { // Get prim path @@ -514,7 +515,7 @@ MStatus ProxyUsdGeomCamera::initialise() m_path = addStringAttr("path", "p", "", kStorable | kWritable); - m_stage = addDataAttr("stage", "s", AL::usdmaya::StageData::kTypeId, kWritable | kHidden | kConnectable, MFnNumericAttribute::kReset); + m_stage = addDataAttr("stage", "s", MayaUsdStageData::mayaTypeId, kWritable | kHidden | kConnectable, MFnNumericAttribute::kReset); m_time = addTimeAttr("time", "tm", MTime(0.0), kCached | kConnectable | kReadable | kWritable | kHidden | kStorable | kAffectsAppearance); diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/Transform.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/Transform.cpp index 0a3d6c1c14..4b260f817e 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/Transform.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/Transform.cpp @@ -15,11 +15,12 @@ // #include "AL/usdmaya/TypeIDs.h" #include "AL/usdmaya/DebugCodes.h" -#include "AL/usdmaya/StageData.h" #include "AL/usdmaya/nodes/ProxyShape.h" #include "AL/usdmaya/nodes/Transform.h" #include "AL/usdmaya/nodes/TransformationMatrix.h" +#include + #include "maya/MBoundingBox.h" #include "maya/MGlobal.h" #include "maya/MTime.h" @@ -107,7 +108,7 @@ MStatus Transform::initialise() addFrame("USD Prim Information"); m_primPath = addStringAttr("primPath", "pp", kReadable | kWritable | kStorable | kConnectable | kAffectsWorldSpace, true); - m_inStageData = addDataAttr("inStageData", "isd", StageData::kTypeId, kWritable | kStorable | kConnectable | kHidden | kAffectsWorldSpace); + m_inStageData = addDataAttr("inStageData", "isd", MayaUsdStageData::mayaTypeId, kWritable | kStorable | kConnectable | kHidden | kAffectsWorldSpace); addFrame("USD Timing Information"); m_time = addTimeAttr("time", "tm", MTime(0.0), kKeyable | kConnectable | kReadable | kWritable | kStorable | kAffectsWorldSpace); @@ -187,7 +188,7 @@ MStatus Transform::compute(const MPlug& plug, MDataBlock& dataBlock) // This should only be computed if there's no connection, so set it to an empty stage // create new stage data MObject data; - StageData* usdStageData = createData(StageData::kTypeId, data); + auto* usdStageData = createData(MayaUsdStageData::mayaTypeId, data); if(!usdStageData) { return MS::kFailure; @@ -446,7 +447,7 @@ MStatus Transform::validateAndSetValue(const MPlug& plug, const MDataHandle& han if(plug == m_inStageData) { MDataBlock dataBlock = forceCache(*(MDGContext *)&context); - StageData* data = inputDataValue(dataBlock, m_inStageData); + auto* data = inputDataValue(dataBlock, m_inStageData); if (data && data->stage) { MString path = inputStringValue(dataBlock, m_primPath); @@ -472,7 +473,7 @@ MStatus Transform::validateAndSetValue(const MPlug& plug, const MDataHandle& han MString path = handle.asString(); outputStringValue(dataBlock, m_primPath, path); - StageData* data = inputDataValue(dataBlock, m_inStageData); + auto* data = inputDataValue(dataBlock, m_inStageData); if (data && data->stage) { SdfPath primPath; diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/wrapProxyShape.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/wrapProxyShape.cpp index ebaca9ac5b..4ff4f0d0af 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/wrapProxyShape.cpp +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/nodes/wrapProxyShape.cpp @@ -16,9 +16,10 @@ #include "AL/usdmaya/nodes/ProxyShape.h" #include "AL/usdmaya/nodes/Transform.h" #include "AL/maya/utils/MayaHelperMacros.h" -#include "AL/usdmaya/StageData.h" #include "AL/maya/utils/Utils.h" +#include + #include #include #include @@ -140,7 +141,7 @@ namespace { MObject stageObject; status = stageDataPlug.getValue(stageObject); MFnPluginData fnData(stageObject); - auto stageData = static_cast(fnData.data()); + auto* stageData = static_cast(fnData.data()); // Validate stage if (stageData && stageData->stage) diff --git a/plugin/al/lib/AL_USDMaya/CMakeLists.txt b/plugin/al/lib/AL_USDMaya/CMakeLists.txt index 4b3d7e7810..b72840d31f 100644 --- a/plugin/al/lib/AL_USDMaya/CMakeLists.txt +++ b/plugin/al/lib/AL_USDMaya/CMakeLists.txt @@ -11,7 +11,6 @@ list(APPEND AL_usdmaya_headers AL/usdmaya/PluginRegister.h AL/usdmaya/SelectabilityDB.h AL/usdmaya/StageCache.h - AL/usdmaya/StageData.h AL/usdmaya/TransformOperation.h AL/usdmaya/TypeIDs.h AL/usdmaya/ForwardDeclares.h @@ -25,7 +24,6 @@ list(APPEND AL_usdmaya_source AL/usdmaya/Metadata.cpp AL/usdmaya/SelectabilityDB.cpp AL/usdmaya/StageCache.cpp - AL/usdmaya/StageData.cpp AL/usdmaya/TransformOperation.cpp AL/usdmaya/CodeTimings.cpp AL/usdmaya/moduleDeps.cpp @@ -157,8 +155,13 @@ add_library(${LIBRARY_NAME} ${AL_usdmaya_nodes_source} ) +if(IS_MACOSX) + set(_macDef OSMac_) +endif() + target_compile_definitions(${LIBRARY_NAME} PRIVATE + ${_macDef} AL_MAYA_MACROS_EXPORT AL_USDMAYA_EXPORT MFB_PACKAGE_NAME="${LIBRARY_NAME}" @@ -213,6 +216,7 @@ target_link_libraries(${LIBRARY_NAME} ${MAYA_OpenMaya_LIBRARY} ${MAYA_OpenMayaRender_LIBRARY} ${UFE_LIBRARY} + mayaUsd ) if(NEED_BOOST_FILESYSTEM) @@ -227,7 +231,7 @@ install(TARGETS ${LIBRARY_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/lib ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib OPTIONAL) endif() @@ -246,8 +250,13 @@ add_library(${PYTHON_LIBRARY_NAME} AL/usdmaya/fileio/translators/wrapTranslatorContext.cpp ) +if(IS_MACOSX) + set(_macDef OSMac_) +endif() + target_compile_definitions(${PYTHON_LIBRARY_NAME} PRIVATE + ${_macDef} MFB_PACKAGE_NAME=${LIBRARY_NAME} MFB_ALT_PACKAGE_NAME=${LIBRARY_NAME} MFB_PACKAGE_MODULE=usdmaya @@ -256,7 +265,7 @@ set_target_properties(${PYTHON_LIBRARY_NAME} PROPERTIES PREFIX "" ) -if(MSVC) +if(IS_WINDOWS) set_target_properties(${PYTHON_LIBRARY_NAME} PROPERTIES SUFFIX ".pyd" diff --git a/plugin/al/mayatest/AL/maya/test/CMakeLists.txt b/plugin/al/mayatest/AL/maya/test/CMakeLists.txt index 6f3378252c..d9fe6a6c26 100755 --- a/plugin/al/mayatest/AL/maya/test/CMakeLists.txt +++ b/plugin/al/mayatest/AL/maya/test/CMakeLists.txt @@ -44,8 +44,8 @@ target_link_libraries(${MAYA_TEST_LIBRARY_NAME} target_include_directories(${MAYA_TEST_LIBRARY_NAME} PRIVATE ${MAYATEST_INCLUDE_LOCATION} - ${MAYA_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS} + ${MAYA_INCLUDE_DIRS} ${USD_INCLUDE_DIR} ) diff --git a/plugin/al/mayautils/AL/maya/CMakeLists.txt b/plugin/al/mayautils/AL/maya/CMakeLists.txt index 804ab77cba..2fe0f9d74c 100755 --- a/plugin/al/mayautils/AL/maya/CMakeLists.txt +++ b/plugin/al/mayautils/AL/maya/CMakeLists.txt @@ -47,8 +47,13 @@ add_library(${MAYAUTILS_LIBRARY_NAME} ${maya_source} ) +if(IS_MACOSX) + set(_macDef OSMac_) +endif() + target_compile_definitions(${MAYAUTILS_LIBRARY_NAME} PRIVATE + ${_macDef} AL_MAYA_EVENTS_EXPORT AL_MAYA_UTILS_EXPORT AL_MAYA_MACROS_EXPORT @@ -88,7 +93,7 @@ install(TARGETS ${MAYAUTILS_LIBRARY_NAME} RUNTIME DESTINATION ${MAYAUTILS_LIBRARY_LOCATION} ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${MAYAUTILS_LIBRARY_LOCATION} OPTIONAL) endif() diff --git a/plugin/al/mayautils/AL/maya/utils/NodeHelper.cpp b/plugin/al/mayautils/AL/maya/utils/NodeHelper.cpp index dc88ab7715..c94220a39b 100644 --- a/plugin/al/mayautils/AL/maya/utils/NodeHelper.cpp +++ b/plugin/al/mayautils/AL/maya/utils/NodeHelper.cpp @@ -194,7 +194,7 @@ MObject NodeHelper::addStringAttr(const char* longName, const char* shortName, u } //---------------------------------------------------------------------------------------------------------------------- -MObject NodeHelper::addStringAttr(const char* longName, const char* shortName, const char* defaultValue, uint32_t flags, bool forceShow) +void NodeHelper::inheritStringAttr(const char* longName, uint32_t flags, bool forceShow) { if(m_internal) { @@ -205,6 +205,11 @@ MObject NodeHelper::addStringAttr(const char* longName, const char* shortName, c frame.m_attributeTypes.push_back(Frame::kNormal); } } +} +//---------------------------------------------------------------------------------------------------------------------- +MObject NodeHelper::addStringAttr(const char* longName, const char* shortName, const char* defaultValue, uint32_t flags, bool forceShow) +{ + inheritStringAttr(longName, flags, forceShow); MFnTypedAttribute fn; MFnStringData stringData; @@ -217,7 +222,7 @@ MObject NodeHelper::addStringAttr(const char* longName, const char* shortName, c } //---------------------------------------------------------------------------------------------------------------------- -MObject NodeHelper::addFilePathAttr(const char* longName, const char* shortName, uint32_t flags, FileMode fileMode, const char* fileFilter) +void NodeHelper::inheritFilePathAttr(const char* longName, uint32_t flags, FileMode fileMode, const char* fileFilter) { if(m_internal) { @@ -229,6 +234,12 @@ MObject NodeHelper::addFilePathAttr(const char* longName, const char* shortName, frame.m_attributeTypes.push_back((Frame::AttributeUiType)fileMode); } } +} + +//---------------------------------------------------------------------------------------------------------------------- +MObject NodeHelper::addFilePathAttr(const char* longName, const char* shortName, uint32_t flags, FileMode fileMode, const char* fileFilter) +{ + inheritFilePathAttr(longName, flags, fileMode, fileFilter); MFnTypedAttribute fn; MObject attribute = fn.create(longName, shortName, MFnData::kString); MStatus status = applyAttributeFlags(fn, flags); @@ -278,7 +289,7 @@ MObject NodeHelper::addInt16Attr(const char* longName, const char* shortName, in } //---------------------------------------------------------------------------------------------------------------------- -MObject NodeHelper::addInt32Attr(const char* longName, const char* shortName, int32_t defaultValue, uint32_t flags) +void NodeHelper::inheritInt32Attr(const char* longName, uint32_t flags) { if(m_internal) { @@ -289,6 +300,13 @@ MObject NodeHelper::addInt32Attr(const char* longName, const char* shortName, in frame.m_attributeTypes.push_back(Frame::kNormal); } } +} + +//---------------------------------------------------------------------------------------------------------------------- +MObject NodeHelper::addInt32Attr(const char* longName, const char* shortName, int32_t defaultValue, uint32_t flags) +{ + inheritInt32Attr(longName, flags); + MFnNumericAttribute fn; MObject attribute = fn.create(longName, shortName, MFnNumericData::kInt, defaultValue); MStatus status = applyAttributeFlags(fn, flags); @@ -338,7 +356,7 @@ MObject NodeHelper::addFloatAttr(const char* longName, const char* shortName, fl } //---------------------------------------------------------------------------------------------------------------------- -MObject NodeHelper::addTimeAttr(const char* longName, const char* shortName, const MTime& defaultValue, uint32_t flags) +void NodeHelper::inheritTimeAttr(const char* longName, uint32_t flags) { if(m_internal) { @@ -349,6 +367,13 @@ MObject NodeHelper::addTimeAttr(const char* longName, const char* shortName, con frame.m_attributeTypes.push_back(Frame::kNormal); } } +} + +//---------------------------------------------------------------------------------------------------------------------- +MObject NodeHelper::addTimeAttr(const char* longName, const char* shortName, const MTime& defaultValue, uint32_t flags) +{ + inheritTimeAttr(longName, flags); + MFnUnitAttribute fn; MObject attribute = fn.create(longName, shortName, defaultValue); MStatus status = applyAttributeFlags(fn, flags); @@ -457,7 +482,7 @@ MObject NodeHelper::addDoubleAttr(const char* longName, const char* shortName, d } //---------------------------------------------------------------------------------------------------------------------- -MObject NodeHelper::addBoolAttr(const char* longName, const char* shortName, bool defaultValue, uint32_t flags) +void NodeHelper::inheritBoolAttr(const char* longName, uint32_t flags) { if(m_internal) { @@ -468,6 +493,13 @@ MObject NodeHelper::addBoolAttr(const char* longName, const char* shortName, boo frame.m_attributeTypes.push_back(Frame::kNormal); } } +} + +//---------------------------------------------------------------------------------------------------------------------- +MObject NodeHelper::addBoolAttr(const char* longName, const char* shortName, bool defaultValue, uint32_t flags) +{ + inheritBoolAttr(longName, flags); + MFnNumericAttribute fn; MObject attribute = fn.create(longName, shortName, MFnNumericData::kBoolean, defaultValue); MStatus status = applyAttributeFlags(fn, flags); diff --git a/plugin/al/mayautils/AL/maya/utils/NodeHelper.h b/plugin/al/mayautils/AL/maya/utils/NodeHelper.h index fa66b57b5d..4c16ecc0ff 100644 --- a/plugin/al/mayautils/AL/maya/utils/NodeHelper.h +++ b/plugin/al/mayautils/AL/maya/utils/NodeHelper.h @@ -651,6 +651,13 @@ class NodeHelper AL_MAYA_UTILS_PUBLIC static MObject addStringAttr(const char* longName, const char* shortName, uint32_t flags, bool forceShow = false); + /// \brief inherit in this node type a string attribute from a base node type. + /// \param longName long name for the attribute + /// \param flags a bitfield containing a mask of the AttributeFlags enumeration. Describes if the attribute is an input/output/etc + /// \param forceShow force attribute to be shown. Used in case attribute is not writable but needs to be shown i.e. read-only. + AL_MAYA_UTILS_PUBLIC + static void inheritStringAttr(const char* longName, uint32_t flags, bool forceShow = false); + /// \brief add a new string attribute to this node type. /// \param longName long name for the attribute /// \param shortName short name for the attribute @@ -672,6 +679,15 @@ class NodeHelper AL_MAYA_UTILS_PUBLIC static MObject addFilePathAttr(const char* longName, const char* shortName, uint32_t flags, FileMode fileMode, const char* fileFilter = ""); + /// \brief inherit in this node type a file path attribute from a base node type. + /// \param longName long name for the attribute + /// \param flags a bitfield containing a mask of the AttributeFlags enumeration. Describes if the attribute is an input/output/etc + /// \param fileMode an enum that determines whether the GUI should display a file open dialog, file save, or directory dialog. + /// \param fileFilter a file filter of the form: + /// "USD Files (*.usd*) (*.usd*);;Alembic Files (*.abc) (*.abc);;All files (*.*) (*.*)" + AL_MAYA_UTILS_PUBLIC + static void inheritFilePathAttr(const char* longName, uint32_t flags, FileMode fileMode, const char* fileFilter = ""); + /// \brief add a new integer attribute to this node type. /// \param longName long name for the attribute /// \param shortName short name for the attribute @@ -699,6 +715,12 @@ class NodeHelper AL_MAYA_UTILS_PUBLIC static MObject addInt32Attr(const char* longName, const char* shortName, int32_t defaultValue, uint32_t flags); + /// \brief inherit in this node type an integer attribute from a base node type. + /// \param longName long name for the attribute + /// \param flags a bitfield containing a mask of the AttributeFlags enumeration. Describes if the attribute is an input/output/etc + AL_MAYA_UTILS_PUBLIC + static void inheritInt32Attr(const char* longName, uint32_t flags); + /// \brief add a new integer attribute to this node type. /// \param longName long name for the attribute /// \param shortName short name for the attribute @@ -735,6 +757,12 @@ class NodeHelper AL_MAYA_UTILS_PUBLIC static MObject addTimeAttr(const char* longName, const char* shortName, const MTime& defaultValue, uint32_t flags); + /// \brief inherit in this node type a time attribute from a base node type. + /// \param longName long name for the attribute + /// \param flags a bitfield containing a mask of the AttributeFlags enumeration. Describes if the attribute is an input/output/etc + AL_MAYA_UTILS_PUBLIC + static void inheritTimeAttr(const char* longName, uint32_t flags); + /// \brief add a new time attribute to this node type. /// \param longName long name for the attribute /// \param shortName short name for the attribute @@ -763,6 +791,12 @@ class NodeHelper AL_MAYA_UTILS_PUBLIC static MObject addBoolAttr(const char* longName, const char* shortName, bool defaultValue, uint32_t flags); + /// \brief inherit in this node type a boolean attribute from a base node type. + /// \param longName long name for the attribute + /// \param flags a bitfield containing a mask of the AttributeFlags enumeration. Describes if the attribute is an input/output/etc + AL_MAYA_UTILS_PUBLIC + static void inheritBoolAttr(const char* longName, uint32_t flags); + /// \brief add a new float3 attribute to this node type. /// \param longName long name for the attribute /// \param shortName short name for the attribute diff --git a/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt b/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt index 60e3e8fc03..43e6cc4bf4 100644 --- a/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt +++ b/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt @@ -62,6 +62,6 @@ install(TARGETS RUNTIME DESTINATION ${INSTALL_DIR_SUFFIX}/plugin ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${INSTALL_DIR_SUFFIX}/plugin OPTIONAL) endif() diff --git a/plugin/al/plugin/AL_USDMayaTestPlugin/test_translators_Translator.cpp b/plugin/al/plugin/AL_USDMayaTestPlugin/test_translators_Translator.cpp index 69fc1122a3..bf3fe239c1 100644 --- a/plugin/al/plugin/AL_USDMayaTestPlugin/test_translators_Translator.cpp +++ b/plugin/al/plugin/AL_USDMayaTestPlugin/test_translators_Translator.cpp @@ -18,9 +18,10 @@ #include "AL/usdmaya/fileio/translators/TranslatorBase.h" #include "AL/usdmaya/fileio/translators/TranslatorContext.h" #include "AL/usdmaya/fileio/translators/TranslatorTestType.h" -#include "AL/usdmaya/StageData.h" #include "AL/usdmaya/nodes/ProxyShape.h" +#include + #include "maya/MDagModifier.h" #include "pxr/base/tf/refPtr.h" diff --git a/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt b/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt index 993e62dfd4..14a17921d5 100644 --- a/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt +++ b/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt @@ -20,6 +20,7 @@ target_include_directories(testMayaSchemas target_link_libraries(testMayaSchemas AL_USDMayaSchemas + mayaUsd ${GTEST_LIBRARIES} sdf tf diff --git a/plugin/al/translators/CMakeLists.txt b/plugin/al/translators/CMakeLists.txt index bef6881145..56be512e5b 100644 --- a/plugin/al/translators/CMakeLists.txt +++ b/plugin/al/translators/CMakeLists.txt @@ -80,7 +80,7 @@ install(TARGETS ${TRANSLATORS_PACKAGE} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/lib ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib OPTIONAL) endif() diff --git a/plugin/al/translators/Mesh.cpp b/plugin/al/translators/Mesh.cpp index 31d5a15954..34754e3951 100644 --- a/plugin/al/translators/Mesh.cpp +++ b/plugin/al/translators/Mesh.cpp @@ -28,6 +28,7 @@ #include "AL/usdmaya/utils/DiffPrimVar.h" #include "AL/usdmaya/utils/MeshUtils.h" +#include "AL/usdmaya/utils/Utils.h" #include "AL/usdmaya/DebugCodes.h" #include "AL/usdmaya/fileio/translators/DagNodeTranslator.h" @@ -217,7 +218,7 @@ MStatus Mesh::preTearDown(UsdPrim& prim) * This crash and error seems to be happening mainly when switching out a variant that contains a Mesh, and that Mesh has been * force translated into Maya. */ - TfNotice::Block block; + AL::usdmaya::utils::BlockNotifications blockNow; //don't use TfNotice::Block, render delegates need to know about the change // Write the overrides back to the path it was imported at MObjectHandle obj; context()->getMObject(prim, obj, MFn::kInvalid); diff --git a/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt b/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt index 64808d967e..58743f15da 100644 --- a/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt +++ b/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt @@ -65,7 +65,7 @@ install(TARGETS ${PXR_TRANSLATORS_PACKAGE} RUNTIME DESTINATION ${INSTALL_DIR_SUFFIX}/plugin ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${INSTALL_DIR_SUFFIX}/plugin OPTIONAL) endif() diff --git a/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt b/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt index 03acec8157..b4d164d692 100755 --- a/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt +++ b/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt @@ -37,8 +37,13 @@ add_library(${USDMAYA_UTILS_LIBRARY_NAME} ${usdmaya_utils_source} ) +if(IS_MACOSX) + set(_macDef OSMac_) +endif() + target_compile_definitions(${USDMAYA_UTILS_LIBRARY_NAME} PRIVATE + ${_macDef} AL_USDMAYA_UTILS_EXPORT ) @@ -79,6 +84,6 @@ install(TARGETS ${USDMAYA_UTILS_LIBRARY_NAME} RUNTIME DESTINATION ${MAYA_UTILS_LIBRARY_LOCATION} ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${MAYA_UTILS_LIBRARY_LOCATION} OPTIONAL) endif() diff --git a/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.cpp b/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.cpp index af0669e940..c9434a0091 100644 --- a/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.cpp +++ b/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.cpp @@ -24,10 +24,31 @@ #include "maya/MMatrix.h" #include "maya/MVector.h" +#include + +namespace { + std::atomic _blockingCount; +} + namespace AL { namespace usdmaya { namespace utils { +//---------------------------------------------------------------------------------------------------------------------- +BlockNotifications::BlockNotifications() { + _blockingCount++; +} + +//---------------------------------------------------------------------------------------------------------------------- +BlockNotifications::~BlockNotifications() { + _blockingCount--; +} + +//---------------------------------------------------------------------------------------------------------------------- +bool BlockNotifications::isBlockingNotifications() { + return _blockingCount.load() > 0; +} + //---------------------------------------------------------------------------------------------------------------------- void matrixToSRT(const GfMatrix4d& value, double S[3], MEulerRotation& R, double T[3]) { diff --git a/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.h b/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.h index 1a15867fb1..073b17232c 100644 --- a/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.h +++ b/plugin/al/usdmayautils/AL/usdmaya/utils/Utils.h @@ -29,6 +29,21 @@ namespace AL { namespace usdmaya { namespace utils { +//---------------------------------------------------------------------------------------------------------------------- +/// \brief Helper class used to stop proxy shape from processing any USD notifications (this affects all threads) +/// \ingroup usdmaya +class BlockNotifications { +public: + AL_USDMAYA_UTILS_PUBLIC + BlockNotifications(); + + AL_USDMAYA_UTILS_PUBLIC + ~BlockNotifications(); + + AL_USDMAYA_UTILS_PUBLIC + static bool isBlockingNotifications(); +}; + //---------------------------------------------------------------------------------------------------------------------- /// \brief Returns the dagPath result of mapping UsdPrim -> Maya Object. /// proxyShapeNode is an optional argument, if it is passed and the passed in mayaObject's path couldn't be determined, diff --git a/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt b/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt index e83427f29a..1c9046300e 100755 --- a/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt +++ b/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt @@ -67,6 +67,6 @@ install(TARGETS ${USDUTILS_LIBRARY_NAME} RUNTIME DESTINATION ${USDUTILS_LIBRARY_LOCATION} ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${USDUTILS_LIBRARY_LOCATION} OPTIONAL) endif() diff --git a/plugin/al/utils/AL/CMakeLists.txt b/plugin/al/utils/AL/CMakeLists.txt index d51a0690e5..be18cb82b2 100755 --- a/plugin/al/utils/AL/CMakeLists.txt +++ b/plugin/al/utils/AL/CMakeLists.txt @@ -44,6 +44,6 @@ install(TARGETS ${EVENTS_LIBRARY_NAME} RUNTIME DESTINATION ${EVENTS_LIBRARY_LOCATION} ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${EVENTS_LIBRARY_LOCATION} OPTIONAL) endif() From 571c61f0feb2075e9a7e8a885e8d0d3b54bd7c41 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Fri, 25 Oct 2019 16:27:57 -0400 Subject: [PATCH 03/15] Add VP2RenderDelegate and enable it for both plugins --- lib/CMakeLists.txt | 58 + lib/nodes/proxyShapePlugin.cpp | 32 +- lib/render/vp2RenderDelegate/debugCodes.cpp | 29 + lib/render/vp2RenderDelegate/debugCodes.h | 32 + lib/render/vp2RenderDelegate/draw_item.cpp | 73 + lib/render/vp2RenderDelegate/draw_item.h | 105 ++ lib/render/vp2RenderDelegate/instancer.cpp | 238 +++ lib/render/vp2RenderDelegate/instancer.h | 65 + lib/render/vp2RenderDelegate/material.cpp | 803 ++++++++++ lib/render/vp2RenderDelegate/material.h | 111 ++ lib/render/vp2RenderDelegate/mesh.cpp | 1358 +++++++++++++++++ lib/render/vp2RenderDelegate/mesh.h | 114 ++ .../vp2RenderDelegate/proxyRenderDelegate.cpp | 563 +++++++ .../vp2RenderDelegate/proxyRenderDelegate.h | 181 +++ .../vp2RenderDelegate/render_delegate.cpp | 661 ++++++++ .../vp2RenderDelegate/render_delegate.h | 133 ++ lib/render/vp2RenderDelegate/render_param.cpp | 36 + lib/render/vp2RenderDelegate/render_param.h | 69 + lib/render/vp2RenderDelegate/render_pass.h | 51 + .../vp2RenderDelegate/resource_registry.h | 59 + lib/render/vp2RenderDelegate/sampler.cpp | 149 ++ lib/render/vp2RenderDelegate/sampler.h | 160 ++ lib/render/vp2RenderDelegate/task_commit.h | 80 + .../vp2ShaderFragments/FallbackCPVShader.xml | 87 ++ .../vp2ShaderFragments/FallbackShader.xml | 88 ++ .../vp2ShaderFragments/UsdPreviewSurface.xml | 136 ++ .../UsdPrimvarReader_float.xml | 46 + .../UsdPrimvarReader_float2.xml | 46 + .../UsdPrimvarReader_float3.xml | 46 + .../UsdPrimvarReader_float4.xml | 46 + .../vp2ShaderFragments/UsdUVTexture.xml | 118 ++ .../vp2ShaderFragments/float4ToFloat3.xml | 58 + .../vp2ShaderFragments/float4ToFloat4.xml | 58 + .../vp2ShaderFragments/float4ToFloatW.xml | 91 ++ .../vp2ShaderFragments/float4ToFloatX.xml | 91 ++ .../vp2ShaderFragments/float4ToFloatY.xml | 91 ++ .../vp2ShaderFragments/float4ToFloatZ.xml | 91 ++ .../lightingContributions.xml | 109 ++ .../opacityToTransparency.xml | 61 + lib/render/vp2ShaderFragments/plugInfo.json | 12 + .../scaledDiffusePassThrough.xml | 57 + .../scaledSpecularPassThrough.xml | 57 + .../vp2ShaderFragments/shaderFragments.cpp | 232 +++ .../vp2ShaderFragments/shaderFragments.h | 41 + .../usdPreviewSurfaceCombiner.xml | 259 ++++ .../usdPreviewSurfaceLighting.xml | 687 +++++++++ .../AL_USDMaya/AL/usdmaya/PluginRegister.h | 15 +- plugin/pxr/maya/plugin/pxrUsd/plugin.cpp | 80 +- 48 files changed, 7824 insertions(+), 39 deletions(-) create mode 100644 lib/render/vp2RenderDelegate/debugCodes.cpp create mode 100644 lib/render/vp2RenderDelegate/debugCodes.h create mode 100644 lib/render/vp2RenderDelegate/draw_item.cpp create mode 100644 lib/render/vp2RenderDelegate/draw_item.h create mode 100644 lib/render/vp2RenderDelegate/instancer.cpp create mode 100644 lib/render/vp2RenderDelegate/instancer.h create mode 100644 lib/render/vp2RenderDelegate/material.cpp create mode 100644 lib/render/vp2RenderDelegate/material.h create mode 100644 lib/render/vp2RenderDelegate/mesh.cpp create mode 100644 lib/render/vp2RenderDelegate/mesh.h create mode 100644 lib/render/vp2RenderDelegate/proxyRenderDelegate.cpp create mode 100644 lib/render/vp2RenderDelegate/proxyRenderDelegate.h create mode 100644 lib/render/vp2RenderDelegate/render_delegate.cpp create mode 100644 lib/render/vp2RenderDelegate/render_delegate.h create mode 100644 lib/render/vp2RenderDelegate/render_param.cpp create mode 100644 lib/render/vp2RenderDelegate/render_param.h create mode 100644 lib/render/vp2RenderDelegate/render_pass.h create mode 100644 lib/render/vp2RenderDelegate/resource_registry.h create mode 100644 lib/render/vp2RenderDelegate/sampler.cpp create mode 100644 lib/render/vp2RenderDelegate/sampler.h create mode 100644 lib/render/vp2RenderDelegate/task_commit.h create mode 100644 lib/render/vp2ShaderFragments/FallbackCPVShader.xml create mode 100644 lib/render/vp2ShaderFragments/FallbackShader.xml create mode 100644 lib/render/vp2ShaderFragments/UsdPreviewSurface.xml create mode 100644 lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml create mode 100644 lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml create mode 100644 lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml create mode 100644 lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml create mode 100644 lib/render/vp2ShaderFragments/UsdUVTexture.xml create mode 100644 lib/render/vp2ShaderFragments/float4ToFloat3.xml create mode 100644 lib/render/vp2ShaderFragments/float4ToFloat4.xml create mode 100644 lib/render/vp2ShaderFragments/float4ToFloatW.xml create mode 100644 lib/render/vp2ShaderFragments/float4ToFloatX.xml create mode 100644 lib/render/vp2ShaderFragments/float4ToFloatY.xml create mode 100644 lib/render/vp2ShaderFragments/float4ToFloatZ.xml create mode 100644 lib/render/vp2ShaderFragments/lightingContributions.xml create mode 100644 lib/render/vp2ShaderFragments/opacityToTransparency.xml create mode 100644 lib/render/vp2ShaderFragments/plugInfo.json create mode 100644 lib/render/vp2ShaderFragments/scaledDiffusePassThrough.xml create mode 100644 lib/render/vp2ShaderFragments/scaledSpecularPassThrough.xml create mode 100644 lib/render/vp2ShaderFragments/shaderFragments.cpp create mode 100644 lib/render/vp2ShaderFragments/shaderFragments.h create mode 100644 lib/render/vp2ShaderFragments/usdPreviewSurfaceCombiner.xml create mode 100644 lib/render/vp2ShaderFragments/usdPreviewSurfaceLighting.xml diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 54d36b56da..1b1957e6fc 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -58,6 +58,46 @@ list(APPEND mayaUsd_src listeners/stageNoticeListener.cpp listeners/notice.cpp listeners/proxyShapeNotice.cpp + # + render/vp2RenderDelegate/debugCodes.cpp + render/vp2RenderDelegate/render_param.cpp + render/vp2RenderDelegate/instancer.cpp + render/vp2RenderDelegate/draw_item.cpp + render/vp2RenderDelegate/material.cpp + render/vp2RenderDelegate/mesh.cpp + render/vp2RenderDelegate/proxyRenderDelegate.cpp + render/vp2RenderDelegate/render_delegate.cpp + render/vp2RenderDelegate/sampler.cpp + # + render/vp2ShaderFragments/shaderFragments.cpp + # +) + +list(APPEND mayaUsdShaderFragments_xmls + # Copied from pxrUsdPreviewSurface with change to support multiple lights + render/vp2ShaderFragments/float4ToFloatX.xml + render/vp2ShaderFragments/float4ToFloatY.xml + render/vp2ShaderFragments/float4ToFloatZ.xml + render/vp2ShaderFragments/float4ToFloatW.xml + render/vp2ShaderFragments/lightingContributions.xml + render/vp2ShaderFragments/opacityToTransparency.xml + render/vp2ShaderFragments/scaledDiffusePassThrough.xml + render/vp2ShaderFragments/scaledSpecularPassThrough.xml + render/vp2ShaderFragments/usdPreviewSurfaceCombiner.xml + render/vp2ShaderFragments/usdPreviewSurfaceLighting.xml + render/vp2ShaderFragments/UsdPreviewSurface.xml + # New fragments + render/vp2ShaderFragments/FallbackCPVShader.xml + render/vp2ShaderFragments/FallbackShader.xml + render/vp2ShaderFragments/float4ToFloat3.xml + render/vp2ShaderFragments/float4ToFloat4.xml + render/vp2ShaderFragments/UsdPrimvarReader_float.xml + render/vp2ShaderFragments/UsdPrimvarReader_float2.xml + render/vp2ShaderFragments/UsdPrimvarReader_float3.xml + render/vp2ShaderFragments/UsdPrimvarReader_float4.xml + render/vp2ShaderFragments/UsdUVTexture.xml + # USD plug info + render/vp2ShaderFragments/plugInfo.json ) list(APPEND mayaUsdBase_headers @@ -87,6 +127,10 @@ list(APPEND mayaUsdListeners_headers listeners/proxyShapeNotice.h ) +list(APPEND mayaUsdVP2RenderDelegate_headers + render/vp2RenderDelegate/proxyRenderDelegate.h +) + add_library(${LIBRARY_NAME} SHARED ${mayaUsd_src} ) @@ -182,6 +226,7 @@ mayaUsd_promoteHeaderList(${mayaUsdBase_headers}) mayaUsd_promoteHeaderList(${mayaUsdUtils_headers}) mayaUsd_promoteHeaderList(${mayaUsdNodes_headers}) mayaUsd_promoteHeaderList(${mayaUsdListeners_headers}) +mayaUsd_promoteHeaderList(${mayaUsdVP2RenderDelegate_headers}) # install public headers install(FILES ${CMAKE_BINARY_DIR}/include/mayaUsd/mayaUsd.h @@ -204,6 +249,19 @@ install(FILES ${mayaUsdListeners_headers} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/listeners/ ) +install(FILES ${mayaUsdVP2RenderDelegate_headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/render/vp2RenderDelegate +) + +install(FILES ${mayaUsdShaderFragments_xmls} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/usd/mayaUsd_ShaderFragments/resources +) + +#install top level plugInfo.json that includes the configured plugInfo.json +install(CODE + "file(WRITE \"${CMAKE_INSTALL_PREFIX}/lib/usd/plugInfo.json\" \"{\n \\\"Includes\\\": [ \\\"*/resources/\\\" ]\n}\")" +) + if(IS_MACOSX) set(_macDef OSMac_) endif() diff --git a/lib/nodes/proxyShapePlugin.cpp b/lib/nodes/proxyShapePlugin.cpp index d7f00384c0..c3ef612f1b 100644 --- a/lib/nodes/proxyShapePlugin.cpp +++ b/lib/nodes/proxyShapePlugin.cpp @@ -15,6 +15,9 @@ // #include "proxyShapePlugin.h" +#include "../render/vp2RenderDelegate/proxyRenderDelegate.h" +#include "../render/vp2ShaderFragments/shaderFragments.h" + #include "stageData.h" #include "proxyShapeBase.h" @@ -33,6 +36,11 @@ int _registrationCount = 0; // Name of the plugin registering the proxy shape base class. MString _registrantPluginName; + +bool _useVP2RenderDelegate = false; + +TF_DEFINE_ENV_SETTING(VP2_RENDER_DELEGATE_PROXY, false, + "Switch proxy shape rendering to VP2 render delegate."); } PXR_NAMESPACE_OPEN_SCOPE @@ -48,6 +56,8 @@ MayaUsdProxyShapePlugin::initialize(MFnPlugin& plugin) _registrantPluginName = plugin.name(); + _useVP2RenderDelegate = TfGetEnvSetting(VP2_RENDER_DELEGATE_PROXY); + MStatus status; // Proxy shape initialization. @@ -66,6 +76,15 @@ MayaUsdProxyShapePlugin::initialize(MFnPlugin& plugin) getProxyShapeClassification()); CHECK_MSTATUS(status); + status = MHWRender::MDrawRegistry::registerSubSceneOverrideCreator( + ProxyRenderDelegate::drawDbClassification, + _RegistrantId, + ProxyRenderDelegate::Creator); + CHECK_MSTATUS(status); + + status = HdVP2ShaderFragments::registerFragments(); + CHECK_MSTATUS(status); + return status; } @@ -88,7 +107,13 @@ MayaUsdProxyShapePlugin::finalize(MFnPlugin& plugin) return MS::kSuccess; } - MStatus status; + MStatus status = HdVP2ShaderFragments::deregisterFragments(); + CHECK_MSTATUS(status); + + status = MHWRender::MDrawRegistry::deregisterSubSceneOverrideCreator( + ProxyRenderDelegate::drawDbClassification, + _RegistrantId); + CHECK_MSTATUS(status); status = plugin.deregisterNode(MayaUsdProxyShapeBase::typeId); CHECK_MSTATUS(status); @@ -101,12 +126,13 @@ MayaUsdProxyShapePlugin::finalize(MFnPlugin& plugin) const MString* MayaUsdProxyShapePlugin::getProxyShapeClassification() { - return nullptr; + return _useVP2RenderDelegate ? &ProxyRenderDelegate::drawDbClassification : + nullptr; } bool MayaUsdProxyShapePlugin::useVP2_NativeUSD_Rendering() { - return false; + return _useVP2RenderDelegate; } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/debugCodes.cpp b/lib/render/vp2RenderDelegate/debugCodes.cpp new file mode 100644 index 0000000000..f0b5ca52db --- /dev/null +++ b/lib/render/vp2RenderDelegate/debugCodes.cpp @@ -0,0 +1,29 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "debugCodes.h" + +#include "pxr/base/tf/registryManager.h" + +PXR_NAMESPACE_OPEN_SCOPE + +TF_REGISTRY_FUNCTION(TfDebug) +{ + TF_DEBUG_ENVIRONMENT_SYMBOL(HDVP2_DEBUG_MATERIAL, "Debug material"); + TF_DEBUG_ENVIRONMENT_SYMBOL(HDVP2_DEBUG_MESH, "Debug mesh"); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/debugCodes.h b/lib/render/vp2RenderDelegate/debugCodes.h new file mode 100644 index 0000000000..77aa94e580 --- /dev/null +++ b/lib/render/vp2RenderDelegate/debugCodes.h @@ -0,0 +1,32 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_DEBUGCODES_H +#define HD_VP2_DEBUGCODES_H + +#include "pxr/pxr.h" +#include "pxr/base/tf/debug.h" + +PXR_NAMESPACE_OPEN_SCOPE + +TF_DEBUG_CODES( + HDVP2_DEBUG_MATERIAL, + HDVP2_DEBUG_MESH +); + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_VP2_DEBUGCODES_H diff --git a/lib/render/vp2RenderDelegate/draw_item.cpp b/lib/render/vp2RenderDelegate/draw_item.cpp new file mode 100644 index 0000000000..11cf334952 --- /dev/null +++ b/lib/render/vp2RenderDelegate/draw_item.cpp @@ -0,0 +1,73 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "pxr/imaging/hd/mesh.h" + +#include "draw_item.h" +#include "render_delegate.h" + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief Constructor. + +Data holder for its corresponding render item to facilitate parallelized evaluation. + +Position buffer is held by HdVP2Mesh and shared among its render items. +*/ +HdVP2DrawItem::HdVP2DrawItem( + HdVP2RenderDelegate* delegate, + const HdRprimSharedData* sharedData, + const HdMeshReprDesc& desc) +: HdDrawItem(sharedData) +, _delegate(delegate) +, _reprDesc(desc) +{ + // In the case of instancing, the ID of a proto has an attribute at the end, + // we keep this info in _renderItemName so if needed we can extract proto ID + // and use it to figure out Rprim path for each instance. For example: + // + // "/Proxy/TreePatch/Tree_1.proto_leaves_id0" + // + _renderItemName = GetRprimID().GetText(); + _renderItemName += TfStringPrintf("/DrawItem_%p", this).c_str(); + + _mesh._indexBuffer.reset( + new MHWRender::MIndexBuffer(MHWRender::MGeometry::kUnsignedInt32)); + + if (desc.geomStyle == HdMeshGeomStyleHull) { + const MHWRender::MVertexBufferDescriptor desc("", + MHWRender::MGeometry::kNormal, MHWRender::MGeometry::kFloat, 3); + _mesh._normalsBuffer.reset(new MHWRender::MVertexBuffer(desc)); + } +} + +//! \brief Destructor. +HdVP2DrawItem::~HdVP2DrawItem() { + if (_delegate) { + auto* const param = static_cast(_delegate->GetRenderParam()); + MSubSceneContainer* subSceneContainer = param ? param->GetContainer() : nullptr; + if(subSceneContainer) { + subSceneContainer->remove(GetRenderItemName()); + } + } +} + +//! \brief Get access to render item data. +HdVP2DrawItem::RenderItemData& HdVP2DrawItem::GetRenderItemData() { + return _mesh; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/draw_item.h b/lib/render/vp2RenderDelegate/draw_item.h new file mode 100644 index 0000000000..c58ec57858 --- /dev/null +++ b/lib/render/vp2RenderDelegate/draw_item.h @@ -0,0 +1,105 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_DRAW_ITEM +#define HD_VP2_DRAW_ITEM + +#include "pxr/pxr.h" +#include "pxr/base/gf/vec3f.h" +#include "pxr/base/vt/array.h" +#include "pxr/imaging/hd/changeTracker.h" +#include "pxr/imaging/hd/drawItem.h" +#include "pxr/usd/usd/timeCode.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +struct HdMeshReprDesc; +class HdVP2RenderDelegate; + +/*! \brief Draw Item holds information necessary for accessing and updating VP2 render items + \class HdVP2DrawItem +*/ +class HdVP2DrawItem final : public HdDrawItem +{ +public: + //! Helper struct providing storage for per frame cache data + struct CachedData { + MBoundingBox _boundingBox; //!< Bounding box cache + VtArray _normals; //!< Normals cache + }; + + //! A primvar vertex buffer map indexed by primvar name. + using PrimvarBufferMap = std::unordered_map< + TfToken, + std::unique_ptr, + TfToken::HashFunctor + >; + + //! Helper struct providing storage for render item data + struct RenderItemData { + //! Render item color buffer - use when updating data + std::unique_ptr _colorBuffer; + //! Render item normals buffer - use when updating data + std::unique_ptr _normalsBuffer; + //! Render item primvar buffers - use when updating data + PrimvarBufferMap _primvarBuffers; + //! Render item index buffer - use when updating data + std::unique_ptr _indexBuffer; + + //! Number of instances currently allocated for render item + unsigned int _instanceCount{ 0 }; + + //! Per frame cache + std::map _cache; + }; + +public: + HdVP2DrawItem(HdVP2RenderDelegate* delegate, const HdRprimSharedData* sharedData, const HdMeshReprDesc& desc); + + ~HdVP2DrawItem(); + + RenderItemData& GetRenderItemData(); + + /*! \brief Get render item name + */ + const MString& GetRenderItemName() const { return _renderItemName; } + + /*! \brief Whether the draw item is enabled. + */ + bool IsEnabled() const { return _enabled; } + + /*! \brief Enable or disable the draw item. + */ + void Enable(bool v) { _enabled = v; } + + /*! \brief Get the repr desc for which the draw item was created. + */ + const HdMeshReprDesc& GetReprDesc() const { return _reprDesc; } + +private: + HdVP2RenderDelegate* _delegate{ nullptr }; //!< VP2 render delegate for which this draw item was created + const HdMeshReprDesc _reprDesc; //!< The repr desc for which the draw item was created. + RenderItemData _mesh; //!< VP2 render item data + MString _renderItemName; //!< Unique name. Use this when searching for render item in subscene override container + bool _enabled{ true }; //!< Whether the draw item is enabled. +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/lib/render/vp2RenderDelegate/instancer.cpp b/lib/render/vp2RenderDelegate/instancer.cpp new file mode 100644 index 0000000000..5e3f5e60dc --- /dev/null +++ b/lib/render/vp2RenderDelegate/instancer.cpp @@ -0,0 +1,238 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Modifications copyright (C) 2019 Autodesk +// +#include "pxr/imaging/glf/glew.h" + +#include "instancer.h" +#include "sampler.h" + +#include "pxr/imaging/hd/sceneDelegate.h" + +#include "pxr/base/gf/vec3f.h" +#include "pxr/base/gf/vec4f.h" +#include "pxr/base/gf/matrix4d.h" +#include "pxr/base/gf/rotation.h" +#include "pxr/base/gf/quaternion.h" +#include "pxr/base/tf/staticTokens.h" + +PXR_NAMESPACE_OPEN_SCOPE + +// Define local tokens for the names of the primvars the instancer +// consumes. +// XXX: These should be hydra tokens... +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + (instanceTransform) + (rotate) + (scale) + (translate) +); + +/*! \brief Constructor. + + \param delegate The scene delegate backing this instancer's data. + \param id The unique id of this instancer. + \param parentId The unique id of the parent instancer, + or an empty id if not applicable. +*/ +HdVP2Instancer::HdVP2Instancer(HdSceneDelegate* delegate, + SdfPath const& id, + SdfPath const &parentId) + : HdInstancer(delegate, id, parentId) +{ +} + +/*! \brief Destructor. +*/ +HdVP2Instancer::~HdVP2Instancer() +{ + TF_FOR_ALL(it, _primvarMap) { + delete it->second; + } + _primvarMap.clear(); +} + + +/*! \brief Checks the change tracker to determine whether instance primvars are + dirty, and if so pulls them. + + Since primvars can only be pulled once, and are cached, this function is not + re-entrant. However, this function is called by ComputeInstanceTransforms, + which is called by HdVP2Mesh::Sync(), which is dispatched in parallel, so it needs + to be guarded by _instanceLock. +*/ +void HdVP2Instancer::_SyncPrimvars() +{ + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + HdChangeTracker &changeTracker = + GetDelegate()->GetRenderIndex().GetChangeTracker(); + SdfPath const& id = GetId(); + + // Use the double-checked locking pattern to check if this instancer's + // primvars are dirty. + int dirtyBits = changeTracker.GetInstancerDirtyBits(id); + if (HdChangeTracker::IsAnyPrimvarDirty(dirtyBits, id)) { + std::lock_guard lock(_instanceLock); + + // If not dirty, then another thread did the job + dirtyBits = changeTracker.GetInstancerDirtyBits(id); + if (HdChangeTracker::IsAnyPrimvarDirty(dirtyBits, id)) { + + // If this instancer has dirty primvars, get the list of + // primvar names and then cache each one. + + TfTokenVector primvarNames; + HdPrimvarDescriptorVector primvars = GetDelegate() + ->GetPrimvarDescriptors(id, HdInterpolationInstance); + + for (HdPrimvarDescriptor const& pv: primvars) { + if (HdChangeTracker::IsPrimvarDirty(dirtyBits, id, pv.name)) { + VtValue value = GetDelegate()->Get(id, pv.name); + if (!value.IsEmpty()) { + if (_primvarMap.count(pv.name) > 0) { + delete _primvarMap[pv.name]; + } + _primvarMap[pv.name] = + new HdVtBufferSource(pv.name, value); + } + } + } + + // Mark the instancer as clean + changeTracker.MarkInstancerClean(id); + } + } +} + +/*! \brief Computes all instance transforms for the provided prototype id. + + Taking into account the scene delegate's instancerTransform and the + instance primvars "instanceTransform", "translate", "rotate", "scale". + Computes and flattens nested transforms, if necessary. + + \param prototypeId The prototype to compute transforms for. + + \return One transform per instance, to apply when drawing. +*/ +VtMatrix4dArray HdVP2Instancer::ComputeInstanceTransforms(SdfPath const &prototypeId) +{ + HD_TRACE_FUNCTION(); + HF_MALLOC_TAG_FUNCTION(); + + _SyncPrimvars(); + + // The transforms for this level of instancer are computed by: + // foreach(index : indices) { + // instancerTransform * translate(index) * rotate(index) * + // scale(index) * instanceTransform(index) + // } + // If any transform isn't provided, it's assumed to be the identity. + + GfMatrix4d instancerTransform = + GetDelegate()->GetInstancerTransform(GetId()); + VtIntArray instanceIndices = + GetDelegate()->GetInstanceIndices(GetId(), prototypeId); + + VtMatrix4dArray transforms(instanceIndices.size()); + for (size_t i = 0; i < instanceIndices.size(); ++i) { + transforms[i] = instancerTransform; + } + + // "translate" holds a translation vector for each index. + if (_primvarMap.count(_tokens->translate) > 0) { + HdVP2BufferSampler sampler(*_primvarMap[_tokens->translate]); + for (size_t i = 0; i < instanceIndices.size(); ++i) { + GfVec3f translate; + if (sampler.Sample(instanceIndices[i], &translate)) { + GfMatrix4d translateMat(1); + translateMat.SetTranslate(GfVec3d(translate)); + transforms[i] = translateMat * transforms[i]; + } + } + } + + // "rotate" holds a quaternion in format for each index. + if (_primvarMap.count(_tokens->rotate) > 0) { + HdVP2BufferSampler sampler(*_primvarMap[_tokens->rotate]); + for (size_t i = 0; i < instanceIndices.size(); ++i) { + GfVec4f quat; + if (sampler.Sample(instanceIndices[i], &quat)) { + GfMatrix4d rotateMat(1); + rotateMat.SetRotate(GfRotation(GfQuaternion( + quat[0], GfVec3d(quat[1], quat[2], quat[3])))); + transforms[i] = rotateMat * transforms[i]; + } + } + } + + // "scale" holds an axis-aligned scale vector for each index. + if (_primvarMap.count(_tokens->scale) > 0) { + HdVP2BufferSampler sampler(*_primvarMap[_tokens->scale]); + for (size_t i = 0; i < instanceIndices.size(); ++i) { + GfVec3f scale; + if (sampler.Sample(instanceIndices[i], &scale)) { + GfMatrix4d scaleMat(1); + scaleMat.SetScale(GfVec3d(scale)); + transforms[i] = scaleMat * transforms[i]; + } + } + } + + // "instanceTransform" holds a 4x4 transform matrix for each index. + if (_primvarMap.count(_tokens->instanceTransform) > 0) { + HdVP2BufferSampler sampler(*_primvarMap[_tokens->instanceTransform]); + for (size_t i = 0; i < instanceIndices.size(); ++i) { + GfMatrix4d instanceTransform; + if (sampler.Sample(instanceIndices[i], &instanceTransform)) { + transforms[i] = instanceTransform * transforms[i]; + } + } + } + + if (GetParentId().IsEmpty()) { + return transforms; + } + + HdInstancer *parentInstancer = + GetDelegate()->GetRenderIndex().GetInstancer(GetParentId()); + if (!TF_VERIFY(parentInstancer)) { + return transforms; + } + + // The transforms taking nesting into account are computed by: + // parentTransforms = parentInstancer->ComputeInstanceTransforms(GetId()) + // foreach (parentXf : parentTransforms, xf : transforms) { + // parentXf * xf + // } + VtMatrix4dArray parentTransforms = + static_cast(parentInstancer)-> + ComputeInstanceTransforms(GetId()); + + VtMatrix4dArray final(parentTransforms.size() * transforms.size()); + for (size_t i = 0; i < parentTransforms.size(); ++i) { + for (size_t j = 0; j < transforms.size(); ++j) { + final[i * transforms.size() + j] = transforms[j] * + parentTransforms[i]; + } + } + return final; +} + +PXR_NAMESPACE_CLOSE_SCOPE + diff --git a/lib/render/vp2RenderDelegate/instancer.h b/lib/render/vp2RenderDelegate/instancer.h new file mode 100644 index 0000000000..c1807fedf6 --- /dev/null +++ b/lib/render/vp2RenderDelegate/instancer.h @@ -0,0 +1,65 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Modifications copyright (C) 2019 Autodesk +// +#ifndef HD_VP2_INSTANCER +#define HD_VP2_INSTANCER + +#include "pxr/pxr.h" + +#include "pxr/imaging/hd/instancer.h" +#include "pxr/imaging/hd/vtBufferSource.h" + +#include "pxr/base/tf/hashmap.h" +#include "pxr/base/tf/token.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief VP2 instancing of prototype geometry with varying transforms + \class HdVP2Instancer + + Nested instancing can be handled by recursion, and by taking the + cartesian product of the transform arrays at each nesting level, to + create a flattened transform array. +*/ +class HdVP2Instancer final : public HdInstancer +{ +public: + HdVP2Instancer(HdSceneDelegate* delegate, SdfPath const& id, + SdfPath const &parentInstancerId); + + ~HdVP2Instancer(); + + VtMatrix4dArray ComputeInstanceTransforms(SdfPath const &prototypeId); + +private: + void _SyncPrimvars(); + + //! Mutex guard for _SyncPrimvars(). + std::mutex _instanceLock; + + /*! Map of the latest primvar data for this instancer, keyed by + primvar name. Primvar values are VtValue, an any-type; they are + interpreted at consumption time (here, in ComputeInstanceTransforms). + */ + TfHashMap _primvarMap; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/lib/render/vp2RenderDelegate/material.cpp b/lib/render/vp2RenderDelegate/material.cpp new file mode 100644 index 0000000000..8292ed3fe4 --- /dev/null +++ b/lib/render/vp2RenderDelegate/material.cpp @@ -0,0 +1,803 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "debugCodes.h" +#include "material.h" +#include "render_delegate.h" + +#include "pxr/imaging/glf/image.h" +#include "pxr/imaging/hd/sceneDelegate.h" +#include "pxr/usd/ar/packageUtils.h" +#include "pxr/usd/sdf/assetPath.h" +#include "pxr/usd/usdHydra/tokens.h" +#include "pxr/usdImaging/usdImaging/tokens.h" + +#include "pxr/base/gf/vec2f.h" +#include "pxr/base/gf/vec3f.h" +#include "pxr/base/gf/vec4f.h" +#include "pxr/base/gf/matrix4d.h" +#include "pxr/base/gf/matrix4f.h" +#include "pxr/base/tf/diagnostic.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { + +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + + (file) + (opacity) + (st) + (varname) + + (input) + (output) + + (rgb) + (r) + (g) + (b) + (a) + + (xyz) + (x) + (y) + (z) + (w) + + (float4ToFloatX) + (float4ToFloatY) + (float4ToFloatZ) + (float4ToFloatW) + (float4ToFloat3) +); + +//! Helper utility function to test whether a node is a UsdShade primvar reader. +bool _IsUsdPrimvarReader(const HdMaterialNode& node) +{ + const TfToken& id = node.identifier; + return (id == UsdImagingTokens->UsdPrimvarReader_float || + id == UsdImagingTokens->UsdPrimvarReader_float2 || + id == UsdImagingTokens->UsdPrimvarReader_float3 || + id == UsdImagingTokens->UsdPrimvarReader_float4); +} + +//! Helper utility function to test whether a node is a UsdShade UV texture. +inline bool _IsUsdUVTexture(const HdMaterialNode& node) +{ + return (node.identifier == UsdImagingTokens->UsdUVTexture); +} + +//! Helper utility function to print nodes, connections and primvars in the +//! specified material network. +void _PrintMaterialNetwork( + const std::string& label, + const SdfPath& id, + const HdMaterialNetwork& mat) +{ + std::cout << label << " material network for " << id << "\n"; + + std::cout << " --Node--\n"; + for (HdMaterialNode const& node: mat.nodes) { + std::cout << " " << node.path << "\n"; + std::cout << " " << node.identifier << "\n"; + + for (auto const& entry: node.parameters) { + std::cout << " param " << entry.first << ": " + << TfStringify(entry.second) << "\n"; + } + } + + std::cout << " --Connections--\n"; + for (const HdMaterialRelationship& rel: mat.relationships) { + std::cout << " " << rel.inputId << "." << rel.inputName + << "->" << rel.outputId << "." << rel.outputName << "\n"; + } + + std::cout << " --Primvars--\n"; + for (TfToken const& primvar: mat.primvars) { + std::cout << " " << primvar << "\n"; + } +} + +//! Helper utility function to apply VP2-specific fixes to the material network. +//! - Add passthrough nodes to read vector component(s). +//! - Fix UsdImagingMaterialAdapter issue for not producing primvar requirements. +//! - Temporary workaround of missing support for normal map. +void _ApplyVP2Fixes(HdMaterialNetwork& outNet, const HdMaterialNetwork& inNet) +{ + unsigned int numPassThroughNodes = 0; + + for (const HdMaterialNode &node : inNet.nodes) + { + outNet.nodes.push_back(node); + + // Copy outgoing connections and if needed add passthrough node/connection. + for (const HdMaterialRelationship& rel : inNet.relationships) { + if (rel.inputId != node.path) { + continue; + } + + TfToken passThroughId; + if (rel.inputName == _tokens->rgb || rel.inputName == _tokens->xyz) { + passThroughId = _tokens->float4ToFloat3; + } + else if (rel.inputName == _tokens->r || rel.inputName == _tokens->x) { + passThroughId = _tokens->float4ToFloatX; + } + else if (rel.inputName == _tokens->g || rel.inputName == _tokens->y) { + passThroughId = _tokens->float4ToFloatY; + } + else if (rel.inputName == _tokens->b || rel.inputName == _tokens->z) { + passThroughId = _tokens->float4ToFloatZ; + } + else if (rel.inputName == _tokens->a || rel.inputName == _tokens->w) { + passThroughId = _tokens->float4ToFloatW; + } + else { + outNet.relationships.push_back(rel); + continue; + } + + const SdfPath passThroughPath = rel.inputId.ReplaceName(TfToken( + TfStringPrintf("HdVP2PassThrough%d", numPassThroughNodes++))); + + const HdMaterialNode passThroughNode = { + passThroughPath, passThroughId, {} + }; + outNet.nodes.push_back(passThroughNode); + + HdMaterialRelationship newRel = { + rel.inputId, _tokens->output, passThroughPath, _tokens->input + }; + outNet.relationships.push_back(newRel); + + newRel = { + passThroughPath, _tokens->output, rel.outputId, rel.outputName + }; + outNet.relationships.push_back(newRel); + } + + // Normal map is not supported yet. For now primvars:normals is used for + // shading, which is also the current behavior of USD/Hydra. + // https://groups.google.com/d/msg/usd-interest/7epU16C3eyY/X9mLW9VFEwAJ + if (node.identifier == UsdImagingTokens->UsdPreviewSurface) { + outNet.primvars.push_back(HdTokens->normals); + } + // UsdImagingMaterialAdapter doesn't create primvar requirements as + // expected. Workaround by manually looking up "varname" parameter. + // https://groups.google.com/forum/#!msg/usd-interest/z-14AgJKOcU/1uJJ1thXBgAJ + else if (_IsUsdPrimvarReader(node)) { + auto it = node.parameters.find(_tokens->varname); + if (it != node.parameters.end()) { + outNet.primvars.push_back(TfToken(TfStringify(it->second))); + } + } + } +} + +//! Helper utility function to convert Hydra texture addressing token to VP2 enum. +MHWRender::MSamplerState::TextureAddress _ConvertToTextureSamplerAddressEnum( + const TfToken& token) +{ + MHWRender::MSamplerState::TextureAddress address; + + if (token == UsdHydraTokens->clamp) { + address = MHWRender::MSamplerState::kTexClamp; + } + else if (token == UsdHydraTokens->mirror) { + address = MHWRender::MSamplerState::kTexMirror; + } + else if (token == UsdHydraTokens->black) { + address = MHWRender::MSamplerState::kTexBorder; + } + else { + address = MHWRender::MSamplerState::kTexWrap; + } + + return address; +} + +//! Get sampler state description as required by the material node. +MHWRender::MSamplerStateDesc _GetSamplerStateDesc(const HdMaterialNode& node) +{ + TF_VERIFY(_IsUsdUVTexture(node)); + + MHWRender::MSamplerStateDesc desc; + desc.filter = MHWRender::MSamplerState::kMinMagMipLinear; + + auto it = node.parameters.find(UsdHydraTokens->wrapS); + if (it != node.parameters.end()) { + const VtValue& value = it->second; + if (value.IsHolding()) { + const TfToken& token = value.UncheckedGet(); + desc.addressU = _ConvertToTextureSamplerAddressEnum(token); + } + } + + it = node.parameters.find(UsdHydraTokens->wrapT); + if (it != node.parameters.end()) { + const VtValue& value = it->second; + if (value.IsHolding()) { + const TfToken& token = value.UncheckedGet(); + desc.addressV = _ConvertToTextureSamplerAddressEnum(token); + } + } + + return desc; +} + +//! Load texture from the specified path +MHWRender::MTexture* _LoadTexture( + const std::string& path, + bool& isColorSpaceSRGB) +{ + isColorSpaceSRGB = false; + + MHWRender::MRenderer* const renderer = MHWRender::MRenderer::theRenderer(); + MHWRender::MTextureManager* const textureMgr = + renderer ? renderer->getTextureManager() : nullptr; + if (!TF_VERIFY(textureMgr)) { + return nullptr; + } + + GlfImageSharedPtr image = GlfImage::OpenForReading(path); + if (!TF_VERIFY(image)) { + return nullptr; + } + + // GlfImage is used for loading pixel data from usdz only and should + // not trigger any OpenGL call. VP2RenderDelegate will transfer the + // texels to GPU memory with VP2 API which is 3D API agnostic. + GlfImage::StorageSpec spec; + spec.width = image->GetWidth(); + spec.height = image->GetHeight(); + spec.depth = 1; + spec.format = image->GetFormat(); + spec.type = image->GetType(); + spec.flipped = false; + + const int bpp = image->GetBytesPerPixel(); + const int bytesPerRow = spec.width * bpp; + const int bytesPerSlice = bytesPerRow * spec.height; + + std::vector storage(bytesPerSlice); + spec.data = storage.data(); + + if (!image->Read(spec)) { + return nullptr; + } + + MHWRender::MTexture* texture = nullptr; + + MHWRender::MTextureDescription desc; + desc.setToDefault2DTexture(); + desc.fWidth = spec.width; + desc.fHeight = spec.height; + desc.fBytesPerRow = bytesPerRow; + desc.fBytesPerSlice = bytesPerSlice; + + switch (spec.format) + { + case GL_RED: + desc.fFormat = (spec.type == GL_FLOAT ? + MHWRender::kR32_FLOAT : MHWRender::kR8_UNORM); + texture = textureMgr->acquireTexture(path.c_str(), desc, spec.data); + break; + case GL_RGB: + if (spec.type == GL_FLOAT) { + desc.fFormat = MHWRender::kR32G32B32_FLOAT; + texture = textureMgr->acquireTexture(path.c_str(), desc, spec.data); + } + else { + // R8G8B8 is not supported by VP2. Converted to R8G8B8A8. + constexpr int bpp_4 = 4; + + desc.fFormat = MHWRender::kR8G8B8A8_UNORM; + desc.fBytesPerRow = spec.width * bpp_4; + desc.fBytesPerSlice = desc.fBytesPerRow * spec.height; + + std::vector texels(desc.fBytesPerSlice); + + for (int y = 0; y < spec.height; y++) { + for (int x = 0; x < spec.width; x++) { + const int t = spec.width * y + x; + texels[t*bpp_4] = storage[t*bpp]; + texels[t*bpp_4 + 1] = storage[t*bpp + 1]; + texels[t*bpp_4 + 2] = storage[t*bpp + 2]; + texels[t*bpp_4 + 3] = 255; + } + } + + texture = textureMgr->acquireTexture(path.c_str(), desc, texels.data()); + isColorSpaceSRGB = image->IsColorSpaceSRGB(); + } + break; + case GL_RGBA: + if (spec.type == GL_FLOAT) { + desc.fFormat = MHWRender::kR32G32B32A32_FLOAT; + } + else { + desc.fFormat = MHWRender::kR8G8B8A8_UNORM; + isColorSpaceSRGB = image->IsColorSpaceSRGB(); + } + texture = textureMgr->acquireTexture(path.c_str(), desc, spec.data); + break; + default: + break; + } + + return texture; +} + +} //anonymous namespace + +/*! \brief Releases the reference to the shader owned by a smart pointer. +*/ +void +HdVP2ShaderDeleter::operator()(MHWRender::MShaderInstance* shader) +{ + MRenderer* const renderer = MRenderer::theRenderer(); + const MShaderManager* const shaderMgr = + renderer ? renderer->getShaderManager() : nullptr; + if (TF_VERIFY(shaderMgr)) { + shaderMgr->releaseShader(shader); + } +} + +/*! \brief Releases the reference to the texture owned by a smart pointer. +*/ +void +HdVP2TextureDeleter::operator()(MHWRender::MTexture* texture) +{ + MRenderer* const renderer = MRenderer::theRenderer(); + MHWRender::MTextureManager* const textureMgr = + renderer ? renderer->getTextureManager() : nullptr; + if (TF_VERIFY(textureMgr)) { + textureMgr->releaseTexture(texture); + } +} + +/*! \brief Constructor +*/ +HdVP2Material::HdVP2Material(HdVP2RenderDelegate* renderDelegate, const SdfPath& id) + : HdMaterial(id) + , _renderDelegate(renderDelegate) +{ +} + +/*! \brief Destructor - will release allocated shader instances. +*/ +HdVP2Material::~HdVP2Material() { +} + +/*! \brief Synchronize VP2 state with scene delegate state based on dirty bits +*/ +void HdVP2Material::Sync( + HdSceneDelegate* sceneDelegate, + HdRenderParam* /*renderParam*/, + HdDirtyBits* dirtyBits) +{ + if (*dirtyBits & (HdMaterial::DirtyResource | HdMaterial::DirtyParams)) { + const SdfPath& id = GetId(); + VtValue vtMatResource = sceneDelegate->GetMaterialResource(id); + + if (vtMatResource.IsHolding()) { + const HdMaterialNetworkMap& networkMap = + vtMatResource.UncheckedGet(); + + HdMaterialNetwork bxdfNet, dispNet; + TfMapLookup(networkMap.map, HdMaterialTerminalTokens->surface, &bxdfNet); + TfMapLookup(networkMap.map, HdMaterialTerminalTokens->displacement, &dispNet); + + if (*dirtyBits & HdMaterial::DirtyResource) { + // Apply VP2 fixes to the material network + HdMaterialNetwork vp2BxdfNet; + _ApplyVP2Fixes(vp2BxdfNet, bxdfNet); + + // Create a shader instance for the material network. + _surfaceShader.reset(_CreateShaderInstance(vp2BxdfNet)); + + if (TfDebug::IsEnabled(HDVP2_DEBUG_MATERIAL)) { + _PrintMaterialNetwork("BXDF", id, bxdfNet); + _PrintMaterialNetwork("BXDF (with VP2 fixes)", id, vp2BxdfNet); + _PrintMaterialNetwork("Displacement", id, dispNet); + + if (_surfaceShader) { + auto tmpDir = boost::filesystem::temp_directory_path(); + tmpDir /= "HdVP2Material_"; + tmpDir += id.GetName(); + tmpDir += ".txt"; + _surfaceShader->writeEffectSourceToFile(tmpDir.c_str()); + + std::cout << "BXDF generated shader code for " << id << ":\n"; + std::cout << " " << tmpDir << "\n"; + } + } + + // Store primvar requirements. + _requiredPrimvars = std::move(vp2BxdfNet.primvars); + } + + _UpdateShaderInstance(bxdfNet); + } + else { + TF_WARN("Expected material resource for <%s> to hold HdMaterialNetworkMap," + "but found %s instead.", + id.GetText(), vtMatResource.GetTypeName().c_str()); + } + } + + *dirtyBits = HdMaterial::Clean; +} + +/*! \brief Reload the shader +*/ +void HdVP2Material::Reload() { +} + +/*! \brief Returns the minimal set of dirty bits to place in the +change tracker for use in the first sync of this prim. +*/ +HdDirtyBits +HdVP2Material::GetInitialDirtyBitsMask() const { + return HdMaterial::AllDirty; +} + +/*! \brief Returns surface shader instance +*/ +MHWRender::MShaderInstance* +HdVP2Material::GetSurfaceShader() const { + return _surfaceShader.get(); +} + +/*! \brief Creates a shader instance for the surface shader. +*/ +MHWRender::MShaderInstance* +HdVP2Material::_CreateShaderInstance(const HdMaterialNetwork& mat) { + MHWRender::MRenderer* const renderer = MHWRender::MRenderer::theRenderer(); + if (!TF_VERIFY(renderer)) { + return nullptr; + } + + const MHWRender::MShaderManager* const shaderMgr = renderer->getShaderManager(); + if (!TF_VERIFY(shaderMgr)) { + return nullptr; + } + + MHWRender::MShaderInstance* shaderInstance = nullptr; + + // Conditional compilation due to Maya API gaps. +#if MAYA_API_VERSION >= 20200000 + + // UsdImagingMaterialAdapter has walked the shader graph and emitted nodes + // and relationships in topological order to avoid forward-references, thus + // we can run a reverse iteration to avoid connecting a fragment before any + // of its downstream fragments. + for (auto rit = mat.nodes.rbegin(); rit != mat.nodes.rend(); rit++) { + const HdMaterialNode& node = *rit; + + const MString nodeId = node.identifier.GetText(); + const MString nodeName = node.path.GetNameToken().GetText(); + + if (shaderInstance == nullptr) { + shaderInstance = shaderMgr->getFragmentShader(nodeId, "outSurfaceFinal", true); + _surfaceShaderId = node.path; + + if (shaderInstance == nullptr) { + TF_WARN("Failed to create shader instance for %s", nodeId.asChar()); + break; + } + + continue; + } + + MStringArray outputNames, inputNames; + + for (const HdMaterialRelationship& rel : mat.relationships) { + if (rel.inputId == node.path) { + MString outputName = rel.inputName.GetText(); + outputNames.append(outputName); + + if (rel.outputId != _surfaceShaderId) { + std::string str = rel.outputId.GetName(); + str += rel.outputName.GetString(); + inputNames.append(str.c_str()); + } + else { + inputNames.append(rel.outputName.GetText()); + + if (rel.outputName == _tokens->opacity) { + shaderInstance->setIsTransparent(true); + } + } + } + } + + if (outputNames.length() > 0) { + MUintArray invalidParamIndices; + MStatus status = shaderInstance->addInputFragmentForMultiParams( + nodeId, nodeName, outputNames, inputNames, &invalidParamIndices); + + if (!status && TfDebug::IsEnabled(HDVP2_DEBUG_MATERIAL)) { + TF_WARN("Error %s happened when connecting shader %s", + status.errorString().asChar(), node.path.GetText()); + + for (unsigned int i = 0; i < invalidParamIndices.length(); i++) { + unsigned int index = invalidParamIndices[i]; + const MString& outputName = outputNames[index]; + const MString& inputName = inputNames[index]; + TF_WARN(" %s -> %s", outputName.asChar(), inputName.asChar()); + } + } + + if (_IsUsdPrimvarReader(node)) { + auto it = node.parameters.find(_tokens->varname); + if (it != node.parameters.end()) { + const MString paramName = HdTokens->primvar.GetText(); + const MString varname = TfStringify(it->second).c_str(); + shaderInstance->renameParameter(paramName, varname); + } + } + } + else { + TF_DEBUG(HDVP2_DEBUG_MATERIAL).Msg("Failed to connect shader %s\n", + node.path.GetText()); + } + } + +#elif MAYA_API_VERSION >= 20190000 + + // UsdImagingMaterialAdapter has walked the shader graph and emitted nodes + // and relationships in topological order to avoid forward-references, thus + // we can run a reverse iteration to avoid connecting a fragment before any + // of its downstream fragments. + for (auto rit = mat.nodes.rbegin(); rit != mat.nodes.rend(); rit++) { + const HdMaterialNode& node = *rit; + + const MString nodeId = node.identifier.GetText(); + const MString nodeName = node.path.GetNameToken().GetText(); + + if (shaderInstance == nullptr) { + shaderInstance = shaderMgr->getFragmentShader(nodeId, "outSurfaceFinal", true); + _surfaceShaderId = node.path; + + if (shaderInstance == nullptr) { + TF_WARN("Failed to create shader instance for %s", nodeId.asChar()); + break; + } + + continue; + } + + MStringArray outputNames, inputNames; + + std::string primvarname; + + for (const HdMaterialRelationship& rel : mat.relationships) { + if (rel.inputId == node.path) { + outputNames.append(rel.inputName.GetText()); + inputNames.append(rel.outputName.GetText()); + + if (rel.outputName == _tokens->opacity) { + shaderInstance->setIsTransparent(true); + } + } + + if (_IsUsdUVTexture(node)) { + if (rel.outputId == node.path && + rel.outputName == _tokens->st) { + for (const HdMaterialNode& n : mat.nodes) { + if (n.path == rel.inputId && _IsUsdPrimvarReader(n)) { + auto it = n.parameters.find(_tokens->varname); + if (it != n.parameters.end()) { + primvarname = TfStringify(it->second); + } + break; + } + } + } + } + } + + // Without multi-connection support for MShaderInstance, this code path + // can only support common patterns of UsdShade material network, i.e. + // a UsdUVTexture is connected to a single input of a USD Preview Surface. + // More generic fix is coming. + if (outputNames.length() == 1) { + MStatus status = shaderInstance->addInputFragment( + nodeId, outputNames[0], inputNames[0]); + + if (!status) { + TF_DEBUG(HDVP2_DEBUG_MATERIAL).Msg( + "Error %s happened when connecting shader %s\n", + status.errorString().asChar(), node.path.GetText()); + } + + if (_IsUsdUVTexture(node)) { + const MString paramNames[] = { + "file", "fileSampler", "isColorSpaceSRGB", "fallback", "scale", "bias" + }; + + for (const MString& paramName : paramNames) { + const MString resolvedName = nodeName + paramName; + shaderInstance->renameParameter(paramName, resolvedName); + } + + const MString paramName = _tokens->st.GetText(); + shaderInstance->setSemantic(paramName, "uvCoord"); + shaderInstance->setAsVarying(paramName, true); + shaderInstance->renameParameter(paramName, primvarname.c_str()); + } + } + else { + TF_DEBUG(HDVP2_DEBUG_MATERIAL).Msg( + "Failed to connect shader %s\n", node.path.GetText()); + } + } + +#endif + + return shaderInstance; +} + +/*! \brief Updates parameters for the surface shader. +*/ +void HdVP2Material::_UpdateShaderInstance(const HdMaterialNetwork& mat) +{ + if (!_surfaceShader) { + return; + } + + for (const HdMaterialNode& node : mat.nodes) { + const MString nodeName = + node.path != _surfaceShaderId ? node.path.GetName().c_str() : ""; + + MStatus samplerStatus = MStatus::kFailure; + + if (_IsUsdUVTexture(node)) { + const MHWRender::MSamplerStateDesc desc = _GetSamplerStateDesc(node); + const MHWRender::MSamplerState* sampler = + _renderDelegate->GetSamplerState(desc); + if (sampler) { + const MString paramName = nodeName + "fileSampler"; + samplerStatus = _surfaceShader->setParameter(paramName, *sampler); + } + } + + for (auto const& entry: node.parameters) { + const TfToken& token = entry.first; + const VtValue& value = entry.second; + + MString paramName = nodeName + token.GetText(); + + MStatus status = MStatus::kFailure; + + if (value.IsHolding()) { + const bool& val = value.UncheckedGet(); + status = _surfaceShader->setParameter(paramName, val); + } + else if (value.IsHolding()) { + const int& val = value.UncheckedGet(); + status = _surfaceShader->setParameter(paramName, val); + } + else if (value.IsHolding()) { + const float& val = value.UncheckedGet(); + status = _surfaceShader->setParameter(paramName, val); + + // The opacity parameter can be found and updated only when it + // has no connection. In this case, transparency of the shader + // is solely determined by the opacity value. + if (nodeName.length() == 0 && token == _tokens->opacity) { + _surfaceShader->setIsTransparent(!status || val < 0.999f); + } + } + else if (value.IsHolding()) { + const float* val = value.UncheckedGet().data(); + status = _surfaceShader->setParameter(paramName, val); + } + else if (value.IsHolding()) { + const float* val = value.UncheckedGet().data(); + status = _surfaceShader->setParameter(paramName, val); + } + else if (value.IsHolding()) { + const float* val = value.UncheckedGet().data(); + status = _surfaceShader->setParameter(paramName, val); + } + else if (value.IsHolding()) { + MMatrix matrix; + value.UncheckedGet().Get(matrix.matrix); + status = _surfaceShader->setParameter(paramName, matrix); + } + else if (value.IsHolding()) { + MFloatMatrix matrix; + value.UncheckedGet().Get(matrix.matrix); + status = _surfaceShader->setParameter(paramName, matrix); + } + else if (value.IsHolding()) { + // The two parameters have been converted to sampler state + // before entering this loop. + if (_IsUsdUVTexture(node) && + (token == UsdHydraTokens->wrapS || + token == UsdHydraTokens->wrapT)) { + status = samplerStatus; + } + } + else if (value.IsHolding()) { + const SdfAssetPath& val = value.UncheckedGet(); + const std::string& resolvedPath = val.GetResolvedPath(); + const std::string& assetPath = val.GetAssetPath(); + if (_IsUsdUVTexture(node) && token == _tokens->file) { + const HdVP2TextureInfo& info = _AcquireTexture( + !resolvedPath.empty() ? resolvedPath : assetPath); + + MHWRender::MTextureAssignment assignment; + assignment.texture = info._texture.get(); + status = _surfaceShader->setParameter(paramName, assignment); + + if (status) { + paramName = nodeName + "isColorSpaceSRGB"; + status = _surfaceShader->setParameter(paramName, + info._isColorSpaceSRGB); + } + } + } + + if (!status) { + TF_DEBUG(HDVP2_DEBUG_MATERIAL).Msg( + "Failed to set shader parameter %s\n", paramName.asChar()); + } + } + } +} + +/*! \brief Acquires a texture for the given image path. +*/ +const HdVP2TextureInfo& +HdVP2Material::_AcquireTexture(const std::string& path) +{ + const auto it = _textureMap.find(path); + if (it != _textureMap.end()) { + return it->second; + } + + bool isSRGB = false; + MHWRender::MTexture* texture = _LoadTexture(path, isSRGB); + + HdVP2TextureInfo& info = _textureMap[path]; + info._texture.reset(texture); + info._isColorSpaceSRGB = isSRGB; + return info; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/material.h b/lib/render/vp2RenderDelegate/material.h new file mode 100644 index 0000000000..e9bfbdc8fb --- /dev/null +++ b/lib/render/vp2RenderDelegate/material.h @@ -0,0 +1,111 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_MATERIAL +#define HD_VP2_MATERIAL + +#include "pxr/pxr.h" +#include "pxr/imaging/hd/material.h" + +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSceneDelegate; +class HdVP2RenderDelegate; + +/*! \brief A deleter for MShaderInstance, for use with smart pointers. +*/ +struct HdVP2ShaderDeleter +{ + void operator () (MHWRender::MShaderInstance*); +}; + +/*! \brief A MShaderInstance owned by a std unique pointer. +*/ +using HdVP2ShaderUniquePtr = std::unique_ptr< + MHWRender::MShaderInstance, + HdVP2ShaderDeleter +>; + +/*! \brief A deleter for MTexture, for use with smart pointers. +*/ +struct HdVP2TextureDeleter +{ + void operator () (MHWRender::MTexture*); +}; + +/*! \brief A MTexture owned by a std unique pointer. +*/ +using HdVP2TextureUniquePtr = std::unique_ptr< + MHWRender::MTexture, + HdVP2TextureDeleter +>; + +/*! \brief Information about the texture. +*/ +struct HdVP2TextureInfo +{ + HdVP2TextureUniquePtr _texture; //!< Unique pointer of the texture + bool _isColorSpaceSRGB; //!< Whether sRGB linearization is needed +}; + +/*! \brief An unordered string-indexed map to cache texture information. +*/ +using HdVP2TextureMap = std::unordered_map; + +/*! \brief A VP2-specific implementation for a Hydra material prim. + \class HdVP2Material + + Provides a basic implementation of a Hydra material. +*/ +class HdVP2Material final : public HdMaterial { +public: + HdVP2Material(HdVP2RenderDelegate*, const SdfPath&); + + ~HdVP2Material() override; + + void Sync(HdSceneDelegate*, HdRenderParam*, HdDirtyBits*) override; + + HdDirtyBits GetInitialDirtyBitsMask() const override; + void Reload() override; + + MHWRender::MShaderInstance* GetSurfaceShader() const; + + /*! \brief Get primvar requirements required by this material. + */ + const TfTokenVector& GetRequiredPrimvars() const { + return _requiredPrimvars; + } + +private: + MHWRender::MShaderInstance* _CreateShaderInstance(const HdMaterialNetwork& mat); + void _UpdateShaderInstance(const HdMaterialNetwork& mat); + const HdVP2TextureInfo& _AcquireTexture(const std::string& path); + + HdVP2RenderDelegate* const _renderDelegate; //!< VP2 render delegate for which this material was created + + HdVP2ShaderUniquePtr _surfaceShader; //!< VP2 surface shader instance + SdfPath _surfaceShaderId; //!< Path of the surface shader + HdVP2TextureMap _textureMap; //!< Textures used by this material + TfTokenVector _requiredPrimvars; //!< primvars required by this material +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/lib/render/vp2RenderDelegate/mesh.cpp b/lib/render/vp2RenderDelegate/mesh.cpp new file mode 100644 index 0000000000..a317c813e8 --- /dev/null +++ b/lib/render/vp2RenderDelegate/mesh.cpp @@ -0,0 +1,1358 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "mesh.h" +#include "debugCodes.h" +#include "draw_item.h" +#include "material.h" +#include "instancer.h" +#include "proxyRenderDelegate.h" + +#include "pxr/base/gf/matrix4d.h" +#include "pxr/imaging/hd/sceneDelegate.h" +#include "pxr/imaging/hd/meshUtil.h" +#include "pxr/imaging/hd/smoothNormals.h" +#include "pxr/imaging/hd/vertexAdjacency.h" +#include "pxr/imaging/pxOsd/tokens.h" + +#include +#include +#include + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace { + + //! A primvar vertex buffer data map indexed by primvar name. + using PrimvarBufferDataMap = std::unordered_map< + TfToken, + float*, + TfToken::HashFunctor + >; + + //! \brief Helper struct used to package all the changes into single commit task + //! (such commit task will be executed on main-thread) + struct CommitState { + HdVP2DrawItem::RenderItemData& _drawItemData; + + //! If valid, new position buffer data to commit + float* _positionBufferData{ nullptr }; + //! If valid, new index buffer data to commit + int* _indexBufferData{ nullptr }; + //! If valid, new color buffer data to commit + float* _colorBufferData{ nullptr }; + //! If valid, new normals buffer data to commit + float* _normalsBufferData{ nullptr }; + //! If valid, new primvar buffer data to commit + PrimvarBufferDataMap _primvarBufferDataMap; + + //! If valid, new shader instance to set + MHWRender::MShaderInstance* _surfaceShader{ nullptr }; + + //! Instancing doesn't have dirty bits, every time we do update, we must update instance transforms + MMatrixArray _instanceTransforms; + + //! Is this object transparent + bool _isTransparent{ false }; + + //! Capture of what has changed on this rprim + HdDirtyBits _dirtyBits; + + //! Construct valid commit state + CommitState(HdVP2DrawItem& drawItem, HdDirtyBits& dirtyBits) + : _drawItemData(drawItem.GetRenderItemData()) + , _dirtyBits(dirtyBits) {} + + //! No default constructor, we need draw item and dirty bits. + CommitState() = delete; + }; + + //! Helper utility function to get number of draw items required for given representation + size_t _GetNumDrawItemsForDesc(const HdMeshReprDesc& reprDesc) + { + // By default, each repr desc item maps to 1 draw item + size_t numDrawItems = 1; + + // Different representations may require different number of draw items + // See HdSt for an example. + switch (reprDesc.geomStyle) { + case HdMeshGeomStyleInvalid: + numDrawItems = 0; + break; + default: + break; + } + + return numDrawItems; + } + + //! Helper utility function to fill primvar data to vertex buffer. + template + void _FillPrimvarData(DEST_TYPE* vertexBuffer, + size_t numVertices, + size_t channelOffset, + bool requiresUnsharedVertices, + const SdfPath& meshId, + const HdMeshTopology& topology, + const TfToken& primvarName, + const VtArray& primvarData, + const HdInterpolation& primvarInterp) + { + switch (primvarInterp) { + case HdInterpolationConstant: + for (size_t v = 0; v < numVertices; v++) { + SRC_TYPE* pointer = reinterpret_cast( + reinterpret_cast(&vertexBuffer[v]) + channelOffset); + *pointer = primvarData[0]; + } + break; + case HdInterpolationVarying: + case HdInterpolationVertex: + if (requiresUnsharedVertices) { + const VtIntArray& faceVertexIndices = topology.GetFaceVertexIndices(); + if (numVertices == faceVertexIndices.size()) { + for (size_t v = 0; v < numVertices; v++) { + SRC_TYPE* pointer = reinterpret_cast( + reinterpret_cast(&vertexBuffer[v]) + channelOffset); + *pointer = primvarData[faceVertexIndices[v]]; + } + } + else { + // numVertices must have been assigned with the number of + // face vertices as required by vertex unsharing. + TF_CODING_ERROR("Invalid Hydra prim '%s': primvar %s " + "requires %zu unshared elements, while the number of " + "face vertices is %zu. Skipping primvar update.", + meshId.GetText(), primvarName.GetText(), + numVertices, faceVertexIndices.size()); + } + } + else if (numVertices <= primvarData.size()) { + // The primvar has more data than needed, we issue a warning but + // don't skip update. Truncate the buffer to the expected length. + if (numVertices < primvarData.size()) { + TF_DEBUG(HDVP2_DEBUG_MESH).Msg("Invalid Hydra prim '%s': " + "primvar %s has %zu elements, while its topology " + "references only upto element index %zu.\n", + meshId.GetText(), primvarName.GetText(), + primvarData.size(), numVertices); + } + + if (channelOffset == 0 && sizeof(DEST_TYPE) == sizeof(SRC_TYPE)) { + memcpy(vertexBuffer, primvarData.cdata(), sizeof(DEST_TYPE) * numVertices); + } + else { + for (size_t v = 0; v < numVertices; v++) { + SRC_TYPE* pointer = reinterpret_cast( + reinterpret_cast(&vertexBuffer[v]) + channelOffset); + *pointer = primvarData[v]; + } + } + } + else { + // The primvar has less data than needed. Issue warning and skip + // update like what is done in HdStMesh. + TF_DEBUG(HDVP2_DEBUG_MESH).Msg("Invalid Hydra prim '%s': " + "primvar %s has only %zu elements, while its topology expects " + "at least %zu elements. Skipping primvar update.\n", + meshId.GetText(), primvarName.GetText(), + primvarData.size(), numVertices); + + memset(vertexBuffer, 0, sizeof(DEST_TYPE) * numVertices); + } + break; + case HdInterpolationUniform: + if (requiresUnsharedVertices) { + const VtIntArray& faceVertexCounts = topology.GetFaceVertexCounts(); + const size_t numFaces = faceVertexCounts.size(); + if (numFaces <= primvarData.size()) { + // The primvar has more data than needed, we issue a warning but + // don't skip update. Truncate the buffer to the expected length. + if (numFaces < primvarData.size()) { + TF_DEBUG(HDVP2_DEBUG_MESH).Msg("Invalid Hydra prim '%s': " + "primvar %s has %zu elements, while its topology " + "references only upto element index %zu.\n", + meshId.GetText(), primvarName.GetText(), + primvarData.size(), numFaces); + } + + for (size_t f = 0, v = 0; f < numFaces; f++) { + const size_t faceVertexCount = faceVertexCounts[f]; + const size_t faceVertexEnd = v + faceVertexCount; + for (; v < faceVertexEnd; v++) { + SRC_TYPE* pointer = reinterpret_cast( + reinterpret_cast(&vertexBuffer[v]) + channelOffset); + *pointer = primvarData[f]; + } + } + } + else { + // The primvar has less data than needed. Issue warning and skip + // update like what is done in HdStMesh. + TF_DEBUG(HDVP2_DEBUG_MESH).Msg("Invalid Hydra prim '%s': " + "primvar %s has only %zu elements, while its topology expects " + "at least %zu elements. Skipping primvar update.\n", + meshId.GetText(), primvarName.GetText(), + primvarData.size(), numFaces); + + memset(vertexBuffer, 0, sizeof(DEST_TYPE) * numVertices); + } + } + else { + TF_CODING_ERROR("Invalid Hydra prim '%s': " + "vertex unsharing is required for uniform primvar %s", + meshId.GetText(), primvarName.GetText()); + } + break; + case HdInterpolationFaceVarying: + if (requiresUnsharedVertices) { + if (numVertices <= primvarData.size()) { + // If the primvar has more data than needed, we issue a warning, + // but don't skip the primvar update. Truncate the buffer to the + // expected length. + if (numVertices < primvarData.size()) { + TF_DEBUG(HDVP2_DEBUG_MESH).Msg("Invalid Hydra prim '%s': " + "primvar %s has %zu elements, while its topology references " + "only upto element index %zu.\n", + meshId.GetText(), primvarName.GetText(), + primvarData.size(), numVertices); + } + + if (channelOffset == 0 && sizeof(DEST_TYPE) == sizeof(SRC_TYPE)) { + memcpy(vertexBuffer, primvarData.cdata(), sizeof(DEST_TYPE) * numVertices); + } + else { + for (size_t v = 0; v < numVertices; v++) { + SRC_TYPE* pointer = reinterpret_cast( + reinterpret_cast(&vertexBuffer[v]) + channelOffset); + *pointer = primvarData[v]; + } + } + } + else { + // It is unexpected to have less data than we index into. Issue + // a warning and skip update. + TF_DEBUG(HDVP2_DEBUG_MESH).Msg("Invalid Hydra prim '%s': " + "primvar %s has only %zu elements, while its topology expects " + "at least %zu elements. Skipping primvar update.\n", + meshId.GetText(), primvarName.GetText(), + primvarData.size(), numVertices); + + memset(vertexBuffer, 0, sizeof(DEST_TYPE) * numVertices); + } + } + else { + TF_CODING_ERROR("Invalid Hydra prim '%s': " + "vertex unsharing is required face-varying primvar %s", + meshId.GetText(), primvarName.GetText()); + } + break; + default: + TF_CODING_ERROR("Invalid Hydra prim '%s': " + "unimplemented interpolation %zu for primvar %s", + meshId.GetText(), (int)primvarInterp, primvarName.GetText()); + break; + } + } + + //! Helper utility function to get number of edge indices + unsigned int _GetNumOfEdgeIndices(const HdMeshTopology& topology) + { + const VtIntArray &faceVertexCounts = topology.GetFaceVertexCounts(); + + unsigned int numIndex = 0; + for (int i = 0; i < faceVertexCounts.size(); i++) + { + numIndex += faceVertexCounts[i]; + } + numIndex *= 2; // each edge has two ends. + return numIndex; + } + + //! Helper utility function to extract edge indices + void _FillEdgeIndices(int* indices, const HdMeshTopology& topology) + { + const VtIntArray &faceVertexCounts = topology.GetFaceVertexCounts(); + const int* currentFaceStart = topology.GetFaceVertexIndices().cdata(); + for (int faceId = 0; faceId < faceVertexCounts.size(); faceId++) + { + int numVertexIndicesInFace = faceVertexCounts[faceId]; + if (numVertexIndicesInFace >= 2) + { + for (int faceVertexId = 0; faceVertexId < numVertexIndicesInFace; faceVertexId++) + { + bool isLastVertex = faceVertexId == numVertexIndicesInFace - 1; + *(indices++) = *(currentFaceStart + faceVertexId); + *(indices++) = isLastVertex ? *currentFaceStart : *(currentFaceStart + faceVertexId + 1); + } + } + currentFaceStart += numVertexIndicesInFace; + } + } + + //! Helper utility function to adapt Maya API changes. + void setWantConsolidation(MHWRender::MRenderItem& renderItem, bool state) + { +#if MAYA_API_VERSION >= 20190000 + renderItem.setWantConsolidation(state); +#else + renderItem.setWantSubSceneConsolidation(state); +#endif + } +} //namespace + + +//! \brief Constructor +HdVP2Mesh::HdVP2Mesh(HdVP2RenderDelegate* delegate, const SdfPath& id, const SdfPath& instancerId) + : HdMesh(id, instancerId) + , _delegate(delegate) { + const MHWRender::MVertexBufferDescriptor vbDesc( + "", MHWRender::MGeometry::kPosition, MHWRender::MGeometry::kFloat, 3); + _positionsBuffer.reset(new MHWRender::MVertexBuffer(vbDesc)); +} + +//! \brief Destructor +HdVP2Mesh::~HdVP2Mesh() { +} + +//! \brief Synchronize VP2 state with scene delegate state based on dirty bits and representation +void HdVP2Mesh::Sync( + HdSceneDelegate* delegate, HdRenderParam* renderParam, + HdDirtyBits* dirtyBits, const TfToken& reprToken) { + + MProfilingScope profilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorC_L2, GetId().GetText(), "HdVP2Mesh Sync"); + + if (*dirtyBits & HdChangeTracker::DirtyMaterialId) { + _SetMaterialId(delegate->GetRenderIndex().GetChangeTracker(), + delegate->GetMaterialId(GetId())); + } + + _UpdateRepr(delegate, reprToken, dirtyBits); + + auto* const param = static_cast(_delegate->GetRenderParam()); + // HdC_TODO: Currently we are running selection highlighting update in a separate execution + // and this execution will update only wire representation. The next execution will update + // remaining representations, but if we clear dirty bits, nothing will get updated. + // We leave the dirty bits unmodified during selection highlighting to workaround the issue, + // but this is not ideal - we shouldn't have to evaluate the same data twice. + if (!param->GetDrawScene().InSelectionHighlightUpdate()) + *dirtyBits &= ~HdChangeTracker::AllSceneDirtyBits; +} + +/*! \brief Returns the minimal set of dirty bits to place in the + change tracker for use in the first sync of this prim. +*/ +HdDirtyBits HdVP2Mesh::GetInitialDirtyBitsMask() const { + return HdChangeTracker::Clean | HdChangeTracker::InitRepr | + HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyTopology | + HdChangeTracker::DirtyTransform | HdChangeTracker::DirtyMaterialId | + HdChangeTracker::DirtyPrimvar | HdChangeTracker::DirtyVisibility | HdChangeTracker::DirtyInstanceIndex; +} + +/*! \brief Add additional dirty bits + + This callback from Rprim gives the prim an opportunity to set + additional dirty bits based on those already set. This is done + before the dirty bits are passed to the scene delegate, so can be + used to communicate that extra information is needed by the prim to + process the changes. + + The return value is the new set of dirty bits, which replaces the bits + passed in. + + See HdRprim::PropagateRprimDirtyBits() +*/ +HdDirtyBits HdVP2Mesh::_PropagateDirtyBits(HdDirtyBits bits) const { + // If subdiv tags are dirty, topology needs to be recomputed. + // The latter implies we'll need to recompute all primvar data. + // Any data fetched by the scene delegate should be marked dirty here. + if (bits & HdChangeTracker::DirtySubdivTags) { + bits |= (HdChangeTracker::DirtyPoints | + HdChangeTracker::DirtyNormals | + HdChangeTracker::DirtyPrimvar | + HdChangeTracker::DirtyTopology | + HdChangeTracker::DirtyDisplayStyle); + } + else if (bits & HdChangeTracker::DirtyTopology) { + // Unlike basis curves, we always request refineLevel when topology is + // dirty + bits |= HdChangeTracker::DirtySubdivTags | + HdChangeTracker::DirtyDisplayStyle; + } + + // A change of material means that the Quadrangulate state may have + // changed. + if (bits & HdChangeTracker::DirtyMaterialId) { + bits |= (HdChangeTracker::DirtyPoints | + HdChangeTracker::DirtyNormals | + HdChangeTracker::DirtyPrimvar | + HdChangeTracker::DirtyTopology); + } + + // If points, display style, or topology changed, recompute normals. + if (bits & (HdChangeTracker::DirtyPoints | + HdChangeTracker::DirtyDisplayStyle | + HdChangeTracker::DirtyTopology)) { + bits |= _customDirtyBitsInUse & + (DirtySmoothNormals | DirtyFlatNormals); + } + + // If the topology is dirty, recompute custom indices resources. + if (bits & HdChangeTracker::DirtyTopology) { + bits |= _customDirtyBitsInUse & + (DirtyIndices | + DirtyHullIndices | + DirtyPointsIndices); + } + + // If normals are dirty and we are doing CPU normals + // then the normals computation needs the points primvar + // so mark points as dirty, so that the scene delegate will provide + // the data. + if ((bits & (DirtySmoothNormals | DirtyFlatNormals))/* && + !HdStGLUtils::IsGpuComputeEnabled()*/) { + bits |= HdChangeTracker::DirtyPoints; + } + + // Sometimes we don't get dirty extent notification + if (bits & (HdChangeTracker::DirtyPoints)) { + bits |= HdChangeTracker::DirtyExtent; + } + + return bits; +} + +/*! \brief Initialize the given representation of this Rprim. + + This is called prior to syncing the prim, the first time the repr + is used. + + \param reprToken the name of the repr to initalize. HdRprim has already + resolved the reprName to its final value. + + \param dirtyBits an in/out value. It is initialized to the dirty bits + from the change tracker. InitRepr can then set additional + dirty bits if additional data is required from the scene + delegate when this repr is synced. + + InitRepr occurs before dirty bit propagation. + + See HdRprim::InitRepr() +*/ +void HdVP2Mesh::_InitRepr(const TfToken& reprToken, HdDirtyBits* dirtyBits) { + _ReprVector::iterator it = std::find_if(_reprs.begin(), _reprs.end(), _ReprComparator(reprToken)); + + HdReprSharedPtr repr; + + const bool isNew = (it == _reprs.end()); + if (isNew) { + _reprs.emplace_back(reprToken, boost::make_shared()); + repr = _reprs.back().second; + } + else { + repr = it->second; + } + + auto* const param = static_cast(_delegate->GetRenderParam()); + MSubSceneContainer* subSceneContainer = param->GetContainer(); + if (!subSceneContainer) + return; + + // Selection highlight uses the wire repr for now, hoping it will be able + // to share draw items with future implementation of wireframe mode. If + // it won't, we can then define a customized "selectionHighlight" repr. + if (reprToken == HdReprTokens->wire) { + const bool selected = param->GetDrawScene().IsProxySelected() || + (param->GetDrawScene().GetPrimSelectionState(GetId()) != nullptr); + if (_EnableWireDrawItems(repr, dirtyBits, selected)) + return; + } + else if (!isNew) { + return; + } + + // set dirty bit to say we need to sync a new repr (buffer array + // ranges may change) + *dirtyBits |= HdChangeTracker::NewRepr; + + _MeshReprConfig::DescArray descs = _GetReprDesc(reprToken); + + for (size_t descIdx = 0; descIdx < descs.size(); ++descIdx) { + const HdMeshReprDesc& desc = descs[descIdx]; + + size_t numDrawItems = _GetNumDrawItemsForDesc(desc); + if (numDrawItems == 0) continue; + + for (size_t itemId = 0; itemId < numDrawItems; itemId++) { + auto* drawItem = new HdVP2DrawItem(_delegate, &_sharedData, desc); + repr->AddDrawItem(drawItem); + + const MString& renderItemName = drawItem->GetRenderItemName(); + + MHWRender::MRenderItem* const renderItem = + _CreateRenderItem(renderItemName, desc); + + _delegate->GetVP2ResourceRegistry().EnqueueCommit( + [subSceneContainer, renderItem]() { + subSceneContainer->add(renderItem); + } + ); + } + + if (desc.geomStyle == HdMeshGeomStyleHull) { + if (desc.flatShadingEnabled) { + if (!(_customDirtyBitsInUse & DirtyFlatNormals)) { + _customDirtyBitsInUse |= DirtyFlatNormals; + *dirtyBits |= DirtyFlatNormals; + } + } + else { + if (!(_customDirtyBitsInUse & DirtySmoothNormals)) { + _customDirtyBitsInUse |= DirtySmoothNormals; + *dirtyBits |= DirtySmoothNormals; + } + } + } + else if (desc.geomStyle == HdMeshGeomStyleHullEdgeOnly) { + *dirtyBits |= HdChangeTracker::DirtyTopology; + } + } +} + +/*! \brief Update the named repr object for this Rprim. + + Repr objects are created to support specific reprName tokens, and contain a list of + HdVP2DrawItems and corresponding RenderItems. +*/ +void HdVP2Mesh::_UpdateRepr(HdSceneDelegate *sceneDelegate, const TfToken& reprToken, HdDirtyBits *dirtyBits) { + + HdReprSharedPtr const &curRepr = _GetRepr(reprToken); + if (!curRepr) { + return; + } + + _MeshReprConfig::DescArray reprDescs = _GetReprDesc(reprToken); + + // Iterate through all reprdescs for the current repr to figure out if any + // of them requires smooth normals or flat normals. If either (or both) + // are required, we will calculate them once and clean the bits. + bool requireSmoothNormals = false; + bool requireFlatNormals = false; + for (size_t descIdx = 0; descIdx < reprDescs.size(); ++descIdx) { + const HdMeshReprDesc &desc = reprDescs[descIdx]; + if (desc.geomStyle == HdMeshGeomStyleHull) { + if (desc.flatShadingEnabled) { + requireFlatNormals = true; + } + else { + requireSmoothNormals = true; + } + } + } + + // For each relevant draw item, update dirty buffer sources. + const HdRepr::DrawItems& items = curRepr->GetDrawItems(); + for (HdDrawItem* item : items) { + if (HdChangeTracker::IsDirty(*dirtyBits)) { + if (auto* drawItem = static_cast(item)) { + const HdMeshReprDesc &desc = drawItem->GetReprDesc(); + _UpdateDrawItem(sceneDelegate, drawItem, dirtyBits, desc, + requireSmoothNormals, requireFlatNormals); + } + } + } +} + +/*! \brief Update the draw item + + This call happens on worker threads and results of the change are collected + in CommitState and enqueued for Commit on main-thread using CommitTasks +*/ +void HdVP2Mesh::_UpdateDrawItem( + HdSceneDelegate* sceneDelegate, + HdVP2DrawItem* drawItem, + HdDirtyBits* dirtyBits, + const HdMeshReprDesc &desc, + bool requireSmoothNormals, + bool requireFlatNormals +) { + auto* const param = static_cast(_delegate->GetRenderParam()); + MSubSceneContainer* subSceneContainer = param->GetContainer(); + if (!subSceneContainer) + return; + + CommitState stateToCommit(*drawItem, *dirtyBits); + HdVP2DrawItem::RenderItemData& drawItemData = stateToCommit._drawItemData; + + static constexpr bool writeOnly = true; + + const SdfPath& id = GetId(); + + const bool topologyDirty = + HdChangeTracker::IsTopologyDirty(*dirtyBits, id); + const bool pointsDirty = + HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->points); + const bool normalsDirty = + HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->normals); + const bool primvarDirty = + HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, HdTokens->primvar); + + if (topologyDirty) { + _topology = GetMeshTopology(sceneDelegate); + } + + if (pointsDirty) { + const VtValue value = sceneDelegate->Get(id, HdTokens->points); + _points = value.Get(); + } + + if (normalsDirty || primvarDirty) { + _UpdatePrimvarSources(sceneDelegate, *dirtyBits); + } + + TfTokenVector matPrimvars; + + const HdRenderIndex& renderIndex = sceneDelegate->GetRenderIndex(); + const HdVP2Material* material = static_cast( + renderIndex.GetSprim(HdPrimTypeTokens->material, GetMaterialId())); + if (material && material->GetSurfaceShader()) { + matPrimvars = material->GetRequiredPrimvars(); + } + else { + matPrimvars.push_back(HdTokens->displayColor); + matPrimvars.push_back(HdTokens->displayOpacity); + matPrimvars.push_back(HdTokens->normals); + } + + // If there is uniform or face-varying primvar, we have to expand shared + // vertices in CPU because OpenGL SSBO technique is not widely supported + // on GPUs and 3D APIs. + bool requiresUnsharedVertices = false; + for (const TfToken& pv : matPrimvars) { + const auto it = _primvarSourceMap.find(pv); + if (it != _primvarSourceMap.end()) { + const HdInterpolation interpolation = it->second.interpolation; + if (interpolation == HdInterpolationUniform || + interpolation == HdInterpolationFaceVarying) { + requiresUnsharedVertices = true; + break; + } + } + } + + const size_t numVertices = requiresUnsharedVertices ? + _topology.GetFaceVertexIndices().size() : _topology.GetNumPoints(); + + // Prepare index buffer. + if (topologyDirty) { + const HdMeshTopology* topologyToUse = &_topology; + HdMeshTopology unsharedTopology; + + if (requiresUnsharedVertices) { + // Fill with sequentially increasing values, starting from 0. The + // new face vertex indices will then be implicitly used to assemble + // all primvar vertex buffers. + VtIntArray newFaceVtxIds; + newFaceVtxIds.resize(_topology.GetFaceVertexIndices().size()); + std::iota(newFaceVtxIds.begin(), newFaceVtxIds.end(), 0); + + unsharedTopology = HdMeshTopology( + _topology.GetScheme(), + _topology.GetOrientation(), + _topology.GetFaceVertexCounts(), + newFaceVtxIds, + _topology.GetHoleIndices(), + _topology.GetRefineLevel() + ); + + topologyToUse = &unsharedTopology; + } + + if (desc.geomStyle == HdMeshGeomStyleHull) { + HdMeshUtil meshUtil(topologyToUse, id); + VtVec3iArray trianglesFaceVertexIndices; + VtIntArray primitiveParam; + meshUtil.ComputeTriangleIndices(&trianglesFaceVertexIndices, &primitiveParam, nullptr); + + const int numIndex = trianglesFaceVertexIndices.size() * 3; + + stateToCommit._indexBufferData = static_cast( + drawItemData._indexBuffer->acquire(numIndex, writeOnly)); + + memcpy(stateToCommit._indexBufferData, trianglesFaceVertexIndices.data(), numIndex * sizeof(int)); + } + else if (desc.geomStyle == HdMeshGeomStyleHullEdgeOnly) { + unsigned int numIndex = _GetNumOfEdgeIndices(*topologyToUse); + + stateToCommit._indexBufferData = static_cast( + drawItemData._indexBuffer->acquire(numIndex, writeOnly)); + + _FillEdgeIndices(stateToCommit._indexBufferData, *topologyToUse); + } + else { + // Index buffer data is not required for point representation. + } + } + + // Prepare position buffer. It is shared among all render items of the rprim + // so it should be updated only once when it gets dirty. + if (pointsDirty) { + void* bufferData = _positionsBuffer->acquire(numVertices, writeOnly); + if (bufferData) { + _FillPrimvarData(static_cast(bufferData), + numVertices, 0, requiresUnsharedVertices, + id, _topology, HdTokens->points, _points, HdInterpolationVertex); + + stateToCommit._positionBufferData = static_cast(bufferData); + } + } + + // Prepare normal buffer. + if (drawItemData._normalsBuffer) { + VtVec3fArray normals; + HdInterpolation interp; + + const auto it = _primvarSourceMap.find(HdTokens->normals); + if (it != _primvarSourceMap.end()) { + const VtValue& value = it->second.data; + if (ARCH_LIKELY(value.IsHolding())) { + normals = value.UncheckedGet(); + interp = it->second.interpolation; + } + } + + bool prepareNormals = false; + + // If there is authored normals, prepare buffer only when it is dirty. + // otherwise, compute smooth normals from points and adjacency and we + // have a custom dirty bit to determine whether update is needed. + if (!normals.empty()) { + prepareNormals = normalsDirty; + } + else if (requireSmoothNormals && (*dirtyBits & DirtySmoothNormals)) { + // note: normals gets dirty when points are marked as dirty, + // at change tracker. + // clear DirtySmoothNormals (this is not a scene dirtybit) + *dirtyBits &= ~DirtySmoothNormals; + + prepareNormals = true; + + Hd_VertexAdjacencySharedPtr adjacency(new Hd_VertexAdjacency()); + HdBufferSourceSharedPtr adjacencyComputation = + adjacency->GetSharedAdjacencyBuilderComputation(&_topology); + adjacencyComputation->Resolve(); // IS the adjacency updated now? + + // The topology doesn't have to reference all of the points, thus + // we compute the number of normals as required by the topology. + normals = Hd_SmoothNormals::ComputeSmoothNormals( + adjacency.get(), _topology.GetNumPoints(), _points.cdata()); + + interp = HdInterpolationVertex; + } + + if (prepareNormals && !normals.empty()) { + void* bufferData = drawItemData._normalsBuffer->acquire(numVertices, writeOnly); + if (bufferData) { + _FillPrimvarData(static_cast(bufferData), + numVertices, 0, requiresUnsharedVertices, + id, _topology, HdTokens->normals, normals, interp); + + stateToCommit._normalsBufferData = static_cast(bufferData); + } + } + } + + // Prepare color buffer. + if (desc.geomStyle == HdMeshGeomStyleHull) { + if (*dirtyBits & HdChangeTracker::DirtyMaterialId) { + if (material) { + stateToCommit._surfaceShader = material->GetSurfaceShader(); + if (stateToCommit._surfaceShader && + stateToCommit._surfaceShader->isTransparent()) { + stateToCommit._isTransparent = true; + } + } + } + + TfTokenVector::const_iterator begin = matPrimvars.cbegin(); + TfTokenVector::const_iterator end = matPrimvars.cend(); + + if (primvarDirty && + (std::find(begin, end, HdTokens->displayColor) != end || + std::find(begin, end, HdTokens->displayOpacity) != end)) { + // If color/opacity is not found, the 18% gray color will be used + // to match the default color of Hydra Storm. + VtVec3fArray colorArray(1, GfVec3f(0.18f, 0.18f, 0.18f)); + VtFloatArray alphaArray(1, 1.0f); + + HdInterpolation colorInterp = HdInterpolationConstant; + HdInterpolation alphaInterp = HdInterpolationConstant; + + auto it = _primvarSourceMap.find(HdTokens->displayColor); + if (it != _primvarSourceMap.end()) { + const VtValue& value = it->second.data; + if (value.IsHolding() && value.GetArraySize() > 0) { + colorArray = value.UncheckedGet(); + colorInterp = it->second.interpolation; + } + } + + it = _primvarSourceMap.find(HdTokens->displayOpacity); + if (it != _primvarSourceMap.end()) { + const VtValue& value = it->second.data; + if (value.IsHolding() && value.GetArraySize() > 0) { + alphaArray = value.UncheckedGet(); + alphaInterp = it->second.interpolation; + } + } + + if (colorInterp == HdInterpolationConstant && + alphaInterp == HdInterpolationConstant) { + // Use fallback shader if there is no material binding or we + // failed to create a shader instance from the material. + if (!stateToCommit._surfaceShader) { + const GfVec3f& color = colorArray[0]; + stateToCommit._surfaceShader = _delegate->GetFallbackShader( + MColor(color[0], color[1], color[2], alphaArray[0])); + } + } + else { + if (!drawItemData._colorBuffer) { + const MHWRender::MVertexBufferDescriptor desc("", + MHWRender::MGeometry::kColor, + MHWRender::MGeometry::kFloat, + 4); + + drawItemData._colorBuffer.reset( + new MHWRender::MVertexBuffer(desc)); + } + + void* bufferData = + drawItemData._colorBuffer->acquire(numVertices, writeOnly); + + // Fill color and opacity into the float4 color stream. + if (bufferData) { + _FillPrimvarData(static_cast(bufferData), + numVertices, 0, requiresUnsharedVertices, + id, _topology, HdTokens->displayColor, colorArray, colorInterp); + + _FillPrimvarData(static_cast(bufferData), + numVertices, 3, requiresUnsharedVertices, + id, _topology, HdTokens->displayOpacity, alphaArray, alphaInterp); + + stateToCommit._colorBufferData = static_cast(bufferData); + } + + // Use fallback CPV shader if there is no material binding or + // we failed to create a shader instance from the material. + if (!stateToCommit._surfaceShader) { + stateToCommit._surfaceShader = _delegate->GetFallbackCPVShader(); + } + } + + // It is possible that all elements in the opacity array are 1. + // Due to the performance indication about transparency, we have to + // traverse the array and enable transparency only when needed. + if (!stateToCommit._isTransparent) { + if (alphaInterp == HdInterpolationConstant) { + stateToCommit._isTransparent = (alphaArray[0] < 0.999f); + } + else { + for (size_t i = 0; i < alphaArray.size(); i++) { + if (alphaArray[i] < 0.999f) { + stateToCommit._isTransparent = true; + break; + } + } + } + } + } + } + + // Prepare primvar buffers required by the material. + if (material) { + for (const TfToken& pv : matPrimvars) { + // Color, opacity and normal have been prepared separately. + if ((pv == HdTokens->displayColor) || + (pv == HdTokens->displayOpacity) || + (pv == HdTokens->normals)) + continue; + + if (!HdChangeTracker::IsPrimvarDirty(*dirtyBits, id, pv)) + continue; + + const auto it = _primvarSourceMap.find(pv); + if (it == _primvarSourceMap.end()) + continue; + + const VtValue& value = it->second.data; + const HdInterpolation& interp = it->second.interpolation; + + if (!value.IsArrayValued() || value.GetArraySize() == 0) + continue; + + MHWRender::MVertexBuffer* buffer = + drawItemData._primvarBuffers[pv].get(); + + void* bufferData = nullptr; + + if (value.IsHolding()) { + if (!buffer) { + const MHWRender::MVertexBufferDescriptor desc("", + MHWRender::MGeometry::kTexture, + MHWRender::MGeometry::kFloat, 1); + + buffer = new MHWRender::MVertexBuffer(desc); + drawItemData._primvarBuffers[pv].reset(buffer); + } + + if (buffer) { + bufferData = buffer->acquire(numVertices, writeOnly); + if (bufferData) { + _FillPrimvarData(static_cast(bufferData), + numVertices, 0, requiresUnsharedVertices, + id, _topology, + pv, value.UncheckedGet(), interp); + } + } + } + else if (value.IsHolding()) { + if (!buffer) { + const MHWRender::MVertexBufferDescriptor desc("", + MHWRender::MGeometry::kTexture, + MHWRender::MGeometry::kFloat, 2); + + buffer = new MHWRender::MVertexBuffer(desc); + drawItemData._primvarBuffers[pv].reset(buffer); + } + + if (buffer) { + bufferData = buffer->acquire(numVertices, writeOnly); + if (bufferData) { + _FillPrimvarData(static_cast(bufferData), + numVertices, 0, requiresUnsharedVertices, + id, _topology, + pv, value.UncheckedGet(), interp); + } + } + } + else if (value.IsHolding()) { + if (!buffer) { + const MHWRender::MVertexBufferDescriptor desc("", + MHWRender::MGeometry::kTexture, + MHWRender::MGeometry::kFloat, 3); + + buffer = new MHWRender::MVertexBuffer(desc); + drawItemData._primvarBuffers[pv].reset(buffer); + } + + if (buffer) { + bufferData = buffer->acquire(numVertices, writeOnly); + if (bufferData) { + _FillPrimvarData(static_cast(bufferData), + numVertices, 0, requiresUnsharedVertices, + id, _topology, + pv, value.UncheckedGet(), interp); + } + } + } + else if (value.IsHolding()) { + if (!buffer) { + const MHWRender::MVertexBufferDescriptor desc("", + MHWRender::MGeometry::kTexture, + MHWRender::MGeometry::kFloat, 4); + + buffer = new MHWRender::MVertexBuffer(desc); + drawItemData._primvarBuffers[pv].reset(buffer); + } + + if (buffer) { + bufferData = buffer->acquire(numVertices, writeOnly); + if (bufferData) { + _FillPrimvarData(static_cast(bufferData), + numVertices, 0, requiresUnsharedVertices, + id, _topology, + pv, value.UncheckedGet(), interp); + } + } + } + else { + TF_WARN("Unsupported primvar array"); + } + + stateToCommit._primvarBufferDataMap[pv] = static_cast(bufferData); + } + } + + // Bounding box is per-prim shared data. + GfRange3d range; + + if (HdChangeTracker::IsExtentDirty(*dirtyBits, id)) { + range = GetExtent(sceneDelegate); + if (!range.IsEmpty()) { + _sharedData.bounds.SetRange(range); + } + + *dirtyBits &= ~HdChangeTracker::DirtyExtent; + } + else { + range = _sharedData.bounds.GetRange(); + } + + const GfVec3d& min = range.GetMin(); + const GfVec3d& max = range.GetMax(); + const MBoundingBox bounds(MPoint(min[0], min[1], min[2]), MPoint(max[0], max[1], max[2])); + + // World matrix is per-prim shared data. + GfMatrix4d transform; + + if (HdChangeTracker::IsTransformDirty(*dirtyBits, id)) { + transform = sceneDelegate->GetTransform(id); + _sharedData.bounds.SetMatrix(transform); + + *dirtyBits &= ~HdChangeTracker::DirtyTransform; + } + else { + transform = _sharedData.bounds.GetMatrix(); + } + + MMatrix worldMatrix; + transform.Get(worldMatrix.matrix); + + if (HdChangeTracker::IsVisibilityDirty(*dirtyBits, id)) { + _UpdateVisibility(sceneDelegate, dirtyBits); + } + + // If the mesh is instanced, create one new instance per transform. + // The current instancer invalidation tracking makes it hard for + // us to tell whether transforms will be dirty, so this code + // pulls them every time something changes. + if (!GetInstancerId().IsEmpty()) { + + // Retrieve instance transforms from the instancer. + HdInstancer *instancer = renderIndex.GetInstancer(GetInstancerId()); + VtMatrix4dArray transforms = + static_cast(instancer)-> + ComputeInstanceTransforms(id); + + MMatrix instanceMatrix; + + if ((desc.geomStyle == HdMeshGeomStyleHullEdgeOnly) && + !param->GetDrawScene().IsProxySelected()) { + if (auto state = param->GetDrawScene().GetPrimSelectionState(id)) { + for (const auto& indexArray : state->instanceIndices) { + for (const auto index : indexArray) { + transforms[index].Get(instanceMatrix.matrix); + instanceMatrix = worldMatrix * instanceMatrix; + stateToCommit._instanceTransforms.append(instanceMatrix); + } + } + } + } + else { + for (size_t i = 0; i < transforms.size(); ++i) { + transforms[i].Get(instanceMatrix.matrix); + instanceMatrix = worldMatrix * instanceMatrix; + stateToCommit._instanceTransforms.append(instanceMatrix); + } + } + } + + // Capture class member for lambda + MHWRender::MVertexBuffer* const positionsBuffer = _positionsBuffer.get(); + + _delegate->GetVP2ResourceRegistry().EnqueueCommit( + [drawItem, stateToCommit, param, positionsBuffer, bounds, worldMatrix]() + { + const MString& renderItemName = drawItem->GetRenderItemName(); + + MProfilingScope profilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorC_L2, renderItemName.asChar(), "HdVP2Mesh Commit Buffers"); + + MHWRender::MRenderItem* renderItem = param->GetContainer()->find(renderItemName); + if(!renderItem) { + TF_CODING_ERROR("Invalid render item: %s\n", renderItemName.asChar()); + return; + } + + MHWRender::MVertexBuffer* colorBuffer = stateToCommit._drawItemData._colorBuffer.get(); + MHWRender::MVertexBuffer* normalsBuffer = stateToCommit._drawItemData._normalsBuffer.get(); + MHWRender::MIndexBuffer* indexBuffer = stateToCommit._drawItemData._indexBuffer.get(); + + HdVP2DrawItem::PrimvarBufferMap& primvarBuffers = stateToCommit._drawItemData._primvarBuffers; + + MHWRender::MVertexBufferArray vertexBuffers; + vertexBuffers.addBuffer("positions", positionsBuffer); + + if (colorBuffer) + vertexBuffers.addBuffer("diffuseColor", colorBuffer); + + if (normalsBuffer) + vertexBuffers.addBuffer("normals", normalsBuffer); + + for (auto& entry : primvarBuffers) { + const TfToken& primvarName = entry.first; + MHWRender::MVertexBuffer* primvarBuffer = entry.second.get(); + if (primvarBuffer) { + vertexBuffers.addBuffer(primvarName.GetText(), primvarBuffer); + } + } + + // If available, something changed + if(stateToCommit._positionBufferData) + positionsBuffer->commit(stateToCommit._positionBufferData); + + // If available, something changed + if (stateToCommit._colorBufferData && colorBuffer) + colorBuffer->commit(stateToCommit._colorBufferData); + + // If available, something changed + if (stateToCommit._normalsBufferData && normalsBuffer) + normalsBuffer->commit(stateToCommit._normalsBufferData); + + // If available, something changed + for (const auto& entry : stateToCommit._primvarBufferDataMap) { + const TfToken& primvarName = entry.first; + float* primvarBufferData = entry.second; + + const auto it = primvarBuffers.find(primvarName); + if (it != primvarBuffers.end()) { + MHWRender::MVertexBuffer* primvarBuffer = it->second.get(); + if (primvarBuffer && primvarBufferData) { + primvarBuffer->commit(primvarBufferData); + } + } + } + + // If available, something changed + if (stateToCommit._indexBufferData && indexBuffer) + indexBuffer->commit(stateToCommit._indexBufferData); + + // If available, something changed + if (stateToCommit._surfaceShader != nullptr && + stateToCommit._surfaceShader != renderItem->getShader()) { + renderItem->setShader(stateToCommit._surfaceShader); + renderItem->setTreatAsTransparent(stateToCommit._isTransparent); + } + + if ((stateToCommit._dirtyBits & HdChangeTracker::DirtyVisibility) != 0) { + renderItem->enable(drawItem->IsEnabled() && drawItem->GetVisible()); + } + + ProxyRenderDelegate& drawScene = param->GetDrawScene(); + drawScene.setGeometryForRenderItem(*renderItem, vertexBuffers, *indexBuffer, &bounds); + + // Important, update instance transforms after setting geometry on render items! + auto& oldInstanceCount = stateToCommit._drawItemData._instanceCount; + auto newInstanceCount = stateToCommit._instanceTransforms.length(); + + if (oldInstanceCount > 1) { + // GPU instancing has been enabled. We cannot switch to consolidation + // without recreating render item, so we keep using GPU instancing. + if (oldInstanceCount == newInstanceCount) { + for (unsigned int i = 0; i < newInstanceCount; i++) { + // VP2 defines instance ID of the first instance to be 1. + drawScene.updateInstanceTransform(*renderItem, + i+1, stateToCommit._instanceTransforms[i]); + } + } else { + drawScene.setInstanceTransformArray(*renderItem, + stateToCommit._instanceTransforms); + } + } + else if (newInstanceCount > 1) { + // Turn off consolidation to allow GPU instancing to be used for + // multiple instances. + setWantConsolidation(*renderItem, false); + drawScene.setInstanceTransformArray(*renderItem, + stateToCommit._instanceTransforms); + } + else if (newInstanceCount == 1) { + // Special case for single instance prims. We will keep the original + // render item to allow consolidation. + renderItem->setMatrix(&stateToCommit._instanceTransforms[0]); + } + else { + // Regular non-instanced prims. Consolidation has been turned on by + // default and will be kept enabled on this case. + renderItem->setMatrix(&worldMatrix); + } + + oldInstanceCount = newInstanceCount; + }); +} + +void HdVP2Mesh::_UpdatePrimvarSources( + HdSceneDelegate* sceneDelegate, + HdDirtyBits dirtyBits) +{ + const SdfPath& id = GetId(); + + // Update _primvarSourceMap, our local cache of raw primvar data. + // This function pulls data from the scene delegate, but defers processing. + // + // While iterating primvars, we skip "points" (vertex positions) because + // the points primvar is processed separately for direct access later. We + // only call GetPrimvar on primvars that have been marked dirty. + // + // Currently, hydra doesn't have a good way of communicating changes in + // the set of primvars, so we only ever add and update to the primvar set. + + for (size_t i = 0; i < HdInterpolationCount; i++) { + const HdInterpolation interp = static_cast(i); + const HdPrimvarDescriptorVector primvars = + GetPrimvarDescriptors(sceneDelegate, interp); + + for (const HdPrimvarDescriptor& pv: primvars) { + if (HdChangeTracker::IsPrimvarDirty(dirtyBits, id, pv.name) && + pv.name != HdTokens->points) { + const VtValue value = GetPrimvar(sceneDelegate, pv.name); + _primvarSourceMap[pv.name] = { value, interp }; + } + } + } +} + +/*! \brief Create render item for points repr. +*/ +MHWRender::MRenderItem* HdVP2Mesh::_CreatePointsRenderItem(const MString& name) const +{ + MHWRender::MRenderItem* const renderItem = MHWRender::MRenderItem::Create( + name, + MHWRender::MRenderItem::DecorationItem, + MHWRender::MGeometry::kPoints + ); + + renderItem->setDrawMode(MHWRender::MGeometry::kSelectionOnly); + renderItem->castsShadows(false); + renderItem->receivesShadows(false); + renderItem->setShader(_delegate->Get3dFatPointShader()); + + MSelectionMask selectionMask(MSelectionMask::kSelectPointsForGravity); + selectionMask.addMask(MSelectionMask::kSelectMeshVerts); + renderItem->setSelectionMask(selectionMask); + + setWantConsolidation(*renderItem, true); + + return renderItem; +} + +/*! \brief Create render item for wireframe repr. +*/ +MHWRender::MRenderItem* HdVP2Mesh::_CreateWireframeRenderItem(const MString& name) const +{ + MHWRender::MRenderItem* const renderItem = MHWRender::MRenderItem::Create( + name, + MHWRender::MRenderItem::DecorationItem, + MHWRender::MGeometry::kLines + ); + + renderItem->depthPriority(MHWRender::MRenderItem::sActiveWireDepthPriority); + renderItem->castsShadows(false); + renderItem->receivesShadows(false); + renderItem->setShader(_delegate->Get3dSolidShader()); + + renderItem->setSelectionMask(MSelectionMask()); + + setWantConsolidation(*renderItem, true); + + return renderItem; +} + +/*! \brief Create render item for smoothHull repr. +*/ +MHWRender::MRenderItem* HdVP2Mesh::_CreateSmoothHullRenderItem(const MString& name) const +{ + MHWRender::MRenderItem* const renderItem = MHWRender::MRenderItem::Create( + name, + MHWRender::MRenderItem::MaterialSceneItem, + MHWRender::MGeometry::kTriangles + ); + + renderItem->setExcludedFromPostEffects(false); + renderItem->castsShadows(true); + renderItem->receivesShadows(true); + renderItem->setShader(_delegate->GetFallbackShader()); + + renderItem->setSelectionMask(MSelectionMask::kSelectMeshes); + + setWantConsolidation(*renderItem, true); + + return renderItem; +} + +/*! \brief Create render item for the specified desc. +*/ +MHWRender::MRenderItem* HdVP2Mesh::_CreateRenderItem( + const MString& name, + const HdMeshReprDesc& desc) const +{ + MHWRender::MRenderItem* renderItem = nullptr; + + switch (desc.geomStyle) { + case HdMeshGeomStyleHull: + renderItem = _CreateSmoothHullRenderItem(name); + break; + case HdMeshGeomStyleHullEdgeOnly: + renderItem = _CreateWireframeRenderItem(name); + break; + case HdMeshGeomStylePoints: + renderItem = _CreatePointsRenderItem(name); + break; + default: + TF_WARN("Unexpected geomStyle"); + break; + } + + return renderItem; +} + +/*! \brief Enable or disable the wireframe draw item. + + \return True if no draw items should be created for the repr. +*/ +bool HdVP2Mesh::_EnableWireDrawItems( + const HdReprSharedPtr& repr, + HdDirtyBits* dirtyBits, + bool enable) +{ + if (_wireItemsEnabled == enable) { + return true; + } + + _wireItemsEnabled = enable; + + if (repr) { + const HdRepr::DrawItems& items = repr->GetDrawItems(); + for (HdDrawItem* item : items) { + if (auto drawItem = static_cast(item)) { + const HdMeshReprDesc& reprDesc = drawItem->GetReprDesc(); + if (reprDesc.geomStyle == HdMeshGeomStyleHullEdgeOnly) { + drawItem->Enable(enable); + *dirtyBits |= HdChangeTracker::DirtyVisibility; + return true; + } + } + } + } + + return false; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/mesh.h b/lib/render/vp2RenderDelegate/mesh.h new file mode 100644 index 0000000000..38c34810eb --- /dev/null +++ b/lib/render/vp2RenderDelegate/mesh.h @@ -0,0 +1,114 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_MESH +#define HD_VP2_MESH + +#include "pxr/pxr.h" +#include "pxr/imaging/hd/mesh.h" + +#include "render_delegate.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class HdSceneDelegate; +class HdVP2DrawItem; +struct HdMeshReprDesc; + +/*! \brief VP2 representation of poly-mesh object. + \class HdVP2Mesh + + The prim object's main function is to bridge the scene description and the + renderable representation. The Hydra image generation algorithm will call + HdRenderIndex::SyncAll() before any drawing; this, in turn, will call + Sync() for each mesh with new data. + + Sync() is passed a set of dirtyBits, indicating which scene buffers are + dirty. It uses these to pull all of the new scene data and constructs + updated embree geometry objects. Commit of changed buffers to GPU happens + in HdVP2RenderDelegate::CommitResources(), which runs on main-thread after + all prims have been updated. +*/ +class HdVP2Mesh final : public HdMesh { +public: + HdVP2Mesh(HdVP2RenderDelegate*, const SdfPath&, const SdfPath& instancerId = SdfPath()); + + ~HdVP2Mesh() override; + + void Sync( + HdSceneDelegate*, HdRenderParam*, + HdDirtyBits*, const TfToken& reprToken) override; + + HdDirtyBits GetInitialDirtyBitsMask() const override; + +private: + HdDirtyBits _PropagateDirtyBits(HdDirtyBits) const override; + + void _InitRepr(const TfToken&, HdDirtyBits*) override; + + void _UpdateRepr(HdSceneDelegate*, const TfToken&, HdDirtyBits*); + + void _UpdateDrawItem( + HdSceneDelegate*, HdVP2DrawItem*, HdDirtyBits*, + const HdMeshReprDesc&, bool requireSmoothNormals, bool requireFlatNormals); + + bool _EnableWireDrawItems(const HdReprSharedPtr& repr, HdDirtyBits* dirtyBits, bool enable); + + void _UpdatePrimvarSources(HdSceneDelegate* sceneDelegate, HdDirtyBits dirtyBits); + + MHWRender::MRenderItem* _CreateRenderItem(const MString& name, const HdMeshReprDesc& desc) const; + MHWRender::MRenderItem* _CreateSmoothHullRenderItem(const MString& name) const; + MHWRender::MRenderItem* _CreateWireframeRenderItem(const MString& name) const; + MHWRender::MRenderItem* _CreatePointsRenderItem(const MString& name) const; + + //! Custom dirty bits used by this mesh + enum DirtyBits : HdDirtyBits { + DirtySmoothNormals = HdChangeTracker::CustomBitsBegin, + DirtyFlatNormals = (DirtySmoothNormals << 1), + DirtyIndices = (DirtyFlatNormals << 1), + DirtyHullIndices = (DirtyIndices << 1), + DirtyPointsIndices = (DirtyHullIndices << 1), + DirtyBoundingBox = (DirtyPointsIndices << 1) + }; + + HdVP2RenderDelegate* _delegate{ nullptr }; //!< VP2 render delegate for which this mesh was created + HdDirtyBits _customDirtyBitsInUse{ 0 }; //!< Storage for custom dirty bits. See _PropagateDirtyBits for details. + + // TODO: Define HdVP2MeshSharedData to hold extra shared data specific to VP2? + std::unique_ptr _positionsBuffer; //!< Per-Rprim position buffer to be shared among render items + bool _wireItemsEnabled{ false }; //!< Whether draw items for the wire repr are enabled + + //! Cached scene data. VtArrays are reference counted, so as long as we + //! only call const accessors keeping them around doesn't incur a buffer + //! copy. + HdMeshTopology _topology; + VtVec3fArray _points; + + //! A local cache of primvar scene data. "data" is a copy-on-write handle to + //! the actual primvar buffer, and "interpolation" is the interpolation mode + //! to be used. + struct PrimvarSource { + VtValue data; + HdInterpolation interpolation; + }; + TfHashMap _primvarSourceMap; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/lib/render/vp2RenderDelegate/proxyRenderDelegate.cpp b/lib/render/vp2RenderDelegate/proxyRenderDelegate.cpp new file mode 100644 index 0000000000..041cb1a308 --- /dev/null +++ b/lib/render/vp2RenderDelegate/proxyRenderDelegate.cpp @@ -0,0 +1,563 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "proxyRenderDelegate.h" +#include "render_delegate.h" + +#include "pxr/base/tf/diagnostic.h" +#include "pxr/usdImaging/usdImaging/delegate.h" +#include "pxr/imaging/hdx/renderTask.h" +#include "pxr/imaging/hdx/selectionTracker.h" +#include "pxr/imaging/hdx/taskController.h" +#include "pxr/imaging/hd/enums.h" +#include "pxr/imaging/hd/mesh.h" +#include "pxr/imaging/hd/repr.h" +#include "pxr/imaging/hd/rprimCollection.h" + +#include "../../nodes/proxyShapeBase.h" +#include "../../nodes/stageData.h" +#include "../../utils/util.h" + +#include +#include +#include +#include +#include + +#if defined(WANT_UFE_BUILD) +#include "ufe/sceneItem.h" +#include "ufe/runTimeMgr.h" +#include "ufe/globalSelection.h" +#include "ufe/observableSelection.h" +#include "ufe/selectionNotification.h" +#endif + +PXR_NAMESPACE_OPEN_SCOPE + +namespace +{ + MGlobal::ListAdjustment GetListAdjustment() + { + // Keyboard modifiers can be queried from QApplication::keyboardModifiers() + // in case running MEL command leads to performance hit. On the other hand + // the advantage of using MEL command is the platform-agnostic state of the + // CONTROL key that it provides for aligning to Maya's implementation. + int modifiers = 0; + MGlobal::executeCommand("getModifiers", modifiers); + + const bool shiftHeld = (modifiers % 2); + const bool ctrlHeld = (modifiers / 4 % 2); + + MGlobal::ListAdjustment listAdjustment = MGlobal::kReplaceList; + + if (shiftHeld && ctrlHeld) + { + listAdjustment = MGlobal::kAddToList; + } + else if (ctrlHeld) + { + listAdjustment = MGlobal::kRemoveFromList; + } + else if (shiftHeld) + { + listAdjustment = MGlobal::kXORWithList; + } + + return listAdjustment; + } + +#if defined(WANT_UFE_BUILD) + class UfeSelectionObserver : public Ufe::Observer + { + public: + UfeSelectionObserver(ProxyRenderDelegate& proxyRenderDelegate) + : Ufe::Observer() + , _proxyRenderDelegate(proxyRenderDelegate) + { + } + + void operator()(const Ufe::Notification& notification) override + { + // During Maya file read, each node will be selected in turn, so we get + // notified for each node in the scene. Prune this out. + if (MFileIO::isOpeningFile()) { + return; + } + + auto selectionChanged = + dynamic_cast(¬ification); + if (selectionChanged != nullptr) { + _proxyRenderDelegate.SelectionChanged(); + } + } + + private: + ProxyRenderDelegate& _proxyRenderDelegate; + }; +#endif + +} // namespace + +//! \brief Draw classification used during plugin load to register in VP2 +const MString ProxyRenderDelegate::drawDbClassification( + TfStringPrintf("drawdb/subscene/vp2RenderDelegate/%s", + MayaUsdProxyShapeBaseTokens->MayaTypeName.GetText()).c_str()); + +//! \brief Factory method registered at plugin load +MHWRender::MPxSubSceneOverride* ProxyRenderDelegate::Creator(const MObject& obj) { + return new ProxyRenderDelegate(obj); +} + +//! \brief Constructor +ProxyRenderDelegate::ProxyRenderDelegate(const MObject& obj) + : MHWRender::MPxSubSceneOverride(obj) + , _mObject(obj) { +} + +//! \brief Destructor +ProxyRenderDelegate::~ProxyRenderDelegate() { + delete _sceneDelegate; + delete _taskController; + delete _renderIndex; + delete _renderDelegate; +} + +//! \brief This drawing routine supports all devices (DirectX and OpenGL) +MHWRender::DrawAPI ProxyRenderDelegate::supportedDrawAPIs() const { + return MHWRender::kAllDevices; +} + +#if defined(MAYA_ENABLE_UPDATE_FOR_SELECTION) +//! \brief Enable subscene update in selection passes for deferred update of selection render items. +bool ProxyRenderDelegate::enableUpdateForSelection() const { + return true; +} +#endif + +//! \brief Always requires update since changes are tracked by Hydraw change tracker and it will guarantee minimal update +bool ProxyRenderDelegate::requiresUpdate(const MSubSceneContainer& container, const MFrameContext& frameContext) const { + return true; +} + +//! \brief Return pointer to DG proxy shape node +MayaUsdProxyShapeBase* ProxyRenderDelegate::getProxyShape() const { + const MFnDependencyNode depNodeFn(_mObject); + MayaUsdProxyShapeBase* usdSubSceneShape = static_cast(depNodeFn.userNode()); + return usdSubSceneShape; +} + +//! \brief One time initialization of this drawing routine +void ProxyRenderDelegate::_InitRenderDelegate() { + // No need to run all the checks if we got till the end + if (_isInitialized()) + return; + + MStatus status; + MayaUsdProxyShapeBase* usdSubSceneShape = getProxyShape(); + if (!usdSubSceneShape) + return; + + if (!_usdStage) { + _usdStage = usdSubSceneShape->getUsdStage(); + } + + if (!_renderDelegate) { + MProfilingScope subProfilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorD_L1, "Allocate VP2RenderDelegate"); + _renderDelegate = new HdVP2RenderDelegate(*this); + } + + if (!_renderIndex) { + MProfilingScope subProfilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorD_L1, "Allocate RenderIndex"); + _renderIndex = HdRenderIndex::New(_renderDelegate); + } + + if (!_sceneDelegate) { + MProfilingScope subProfilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorD_L1, "Allocate SceneDelegate"); + + SdfPath delegateID = SdfPath::AbsoluteRootPath().AppendChild(TfToken(TfStringPrintf( + "Proxy_%s_%p", usdSubSceneShape->name().asChar(), usdSubSceneShape))); + + _sceneDelegate = new UsdImagingDelegate(_renderIndex, delegateID); + + _taskController = new HdxTaskController(_renderIndex, + delegateID.AppendChild(TfToken(TfStringPrintf("_UsdImaging_VP2_%p", this))) ); + + // Assign a collection of active representations, then each Rprim will + // initialize and update draw data for these active repr(s). If update + // for selection is enabled, the draw data for the "points" repr won't + // be prepared until point snapping is activated; otherwise they have + // to be early prepared for potential activation of point snapping. + _defaultCollection.reset(new HdRprimCollection(HdTokens->geometry, +#if defined(MAYA_ENABLE_UPDATE_FOR_SELECTION) + HdReprSelector(HdReprTokens->smoothHull) +#else + HdReprSelector(HdReprTokens->smoothHull, TfToken(), HdReprTokens->points) +#endif + )); + + _taskController->SetCollection(*_defaultCollection); + + _selectionHighlightCollection.reset(new HdRprimCollection(HdTokens->geometry, + HdReprSelector(HdReprTokens->wire) + )); + + _selection.reset(new HdSelection); + +#if defined(WANT_UFE_BUILD) + if (!_ufeSelectionObserver) { + auto globalSelection = Ufe::GlobalSelection::get(); + if (globalSelection) { + _ufeSelectionObserver = std::make_shared(*this); + globalSelection->addObserver(_ufeSelectionObserver); + } + } +#endif + + // We don't really need any HdTask because VP2RenderDelegate uses Hydra + // engine for data preparation only, but we have to add a dummy render + // task to bootstrap data preparation. + const HdTaskSharedPtrVector tasks = _taskController->GetRenderingTasks(); + for (const HdTaskSharedPtr& task : tasks) { + if (dynamic_cast(task.get())) { + _dummyTasks.push_back(task); + break; + } + } + } +} + +//! \brief Populate render index with prims coming from scene delegate. +//! \return True when delegate is ready to draw +bool ProxyRenderDelegate::_Populate() { + if (!_isInitialized()) + return false; + + auto* proxyShape = getProxyShape(); + if (_usdStage && (!_isPopulated || proxyShape->getExcludePrimPathsVersion() != _excludePrimPathsVersion) ) { + MProfilingScope subProfilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorD_L1, "Populate"); + + // It might have been already populated, clear it if so. + SdfPathVector excludePrimPaths = proxyShape->getExcludePrimPaths(); + for (auto& excludePrim : excludePrimPaths) { + SdfPath indexPath = _sceneDelegate->ConvertCachePathToIndexPath(excludePrim); + if (_renderIndex->HasRprim(indexPath)) { + _renderIndex->RemoveRprim(indexPath); + } + } + + _sceneDelegate->Populate(_usdStage->GetPseudoRoot(),excludePrimPaths); + + _isPopulated = true; + _excludePrimPathsVersion = proxyShape->getExcludePrimPathsVersion(); + } + + return _isPopulated; +} + +//! \brief Synchronize USD scene delegate time with Maya's scene time. +void ProxyRenderDelegate::_UpdateTime() { + MProfilingScope profilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorC_L1, "Update Time"); + + MayaUsdProxyShapeBase* usdSubSceneShape = getProxyShape(); + if(usdSubSceneShape && _sceneDelegate) { + UsdTimeCode timeCode = usdSubSceneShape->getTime(); + _sceneDelegate->SetTime(timeCode); + } +} + +//! \brief Execute Hydra engine which will performe minimal update VP2 state update based on change tracker. +void ProxyRenderDelegate::_Execute(const MHWRender::MFrameContext& frameContext) { + MProfilingScope profilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorC_L1, "Execute"); + +#if defined(MAYA_ENABLE_UPDATE_FOR_SELECTION) + // Since Maya 2020, subscene update can be invoked in a selection pass. + const bool inSelectionPass = (frameContext.getSelectionInfo() != nullptr); + const bool inPointSnapping = pointSnappingActive(); + + // Query selection adjustment mode only if the update is triggered in a selection pass. + _globalListAdjustment = (inSelectionPass && !inPointSnapping) ? GetListAdjustment() : MGlobal::kReplaceList; +#else + // Before Maya 2020, subscene update would never be invoked in a selection pass. + constexpr bool inSelectionPass = false; + constexpr bool inPointSnapping = false; +#endif + + // HdC_TODO: we need to figure out how to use Hydra repr more effectively + // when more reprs are coming. + if (inSelectionPass) { + if (inPointSnapping) { + // Update display reprs at the first frame where point snapping is activated. + if (!_defaultCollection->GetReprSelector().Contains(HdReprTokens->points)) { + const HdReprSelector under(TfToken(), TfToken(), HdReprTokens->points); + const HdReprSelector reprSelector = + _defaultCollection->GetReprSelector().CompositeOver(under); + _defaultCollection->SetReprSelector(reprSelector); + _taskController->SetCollection(*_defaultCollection); + } + } + } + else if (_selectionChanged) { + _selectionChanged = false; + + if (_UpdateSelectionHighlight()) { + // Update display reprs at the first frame where selection highlight is activated. + if (!_defaultCollection->GetReprSelector().Contains(HdReprTokens->wire)) { + const HdReprSelector under(TfToken(), HdReprTokens->wire, TfToken()); + const HdReprSelector reprSelector = + _defaultCollection->GetReprSelector().CompositeOver(under); + _defaultCollection->SetReprSelector(reprSelector); + _taskController->SetCollection(*_defaultCollection); + } + } + } + + _engine.Execute(_renderIndex, &_dummyTasks); +} + +//! \brief Main update entry from subscene override. +void ProxyRenderDelegate::update(MSubSceneContainer& container, const MFrameContext& frameContext) { + MProfilingScope profilingScope(HdVP2RenderDelegate::sProfilerCategory, + MProfiler::kColorD_L1, "ProxyRenderDelegate::update"); + + _InitRenderDelegate(); + + // Give access to current time and subscene container to the rest of render delegate world via render param's. + auto* param = reinterpret_cast(_renderDelegate->GetRenderParam()); + param->BeginUpdate(container, _sceneDelegate->GetTime()); + + if (_Populate()) { + _UpdateTime(); + _Execute(frameContext); + } + param->EndUpdate(); +} + +//! \brief Switch to component-level selection for point snapping. +void ProxyRenderDelegate::updateSelectionGranularity( + const MDagPath& path, + MHWRender::MSelectionContext& selectionContext) +{ + if (pointSnappingActive()) + { + selectionContext.setSelectionLevel(MHWRender::MSelectionContext::kComponent); + } +} + +//! \brief UFE-based selection for both instanced and non-instanced cases. +bool ProxyRenderDelegate::getInstancedSelectionPath( + const MHWRender::MRenderItem& renderItem, + const MHWRender::MIntersection& intersection, + MDagPath& dagPath) const +{ +#if defined(WANT_UFE_BUILD) + // When point snapping, only the point position matters, so return false + // to use the DAG path from the default implementation and avoid the UFE + // global selection list to be updated. + if (pointSnappingActive()) + return false; + + auto handler = Ufe::RunTimeMgr::instance().hierarchyHandler(USD_UFE_RUNTIME_ID); + if (handler == nullptr) + return false; + + MayaUsdProxyShapeBase* proxyShape = getProxyShape(); + if (proxyShape == nullptr) + return false; + + // Extract id of the owner Rprim. A SdfPath directly created from the render + // item name could be ill-formed if the render item represents instancing: + // "/TreePatch/Tree_1.proto_leaves_id0/DrawItem_xxxxxxxx". Thus std::string + // is used instead to extract Rprim id. + const std::string renderItemName = renderItem.name().asChar(); + const auto pos = renderItemName.find_last_of(USD_UFE_SEPARATOR); + SdfPath rprimId(renderItemName.substr(0, pos)); + + // If the selection hit comes from an instanced render item, its instance + // transform matrices should have been sorted according to USD instance ID, + // therefore drawInstID is usdInstID plus 1 considering VP2 defines the + // instance ID of the first instance as 1. + const int drawInstID = intersection.instanceID(); + if (drawInstID > 0) { + const int usdInstID = drawInstID - 1; + rprimId = _sceneDelegate->GetPathForInstanceIndex(rprimId, usdInstID, nullptr); + } + + const SdfPath usdPath(_sceneDelegate->ConvertIndexPathToCachePath(rprimId)); + + const Ufe::PathSegment pathSegment(usdPath.GetText(), USD_UFE_RUNTIME_ID, USD_UFE_SEPARATOR); + const Ufe::SceneItem::Ptr& si = handler->createItem(proxyShape->ufePath() + pathSegment); + if (!si) { + TF_WARN("UFE runtime is not updated for the USD stage. Please save scene and reopen."); + return false; + } + + auto globalSelection = Ufe::GlobalSelection::get(); + + // If update for selection is enabled, we can query selection list adjustment + // mode once per selection update to avoid any potential performance hit due + // to MEL command execution. +#if defined(MAYA_ENABLE_UPDATE_FOR_SELECTION) + switch (_globalListAdjustment) +#else + switch (GetListAdjustment()) +#endif + { + case MGlobal::kReplaceList: + // The list has been cleared before viewport selection runs, so we + // can add the new hits directly. UFE selection list is a superset + // of Maya selection list, calling clear()/replaceWith() on UFE + // selection list would clear Maya selection list. + globalSelection->append(si); + break; + case MGlobal::kAddToList: + globalSelection->append(si); + break; + case MGlobal::kRemoveFromList: + globalSelection->remove(si); + break; + case MGlobal::kXORWithList: + if (!globalSelection->remove(si)) + { + globalSelection->append(si); + } + break; + default: + TF_WARN("Unexpected MGlobal::ListAdjustment enum for selection."); + break; + } + + return true; +#else + return false; +#endif +} + +//! \brief Notify of selection change. +void ProxyRenderDelegate::SelectionChanged() +{ + _selectionChanged = true; +} + +//! \brief Filter selection for Rprims under the proxy shape. +void ProxyRenderDelegate::_FilterSelection() +{ +#if defined(WANT_UFE_BUILD) + MayaUsdProxyShapeBase* proxyShape = getProxyShape(); + if (proxyShape == nullptr) { + return; + } + + _selection.reset(new HdSelection); + + const auto proxyPath = proxyShape->ufePath(); + const auto globalSelection = Ufe::GlobalSelection::get(); + + for (const Ufe::SceneItem::Ptr& item : *globalSelection) { + if (item->runTimeId() != USD_UFE_RUNTIME_ID) { + continue; + } + + const Ufe::Path::Segments& segments = item->path().getSegments(); + if ((segments.size() != 2) || (proxyPath != segments[0])) { + continue; + } + + const SdfPath usdPath(segments[1].string()); + const SdfPath idxPath(_sceneDelegate->ConvertCachePathToIndexPath(usdPath)); + + _sceneDelegate->PopulateSelection(HdSelection::HighlightModeSelect, + idxPath, UsdImagingDelegate::ALL_INSTANCES, _selection); + } +#endif +} + +/*! \brief Update for selection highlight + + \return True if selection highlight needs to be shown. +*/ +bool ProxyRenderDelegate::_UpdateSelectionHighlight() +{ + bool retVal = false; + + const bool wasProxySelected = _isProxySelected; + + MDagPath proxyDagPath; + MDagPath::getAPathTo(_mObject, proxyDagPath); + auto status = MHWRender::MGeometryUtilities::displayStatus(proxyDagPath); + _isProxySelected = ((status == MHWRender::kHilite) || (status == MHWRender::kLead)); + + constexpr HdSelection::HighlightMode mode = HdSelection::HighlightModeSelect; + + SdfPathVector rootPaths; + + if (_isProxySelected) { + rootPaths.push_back(SdfPath::AbsoluteRootPath()); + retVal = true; + } + else if (wasProxySelected) { + rootPaths.push_back(SdfPath::AbsoluteRootPath()); + _FilterSelection(); + retVal = !_selection->GetSelectedPrimPaths(mode).empty(); + } + else { + SdfPathVector oldPaths = _selection->GetSelectedPrimPaths(mode); + _FilterSelection(); + SdfPathVector newPaths = _selection->GetSelectedPrimPaths(mode); + + if (!oldPaths.empty() || !newPaths.empty()) { + rootPaths = std::move(oldPaths); + rootPaths.reserve(rootPaths.size() + newPaths.size()); + rootPaths.insert(rootPaths.end(), newPaths.begin(), newPaths.end()); + } + + retVal = !newPaths.empty(); + } + + if (!rootPaths.empty()) { + _inSelectionHighlightUpdate = true; + + _selectionHighlightCollection->SetRootPaths(rootPaths); + _taskController->SetCollection(*_selectionHighlightCollection); + _engine.Execute(_renderIndex, &_dummyTasks); + _taskController->SetCollection(*_defaultCollection); + + _inSelectionHighlightUpdate = false; + } + + return retVal; +} + +//! \brief Query whether the proxy is selected. +bool ProxyRenderDelegate::IsProxySelected() const +{ + return _isProxySelected; +} + +//! \brief Query the selection state of a given prim. +const HdSelection::PrimSelectionState* +ProxyRenderDelegate::GetPrimSelectionState(const SdfPath& path) const +{ + return (_selection == nullptr) ? nullptr : + _selection->GetPrimSelectionState(HdSelection::HighlightModeSelect, path); +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/proxyRenderDelegate.h b/lib/render/vp2RenderDelegate/proxyRenderDelegate.h new file mode 100644 index 0000000000..3ed998cf8b --- /dev/null +++ b/lib/render/vp2RenderDelegate/proxyRenderDelegate.h @@ -0,0 +1,181 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef PROXY_RENDER_DELEGATE +#define PROXY_RENDER_DELEGATE + +#include "pxr/pxr.h" + +#include "pxr/imaging/hd/engine.h" +#include "pxr/imaging/hd/selection.h" +#include "pxr/imaging/hd/task.h" +#include "pxr/usd/sdf/path.h" +#include "pxr/usd/usd/prim.h" + +#include "../../base/api.h" + +#include +#include +#include +#include +#include +#include + +#include + +#if defined(WANT_UFE_BUILD) +#include "ufe/observer.h" +#endif + +// Conditional compilation due to Maya API gap. +#if MAYA_API_VERSION >= 20200000 +#define MAYA_ENABLE_UPDATE_FOR_SELECTION +#endif + +PXR_NAMESPACE_OPEN_SCOPE + +class HdRenderDelegate; +class HdRenderIndex; +class HdRprimCollection; +class UsdImagingDelegate; +class MayaUsdProxyShapeBase; +class HdxTaskController; + +/*! \brief USD Proxy rendering routine via VP2 MPxSubSceneOverride + + This drawing routine leverages HdVP2RenderDelegate for synchronization + of data between scene delegate and VP2. Final rendering is done by VP2 + as part of subscene override mechanism. + + USD Proxy can be rendered in a number of ways, to enable this drawing + path set VP2_RENDER_DELEGATE_PROXY env variable before loading USD + plugin. +*/ +class ProxyRenderDelegate : public MHWRender::MPxSubSceneOverride +{ + ProxyRenderDelegate(const MObject& obj); + +public: + MAYAUSD_CORE_PUBLIC + ~ProxyRenderDelegate() override; + + MAYAUSD_CORE_PUBLIC + static const MString drawDbClassification; + + MAYAUSD_CORE_PUBLIC + static MHWRender::MPxSubSceneOverride* Creator(const MObject& obj); + + MAYAUSD_CORE_PUBLIC + MHWRender::DrawAPI supportedDrawAPIs() const override; + +#if defined(MAYA_ENABLE_UPDATE_FOR_SELECTION) + MAYAUSD_CORE_PUBLIC + bool enableUpdateForSelection() const override; +#endif + + MAYAUSD_CORE_PUBLIC + bool requiresUpdate(const MSubSceneContainer& container, const MFrameContext& frameContext) const override; + + MAYAUSD_CORE_PUBLIC + void update(MSubSceneContainer& container, const MFrameContext& frameContext) override; + + MAYAUSD_CORE_PUBLIC + void updateSelectionGranularity( + const MDagPath& path, + MSelectionContext& selectionContext) override; + + MAYAUSD_CORE_PUBLIC + bool getInstancedSelectionPath( + const MRenderItem& renderItem, + const MIntersection& intersection, + MDagPath& dagPath) const override; + + MAYAUSD_CORE_PUBLIC + MayaUsdProxyShapeBase* getProxyShape() const; + + MAYAUSD_CORE_PUBLIC + void SelectionChanged(); + + MAYAUSD_CORE_PUBLIC + bool IsProxySelected() const; + + MAYAUSD_CORE_PUBLIC + bool InSelectionHighlightUpdate() const { return _inSelectionHighlightUpdate; } + + MAYAUSD_CORE_PUBLIC + const HdSelection::PrimSelectionState* GetPrimSelectionState(const SdfPath& path) const; + +private: + ProxyRenderDelegate(const ProxyRenderDelegate&) = delete; + ProxyRenderDelegate& operator=(const ProxyRenderDelegate&) = delete; + + void _InitRenderDelegate(); + bool _Populate(); + void _UpdateTime(); + void _Execute(const MHWRender::MFrameContext& frameContext); + + bool _isInitialized(); + + void _FilterSelection(); + bool _UpdateSelectionHighlight(); + + MObject _mObject; //!< Proxy shape MObject + + // USD & Hydra Objects + HdEngine _engine; //!< Hydra engine responsible for running synchronization between scene delegate and VP2RenderDelegate + HdTaskSharedPtrVector _dummyTasks; //!< Dummy task to bootstrap data preparation inside Hydra engine + UsdStageRefPtr _usdStage; //!< USD stage pointer + HdRenderDelegate* _renderDelegate{ nullptr }; //!< VP2RenderDelegate + HdRenderIndex* _renderIndex{ nullptr }; //!< Flattened representation of client scene graph + HdxTaskController* _taskController{ nullptr }; //!< Task controller necessary for execution with hydra engine (we don't really need it, but there doesn't seem to be a way to get synchronization running without it) + UsdImagingDelegate* _sceneDelegate{ nullptr }; //!< USD scene delegate + + size_t _excludePrimPathsVersion{ 0 }; //!< Last version of exluded prims used during render index populate + + bool _isPopulated{ false }; //!< If false, scene delegate wasn't populated yet within render index + bool _selectionChanged{ false }; //!< Whether there is any selection change or not + bool _isProxySelected{ false }; //!< Whether the proxy shape is selected + bool _inSelectionHighlightUpdate{ false };//!< Set to true when selection highlight update is executing + + //! A collection of Rprims to prepare render data for specified reprs + std::unique_ptr _defaultCollection; + + //! A collection of Rprims to update selection highlight + std::unique_ptr _selectionHighlightCollection; + + //! A collection of Rprims being selected + HdSelectionSharedPtr _selection; + +#if defined(WANT_UFE_BUILD) + //! Observer for UFE global selection change + Ufe::Observer::Ptr _ufeSelectionObserver; +#endif + +#if defined(MAYA_ENABLE_UPDATE_FOR_SELECTION) + //! Adjustment mode for global selection list: ADD, REMOVE, REPLACE, XOR + MGlobal::ListAdjustment _globalListAdjustment; +#endif +}; + +/*! \brief Is this object properly initialized and can start receiving updates. Once this is done, render index needs to be populated and then we rely on change tracker. +*/ +inline bool ProxyRenderDelegate::_isInitialized() { + return (_sceneDelegate != nullptr); +} + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/lib/render/vp2RenderDelegate/render_delegate.cpp b/lib/render/vp2RenderDelegate/render_delegate.cpp new file mode 100644 index 0000000000..a31be5ac88 --- /dev/null +++ b/lib/render/vp2RenderDelegate/render_delegate.cpp @@ -0,0 +1,661 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "render_delegate.h" + +#include "material.h" +#include "mesh.h" + +#include "render_pass.h" +#include "instancer.h" + +#include "pxr/imaging/hd/bprim.h" +#include "pxr/imaging/hd/camera.h" +#include "pxr/imaging/hd/instancer.h" +#include "pxr/imaging/hd/resourceRegistry.h" +#include "pxr/imaging/hd/rprim.h" +#include "pxr/imaging/hd/tokens.h" + +#include + +#include + +#include "tbb/spin_rw_mutex.h" +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +namespace +{ + /*! \brief List of supported Rprims by VP2 render delegate + */ + inline const TfTokenVector& _SupportedRprimTypes() { + static const TfTokenVector r{ HdPrimTypeTokens->mesh }; + return r; + } + + /*! \brief List of supported Sprims by VP2 render delegate + */ + inline const TfTokenVector& _SupportedSprimTypes() { + static const TfTokenVector r{ + HdPrimTypeTokens->material, HdPrimTypeTokens->camera }; + return r; + } + + /*! \brief List of supported Bprims by VP2 render delegate + */ + inline const TfTokenVector& _SupportedBprimTypes() { + static const TfTokenVector r{ HdPrimTypeTokens->texture }; + return r; + } + + const MString _diffuseColorParameterName = "diffuseColor"; //!< Shader parameter name + const MString _solidColorParameterName = "solidColor"; //!< Shader parameter name + const MString _pointSizeParameterName = "pointSize"; //!< Shader parameter name + const MString _fallbackShaderName = "FallbackShader"; //!< Name of the fallback shader + const MString _structOutputName = "outSurfaceFinal"; //!< Output struct name of the fallback shader + + /*! \brief Color hash helper class, used by shader registry + */ + struct MColorHash + { + std::size_t operator()(const MColor& color) const + { + std::size_t seed = 0; + boost::hash_combine(seed, color.r); + boost::hash_combine(seed, color.g); + boost::hash_combine(seed, color.b); + boost::hash_combine(seed, color.a); + return seed; + } + }; + + /*! \brief Color-indexed shader map. + */ + using MShaderMap = std::unordered_map< + MColor, + MHWRender::MShaderInstance*, + MColorHash + >; + + /*! \brief Shader cache. + */ + class MShaderCache final + { + public: + /*! \brief Initialize shaders. + */ + void Initialize() + { + if (_isInitialized) + return; + + MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer(); + const MHWRender::MShaderManager* shaderMgr = + renderer ? renderer->getShaderManager() : nullptr; + if (!TF_VERIFY(shaderMgr)) + return; + + _fallbackCPVShader = shaderMgr->getFragmentShader( + "FallbackCPVShader", _structOutputName, true); + + TF_VERIFY(_fallbackCPVShader); + + _3dSolidShader = shaderMgr->getStockShader( + MHWRender::MShaderManager::k3dSolidShader); + + if (TF_VERIFY(_3dSolidShader)) { + constexpr float color[] = { 0.056f, 1.0f, 0.366f, 1.0f }; + _3dSolidShader->setParameter(_solidColorParameterName, color); + } + + _3dFatPointShader = shaderMgr->getStockShader( + MHWRender::MShaderManager::k3dFatPointShader); + + if (TF_VERIFY(_3dFatPointShader)) { + constexpr float white[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + constexpr float size[] = { 5.0, 5.0 }; + + _3dFatPointShader->setParameter(_solidColorParameterName, white); + _3dFatPointShader->setParameter(_pointSizeParameterName, size); + } + + _isInitialized = true; + } + + /*! \brief Returns a fallback CPV shader instance when no material is bound. + */ + MHWRender::MShaderInstance* GetFallbackCPVShader() const { + return _fallbackCPVShader; + } + + /*! \brief Returns a 3d green shader that can be used for selection highlight. + */ + MHWRender::MShaderInstance* Get3dSolidShader() const { + return _3dSolidShader; + } + + /*! \brief Returns a white 3d fat point shader. + */ + MHWRender::MShaderInstance* Get3dFatPointShader() const { + return _3dFatPointShader; + } + + /*! \brief Returns a fallback shader instance when no material is bound. + + This method is keeping registry of all fallback shaders generated, + allowing only one instance per color which enables consolidation of + draw calls with same shader instance. + + \param color Color to set on given shader instance + + \return A new or existing copy of shader instance with given color + */ + MHWRender::MShaderInstance* GetFallbackShader(const MColor& color) + { + // Look for it first with reader lock + { + tbb::reader_writer_lock::scoped_lock_read lockToRead(_fallbackShaderMutex); + + auto it = _fallbackShaders.find(color); + if (it != _fallbackShaders.end()) { + return it->second; + } + } + + MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer(); + const MHWRender::MShaderManager* shaderMgr = + renderer ? renderer->getShaderManager() : nullptr; + if (!TF_VERIFY(shaderMgr)) + return nullptr; + + MHWRender::MShaderInstance* shader = shaderMgr->getFragmentShader( + _fallbackShaderName, _structOutputName, true); + + if (TF_VERIFY(shader)) { + float diffuseColor[] = { color.r, color.g, color.b, color.a }; + shader->setParameter(_diffuseColorParameterName, diffuseColor); + + tbb::reader_writer_lock::scoped_lock lockToWrite(_fallbackShaderMutex); + + // Double check that it wasn't inserted by another thread + auto it = _fallbackShaders.find(color); + if (it != _fallbackShaders.end()) { + return it->second; + } + + // Insert instance we just created + _fallbackShaders[color] = shader; + } + + return shader; + } + + private: + bool _isInitialized { false }; //!< Whether the shader cache is initialized + + tbb::reader_writer_lock _fallbackShaderMutex; //!< Synchronization used to protect concurrent read from serial writes + MShaderMap _fallbackShaders; //!< Shader registry used by fallback shaders + + MHWRender::MShaderInstance* _fallbackCPVShader { nullptr }; //!< Fallback shader with CPV support + MHWRender::MShaderInstance* _3dSolidShader { nullptr }; //!< 3d shader for solid color + MHWRender::MShaderInstance* _3dFatPointShader { nullptr }; //!< 3d shader for points + }; + + MShaderCache sShaderCache; //!< Global shader cache to minimize the number of unique shaders. + + /*! \brief Sampler state desc hash helper class, used by sampler state cache. + */ + struct MSamplerStateDescHash + { + std::size_t operator()(const MHWRender::MSamplerStateDesc& desc) const + { + std::size_t seed = 0; + boost::hash_combine(seed, desc.filter); + boost::hash_combine(seed, desc.comparisonFn); + boost::hash_combine(seed, desc.addressU); + boost::hash_combine(seed, desc.addressV); + boost::hash_combine(seed, desc.addressW); + boost::hash_combine(seed, desc.borderColor[0]); + boost::hash_combine(seed, desc.borderColor[1]); + boost::hash_combine(seed, desc.borderColor[2]); + boost::hash_combine(seed, desc.borderColor[3]); + boost::hash_combine(seed, desc.mipLODBias); + boost::hash_combine(seed, desc.minLOD); + boost::hash_combine(seed, desc.maxLOD); + boost::hash_combine(seed, desc.maxAnisotropy); + boost::hash_combine(seed, desc.coordCount); + boost::hash_combine(seed, desc.elementIndex); + return seed; + } + }; + + /*! Sampler state desc equality helper class, used by sampler state cache. + */ + struct MSamplerStateDescEquality + { + bool operator() ( + const MHWRender::MSamplerStateDesc& a, + const MHWRender::MSamplerStateDesc& b) const + { + return ( + a.filter == b.filter && + a.comparisonFn == b.comparisonFn && + a.addressU == b.addressU && + a.addressV == b.addressV && + a.addressW == b.addressW && + a.borderColor[0] == b.borderColor[0] && + a.borderColor[1] == b.borderColor[1] && + a.borderColor[2] == b.borderColor[2] && + a.borderColor[3] == b.borderColor[3] && + a.mipLODBias == b.mipLODBias && + a.minLOD == b.minLOD && + a.maxLOD == b.maxLOD && + a.maxAnisotropy == b.maxAnisotropy && + a.coordCount == b.coordCount && + a.elementIndex == b.elementIndex + ); + } + }; + + using MSamplerStateCache = std::unordered_map< + MHWRender::MSamplerStateDesc, + const MHWRender::MSamplerState*, + MSamplerStateDescHash, + MSamplerStateDescEquality + >; + + MSamplerStateCache sSamplerStates; //!< Sampler state cache + tbb::spin_rw_mutex sSamplerRWMutex; //!< Synchronization used to protect concurrent read from serial writes + +} // namespace + +const int HdVP2RenderDelegate::sProfilerCategory = MProfiler::addCategory( +#if MAYA_API_VERSION >= 20190000 + "HdVP2RenderDelegate", "HdVP2RenderDelegate" +#else + "HdVP2RenderDelegate" +#endif +); + +std::mutex HdVP2RenderDelegate::_mutexResourceRegistry; +std::atomic_int HdVP2RenderDelegate::_counterResourceRegistry; +HdResourceRegistrySharedPtr HdVP2RenderDelegate::_resourceRegistry; + +/*! \brief Constructor. +*/ +HdVP2RenderDelegate::HdVP2RenderDelegate(ProxyRenderDelegate& drawScene) { + _id = SdfPath(TfToken(TfStringPrintf("/HdVP2RenderDelegate_%p", this))); + + std::lock_guard guard(_mutexResourceRegistry); + if (_counterResourceRegistry.fetch_add(1) == 0) { + _resourceRegistry.reset(new HdResourceRegistry()); + } + + _renderParam.reset(new HdVP2RenderParam(drawScene)); + + // The shader cache should be initialized after mayaUsd loads shader fragments. + sShaderCache.Initialize(); +} + +/*! \brief Destructor. +*/ +HdVP2RenderDelegate::~HdVP2RenderDelegate() { + std::lock_guard guard(_mutexResourceRegistry); + + if (_counterResourceRegistry.fetch_sub(1) == 1) { + _resourceRegistry.reset(); + } +} + +/*! \brief Return delegate's HdVP2RenderParam, giving access to things like MPxSubSceneOverride. +*/ +HdRenderParam* HdVP2RenderDelegate::GetRenderParam() const { + return _renderParam.get(); +} + +/*! \brief Notification to commit resources to GPU & compute before rendering + + This notification sent by HdEngine happens after parallel synchronization of data, + prim's via VP2 resource registry are inserting work to commit. Now is the time + on main thread to commit resources and compute missing streams. + + In future we will better leverage evaluation time to perform synchronization + of data and allow main-thread task execution during the compute as it's done + for the rest of VP2 synchronization with DG data. +*/ +void HdVP2RenderDelegate::CommitResources(HdChangeTracker* tracker) { + TF_UNUSED(tracker); + + MProfilingScope profilingScope(sProfilerCategory, MProfiler::kColorC_L2, "Commit resources"); + + // --------------------------------------------------------------------- // + // RESOLVE, COMPUTE & COMMIT PHASE + // --------------------------------------------------------------------- // + // All the required input data is now resident in memory, next we must: + // + // 1) Execute compute as needed for normals, tessellation, etc. + // 2) Commit resources to the GPU. + // 3) Update any scene-level acceleration structures. + + _resourceRegistryVP2.Commit(); +} + +/*! \brief Return a list of which Rprim types can be created by this class's. +*/ +const TfTokenVector& HdVP2RenderDelegate::GetSupportedRprimTypes() const { + return _SupportedRprimTypes(); +} + +/*! \brief Return a list of which Sprim types can be created by this class's. +*/ +const TfTokenVector& HdVP2RenderDelegate::GetSupportedSprimTypes() const { + return _SupportedSprimTypes(); +} + +/*! \brief Return a list of which Bprim types can be created by this class's. +*/ +const TfTokenVector& HdVP2RenderDelegate::GetSupportedBprimTypes() const { + return _SupportedBprimTypes(); +} + +/*! \brief Return unused global resource registry. +*/ +HdResourceRegistrySharedPtr HdVP2RenderDelegate::GetResourceRegistry() const { + return _resourceRegistry; +} + +/*! \brief Return VP2 resource registry, holding access to commit execution enqueue. +*/ +HdVP2ResourceRegistry& HdVP2RenderDelegate::GetVP2ResourceRegistry() { + return _resourceRegistryVP2; +} + +/*! \brief Create a renderpass for rendering a given collection. +*/ +HdRenderPassSharedPtr HdVP2RenderDelegate::CreateRenderPass(HdRenderIndex* index, const HdRprimCollection& collection) { + return HdRenderPassSharedPtr(new HdVP2RenderPass(this, index, collection)); +} + +/*! \brief Request to create a new VP2 instancer. + + \param id The unique identifier of this instancer. + \param instancerId The unique identifier for the parent instancer that + uses this instancer as a prototype (may be empty). + + \return A pointer to the new instancer or nullptr on error. +*/ +HdInstancer* HdVP2RenderDelegate::CreateInstancer( + HdSceneDelegate* delegate, const SdfPath& id, const SdfPath& instancerId) { + + return new HdVP2Instancer(delegate, id, instancerId); +} + +/*! \brief Destroy instancer instance +*/ +void HdVP2RenderDelegate::DestroyInstancer(HdInstancer* instancer) { + delete instancer; +} + +/*! \brief Request to Allocate and Construct a new, VP2 specialized Rprim. + + \param typeId the type identifier of the prim to allocate + \param rprimId a unique identifier for the prim + \param instancerId the unique identifier for the instancer that uses + the prim (optional: May be empty). + + \return A pointer to the new prim or nullptr on error. +*/ +HdRprim* HdVP2RenderDelegate::CreateRprim( + const TfToken& typeId, const SdfPath& rprimId, const SdfPath& instancerId) { + if (typeId == HdPrimTypeTokens->mesh) { + return new HdVP2Mesh(this, rprimId, instancerId); + } + //if (typeId == HdPrimTypeTokens->volume) { + // return new HdVP2Volume(this, rprimId, instancerId); + //} + TF_CODING_ERROR("Unknown Rprim Type %s", typeId.GetText()); + return nullptr; +} + +/*! \brief Destroy & deallocate Rprim instance +*/ +void HdVP2RenderDelegate::DestroyRprim(HdRprim* rPrim) { + delete rPrim; +} + +/*! \brief Request to Allocate and Construct a new, VP2 specialized Sprim. + + \param typeId the type identifier of the prim to allocate + \param sprimId a unique identifier for the prim + + \return A pointer to the new prim or nullptr on error. +*/ +HdSprim* HdVP2RenderDelegate::CreateSprim( + const TfToken& typeId, const SdfPath& sprimId) { + if (typeId == HdPrimTypeTokens->material) { + return new HdVP2Material(this, sprimId); + } + if (typeId == HdPrimTypeTokens->camera) { + return new HdCamera(sprimId); + } + /* + if (typeId == HdPrimTypeTokens->sphereLight) { + return HdVP2Light::CreatePointLight(this, sprimId); + } + if (typeId == HdPrimTypeTokens->distantLight) { + return HdVP2Light::CreateDistantLight(this, sprimId); + } + if (typeId == HdPrimTypeTokens->diskLight) { + return HdVP2Light::CreateDiskLight(this, sprimId); + } + if (typeId == HdPrimTypeTokens->rectLight) { + return HdVP2Light::CreateRectLight(this, sprimId); + } + if (typeId == HdPrimTypeTokens->cylinderLight) { + return HdVP2Light::CreateCylinderLight(this, sprimId); + } + if (typeId == HdPrimTypeTokens->domeLight) { + return HdVP2Light::CreateDomeLight(this, sprimId); + } + */ + TF_CODING_ERROR("Unknown Sprim Type %s", typeId.GetText()); + return nullptr; +} + +/*! \brief Request to Allocate and Construct an Sprim to use as a standin, if there + if an error with another another Sprim of the same type.For example, + if another prim references a non - exisiting Sprim, the fallback could + be used. + + \param typeId the type identifier of the prim to allocate + + \return A pointer to the new prim or nullptr on error. +*/ +HdSprim* HdVP2RenderDelegate::CreateFallbackSprim(const TfToken& typeId) { + if (typeId == HdPrimTypeTokens->material) { + return new HdVP2Material(this, SdfPath::EmptyPath()); + } + if (typeId == HdPrimTypeTokens->camera) { + return new HdCamera(SdfPath::EmptyPath()); + } + /* + if (typeId == HdPrimTypeTokens->sphereLight) { + return HdVP2Light::CreatePointLight(this, SdfPath::EmptyPath()); + } + if (typeId == HdPrimTypeTokens->distantLight) { + return HdVP2Light::CreateDistantLight(this, SdfPath::EmptyPath()); + } + if (typeId == HdPrimTypeTokens->diskLight) { + return HdVP2Light::CreateDiskLight(this, SdfPath::EmptyPath()); + } + if (typeId == HdPrimTypeTokens->rectLight) { + return HdVP2Light::CreateRectLight(this, SdfPath::EmptyPath()); + } + if (typeId == HdPrimTypeTokens->cylinderLight) { + return HdVP2Light::CreateCylinderLight(this, SdfPath::EmptyPath()); + } + if (typeId == HdPrimTypeTokens->domeLight) { + return HdVP2Light::CreateDomeLight(this, SdfPath::EmptyPath()); + } + */ + TF_CODING_ERROR("Unknown Sprim Type %s", typeId.GetText()); + return nullptr; +} + +/*! \brief Destroy & deallocate Sprim instance +*/ +void HdVP2RenderDelegate::DestroySprim(HdSprim* sPrim) { + delete sPrim; +} + +/*! \brief Request to Allocate and Construct a new, VP2 specialized Bprim. + + \param typeId the type identifier of the prim to allocate + \param sprimId a unique identifier for the prim + + \return A pointer to the new prim or nullptr on error. +*/ +HdBprim* HdVP2RenderDelegate::CreateBprim( + const TfToken& typeId, const SdfPath& bprimId) { + /* + if (typeId == HdPrimTypeTokens->texture) { + return new HdVP2Texture(this, bprimId); + } + if (typeId == HdPrimTypeTokens->renderBuffer) { + return new HdVP2RenderBuffer(bprimId); + } + if (typeId == _tokens->openvdbAsset) { + return new HdVP2OpenvdbAsset(this, bprimId); + } + TF_CODING_ERROR("Unknown Bprim Type %s", typeId.GetText()); + */ + return nullptr; +} + +/*! \brief Request to Allocate and Construct a Bprim to use as a standin, if there + if an error with another another Bprim of the same type. For example, + if another prim references a non-exisiting Bprim, the fallback could + be used. + + \param typeId the type identifier of the prim to allocate + + \return A pointer to the new prim or nullptr on error. +*/ +HdBprim* HdVP2RenderDelegate::CreateFallbackBprim(const TfToken& typeId) { + /* + if (typeId == HdPrimTypeTokens->texture) { + return new HdVP2Texture(this, SdfPath::EmptyPath()); + } + if (typeId == HdPrimTypeTokens->renderBuffer) { + return new HdVP2RenderBuffer(SdfPath::EmptyPath()); + } + if (typeId == _tokens->openvdbAsset) { + return new HdVP2OpenvdbAsset(this, SdfPath::EmptyPath()); + } + TF_CODING_ERROR("Unknown Bprim Type %s", typeId.GetText()); + */ + return nullptr; +} + +/*! \brief Destroy & deallocate Bprim instance +*/ +void HdVP2RenderDelegate::DestroyBprim(HdBprim* bPrim) { + delete bPrim; +} + +/*! \brief Returns a token that indicates material bindings purpose. + + The full material purpose is suggested according to + https://github.com/PixarAnimationStudios/USD/pull/853 +*/ +TfToken HdVP2RenderDelegate::GetMaterialBindingPurpose() const { + return HdTokens->full; +} + +/*! \brief Returns a node name made as a child of delegate's id. +*/ +MString HdVP2RenderDelegate::GetLocalNodeName(const MString& name) const { + return MString(_id.AppendChild(TfToken(name.asChar())).GetText()); +} + +/*! \brief Returns a fallback shader instance when no material is bound. + + This method is keeping registry of all fallback shaders generated, allowing only + one instance per color which enables consolidation of render calls with same shader + instance. + + \param color Color to set on given shader instance + + \return A new or existing copy of shader instance with given color parameter set +*/ +MHWRender::MShaderInstance* HdVP2RenderDelegate::GetFallbackShader(MColor color) const +{ + return sShaderCache.GetFallbackShader(color); +} + +/*! \brief Returns a fallback CPV shader instance when no material is bound. +*/ +MHWRender::MShaderInstance* HdVP2RenderDelegate::GetFallbackCPVShader() const +{ + return sShaderCache.GetFallbackCPVShader(); +} + +/*! \brief Returns a 3d green shader that can be used for selection highlight. +*/ +MHWRender::MShaderInstance* HdVP2RenderDelegate::Get3dSolidShader() const +{ + return sShaderCache.Get3dSolidShader(); +} + +/*! \brief Returns a white 3d fat point shader. +*/ +MHWRender::MShaderInstance* HdVP2RenderDelegate::Get3dFatPointShader() const +{ + return sShaderCache.Get3dFatPointShader(); +} + +/*! \brief Returns a sampler state as specified by the description. +*/ +const MHWRender::MSamplerState* HdVP2RenderDelegate::GetSamplerState( + const MHWRender::MSamplerStateDesc& desc) const +{ + // Look for it first with reader lock + tbb::spin_rw_mutex::scoped_lock lock(sSamplerRWMutex, false/*write*/); + + auto it = sSamplerStates.find(desc); + if (it != sSamplerStates.end()) { + return it->second; + } + + // Upgrade to writer lock. + lock.upgrade_to_writer(); + + // Double check that it wasn't inserted by another thread + it = sSamplerStates.find(desc); + if (it != sSamplerStates.end()) { + return it->second; + } + + // Create and cache. + const MHWRender::MSamplerState* samplerState = + MHWRender::MStateManager::acquireSamplerState(desc); + sSamplerStates[desc] = samplerState; + return samplerState; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/render_delegate.h b/lib/render/vp2RenderDelegate/render_delegate.h new file mode 100644 index 0000000000..73479518cf --- /dev/null +++ b/lib/render/vp2RenderDelegate/render_delegate.h @@ -0,0 +1,133 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_RENDER_DELEGATE +#define HD_VP2_RENDER_DELEGATE + +#include "pxr/pxr.h" +#include "pxr/imaging/hd/renderDelegate.h" +#include "pxr/imaging/hd/resourceRegistry.h" + +#include "render_param.h" +#include "resource_registry.h" + +#include +#include + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class ProxyRenderDelegate; + +/*! \brief VP2 render delegate + \class HdVP2RenderDelegate + + Render delegates provide renderer-specific functionality to the render + index, the main hydra state management structure. The render index uses + the render delegate to create and delete scene primitives, which include + geometry and also non-drawable objects. + + Primitives in Hydra are split into Rprims (drawables), Sprims (state + objects like cameras and materials), and Bprims (buffer objects like + textures). The minimum set of primitives a renderer needs to support is + one Rprim (so the scene's not empty) and the "camera" Sprim, which is + required by HdxRenderTask, the task implementing basic hydra drawing. + + VP2 render delegate reports which prim types it supports via + GetSupportedRprimTypes() (and Sprim, Bprim). + + VP2 Rprims create MRenderItem geometry objects in the MPxSubSceneOverride. + Render delegate renderpasses are not utilized, since subscene is only + a subset of what's being draw in the viewport and overall control is left + to the application. + + The render delegate also has a hook for the main hydra execution algorithm + (HdEngine::Execute()): between HdRenderIndex::SyncAll(), which pulls new + scene data, and execution of tasks, the engine calls back to + CommitResources(). This commit is perfoming execution which must happen + on the main-thread. In the future we will further split engine execution, + levering evaluation time to do HdRenderIndex::SyncAll together with + parallel DG computation and perform commit from reserved thread + via main-thread tasks. +*/ +class HdVP2RenderDelegate final : public HdRenderDelegate { +public: + HdVP2RenderDelegate(ProxyRenderDelegate& proxyDraw); + + ~HdVP2RenderDelegate() override; + + HdRenderParam* GetRenderParam() const override; + + const TfTokenVector& GetSupportedRprimTypes() const override; + + const TfTokenVector& GetSupportedSprimTypes() const override; + + const TfTokenVector& GetSupportedBprimTypes() const override; + + HdResourceRegistrySharedPtr GetResourceRegistry() const override; + + HdVP2ResourceRegistry& GetVP2ResourceRegistry(); + + HdRenderPassSharedPtr CreateRenderPass(HdRenderIndex* index, HdRprimCollection const& collection) override; + + HdInstancer* CreateInstancer(HdSceneDelegate* delegate, SdfPath const& id, SdfPath const& instancerId) override; + void DestroyInstancer(HdInstancer* instancer) override; + + HdRprim* CreateRprim(TfToken const& typeId, SdfPath const& rprimId, SdfPath const& instancerId) override; + void DestroyRprim(HdRprim* rPrim) override; + + HdSprim* CreateSprim(TfToken const& typeId, SdfPath const& sprimId) override; + HdSprim* CreateFallbackSprim(TfToken const& typeId) override; + void DestroySprim(HdSprim* sPrim) override; + + HdBprim* CreateBprim(TfToken const& typeId, SdfPath const& bprimId) override; + HdBprim* CreateFallbackBprim(TfToken const& typeId) override; + void DestroyBprim(HdBprim* bPrim) override; + + void CommitResources(HdChangeTracker* tracker) override; + + TfToken GetMaterialBindingPurpose() const override; + + MString GetLocalNodeName(const MString& name) const; + + MHWRender::MShaderInstance* GetFallbackShader(MColor color=MColor(0.18f,0.18f,0.18f,1.0f)) const; + MHWRender::MShaderInstance* GetFallbackCPVShader() const; + MHWRender::MShaderInstance* Get3dSolidShader() const; + MHWRender::MShaderInstance* Get3dFatPointShader() const; + + const MHWRender::MSamplerState* GetSamplerState( + const MHWRender::MSamplerStateDesc& desc) const; + + static const int sProfilerCategory; //!< Profiler category + +private: + HdVP2RenderDelegate(const HdVP2RenderDelegate&) = delete; + HdVP2RenderDelegate& operator=(const HdVP2RenderDelegate&) = delete; + + static std::mutex _mutexResourceRegistry; //!< Mutex protecting construction/destruction of resource registry + static std::atomic_int _counterResourceRegistry; //!< Number of render delegates sharing this resource registry. Last one deletes the instance. + static HdResourceRegistrySharedPtr _resourceRegistry; //!< Shared and unused by VP2 resource registry + + std::unique_ptr _renderParam; //!< Render param used to provided access to VP2 during prim synchronization + SdfPath _id; //!< Render delegate IDs + HdVP2ResourceRegistry _resourceRegistryVP2; //!< VP2 resource registry used for enqueue and execution of commits +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/lib/render/vp2RenderDelegate/render_param.cpp b/lib/render/vp2RenderDelegate/render_param.cpp new file mode 100644 index 0000000000..3aa1f99ded --- /dev/null +++ b/lib/render/vp2RenderDelegate/render_param.cpp @@ -0,0 +1,36 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "render_param.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief Begin update before rendering of VP2 starts. +*/ +void HdVP2RenderParam::BeginUpdate(MSubSceneContainer& container, UsdTimeCode frame) { + _container = &container; + _frame = frame; +} + +/*! \brief End update & clear access to render item container which pass this point won't be valid. +*/ +void HdVP2RenderParam::EndUpdate() { + _container = nullptr; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/render_param.h b/lib/render/vp2RenderDelegate/render_param.h new file mode 100644 index 0000000000..47cc8413c7 --- /dev/null +++ b/lib/render/vp2RenderDelegate/render_param.h @@ -0,0 +1,69 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_RENDER_PARAM +#define HD_VP2_RENDER_PARAM + +#include "pxr/pxr.h" + +#include "pxr/imaging/hd/renderDelegate.h" +#include "pxr/usd/usd/timeCode.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class ProxyRenderDelegate; + +/*! \brief The HdRenderParam is an opaque(to core Hydra) handle, passed to each prim + during Sync processing and providing access to VP2. + \class HdVP2RenderParam +*/ +class HdVP2RenderParam final : public HdRenderParam { +public: + /*! \brief Constructor + */ + HdVP2RenderParam(ProxyRenderDelegate& drawScene) + : _drawScene(drawScene) {} + + /*! \brief Destructor + */ + ~HdVP2RenderParam() override = default; + + void BeginUpdate(MSubSceneContainer& container, UsdTimeCode frame); + void EndUpdate(); + + /*! \brief Get access to subscene override used to draw the scene + */ + ProxyRenderDelegate& GetDrawScene() const { return _drawScene; } + + /*! \brief Get access to render item container - only valid during draw update + */ + MSubSceneContainer* GetContainer() const { return _container; } + + /*! \brief Refreshed during each update, provides info about currently refreshed frame + */ + UsdTimeCode GetFrame() const { return _frame; } + +protected: + ProxyRenderDelegate& _drawScene; //!< Subscene override used as integration interface for HdVP2RenderDelegate + MSubSceneContainer* _container{ nullptr }; //!< Container to all render items, only valid between begin and end update of subscene override. + UsdTimeCode _frame{ UsdTimeCode::Default() }; //!< Rendered frame (useful for caching of data) +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/lib/render/vp2RenderDelegate/render_pass.h b/lib/render/vp2RenderDelegate/render_pass.h new file mode 100644 index 0000000000..4bd5d2b044 --- /dev/null +++ b/lib/render/vp2RenderDelegate/render_pass.h @@ -0,0 +1,51 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_RENDER_PASS +#define HD_VP2_RENDER_PASS + +#include "pxr/pxr.h" +#include "pxr/imaging/hd/renderPass.h" + +PXR_NAMESPACE_OPEN_SCOPE + +class HdVP2RenderDelegate; + +/*! \brief Empty render pass class. Unused for rendering with VP2 but required by HdEngine & HdRenderDelegate. + \class HdVP2RenderPass +*/ +class HdVP2RenderPass final : public HdRenderPass +{ +public: + //! \brief Constructor + HdVP2RenderPass(HdVP2RenderDelegate* delegate, HdRenderIndex *index, HdRprimCollection const &collection) + : HdRenderPass(index, collection), _delegate(delegate) + {} + + //! \brief Destructor + virtual ~HdVP2RenderPass() {} + + //! \brief Empty execute + void _Execute(HdRenderPassStateSharedPtr const &renderPassState, TfTokenVector const &renderTags) override {} + +private: + HdVP2RenderDelegate* _delegate{ nullptr }; // !< VP2 render delegate for which this render pass was created + +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif \ No newline at end of file diff --git a/lib/render/vp2RenderDelegate/resource_registry.h b/lib/render/vp2RenderDelegate/resource_registry.h new file mode 100644 index 0000000000..afc52e59aa --- /dev/null +++ b/lib/render/vp2RenderDelegate/resource_registry.h @@ -0,0 +1,59 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_RESOURCE_REGISTRY +#define HD_VP2_RESOURCE_REGISTRY + +#include "task_commit.h" + +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief Central place to manage GPU resources commits and any resources not managed by VP2 directly + \class HdVP2ResourceRegistry +*/ +class HdVP2ResourceRegistry +{ +public: + //! \brief Default constructor + HdVP2ResourceRegistry() = default; + //! \brief Default destructor + ~HdVP2ResourceRegistry() = default; + + //! \brief Execute commit tasks (called by render delegate) + void Commit() { + HdVP2TaskCommit* commitTask; + while (_commitTasks.try_pop(commitTask)) { + (*commitTask)(); + commitTask->destroy(); + } + } + + //! \brief Enqueue commit task. Call is thread safe. + template + void EnqueueCommit(Body taskBody) { + _commitTasks.push(HdVP2TaskCommitBody::construct(taskBody)); + } +private: + //! Concurrent queue for commit tasks + tbb::concurrent_queue> _commitTasks; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif \ No newline at end of file diff --git a/lib/render/vp2RenderDelegate/sampler.cpp b/lib/render/vp2RenderDelegate/sampler.cpp new file mode 100644 index 0000000000..a9e5d60475 --- /dev/null +++ b/lib/render/vp2RenderDelegate/sampler.cpp @@ -0,0 +1,149 @@ +// +// Copyright 2017 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Modifications copyright (C) 2019 Autodesk +// +#include "sampler.h" + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief The constructor takes a reference to a buffer source. The data is + owned externally; + + The caller is responsible for ensuring the buffer is alive while Sample() is being called. + + \param buffer The buffer being sampled. +*/ +HdVP2BufferSampler::HdVP2BufferSampler(HdVtBufferSource const& buffer) + : _buffer(buffer) {} + +/*! \brief Sample the buffer at given element index + + Sample the buffer at element index \p index, and write the sample to + \p value.Interpret \p value as having arity \p numComponents, each of + type \p componentType.These parameters may not match the datatype + declaration of the underlying buffer, in which case Sample returns + false.Sample also returns false if \p index is out of bounds. + + For example, to sample data as GfVec3, \p dataType would be + HdTupleType{ HdTypeFloatVec3, 1 }. + + \param index The element index to sample. + \param value The memory to write the value to(only written on success). + \param dataType The HdTupleType describing element values. + + \return True if the value was successfully sampled. +*/ +bool HdVP2BufferSampler::Sample(int index, void* value, + HdTupleType dataType) const +{ + // Sanity checks: index is within the bounds of buffer, + // and the sample type and buffer type (defined by the dataType) + // are the same. + if (_buffer.GetNumElements() <= (size_t)index || + _buffer.GetTupleType() != dataType) { + return false; + } + + // Calculate the element's byte offset in the array. + size_t elemSize = HdDataSizeOfTupleType(dataType); + size_t offset = elemSize * index; + + // Equivalent to: + // *static_cast(value) = + // static_cast(_buffer.GetData())[index]; + memcpy(value, + static_cast(_buffer.GetData()) + offset, elemSize); + + return true; +} + +template +static void +_InterpolateImpl(void* out, void** samples, float* weights, + size_t sampleCount, short numComponents) { + // This is an implementation of a general blend of samples: + // out = sum_j { sample[j] * weights[j] }. + // Since the vector length comes in as a parameter, and not part + // of the type, the blend is implemented per component. + for (short i = 0; i < numComponents; ++i) { + static_cast(out)[i] = 0; + for (size_t j = 0; j < sampleCount; ++j) { + static_cast(out)[i] += + static_cast(samples[j])[i] * weights[j]; + } + } +} + +/*! \brief Utility function for derived classes: combine multiple samples with + blend weights: \p out = sum_i { \p samples[i] * \p weights[i] }. + + \param out The memory to write the output to (only written on success). + \param samples The array of sample pointers (length \p sampleCount). + \param weights The array of sample weights (length \p sampleCount). + \param sampleCount The number of samples to combine. + \param dataType The HdTupleType describing element values. + + \return True if the samples were successfully combined. +*/ +bool HdVP2PrimvarSampler::_Interpolate(void* out, void** samples, float* weights, + size_t sampleCount, HdTupleType dataType) { + // Combine maps from component type tag to C++ type, and delegates to + // the templated _InterpolateImpl. + + // Combine number of components in the underlying type and tuple arity. + short numComponents = HdGetComponentCount(dataType.type) * dataType.count; + + HdType componentType = HdGetComponentType(dataType.type); + + switch(componentType) { + case HdTypeBool: + /* This function isn't meaningful on boolean types. */ + return false; + case HdTypeInt8: + _InterpolateImpl(out, samples, weights, sampleCount, + numComponents); + case HdTypeInt16: + _InterpolateImpl(out, samples, weights, sampleCount, + numComponents); + return true; + case HdTypeUInt16: + _InterpolateImpl(out, samples, weights, sampleCount, + numComponents); + return true; + case HdTypeInt32: + _InterpolateImpl(out, samples, weights, sampleCount, + numComponents); + return true; + case HdTypeUInt32: + _InterpolateImpl(out, samples, weights, sampleCount, + numComponents); + return true; + case HdTypeFloat: + _InterpolateImpl(out, samples, weights, sampleCount, + numComponents); + return true; + case HdTypeDouble: + _InterpolateImpl(out, samples, weights, sampleCount, + numComponents); + return true; + default: + TF_CODING_ERROR("Unsupported type '%s' passed to _Interpolate", + TfEnum::GetName(componentType).c_str()); + return false; + } +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2RenderDelegate/sampler.h b/lib/render/vp2RenderDelegate/sampler.h new file mode 100644 index 0000000000..342a1e2737 --- /dev/null +++ b/lib/render/vp2RenderDelegate/sampler.h @@ -0,0 +1,160 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Modifications copyright (C) 2019 Autodesk +// +#ifndef HD_VP2_SAMPLER_H +#define HD_VP2_SAMPLER_H + +#include "pxr/pxr.h" +#include + +#include "pxr/imaging/glf/glew.h" + +#include "pxr/imaging/hd/enums.h" +#include "pxr/imaging/hd/vtBufferSource.h" + +#include "pxr/base/gf/matrix4d.h" +#include "pxr/base/gf/matrix4f.h" +#include "pxr/base/gf/vec2d.h" +#include "pxr/base/gf/vec2f.h" +#include "pxr/base/gf/vec2i.h" +#include "pxr/base/gf/vec3d.h" +#include "pxr/base/gf/vec3f.h" +#include "pxr/base/gf/vec3i.h" +#include "pxr/base/gf/vec4d.h" +#include "pxr/base/gf/vec4f.h" +#include "pxr/base/gf/vec4i.h" + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief A utility class that helps map between C++ types and Hd type tags. + \class HdVP2TypeHelper +*/ +class HdVP2TypeHelper { +public: + //! Define a type that can hold one sample of any primvar. + using PrimvarTypeContainer = char[sizeof(GfMatrix4d)]; + + /*! \brief Return the HdTupleType corresponding to the given C++ type. + */ + template + static HdTupleType GetTupleType(); +}; + +// Define template specializations of HdVP2TypeHelper methods for +// all our supported types... +#define TYPE_HELPER(T,type)\ +template<> inline HdTupleType \ +HdVP2TypeHelper::GetTupleType() { return HdTupleType{type, 1}; } + + TYPE_HELPER(bool, HdTypeBool) + TYPE_HELPER(char, HdTypeInt8) + TYPE_HELPER(short, HdTypeInt16) + TYPE_HELPER(unsigned short, HdTypeUInt16) + TYPE_HELPER(int, HdTypeInt32) + TYPE_HELPER(GfVec2i, HdTypeInt32Vec2) + TYPE_HELPER(GfVec3i, HdTypeInt32Vec3) + TYPE_HELPER(GfVec4i, HdTypeInt32Vec4) + TYPE_HELPER(unsigned int, HdTypeUInt32) + TYPE_HELPER(float, HdTypeFloat) + TYPE_HELPER(GfVec2f, HdTypeFloatVec2) + TYPE_HELPER(GfVec3f, HdTypeFloatVec3) + TYPE_HELPER(GfVec4f, HdTypeFloatVec4) + TYPE_HELPER(double, HdTypeDouble) + TYPE_HELPER(GfVec2d, HdTypeDoubleVec2) + TYPE_HELPER(GfVec3d, HdTypeDoubleVec3) + TYPE_HELPER(GfVec4d, HdTypeDoubleVec4) + TYPE_HELPER(GfMatrix4f, HdTypeFloatMat4) + TYPE_HELPER(GfMatrix4d, HdTypeDoubleMat4) +#undef TYPE_HELPER + + +/*! \brief A utility class that knows how to sample an element from a type-tagged + buffer (like HdVtBufferSource). + \class HdVP2BufferSampler + + This class provides templated accessors to let the caller directly get the + final sample type; it also does bounds checks and type checks. +*/ +class HdVP2BufferSampler { +public: + HdVP2BufferSampler(HdVtBufferSource const& buffer); + + bool Sample(int index, void* value, HdTupleType dataType) const; + + // Convenient, templated frontend for Sample(). + template bool Sample(int index, T* value) const { + return Sample(index, static_cast(value), + HdVP2TypeHelper::GetTupleType()); + } + +private: + HdVtBufferSource const& _buffer; //!< Buffer source to sample +}; + +/*! \brief An abstract base class that knows how to sample a primvar. + \class HdVP2PrimvarSampler + + An abstract base class that knows how to sample a primvar signal given + a ray hit coordinate: an tuple. It provides templated + accessors, but derived classes are responsible for implementing appropriate + sampling or interpolation modes. +*/ +class HdVP2PrimvarSampler { +public: + //! Default constructor. + HdVP2PrimvarSampler() = default; + //! Default destructor. + virtual ~HdVP2PrimvarSampler() = default; + + /*! \brief Sample the primvar at element index and local basis coordinates. + + Sample the primvar at element index \p index and local basis coordinates + \p u and \p v, writing the sample to \p value. Interpret \p value as + having arity \p numComponents, each of type \p componentType. These + parameters may not match the datatype declaration of the underlying + buffer. + + Derived classes are responsible for implementing sampling logic for + their particular interpolation modes. Sample returns true if a value + was successfully retrieved. + + \param element The element index to sample. + \param u The u coordinate to sample. + \param v The v coordinate to sample. + \param value The memory to write the value to (only written on success). + \param dataType The HdTupleType describing element values. + + \return True if the value was successfully sampled. + */ + virtual bool Sample(unsigned int element, float u, float v, void* value, + HdTupleType dataType) const = 0; + + //! Convenient, templated frontend for Sample(). + template bool Sample(unsigned int element, float u, float v, + T* value) const { + return Sample(element, u, v, static_cast(value), + HdVP2TypeHelper::GetTupleType()); + } + +protected: + static bool _Interpolate(void* out, void** samples, float* weights, + size_t sampleCount, HdTupleType dataType); +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_VP2_SAMPLER_H diff --git a/lib/render/vp2RenderDelegate/task_commit.h b/lib/render/vp2RenderDelegate/task_commit.h new file mode 100644 index 0000000000..88d8a5310e --- /dev/null +++ b/lib/render/vp2RenderDelegate/task_commit.h @@ -0,0 +1,80 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_TASK_COMMIT +#define HD_VP2_TASK_COMMIT + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief Base commit task class + \class HdVP2TaskCommit +*/ +class HdVP2TaskCommit +{ +public: + virtual ~HdVP2TaskCommit() = default; + + //! Execute the task + virtual void operator()() = 0; + + //! Destroy & deallocated this task + virtual void destroy() = 0; +}; + +/*! \brief Wrapper of a task body into commit task. + \class HdVP2TaskCommit +*/ +template +class HdVP2TaskCommitBody final : public HdVP2TaskCommit { + //! Use scalable allocator to prevent heap contention + using my_allocator_type = tbb::tbb_allocator>; + + //! Private constructor to force usage of construct method & allocation with + //! scalable allocator. + HdVP2TaskCommitBody(const Body& body) : fBody(body) {} + +public: + ~HdVP2TaskCommitBody() override = default; + + //! Execute body task. + void operator()() override { + fBody(); + } + + //! Objects of this type are allocated with tbb_allocator. + //! Release the memory using same allocator by calling destroy method. + void destroy() override { + my_allocator_type().destroy(this); + my_allocator_type().deallocate(this, 1); + } + + /*! Allocate a new object of type Body using scalable allocator. + Always free this object by calling destroy method! + */ + static HdVP2TaskCommitBody* construct(const Body& body) { + void* mem = my_allocator_type().allocate(1); + return new (mem) HdVP2TaskCommitBody(body); + } + +private: + Body fBody; //!< Function object providing execution "body" for this task +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/FallbackCPVShader.xml b/lib/render/vp2ShaderFragments/FallbackCPVShader.xml new file mode 100644 index 0000000000..3b28bfe9ca --- /dev/null +++ b/lib/render/vp2ShaderFragments/FallbackCPVShader.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/FallbackShader.xml b/lib/render/vp2ShaderFragments/FallbackShader.xml new file mode 100644 index 0000000000..9f55772de7 --- /dev/null +++ b/lib/render/vp2ShaderFragments/FallbackShader.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/UsdPreviewSurface.xml b/lib/render/vp2ShaderFragments/UsdPreviewSurface.xml new file mode 100644 index 0000000000..f99998ed8a --- /dev/null +++ b/lib/render/vp2ShaderFragments/UsdPreviewSurface.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml new file mode 100644 index 0000000000..bc00166e86 --- /dev/null +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml new file mode 100644 index 0000000000..93d2e554de --- /dev/null +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml new file mode 100644 index 0000000000..f58e0919f2 --- /dev/null +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml new file mode 100644 index 0000000000..4d4bbfd3e4 --- /dev/null +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/UsdUVTexture.xml b/lib/render/vp2ShaderFragments/UsdUVTexture.xml new file mode 100644 index 0000000000..9c1f43e472 --- /dev/null +++ b/lib/render/vp2ShaderFragments/UsdUVTexture.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + breakPnt) ? float4(1., 1., 1., 1.) : float4(0., 0., 0., 0.)); + float4 linSeg = outColor * slope; + float4 powSeg = pow( max( float4(0., 0., 0., 0.), scale * outColor + offset), gamma); + outColor = isAboveBreak * powSeg + ( float4(1., 1., 1., 1.) - isAboveBreak ) * linSeg; + } + return outColor * scale + bias; +} + ]]> + + + + + + breakPnt) ? float4(1., 1., 1., 1.) : float4(0., 0., 0., 0.)); + float4 linSeg = outColor * slope; + float4 powSeg = pow( max( float4(0., 0., 0., 0.), scale * outColor + offset), gamma); + outColor = isAboveBreak * powSeg + ( float4(1., 1., 1., 1.) - isAboveBreak ) * linSeg; + } + return outColor * scale + bias; +} + ]]> + + + + diff --git a/lib/render/vp2ShaderFragments/float4ToFloat3.xml b/lib/render/vp2ShaderFragments/float4ToFloat3.xml new file mode 100644 index 0000000000..f2f4a3463e --- /dev/null +++ b/lib/render/vp2ShaderFragments/float4ToFloat3.xml @@ -0,0 +1,58 @@ + + + Extracts the XYZ component of a 4D float vector. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloat4.xml b/lib/render/vp2ShaderFragments/float4ToFloat4.xml new file mode 100644 index 0000000000..d2c55c8509 --- /dev/null +++ b/lib/render/vp2ShaderFragments/float4ToFloat4.xml @@ -0,0 +1,58 @@ + + + Passthrough of a 4D float vector. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/float4ToFloatW.xml b/lib/render/vp2ShaderFragments/float4ToFloatW.xml new file mode 100644 index 0000000000..7c22175b03 --- /dev/null +++ b/lib/render/vp2ShaderFragments/float4ToFloatW.xml @@ -0,0 +1,91 @@ + + + + Extracts the W component of a 4D float vector. + + + + + + + + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatW(float4 input) +{ + return input.w; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatW(float4 input) +{ + return input.w; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatW(float4 input) +{ + return input.w; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatW(float4 input) +{ + return input.w; +} + +]]> + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloatX.xml b/lib/render/vp2ShaderFragments/float4ToFloatX.xml new file mode 100644 index 0000000000..2e41dddc20 --- /dev/null +++ b/lib/render/vp2ShaderFragments/float4ToFloatX.xml @@ -0,0 +1,91 @@ + + + + Extracts the X component of a 4D float vector. + + + + + + + + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatX(float4 input) +{ + return input.x; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatX(float4 input) +{ + return input.x; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatX(float4 input) +{ + return input.x; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatX(float4 input) +{ + return input.x; +} + +]]> + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloatY.xml b/lib/render/vp2ShaderFragments/float4ToFloatY.xml new file mode 100644 index 0000000000..9790797a08 --- /dev/null +++ b/lib/render/vp2ShaderFragments/float4ToFloatY.xml @@ -0,0 +1,91 @@ + + + + Extracts the Y component of a 4D float vector. + + + + + + + + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatY(float4 input) +{ + return input.y; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatY(float4 input) +{ + return input.y; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatY(float4 input) +{ + return input.y; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatY(float4 input) +{ + return input.y; +} + +]]> + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloatZ.xml b/lib/render/vp2ShaderFragments/float4ToFloatZ.xml new file mode 100644 index 0000000000..ecb4b982d8 --- /dev/null +++ b/lib/render/vp2ShaderFragments/float4ToFloatZ.xml @@ -0,0 +1,91 @@ + + + + Extracts the Z component of a 4D float vector. + + + + + + + + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatZ(float4 input) +{ + return input.z; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatZ(float4 input) +{ + return input.z; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatZ(float4 input) +{ + return input.z; +} + +]]> + + + + + + = 110) +#define float4 vec4 +#endif + +float +float4ToFloatZ(float4 input) +{ + return input.z; +} + +]]> + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/lightingContributions.xml b/lib/render/vp2ShaderFragments/lightingContributions.xml new file mode 100644 index 0000000000..6ce6eb6247 --- /dev/null +++ b/lib/render/vp2ShaderFragments/lightingContributions.xml @@ -0,0 +1,109 @@ + + + + Structure to hold computed diffuse and specular lighting contributions for a light. + + + + + + + + + + + + + + + = 110) +#define float3 vec3 +#endif + +// line 101 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +struct LightingContributions +{ + float3 diffuse; + float3 specular; +}; + + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +// line 101 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +struct LightingContributions +{ + float3 diffuse; + float3 specular; +}; + + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +// line 101 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +struct LightingContributions +{ + float3 diffuse; + float3 specular; +}; + + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +// line 101 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +struct LightingContributions +{ + float3 diffuse; + float3 specular; +}; + + +]]> + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/opacityToTransparency.xml b/lib/render/vp2ShaderFragments/opacityToTransparency.xml new file mode 100644 index 0000000000..dd01a39c85 --- /dev/null +++ b/lib/render/vp2ShaderFragments/opacityToTransparency.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/plugInfo.json b/lib/render/vp2ShaderFragments/plugInfo.json new file mode 100644 index 0000000000..e1889662d0 --- /dev/null +++ b/lib/render/vp2ShaderFragments/plugInfo.json @@ -0,0 +1,12 @@ +{ + "Plugins": [ + { + "Name": "mayaUsd_ShaderFragments", + "Info": {}, + "LibraryPath": "../../usd/mayaUsd_ShaderFragments", + "ResourcePath": "../../usd/mayaUsd_ShaderFragments/resources", + "Root": "..", + "Type": "library" + } + ] +} diff --git a/lib/render/vp2ShaderFragments/scaledDiffusePassThrough.xml b/lib/render/vp2ShaderFragments/scaledDiffusePassThrough.xml new file mode 100644 index 0000000000..4c53656225 --- /dev/null +++ b/lib/render/vp2ShaderFragments/scaledDiffusePassThrough.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/scaledSpecularPassThrough.xml b/lib/render/vp2ShaderFragments/scaledSpecularPassThrough.xml new file mode 100644 index 0000000000..6fb0f086cb --- /dev/null +++ b/lib/render/vp2ShaderFragments/scaledSpecularPassThrough.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/shaderFragments.cpp b/lib/render/vp2ShaderFragments/shaderFragments.cpp new file mode 100644 index 0000000000..f88d0d3732 --- /dev/null +++ b/lib/render/vp2ShaderFragments/shaderFragments.cpp @@ -0,0 +1,232 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "shaderFragments.h" + +#include "pxr/base/plug/plugin.h" +#include "pxr/base/plug/thisPlugin.h" +#include "pxr/base/tf/stringUtils.h" + +#include "pxr/usdImaging/usdImaging/tokens.h" + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +TF_DEFINE_PRIVATE_TOKENS( + _tokens, + + (FallbackCPVShader) + (FallbackShader) + + (float4ToFloatX) + (float4ToFloatY) + (float4ToFloatZ) + (float4ToFloatW) + (float4ToFloat3) + (float4ToFloat4) + + (lightingContributions) + (scaledDiffusePassThrough) + (scaledSpecularPassThrough) + (opacityToTransparency) + (usdPreviewSurfaceLighting) + (usdPreviewSurfaceCombiner) + + (UsdUVTexture) + + (UsdPrimvarReader_float) + (UsdPrimvarReader_float2) + (UsdPrimvarReader_float3) + (UsdPrimvarReader_float4) + + (UsdPreviewSurface) +); + +static const TfTokenVector _FragmentNames = { + _tokens->UsdUVTexture, + + _tokens->UsdPrimvarReader_float, + _tokens->UsdPrimvarReader_float2, + _tokens->UsdPrimvarReader_float3, + _tokens->UsdPrimvarReader_float4, + + _tokens->float4ToFloatX, + _tokens->float4ToFloatY, + _tokens->float4ToFloatZ, + _tokens->float4ToFloatW, + _tokens->float4ToFloat3, + _tokens->float4ToFloat4, + + _tokens->lightingContributions, + _tokens->scaledDiffusePassThrough, + _tokens->scaledSpecularPassThrough, + _tokens->opacityToTransparency, + _tokens->usdPreviewSurfaceLighting, + _tokens->usdPreviewSurfaceCombiner +}; + +static const TfTokenVector _FragmentGraphNames = { + _tokens->FallbackCPVShader, + _tokens->FallbackShader, + _tokens->UsdPreviewSurface +}; + + +// Helper methods +namespace +{ + std::string _GetResourcePath(const std::string& resource) + { + static PlugPluginPtr plugin = + PlugRegistry::GetInstance().GetPluginWithName("mayaUsd_ShaderFragments"); + if (!TF_VERIFY(plugin, "Could not get plugin\n")) { + return std::string(); + } + + const std::string path = PlugFindPluginResource(plugin, resource); + TF_VERIFY(!path.empty(), "Could not find resource: %s\n", resource.c_str()); + + return path; + } +} + +// Fragment registration +MStatus HdVP2ShaderFragments::registerFragments() +{ + MHWRender::MRenderer* theRenderer = MHWRender::MRenderer::theRenderer(); + if (!theRenderer) { + return MS::kFailure; + } + + MHWRender::MFragmentManager* fragmentManager = + theRenderer->getFragmentManager(); + if (!fragmentManager) { + return MS::kFailure; + } + + // Register all fragments. + for (const TfToken& fragNameToken : _FragmentNames) { + const MString fragName(fragNameToken.GetText()); + + if (fragmentManager->hasFragment(fragName)) { + continue; + } + + const std::string fragXmlFile = + TfStringPrintf("%s.xml", fragName.asChar()); + const std::string fragXmlPath = _GetResourcePath(fragXmlFile); + + const MString addedName = + fragmentManager->addShadeFragmentFromFile( + fragXmlPath.c_str(), + false); + + if (addedName != fragName) { + MGlobal::displayError( + TfStringPrintf("Failed to register fragment '%s' from file: %s", + fragName.asChar(), + fragXmlPath.c_str()).c_str()); + return MS::kFailure; + } + } + + // Register all fragment graphs. + for (const TfToken& fragGraphNameToken : _FragmentGraphNames) { + const MString fragGraphName(fragGraphNameToken.GetText()); + + if (fragmentManager->hasFragment(fragGraphName)) { + continue; + } + + const std::string fragGraphXmlFile = + TfStringPrintf("%s.xml", fragGraphName.asChar()); + const std::string fragGraphXmlPath = _GetResourcePath(fragGraphXmlFile); + + const MString addedName = + fragmentManager->addFragmentGraphFromFile(fragGraphXmlPath.c_str()); + if (addedName != fragGraphName) { + MGlobal::displayError( + TfStringPrintf("Failed to register fragment graph '%s' from file: %s", + fragGraphName.asChar(), + fragGraphXmlPath.c_str()).c_str()); + return MS::kFailure; + } + } + + return MS::kSuccess; +} + +// Fragment deregistration +MStatus HdVP2ShaderFragments::deregisterFragments() +{ + MHWRender::MRenderer* theRenderer = MHWRender::MRenderer::theRenderer(); + if (!theRenderer) { + return MS::kFailure; + } + + MHWRender::MFragmentManager* fragmentManager = + theRenderer->getFragmentManager(); + if (!fragmentManager) { + return MS::kFailure; + } + + // De-register all fragment graphs. + for (const TfToken& fragGraphNameToken : _FragmentGraphNames) { + const MString fragGraphName(fragGraphNameToken.GetText()); + + if (!fragmentManager->removeFragment(fragGraphName)) { + MGlobal::displayWarning( + TfStringPrintf("Failed to remove fragment graph: %s", + fragGraphName.asChar()).c_str()); + return MS::kFailure; + } + } + + // De-register all fragments. + for (const TfToken& fragNameToken : _FragmentNames) { + const MString fragName(fragNameToken.GetText()); + + if (!fragmentManager->removeFragment(fragName)) { + MGlobal::displayWarning( + TfStringPrintf("Failed to remove fragment: %s", + fragName.asChar()).c_str()); + return MS::kFailure; + } + } + +#if MAYA_API_VERSION >= 201700 + // Clear the shader manager's effect cache as well so that any changes to + // the fragments will get picked up if they are re-registered. + const MHWRender::MShaderManager* shaderMgr = theRenderer->getShaderManager(); + if (!shaderMgr) { + return MS::kFailure; + } + + MStatus status = shaderMgr->clearEffectCache(); + if (status != MS::kSuccess) { + MGlobal::displayWarning("Failed to clear shader manager effect cache"); + return status; + } +#endif + + return MS::kSuccess; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/render/vp2ShaderFragments/shaderFragments.h b/lib/render/vp2ShaderFragments/shaderFragments.h new file mode 100644 index 0000000000..7c4db7a285 --- /dev/null +++ b/lib/render/vp2ShaderFragments/shaderFragments.h @@ -0,0 +1,41 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef HD_VP2_SHADER_FRAGMENTS +#define HD_VP2_SHADER_FRAGMENTS + +#include "pxr/pxr.h" + +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/*! \brief + \class HdVP2ShaderFragments +*/ +class HdVP2ShaderFragments +{ +public: + // Loads all fragments into VP2 + static MStatus registerFragments(); + + // Unload all fragments from VP2 + static MStatus deregisterFragments(); +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif // HD_VP2_SHADER_FRAGMENTS diff --git a/lib/render/vp2ShaderFragments/usdPreviewSurfaceCombiner.xml b/lib/render/vp2ShaderFragments/usdPreviewSurfaceCombiner.xml new file mode 100644 index 0000000000..35bf943e81 --- /dev/null +++ b/lib/render/vp2ShaderFragments/usdPreviewSurfaceCombiner.xml @@ -0,0 +1,259 @@ + + + + Combines material and lighting components. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + = 110) +#define float3 vec3 +#endif + +mayaSurfaceShaderOutput +usdPreviewSurfaceCombiner( + float3 diffuseIrradianceIn, + float3 specularIrradianceIn, + float3 ambientIn, + float3 irradianceEnv, + float3 specularEnv, + float3 diffuseColor, + float3 specularColor, + float3 emissiveColor, + float3 transparency) +{ + mayaSurfaceShaderOutput result; + + // Ambient + result.outColor = ambientIn * diffuseColor; + + // Diffuse + result.outColor += diffuseIrradianceIn; + result.outColor += diffuseColor * irradianceEnv; + + if (!mayaAlphaCut) { + result.outColor *= saturate(1.0 - transparency); + } + + // Specular + result.outColor += specularIrradianceIn; + result.outColor += specularColor * specularEnv; + + // Emissive + result.outColor += emissiveColor; + + // Transparency + result.outTransparency = transparency; + + result.outGlowColor = float3(0.0, 0.0, 0.0); + result.outMatteOpacity = (1.0 - transparency); + + return result; +} + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +mayaSurfaceShaderOutput +usdPreviewSurfaceCombiner( + float3 diffuseIrradianceIn, + float3 specularIrradianceIn, + float3 ambientIn, + float3 irradianceEnv, + float3 specularEnv, + float3 diffuseColor, + float3 specularColor, + float3 emissiveColor, + float3 transparency) +{ + mayaSurfaceShaderOutput result; + + // Ambient + result.outColor = ambientIn * diffuseColor; + + // Diffuse + result.outColor += diffuseIrradianceIn; + result.outColor += diffuseColor * irradianceEnv; + + if (!mayaAlphaCut) { + result.outColor *= saturate(1.0 - transparency); + } + + // Specular + result.outColor += specularIrradianceIn; + result.outColor += specularColor * specularEnv; + + // Emissive + result.outColor += emissiveColor; + + // Transparency + result.outTransparency = transparency; + + result.outGlowColor = float3(0.0, 0.0, 0.0); + result.outMatteOpacity = (1.0 - transparency); + + return result; +} + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +mayaSurfaceShaderOutput +usdPreviewSurfaceCombiner( + float3 diffuseIrradianceIn, + float3 specularIrradianceIn, + float3 ambientIn, + float3 irradianceEnv, + float3 specularEnv, + float3 diffuseColor, + float3 specularColor, + float3 emissiveColor, + float3 transparency) +{ + mayaSurfaceShaderOutput result; + + // Ambient + result.outColor = ambientIn * diffuseColor; + + // Diffuse + result.outColor += diffuseIrradianceIn; + result.outColor += diffuseColor * irradianceEnv; + + if (!mayaAlphaCut) { + result.outColor *= saturate(1.0 - transparency); + } + + // Specular + result.outColor += specularIrradianceIn; + result.outColor += specularColor * specularEnv; + + // Emissive + result.outColor += emissiveColor; + + // Transparency + result.outTransparency = transparency; + + result.outGlowColor = float3(0.0, 0.0, 0.0); + result.outMatteOpacity = (1.0 - transparency); + + return result; +} + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +mayaSurfaceShaderOutput +usdPreviewSurfaceCombiner( + float3 diffuseIrradianceIn, + float3 specularIrradianceIn, + float3 ambientIn, + float3 irradianceEnv, + float3 specularEnv, + float3 diffuseColor, + float3 specularColor, + float3 emissiveColor, + float3 transparency) +{ + mayaSurfaceShaderOutput result; + + // Ambient + result.outColor = ambientIn * diffuseColor; + + // Diffuse + result.outColor += diffuseIrradianceIn; + result.outColor += diffuseColor * irradianceEnv; + + if (!mayaAlphaCut) { + result.outColor *= saturate(1.0 - transparency); + } + + // Specular + result.outColor += specularIrradianceIn; + result.outColor += specularColor * specularEnv; + + // Emissive + result.outColor += emissiveColor; + + // Transparency + result.outTransparency = transparency; + + result.outGlowColor = float3(0.0, 0.0, 0.0); + result.outMatteOpacity = (1.0 - transparency); + + return result; +} + +]]> + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/usdPreviewSurfaceLighting.xml b/lib/render/vp2ShaderFragments/usdPreviewSurfaceLighting.xml new file mode 100644 index 0000000000..6c0e09ab3a --- /dev/null +++ b/lib/render/vp2ShaderFragments/usdPreviewSurfaceLighting.xml @@ -0,0 +1,687 @@ + + + + Computes the diffuse and specular lighting contributions for a light. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + = 110) +#define float3 vec3 +#endif + +// line 110 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +#define PI 3.1415 +#define EPSILON 0.001 + + +float +SchlickFresnel(float EdotH) +{ + return pow(max(0.0, 1.0 - EdotH), 5.0); +} + +float +NormalDistribution(float specularRoughness, float NdotH) +{ + float alpha = specularRoughness * specularRoughness; + float alpha2 = alpha * alpha; + float NdotH2 = NdotH * NdotH; + float DDenom = (NdotH2 * (alpha2 - 1.0)) + 1.0; + DDenom *= DDenom; + DDenom *= PI; + float D = (alpha2 + EPSILON) / DDenom; + return D; +} + +float +Geometric( + float specularRoughness, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float alpha = specularRoughness * specularRoughness; + float k = alpha * 0.5; + float G = NdotE / (NdotE * (1.0 - k) + k); + G *= NdotL / (NdotL * (1.0 - k) + k); + return G; +} + +float +evaluateDirectDiffuse() +{ + return 1.0 / PI; +} + +float3 +evaluateDirectSpecular( + float3 specularColorF0, + float3 specularColorF90, + float specularRoughness, + float fresnel, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float3 F = mix(specularColorF0, specularColorF90, fresnel); + float D = NormalDistribution(specularRoughness, NdotH); + float G = Geometric(specularRoughness, NdotL, NdotE, NdotH, EdotH); + float3 RNum = F * G * D; + float RDenom = 4.0f * NdotL * NdotE + EPSILON; + return RNum / RDenom; +} + +LightingContributions +usdPreviewSurfaceLighting( + float3 diffuseColor, + bool useSpecularWorkflow, + float ior, + float metallic, + float specularAmount, + float3 specularColor, + float specularRoughness, + float clearcoatAmount, + float3 clearcoatColor, + float clearcoatRoughness, + float occlusion, + float NdotL, + float NdotE, + float NdotH, + float EdotH, + float3 lightDiffuseIrradiance, + float3 lightSpecularIrradiance) +{ + specularRoughness = max(0.001, specularRoughness); + clearcoatRoughness = max(0.001, clearcoatRoughness); + + float fresnel = SchlickFresnel(EdotH); + + // Evaluate diffuse + float3 d = diffuseColor * evaluateDirectDiffuse(); + + // Evaluate specular first lobe + float3 s1 = float3(0.0, 0.0, 0.0); + if (specularAmount > 0.0) { + float3 F0 = specularColor; + float3 F90 = float3(1.0, 1.0, 1.0); + + if (!useSpecularWorkflow) { + float R = (1.0 - ior) / (1.0 + ior); + float3 specColor = mix(float3(1.0, 1.0, 1.0), diffuseColor, metallic); + F0 = R * R * specColor; + F90 = specColor; + + // For metallic workflows, pure metals have no diffuse + d *= 1.0 - metallic; + } + + s1 = specularAmount * evaluateDirectSpecular( + F0, // Specular color 0 + F90, // Specular color 90 + specularRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + + // Adjust the diffuse so glazing angles have less diffuse + d *= (1.0 - mix(F0, F90, fresnel)); + } + + // Evaluate clearcoat + float3 s2 = float3(0.0, 0.0, 0.0); + if (clearcoatAmount > 0.0) { + s2 = clearcoatAmount * evaluateDirectSpecular( + clearcoatColor, // Clearcoat color 0 + clearcoatColor, // Clearcoat color 90 + clearcoatRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + } + + LightingContributions lightingContrib; + + lightingContrib.diffuse = + occlusion * NdotL * d * lightDiffuseIrradiance; + + lightingContrib.specular = + occlusion * NdotL * (s1 + s2) * lightSpecularIrradiance; + + return lightingContrib; +} + + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +// line 110 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +#define PI 3.1415 +#define EPSILON 0.001 + + +float +SchlickFresnel(float EdotH) +{ + return pow(max(0.0, 1.0 - EdotH), 5.0); +} + +float +NormalDistribution(float specularRoughness, float NdotH) +{ + float alpha = specularRoughness * specularRoughness; + float alpha2 = alpha * alpha; + float NdotH2 = NdotH * NdotH; + float DDenom = (NdotH2 * (alpha2 - 1.0)) + 1.0; + DDenom *= DDenom; + DDenom *= PI; + float D = (alpha2 + EPSILON) / DDenom; + return D; +} + +float +Geometric( + float specularRoughness, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float alpha = specularRoughness * specularRoughness; + float k = alpha * 0.5; + float G = NdotE / (NdotE * (1.0 - k) + k); + G *= NdotL / (NdotL * (1.0 - k) + k); + return G; +} + +float +evaluateDirectDiffuse() +{ + return 1.0 / PI; +} + +float3 +evaluateDirectSpecular( + float3 specularColorF0, + float3 specularColorF90, + float specularRoughness, + float fresnel, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float3 F = mix(specularColorF0, specularColorF90, fresnel); + float D = NormalDistribution(specularRoughness, NdotH); + float G = Geometric(specularRoughness, NdotL, NdotE, NdotH, EdotH); + float3 RNum = F * G * D; + float RDenom = 4.0f * NdotL * NdotE + EPSILON; + return RNum / RDenom; +} + +LightingContributions +usdPreviewSurfaceLighting( + float3 diffuseColor, + bool useSpecularWorkflow, + float ior, + float metallic, + float specularAmount, + float3 specularColor, + float specularRoughness, + float clearcoatAmount, + float3 clearcoatColor, + float clearcoatRoughness, + float occlusion, + float NdotL, + float NdotE, + float NdotH, + float EdotH, + float3 lightDiffuseIrradiance, + float3 lightSpecularIrradiance) +{ + specularRoughness = max(0.001, specularRoughness); + clearcoatRoughness = max(0.001, clearcoatRoughness); + + float fresnel = SchlickFresnel(EdotH); + + // Evaluate diffuse + float3 d = diffuseColor * evaluateDirectDiffuse(); + + // Evaluate specular first lobe + float3 s1 = float3(0.0, 0.0, 0.0); + if (specularAmount > 0.0) { + float3 F0 = specularColor; + float3 F90 = float3(1.0, 1.0, 1.0); + + if (!useSpecularWorkflow) { + float R = (1.0 - ior) / (1.0 + ior); + float3 specColor = mix(float3(1.0, 1.0, 1.0), diffuseColor, metallic); + F0 = R * R * specColor; + F90 = specColor; + + // For metallic workflows, pure metals have no diffuse + d *= 1.0 - metallic; + } + + s1 = specularAmount * evaluateDirectSpecular( + F0, // Specular color 0 + F90, // Specular color 90 + specularRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + + // Adjust the diffuse so glazing angles have less diffuse + d *= (1.0 - mix(F0, F90, fresnel)); + } + + // Evaluate clearcoat + float3 s2 = float3(0.0, 0.0, 0.0); + if (clearcoatAmount > 0.0) { + s2 = clearcoatAmount * evaluateDirectSpecular( + clearcoatColor, // Clearcoat color 0 + clearcoatColor, // Clearcoat color 90 + clearcoatRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + } + + LightingContributions lightingContrib; + + lightingContrib.diffuse = + occlusion * NdotL * d * lightDiffuseIrradiance; + + lightingContrib.specular = + occlusion * NdotL * (s1 + s2) * lightSpecularIrradiance; + + return lightingContrib; +} + + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +// line 110 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +#define PI 3.1415 +#define EPSILON 0.001 + + +float +SchlickFresnel(float EdotH) +{ + return pow(max(0.0, 1.0 - EdotH), 5.0); +} + +float +NormalDistribution(float specularRoughness, float NdotH) +{ + float alpha = specularRoughness * specularRoughness; + float alpha2 = alpha * alpha; + float NdotH2 = NdotH * NdotH; + float DDenom = (NdotH2 * (alpha2 - 1.0)) + 1.0; + DDenom *= DDenom; + DDenom *= PI; + float D = (alpha2 + EPSILON) / DDenom; + return D; +} + +float +Geometric( + float specularRoughness, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float alpha = specularRoughness * specularRoughness; + float k = alpha * 0.5; + float G = NdotE / (NdotE * (1.0 - k) + k); + G *= NdotL / (NdotL * (1.0 - k) + k); + return G; +} + +float +evaluateDirectDiffuse() +{ + return 1.0 / PI; +} + +float3 +evaluateDirectSpecular( + float3 specularColorF0, + float3 specularColorF90, + float specularRoughness, + float fresnel, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float3 F = lerp(specularColorF0, specularColorF90, fresnel); + float D = NormalDistribution(specularRoughness, NdotH); + float G = Geometric(specularRoughness, NdotL, NdotE, NdotH, EdotH); + float3 RNum = F * G * D; + float RDenom = 4.0f * NdotL * NdotE + EPSILON; + return RNum / RDenom; +} + +LightingContributions +usdPreviewSurfaceLighting( + float3 diffuseColor, + bool useSpecularWorkflow, + float ior, + float metallic, + float specularAmount, + float3 specularColor, + float specularRoughness, + float clearcoatAmount, + float3 clearcoatColor, + float clearcoatRoughness, + float occlusion, + float NdotL, + float NdotE, + float NdotH, + float EdotH, + float3 lightDiffuseIrradiance, + float3 lightSpecularIrradiance) +{ + specularRoughness = max(0.001, specularRoughness); + clearcoatRoughness = max(0.001, clearcoatRoughness); + + float fresnel = SchlickFresnel(EdotH); + + // Evaluate diffuse + float3 d = diffuseColor * evaluateDirectDiffuse(); + + // Evaluate specular first lobe + float3 s1 = float3(0.0, 0.0, 0.0); + if (specularAmount > 0.0) { + float3 F0 = specularColor; + float3 F90 = float3(1.0, 1.0, 1.0); + + if (!useSpecularWorkflow) { + float R = (1.0 - ior) / (1.0 + ior); + float3 specColor = lerp(float3(1.0, 1.0, 1.0), diffuseColor, metallic); + F0 = R * R * specColor; + F90 = specColor; + + // For metallic workflows, pure metals have no diffuse + d *= 1.0 - metallic; + } + + s1 = specularAmount * evaluateDirectSpecular( + F0, // Specular color 0 + F90, // Specular color 90 + specularRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + + // Adjust the diffuse so glazing angles have less diffuse + d *= (1.0 - lerp(F0, F90, fresnel)); + } + + // Evaluate clearcoat + float3 s2 = float3(0.0, 0.0, 0.0); + if (clearcoatAmount > 0.0) { + s2 = clearcoatAmount * evaluateDirectSpecular( + clearcoatColor, // Clearcoat color 0 + clearcoatColor, // Clearcoat color 90 + clearcoatRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + } + + LightingContributions lightingContrib; + + lightingContrib.diffuse = + occlusion * NdotL * d * lightDiffuseIrradiance; + + lightingContrib.specular = + occlusion * NdotL * (s1 + s2) * lightSpecularIrradiance; + + return lightingContrib; +} + + +]]> + + + + + + = 110) +#define float3 vec3 +#endif + +// line 110 of "../../../../pxr/usdImaging/lib/usdShaders/shaders/previewSurface.glslfx" + +#define PI 3.1415 +#define EPSILON 0.001 + + +float +SchlickFresnel(float EdotH) +{ + return pow(max(0.0, 1.0 - EdotH), 5.0); +} + +float +NormalDistribution(float specularRoughness, float NdotH) +{ + float alpha = specularRoughness * specularRoughness; + float alpha2 = alpha * alpha; + float NdotH2 = NdotH * NdotH; + float DDenom = (NdotH2 * (alpha2 - 1.0)) + 1.0; + DDenom *= DDenom; + DDenom *= PI; + float D = (alpha2 + EPSILON) / DDenom; + return D; +} + +float +Geometric( + float specularRoughness, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float alpha = specularRoughness * specularRoughness; + float k = alpha * 0.5; + float G = NdotE / (NdotE * (1.0 - k) + k); + G *= NdotL / (NdotL * (1.0 - k) + k); + return G; +} + +float +evaluateDirectDiffuse() +{ + return 1.0 / PI; +} + +float3 +evaluateDirectSpecular( + float3 specularColorF0, + float3 specularColorF90, + float specularRoughness, + float fresnel, + float NdotL, + float NdotE, + float NdotH, + float EdotH) +{ + float3 F = lerp(specularColorF0, specularColorF90, fresnel); + float D = NormalDistribution(specularRoughness, NdotH); + float G = Geometric(specularRoughness, NdotL, NdotE, NdotH, EdotH); + float3 RNum = F * G * D; + float RDenom = 4.0f * NdotL * NdotE + EPSILON; + return RNum / RDenom; +} + +LightingContributions +usdPreviewSurfaceLighting( + float3 diffuseColor, + bool useSpecularWorkflow, + float ior, + float metallic, + float specularAmount, + float3 specularColor, + float specularRoughness, + float clearcoatAmount, + float3 clearcoatColor, + float clearcoatRoughness, + float occlusion, + float NdotL, + float NdotE, + float NdotH, + float EdotH, + float3 lightDiffuseIrradiance, + float3 lightSpecularIrradiance) +{ + specularRoughness = max(0.001, specularRoughness); + clearcoatRoughness = max(0.001, clearcoatRoughness); + + float fresnel = SchlickFresnel(EdotH); + + // Evaluate diffuse + float3 d = diffuseColor * evaluateDirectDiffuse(); + + // Evaluate specular first lobe + float3 s1 = float3(0.0, 0.0, 0.0); + if (specularAmount > 0.0) { + float3 F0 = specularColor; + float3 F90 = float3(1.0, 1.0, 1.0); + + if (!useSpecularWorkflow) { + float R = (1.0 - ior) / (1.0 + ior); + float3 specColor = lerp(float3(1.0, 1.0, 1.0), diffuseColor, metallic); + F0 = R * R * specColor; + F90 = specColor; + + // For metallic workflows, pure metals have no diffuse + d *= 1.0 - metallic; + } + + s1 = specularAmount * evaluateDirectSpecular( + F0, // Specular color 0 + F90, // Specular color 90 + specularRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + + // Adjust the diffuse so glazing angles have less diffuse + d *= (1.0 - lerp(F0, F90, fresnel)); + } + + // Evaluate clearcoat + float3 s2 = float3(0.0, 0.0, 0.0); + if (clearcoatAmount > 0.0) { + s2 = clearcoatAmount * evaluateDirectSpecular( + clearcoatColor, // Clearcoat color 0 + clearcoatColor, // Clearcoat color 90 + clearcoatRoughness, // Roughness + fresnel, // Fresnel + NdotL, NdotE, NdotH, EdotH); // Dot products needed for lights + } + + LightingContributions lightingContrib; + + lightingContrib.diffuse = + occlusion * NdotL * d * lightDiffuseIrradiance; + + lightingContrib.specular = + occlusion * NdotL * (s1 + s2) * lightSpecularIrradiance; + + return lightingContrib; +} + + +]]> + + + + \ No newline at end of file diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h b/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h index 13186e75c5..f2e479db7c 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h @@ -276,7 +276,20 @@ MStatus registerPlugin(AFnPlugin& plugin) status = MayaUsdProxyShapePlugin::initialize(plugin); CHECK_MSTATUS(status); - AL_REGISTER_SHAPE_NODE(plugin, AL::usdmaya::nodes::ProxyShape, AL::usdmaya::nodes::ProxyShapeUI, AL::usdmaya::nodes::ProxyDrawOverride); + if (MayaUsdProxyShapePlugin::useVP2_NativeUSD_Rendering()) { + status = plugin.registerShape( + AL::usdmaya::nodes::ProxyShape::kTypeName, + AL::usdmaya::nodes::ProxyShape::kTypeId, + AL::usdmaya::nodes::ProxyShape::creator, + AL::usdmaya::nodes::ProxyShape::initialise, + AL::usdmaya::nodes::ProxyShapeUI::creator, + MayaUsdProxyShapePlugin::getProxyShapeClassification() + ); + CHECK_MSTATUS(status); + } + else { + AL_REGISTER_SHAPE_NODE(plugin, AL::usdmaya::nodes::ProxyShape, AL::usdmaya::nodes::ProxyShapeUI, AL::usdmaya::nodes::ProxyDrawOverride); + } AL_REGISTER_TRANSFORM_NODE(plugin, AL::usdmaya::nodes::Transform, AL::usdmaya::nodes::TransformationMatrix); AL_REGISTER_DEPEND_NODE(plugin, AL::usdmaya::nodes::RendererManager); diff --git a/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp b/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp index 38e790c78d..5a187d02e9 100644 --- a/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp +++ b/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp @@ -77,14 +77,51 @@ initializePlugin(MObject obj) MPxNode::kDeformerNode); CHECK_MSTATUS(status); - status = plugin.registerShape( - UsdMayaProxyShape::typeName, - UsdMayaProxyShape::typeId, - UsdMayaProxyShape::creator, - UsdMayaProxyShape::initialize, - UsdMayaProxyShapeUI::creator, - &UsdMayaProxyDrawOverride::drawDbClassification); - CHECK_MSTATUS(status); + if (MayaUsdProxyShapePlugin::useVP2_NativeUSD_Rendering()) { + status = plugin.registerShape( + UsdMayaProxyShape::typeName, + UsdMayaProxyShape::typeId, + UsdMayaProxyShape::creator, + UsdMayaProxyShape::initialize, + UsdMayaProxyShapeUI::creator, + MayaUsdProxyShapePlugin::getProxyShapeClassification()); + CHECK_MSTATUS(status); + } + else { + status = plugin.registerShape( + UsdMayaProxyShape::typeName, + UsdMayaProxyShape::typeId, + UsdMayaProxyShape::creator, + UsdMayaProxyShape::initialize, + UsdMayaProxyShapeUI::creator, + &UsdMayaProxyDrawOverride::drawDbClassification); + CHECK_MSTATUS(status); + status = plugin.registerShape( + PxrMayaHdImagingShape::typeName, + PxrMayaHdImagingShape::typeId, + PxrMayaHdImagingShape::creator, + PxrMayaHdImagingShape::initialize, + PxrMayaHdImagingShapeUI::creator, + &PxrMayaHdImagingShapeDrawOverride::drawDbClassification); + CHECK_MSTATUS(status); + status = MHWRender::MDrawRegistry::registerDrawOverrideCreator( + PxrMayaHdImagingShapeDrawOverride::drawDbClassification, + _RegistrantId, + PxrMayaHdImagingShapeDrawOverride::creator); + CHECK_MSTATUS(status); + + status = MHWRender::MDrawRegistry::registerDrawOverrideCreator( + UsdMayaProxyDrawOverride::drawDbClassification, + _RegistrantId, + UsdMayaProxyDrawOverride::Creator); + CHECK_MSTATUS(status); + + status = plugin.registerDisplayFilter( + UsdMayaProxyShape::displayFilterName, + UsdMayaProxyShape::displayFilterLabel, + UsdMayaProxyDrawOverride::drawDbClassification); + CHECK_MSTATUS(status); + } status = plugin.registerNode( UsdMayaReferenceAssembly::typeName, @@ -95,33 +132,6 @@ initializePlugin(MObject obj) &UsdMayaReferenceAssembly::_classification); CHECK_MSTATUS(status); - status = plugin.registerShape( - PxrMayaHdImagingShape::typeName, - PxrMayaHdImagingShape::typeId, - PxrMayaHdImagingShape::creator, - PxrMayaHdImagingShape::initialize, - PxrMayaHdImagingShapeUI::creator, - &PxrMayaHdImagingShapeDrawOverride::drawDbClassification); - CHECK_MSTATUS(status); - - status = MHWRender::MDrawRegistry::registerDrawOverrideCreator( - PxrMayaHdImagingShapeDrawOverride::drawDbClassification, - _RegistrantId, - PxrMayaHdImagingShapeDrawOverride::creator); - CHECK_MSTATUS(status); - - status = MHWRender::MDrawRegistry::registerDrawOverrideCreator( - UsdMayaProxyDrawOverride::drawDbClassification, - _RegistrantId, - UsdMayaProxyDrawOverride::Creator); - CHECK_MSTATUS(status); - - status = plugin.registerDisplayFilter( - UsdMayaProxyShape::displayFilterName, - UsdMayaProxyShape::displayFilterLabel, - UsdMayaProxyDrawOverride::drawDbClassification); - CHECK_MSTATUS(status); - status = MGlobal::sourceFile("usdMaya.mel"); CHECK_MSTATUS(status); From 5e5260eaf2c1172d084bbb14776f6ae5fd87888b Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Fri, 25 Oct 2019 21:16:42 -0400 Subject: [PATCH 04/15] Add UFE-USD plugin --- CMakeLists.txt | 10 + cmake/modules/FindUFE.cmake | 46 +- lib/CMakeLists.txt | 199 ++++++- lib/ufe/Global.cpp | 132 +++++ lib/ufe/Global.h | 36 ++ lib/ufe/ProxyShapeHandler.cpp | 90 ++++ lib/ufe/ProxyShapeHandler.h | 62 +++ lib/ufe/ProxyShapeHierarchy.cpp | 148 ++++++ lib/ufe/ProxyShapeHierarchy.h | 80 +++ lib/ufe/ProxyShapeHierarchyHandler.cpp | 63 +++ lib/ufe/ProxyShapeHierarchyHandler.h | 69 +++ lib/ufe/StagesSubject.cpp | 217 ++++++++ lib/ufe/StagesSubject.h | 90 ++++ lib/ufe/UsdAttribute.cpp | 489 ++++++++++++++++++ lib/ufe/UsdAttribute.h | 215 ++++++++ lib/ufe/UsdAttributes.cpp | 249 +++++++++ lib/ufe/UsdAttributes.h | 70 +++ lib/ufe/UsdAttributesHandler.cpp | 53 ++ lib/ufe/UsdAttributesHandler.h | 53 ++ lib/ufe/UsdHierarchy.cpp | 183 +++++++ lib/ufe/UsdHierarchy.h | 73 +++ lib/ufe/UsdHierarchyHandler.cpp | 67 +++ lib/ufe/UsdHierarchyHandler.h | 66 +++ lib/ufe/UsdRootChildHierarchy.cpp | 68 +++ lib/ufe/UsdRootChildHierarchy.h | 55 ++ ...UsdRotatePivotTranslateUndoableCommand.cpp | 77 +++ .../UsdRotatePivotTranslateUndoableCommand.h | 65 +++ lib/ufe/UsdRotateUndoableCommand.cpp | 102 ++++ lib/ufe/UsdRotateUndoableCommand.h | 71 +++ lib/ufe/UsdScaleUndoableCommand.cpp | 82 +++ lib/ufe/UsdScaleUndoableCommand.h | 68 +++ lib/ufe/UsdSceneItem.cpp | 53 ++ lib/ufe/UsdSceneItem.h | 57 ++ lib/ufe/UsdSceneItemOps.cpp | 110 ++++ lib/ufe/UsdSceneItemOps.h | 69 +++ lib/ufe/UsdSceneItemOpsHandler.cpp | 53 ++ lib/ufe/UsdSceneItemOpsHandler.h | 56 ++ lib/ufe/UsdStageMap.cpp | 63 +++ lib/ufe/UsdStageMap.h | 73 +++ lib/ufe/UsdTransform3d.cpp | 196 +++++++ lib/ufe/UsdTransform3d.h | 78 +++ lib/ufe/UsdTransform3dHandler.cpp | 54 ++ lib/ufe/UsdTransform3dHandler.h | 56 ++ lib/ufe/UsdTranslateUndoableCommand.cpp | 82 +++ lib/ufe/UsdTranslateUndoableCommand.h | 68 +++ lib/ufe/UsdUndoCreateGroupCommand.cpp | 80 +++ lib/ufe/UsdUndoCreateGroupCommand.h | 62 +++ lib/ufe/UsdUndoDeleteCommand.cpp | 58 +++ lib/ufe/UsdUndoDeleteCommand.h | 62 +++ lib/ufe/UsdUndoDuplicateCommand.cpp | 124 +++++ lib/ufe/UsdUndoDuplicateCommand.h | 77 +++ lib/ufe/UsdUndoRenameCommand.cpp | 118 +++++ lib/ufe/UsdUndoRenameCommand.h | 75 +++ lib/ufe/Utils.cpp | 222 ++++++++ lib/ufe/Utils.h | 85 +++ lib/ufe/__init__.py | 0 lib/ufe/private/InPathChange.h | 43 ++ lib/ufe/private/Utils.cpp | 214 ++++++++ lib/ufe/private/Utils.h | 71 +++ lib/ufe/wrapUsdSceneItem.cpp | 195 +++++++ lib/ufe/wrapUtils.cpp | 71 +++ plugin/al/CMakeLists.txt | 22 +- .../AL_USDMaya/AL/usdmaya/PluginRegister.h | 16 + plugin/al/lib/AL_USDMaya/CMakeLists.txt | 5 +- plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt | 15 + plugin/pxr/maya/plugin/pxrUsd/plugin.cpp | 15 + 66 files changed, 6012 insertions(+), 34 deletions(-) create mode 100644 lib/ufe/Global.cpp create mode 100644 lib/ufe/Global.h create mode 100644 lib/ufe/ProxyShapeHandler.cpp create mode 100644 lib/ufe/ProxyShapeHandler.h create mode 100644 lib/ufe/ProxyShapeHierarchy.cpp create mode 100644 lib/ufe/ProxyShapeHierarchy.h create mode 100644 lib/ufe/ProxyShapeHierarchyHandler.cpp create mode 100644 lib/ufe/ProxyShapeHierarchyHandler.h create mode 100644 lib/ufe/StagesSubject.cpp create mode 100644 lib/ufe/StagesSubject.h create mode 100644 lib/ufe/UsdAttribute.cpp create mode 100644 lib/ufe/UsdAttribute.h create mode 100644 lib/ufe/UsdAttributes.cpp create mode 100644 lib/ufe/UsdAttributes.h create mode 100644 lib/ufe/UsdAttributesHandler.cpp create mode 100644 lib/ufe/UsdAttributesHandler.h create mode 100644 lib/ufe/UsdHierarchy.cpp create mode 100644 lib/ufe/UsdHierarchy.h create mode 100644 lib/ufe/UsdHierarchyHandler.cpp create mode 100644 lib/ufe/UsdHierarchyHandler.h create mode 100644 lib/ufe/UsdRootChildHierarchy.cpp create mode 100644 lib/ufe/UsdRootChildHierarchy.h create mode 100644 lib/ufe/UsdRotatePivotTranslateUndoableCommand.cpp create mode 100644 lib/ufe/UsdRotatePivotTranslateUndoableCommand.h create mode 100644 lib/ufe/UsdRotateUndoableCommand.cpp create mode 100644 lib/ufe/UsdRotateUndoableCommand.h create mode 100644 lib/ufe/UsdScaleUndoableCommand.cpp create mode 100644 lib/ufe/UsdScaleUndoableCommand.h create mode 100644 lib/ufe/UsdSceneItem.cpp create mode 100644 lib/ufe/UsdSceneItem.h create mode 100644 lib/ufe/UsdSceneItemOps.cpp create mode 100644 lib/ufe/UsdSceneItemOps.h create mode 100644 lib/ufe/UsdSceneItemOpsHandler.cpp create mode 100644 lib/ufe/UsdSceneItemOpsHandler.h create mode 100644 lib/ufe/UsdStageMap.cpp create mode 100644 lib/ufe/UsdStageMap.h create mode 100644 lib/ufe/UsdTransform3d.cpp create mode 100644 lib/ufe/UsdTransform3d.h create mode 100644 lib/ufe/UsdTransform3dHandler.cpp create mode 100644 lib/ufe/UsdTransform3dHandler.h create mode 100644 lib/ufe/UsdTranslateUndoableCommand.cpp create mode 100644 lib/ufe/UsdTranslateUndoableCommand.h create mode 100644 lib/ufe/UsdUndoCreateGroupCommand.cpp create mode 100644 lib/ufe/UsdUndoCreateGroupCommand.h create mode 100644 lib/ufe/UsdUndoDeleteCommand.cpp create mode 100644 lib/ufe/UsdUndoDeleteCommand.h create mode 100644 lib/ufe/UsdUndoDuplicateCommand.cpp create mode 100644 lib/ufe/UsdUndoDuplicateCommand.h create mode 100644 lib/ufe/UsdUndoRenameCommand.cpp create mode 100644 lib/ufe/UsdUndoRenameCommand.h create mode 100644 lib/ufe/Utils.cpp create mode 100644 lib/ufe/Utils.h create mode 100644 lib/ufe/__init__.py create mode 100644 lib/ufe/private/InPathChange.h create mode 100644 lib/ufe/private/Utils.cpp create mode 100644 lib/ufe/private/Utils.h create mode 100644 lib/ufe/wrapUsdSceneItem.cpp create mode 100644 lib/ufe/wrapUtils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e1d36fd9c..49770f2037 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ option(BUILD_MAYAUSD_LIBRARY "Build Core USD libraries." ON) option(BUILD_PXR_PLUGIN "Build the Pixar USD plugin and libraries." ON) option(BUILD_AL_PLUGIN "Build the Animal Logic USD plugin and libraries." ON) option(WANT_USD_RELATIVE_PATH "Use relative rpaths for USD libraries" OFF) +option(CMAKE_WANT_UFE_BUILD "Enable building with UFE (if found)." ON) # Avoid noisy install messages set(CMAKE_INSTALL_MESSAGE "NEVER") @@ -67,6 +68,15 @@ find_package(USD REQUIRED) include(cmake/usd.cmake) include(${USD_CONFIG_FILE}) +if(CMAKE_WANT_UFE_BUILD) + find_package(UFE QUIET) + if(UFE_FOUND) + message(STATUS "Building with UFE ${UFE_VERSION} features enabled.") + else() + message(STATUS "UFE not found. UFE features will be disabled.") + endif() +endif() + #============================================================================== # Compiler #============================================================================== diff --git a/cmake/modules/FindUFE.cmake b/cmake/modules/FindUFE.cmake index 3a3ff0d09b..cd92e4ce0e 100644 --- a/cmake/modules/FindUFE.cmake +++ b/cmake/modules/FindUFE.cmake @@ -8,11 +8,13 @@ # UFE_FOUND Defined if a UFE installation has been detected # UFE_LIBRARY Path to UFE library # UFE_INCLUDE_DIR Path to the UFE include directory +# UFE_VERSION UFE version (major.minor.patch) from ufe.h # find_path(UFE_INCLUDE_DIR ufe/versionInfo.h HINTS + $ENV{UFE_INCLUDE_ROOT} ${UFE_INCLUDE_ROOT} ${MAYA_DEVKIT_LOCATION} ${MAYA_LOCATION} @@ -25,30 +27,38 @@ find_path(UFE_INCLUDE_DIR "UFE header path" ) -# get UFE_VERSION from ufe.h +# Get the UFE_VERSION and features from ufe.h if(UFE_INCLUDE_DIR AND EXISTS "${UFE_INCLUDE_DIR}/ufe/ufe.h") - foreach(_ufe_comp MAJOR MINOR) - file(STRINGS - "${UFE_INCLUDE_DIR}/ufe/ufe.h" - _ufe_tmp - REGEX "#define UFE_${_ufe_comp}_VERSION .*$") - string(REGEX MATCHALL "[0-9]+" UFE_${_ufe_comp}_VERSION ${_ufe_tmp}) - endforeach() - foreach(_ufe_comp PATCH) - file(STRINGS - "${UFE_INCLUDE_DIR}/ufe/ufe.h" - _ufe_tmp - REGEX "#define UFE_${_ufe_comp}_LEVEL .*$") - string(REGEX MATCHALL "[0-9]+" UFE_${_ufe_comp}_LEVEL ${_ufe_tmp}) - endforeach() + # Parse the file and get the three lines that have the version info. + file(STRINGS + "${UFE_INCLUDE_DIR}/ufe/ufe.h" + _ufe_vers + REGEX "#define[ ]+(UFE_MAJOR_VERSION|UFE_MINOR_VERSION|UFE_PATCH_LEVEL)[ ]+[0-9]+$") + # Then extract the number from each one. + foreach(_ufe_tmp ${_ufe_vers}) + if(_ufe_tmp MATCHES "#define[ ]+(UFE_MAJOR_VERSION|UFE_MINOR_VERSION|UFE_PATCH_LEVEL)[ ]+([0-9]+)$") + set(${CMAKE_MATCH_1} ${CMAKE_MATCH_2}) + endif() + endforeach() set(UFE_VERSION ${UFE_MAJOR_VERSION}.${UFE_MINOR_VERSION}.${UFE_PATCH_LEVEL}) + + file(STRINGS + "${UFE_INCLUDE_DIR}/ufe/ufe.h" + _ufe_features + REGEX "#define UFE_V[0-9]+_FEATURES_AVAILABLE$") + foreach(_ufe_tmp ${_ufe_features}) + if(_ufe_tmp MATCHES "#define UFE_V([0-9]+)_FEATURES_AVAILABLE$") + set(CMAKE_UFE_V${CMAKE_MATCH_1}_FEATURES_AVAILABLE ON) + endif() + endforeach() endif() find_library(UFE_LIBRARY NAMES ufe_${UFE_MAJOR_VERSION} HINTS + $ENV{UFE_LIB_ROOT} ${UFE_LIB_ROOT} ${MAYA_DEVKIT_LOCATION} ${MAYA_LOCATION} @@ -75,3 +85,9 @@ find_package_handle_standard_args(UFE VERSION_VAR UFE_VERSION ) + +if(UFE_FOUND) + message(STATUS "UFE include dir: ${UFE_INCLUDE_DIR}") + message(STATUS "UFE library: ${UFE_LIBRARY}") + message(STATUS "UFE version: ${UFE_VERSION}") +endif() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 1b1957e6fc..d53168838e 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -100,6 +100,44 @@ list(APPEND mayaUsdShaderFragments_xmls render/vp2ShaderFragments/plugInfo.json ) +if(UFE_FOUND) + list(APPEND mayaUsd_src + ufe/Global.cpp + ufe/Utils.cpp + ufe/ProxyShapeHandler.cpp + ufe/ProxyShapeHierarchy.cpp + ufe/ProxyShapeHierarchyHandler.cpp + ufe/StagesSubject.cpp + ufe/UsdHierarchy.cpp + ufe/UsdHierarchyHandler.cpp + ufe/UsdRootChildHierarchy.cpp + ufe/UsdRotatePivotTranslateUndoableCommand.cpp + ufe/UsdRotateUndoableCommand.cpp + ufe/UsdScaleUndoableCommand.cpp + ufe/UsdSceneItem.cpp + ufe/UsdSceneItemOps.cpp + ufe/UsdSceneItemOpsHandler.cpp + ufe/UsdStageMap.cpp + ufe/UsdTransform3d.cpp + ufe/UsdTransform3dHandler.cpp + ufe/UsdTranslateUndoableCommand.cpp + ufe/UsdUndoDeleteCommand.cpp + ufe/UsdUndoDuplicateCommand.cpp + ufe/UsdUndoRenameCommand.cpp + # + ufe/private/Utils.cpp + ) + + if(CMAKE_UFE_V2_FEATURES_AVAILABLE) + list(APPEND mayaUsd_src + ufe/UsdUndoCreateGroupCommand.cpp + ufe/UsdAttribute.cpp + ufe/UsdAttributes.cpp + ufe/UsdAttributesHandler.cpp + ) + endif() +endif() + list(APPEND mayaUsdBase_headers base/api.h base/debugCodes.h @@ -131,6 +169,64 @@ list(APPEND mayaUsdVP2RenderDelegate_headers render/vp2RenderDelegate/proxyRenderDelegate.h ) +if(UFE_FOUND) + list(APPEND mayaUsdUfe_headers + ufe/Global.h + ufe/Utils.h + ufe/ProxyShapeHandler.h + ufe/ProxyShapeHierarchy.h + ufe/ProxyShapeHierarchyHandler.h + ufe/StagesSubject.h + ufe/UsdHierarchy.h + ufe/UsdHierarchyHandler.h + ufe/UsdRootChildHierarchy.h + ufe/UsdRotatePivotTranslateUndoableCommand.h + ufe/UsdRotateUndoableCommand.h + ufe/UsdScaleUndoableCommand.h + ufe/UsdSceneItem.h + ufe/UsdSceneItemOps.h + ufe/UsdSceneItemOpsHandler.h + ufe/UsdStageMap.h + ufe/UsdTransform3d.h + ufe/UsdTransform3dHandler.h + ufe/UsdTranslateUndoableCommand.h + ufe/UsdUndoDeleteCommand.h + ufe/UsdUndoDuplicateCommand.h + ufe/UsdUndoRenameCommand.h + ) + + if(CMAKE_UFE_V2_FEATURES_AVAILABLE) + list(APPEND mayaUsdUfe_headers + ufe/UsdUndoCreateGroupCommand.h + ufe/UsdAttribute.h + ufe/UsdAttributes.h + ufe/UsdAttributesHandler.h + ) + endif() +endif() + +list(APPEND mayaUsdPxrUsdMayaGL_headers + render/pxrUsdMayaGL/batchRenderer.h + render/pxrUsdMayaGL/debugCodes.h + render/pxrUsdMayaGL/hdImagingShapeUI.h + render/pxrUsdMayaGL/hdImagingShapeDrawOverride.h + render/pxrUsdMayaGL/instancerImager.h + render/pxrUsdMayaGL/instancerShapeAdapter.h + render/pxrUsdMayaGL/proxyShapeUI.h + render/pxrUsdMayaGL/proxyDrawOverride.h + render/pxrUsdMayaGL/renderParams.h + render/pxrUsdMayaGL/sceneDelegate.h + render/pxrUsdMayaGL/shapeAdapter.h + render/pxrUsdMayaGL/softSelectHelper.h + render/pxrUsdMayaGL/usdProxyShapeAdapter.h + render/pxrUsdMayaGL/userData.h +) + +list(APPEND mayaUsdPxVP20_headers + render/px_vp20/utils.h + render/px_vp20/utils_legacy.h +) + add_library(${LIBRARY_NAME} SHARED ${mayaUsd_src} ) @@ -143,6 +239,9 @@ set(compile_definitions_list MFB_PACKAGE_MODULE=mayaUsd ) +if(UFE_FOUND) + list(APPEND compile_definitions_list WANT_UFE_BUILD=1) +endif() if(IS_MACOSX) list(APPEND compile_definitions_list OSMac_) endif() @@ -151,12 +250,23 @@ target_compile_definitions(${LIBRARY_NAME} PRIVATE ${compile_definitions_list} ) +# Some of the UFE classes are exporting STL classes which causes this warning. +if(UFE_FOUND AND MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251") + string(REPLACE " " ";" REPLACED_FLAGS ${CMAKE_CXX_FLAGS}) + target_compile_options(${LIBRARY_NAME} INTERFACE ${REPLACED_FLAGS}) +endif() + set(include_directories_list ${MAYA_INCLUDE_DIRS} ${PXR_INCLUDE_DIRS} ${CMAKE_BINARY_DIR}/include ) +if(UFE_FOUND) + list(APPEND include_directories_list ${UFE_INCLUDE_DIR}) +endif() + target_include_directories( ${LIBRARY_NAME} PUBLIC ${include_directories_list} ) @@ -190,6 +300,10 @@ target_link_libraries(${LIBRARY_NAME} vt ) +if(UFE_FOUND) + target_link_libraries(${LIBRARY_NAME} ${UFE_LIBRARY}) +endif() + if(IS_MACOSX OR IS_LINUX) mayaUsd_init_rpath(rpath "plugin") if(WANT_USD_RELATIVE_PATH) @@ -227,6 +341,9 @@ mayaUsd_promoteHeaderList(${mayaUsdUtils_headers}) mayaUsd_promoteHeaderList(${mayaUsdNodes_headers}) mayaUsd_promoteHeaderList(${mayaUsdListeners_headers}) mayaUsd_promoteHeaderList(${mayaUsdVP2RenderDelegate_headers}) +if(UFE_FOUND) + mayaUsd_promoteHeaderList(${mayaUsdUfe_headers}) +endif() # install public headers install(FILES ${CMAKE_BINARY_DIR}/include/mayaUsd/mayaUsd.h @@ -253,14 +370,88 @@ install(FILES ${mayaUsdVP2RenderDelegate_headers} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/render/vp2RenderDelegate ) +if(UFE_FOUND) + install(FILES ${mayaUsdUfe_headers} + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/mayaUsd/ufe + ) +endif() + install(FILES ${mayaUsdShaderFragments_xmls} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/usd/mayaUsd_ShaderFragments/resources ) -#install top level plugInfo.json that includes the configured plugInfo.json -install(CODE - "file(WRITE \"${CMAKE_INSTALL_PREFIX}/lib/usd/plugInfo.json\" \"{\n \\\"Includes\\\": [ \\\"*/resources/\\\" ]\n}\")" -) +if(UFE_FOUND) + # UFE Python Bindings + set(UFE_PYTHON_LIBRARY_NAME ufe) + + add_library(${UFE_PYTHON_LIBRARY_NAME} + SHARED + ufe/wrapUtils.cpp + ) + + target_compile_definitions(${UFE_PYTHON_LIBRARY_NAME} + PRIVATE + MFB_PACKAGE_NAME=${LIBRARY_NAME} + MFB_ALT_PACKAGE_NAME=${LIBRARY_NAME} + MFB_PACKAGE_MODULE=ufe + ) + set_target_properties(${UFE_PYTHON_LIBRARY_NAME} + PROPERTIES + PREFIX "" + ) + if(IS_WINDOWS) + set_target_properties(${UFE_PYTHON_LIBRARY_NAME} + PROPERTIES + SUFFIX ".pyd" + ) + elseif(IS_MACOSX) + set_target_properties(${UFE_PYTHON_LIBRARY_NAME} + PROPERTIES + SUFFIX ".so" + ) + endif() + + target_link_libraries(${UFE_PYTHON_LIBRARY_NAME} + ${LIBRARY_NAME} + ) + + if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "${PROJECT_NAME}") + mayaUsd_add_rpath(rpath "../..") + mayaUsd_install_rpath(rpath ${UFE_PYTHON_LIBRARY_NAME}) + endif() + + install(TARGETS ${UFE_PYTHON_LIBRARY_NAME} + LIBRARY + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/${PROJECT_NAME} + RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/${PROJECT_NAME} + ) + + if(IS_WINDOWS) + install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/${PROJECT_NAME} OPTIONAL) + endif() + + install(FILES ufe/__init__.py + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/${PROJECT_NAME} + ) + + if (CMAKE_UFE_V2_FEATURES_AVAILABLE) + # Maya Attribute Editor python template files + install(FILES ae/mesh/ae_template.py + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/ufe_ae/usd/nodes/mesh + ) + # We need an empty __init__.py file in each subfolder so it is considered a python module. + set(_INIT_PY_DEST ${CMAKE_INSTALL_PREFIX}/lib/python/) + foreach(_SUBDIR ufe_ae usd nodes mesh) + string(APPEND _INIT_PY_DEST "/${_SUBDIR}") + install(FILES ae/__init__.py + DESTINATION ${_INIT_PY_DEST} + ) + endforeach() + endif() + +endif() if(IS_MACOSX) set(_macDef OSMac_) diff --git a/lib/ufe/Global.cpp b/lib/ufe/Global.cpp new file mode 100644 index 0000000000..865b142278 --- /dev/null +++ b/lib/ufe/Global.cpp @@ -0,0 +1,132 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "Global.h" +#include "ProxyShapeHandler.h" +#include "StagesSubject.h" +#include "private/InPathChange.h" +#include "UsdHierarchyHandler.h" +#include "UsdTransform3dHandler.h" +#include "UsdSceneItemOpsHandler.h" + +#include "ufe/rtid.h" +#include "ufe/runTimeMgr.h" +#include "ufe/hierarchyHandler.h" +#include "ufe/ProxyShapeHierarchyHandler.h" + +#ifdef UFE_V2_FEATURES_AVAILABLE +// Note: must come after include of ufe files so we have the define. +#include "UsdAttributesHandler.h" +#endif + +#include +#include + +namespace { + int gRegistrationCount = 0; +} + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Global variables +//------------------------------------------------------------------------------ + +// Maya's UFE run-time name and ID. +static const std::string kMayaRunTimeName("Maya-DG"); +Ufe::Rtid g_MayaRtid = 0; + +// Register this run-time with UFE under the following name. +static const std::string kUSDRunTimeName("USD"); + +// Our run-time ID, allocated by UFE at registration time. Initialize it +// with illegal 0 value. +Ufe::Rtid g_USDRtid = 0; + +// The normal Maya hierarchy handler, which we decorate for ProxyShape support. +// Keep a reference to it to restore on finalization. +Ufe::HierarchyHandler::Ptr g_MayaHierarchyHandler; + +// Subject singleton for observation of all USD stages. +StagesSubject::Ptr g_StagesSubject; + +bool InPathChange::fInPathChange = false; + +//------------------------------------------------------------------------------ +// Functions +//------------------------------------------------------------------------------ + +// Only intended to be called by the plugin initialization, to +// initialize the stage model. +MStatus initialize() +{ + // If we're already registered, do nothing. + if (gRegistrationCount++ > 0) + return MS::kSuccess; + + // Replace the Maya hierarchy handler with ours. + g_MayaRtid = Ufe::RunTimeMgr::instance().getId(kMayaRunTimeName); +#if !defined(NDEBUG) + assert(g_MayaRtid != 0); +#endif + if (g_MayaRtid == 0) + return MS::kFailure; + + g_MayaHierarchyHandler = Ufe::RunTimeMgr::instance().hierarchyHandler(g_MayaRtid); + auto proxyShapeHandler = ProxyShapeHierarchyHandler::create(g_MayaHierarchyHandler); + Ufe::RunTimeMgr::instance().setHierarchyHandler(g_MayaRtid, proxyShapeHandler); + + auto usdHierHandler = UsdHierarchyHandler::create(); + auto usdTrans3dHandler = UsdTransform3dHandler::create(); + auto usdSceneItemOpsHandler = UsdSceneItemOpsHandler::create(); +#ifdef UFE_V2_FEATURES_AVAILABLE + auto usdAttributesHandler = UsdAttributesHandler::create(); + g_USDRtid = Ufe::RunTimeMgr::instance().register_( + kUSDRunTimeName, usdHierHandler, usdTrans3dHandler, usdSceneItemOpsHandler, usdAttributesHandler); +#else + g_USDRtid = Ufe::RunTimeMgr::instance().register_( + kUSDRunTimeName, usdHierHandler, usdTrans3dHandler, usdSceneItemOpsHandler); +#endif +#if !defined(NDEBUG) + assert(g_USDRtid != 0); +#endif + if (g_USDRtid == 0) + return MS::kFailure; + + g_StagesSubject = StagesSubject::create(); + + return MS::kSuccess; +} + +MStatus finalize() +{ + // If more than one plugin still has us registered, do nothing. + if (gRegistrationCount-- > 1) + return MS::kSuccess; + + // Restore the normal Maya hierarchy handler, and unregister. + Ufe::RunTimeMgr::instance().setHierarchyHandler(g_MayaRtid, g_MayaHierarchyHandler); + Ufe::RunTimeMgr::instance().unregister(g_USDRtid); + g_MayaHierarchyHandler.reset(); + + g_StagesSubject.Reset(); + + return MS::kSuccess; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/Global.h b/lib/ufe/Global.h new file mode 100644 index 0000000000..b38dcd5d3e --- /dev/null +++ b/lib/ufe/Global.h @@ -0,0 +1,36 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "maya/MStatus.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +// Only intended to be called by the plugin initialization, to +// initialize the handlers and stage model. +MAYAUSD_CORE_PUBLIC +MStatus initialize(); + +//! Only intended to be called by the plugin finalization, to +//! finalize the handlers stage model. +MAYAUSD_CORE_PUBLIC +MStatus finalize(); + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/ProxyShapeHandler.cpp b/lib/ufe/ProxyShapeHandler.cpp new file mode 100644 index 0000000000..8bf45bdfa4 --- /dev/null +++ b/lib/ufe/ProxyShapeHandler.cpp @@ -0,0 +1,90 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "ProxyShapeHandler.h" + +#include "../utils/query.h" + +#include "maya/MGlobal.h" +#include "maya/MString.h" +#include "maya/MStringArray.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Global variables +//------------------------------------------------------------------------------ +const std::string ProxyShapeHandler::fMayaUsdGatewayNodeType = "mayaUsdProxyShapeBase"; + +//------------------------------------------------------------------------------ +// ProxyShapeHandler +//------------------------------------------------------------------------------ + +/*static*/ +const std::string& ProxyShapeHandler::gatewayNodeType() +{ + return fMayaUsdGatewayNodeType; +} + +/*static*/ +std::vector ProxyShapeHandler::getAllNames() +{ + std::vector names; + MString cmd; + MStringArray result; + cmd.format("ls -type ^1s -long", fMayaUsdGatewayNodeType.c_str()); + if (MS::kSuccess == MGlobal::executeCommand(cmd, result)) + { + for (MString& name : result) + { + names.push_back(name.asChar()); + } + } + return names; +} + +/*static*/ +UsdStageWeakPtr ProxyShapeHandler::dagPathToStage(const std::string& dagPath) +{ + return UsdMayaQuery::GetPrim(dagPath).GetStage(); +} + +/*static*/ +std::vector ProxyShapeHandler::getAllStages() +{ + // According to Pixar, the following should work: + // return UsdMayaStageCache::Get().GetAllStages(); + // but after a file open of a scene with one or more Pixar proxy shapes, + // returns an empty list. To be investigated, PPT, 28-Feb-2019. + + // When using an unmodified AL plugin, the following line crashes + // Maya, so it requires the AL proxy shape inheritance from + // MayaUsdProxyShapeBase. PPT, 12-Apr-2019. + std::vector stages; + for (const auto& name : getAllNames()) + { + UsdStageWeakPtr stage = dagPathToStage(name); + if (stage) + { + stages.push_back(stage); + } + } + return stages; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/ProxyShapeHandler.h b/lib/ufe/ProxyShapeHandler.h new file mode 100644 index 0000000000..553e6eea8d --- /dev/null +++ b/lib/ufe/ProxyShapeHandler.h @@ -0,0 +1,62 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "pxr/pxr.h" +#include "pxr/usd/usd/prim.h" + +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +/*! + Proxy shape abstraction, to support use of USD proxy shape with any plugin + that has a proxy shape derived from MayaUsdProxyShapeBase. + */ +class MAYAUSD_CORE_PUBLIC ProxyShapeHandler +{ +public: + ProxyShapeHandler() = default; + ~ProxyShapeHandler() = default; + + // Delete the copy/move constructors assignment operators. + ProxyShapeHandler(const ProxyShapeHandler&) = delete; + ProxyShapeHandler& operator=(const ProxyShapeHandler&) = delete; + ProxyShapeHandler(ProxyShapeHandler&&) = delete; + ProxyShapeHandler& operator=(ProxyShapeHandler&&) = delete; + + //! \return Type of the Maya shape node at the root of a USD hierarchy. + static const std::string& gatewayNodeType(); + + static std::vector getAllNames(); + + static UsdStageWeakPtr dagPathToStage(const std::string& dagPath); + + static std::vector getAllStages(); + +private: + static const std::string fMayaUsdGatewayNodeType; + +}; // ProxyShapeHandler + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/ProxyShapeHierarchy.cpp b/lib/ufe/ProxyShapeHierarchy.cpp new file mode 100644 index 0000000000..6f302af007 --- /dev/null +++ b/lib/ufe/ProxyShapeHierarchy.cpp @@ -0,0 +1,148 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "ProxyShapeHierarchy.h" +#include "Utils.h" + +#include "ufe/pathComponent.h" +#include "ufe/pathSegment.h" +#include "ufe/rtid.h" + +#include "pxr/usd/usd/stage.h" + +#include + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Global variables +//------------------------------------------------------------------------------ +extern Ufe::Rtid g_USDRtid; + +//------------------------------------------------------------------------------ +// ProxyShapeHierarchy +//------------------------------------------------------------------------------ + +ProxyShapeHierarchy::ProxyShapeHierarchy(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler) + : Ufe::Hierarchy() + , fMayaHierarchyHandler(mayaHierarchyHandler) +{ +} + +ProxyShapeHierarchy::~ProxyShapeHierarchy() +{ +} + +/*static*/ +ProxyShapeHierarchy::Ptr ProxyShapeHierarchy::create(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler) +{ + return std::make_shared(mayaHierarchyHandler); +} + +void ProxyShapeHierarchy::setItem(const Ufe::SceneItem::Ptr& item) +{ + // Our USD root prim is from the stage, which is from the item. So if we are + // changing the item, it's possible that we won't have the same stage (and + // thus the same root prim). To be safe, clear our stored root prim. + if (fItem != item) + { + fUsdRootPrim = UsdPrim(); + } + fItem = item; + fMayaHierarchy = fMayaHierarchyHandler->hierarchy(item); +} + +const UsdPrim& ProxyShapeHierarchy::getUsdRootPrim() const +{ + if (!fUsdRootPrim.IsValid()) + { + // FIXME During AL_usdmaya_ProxyShapeImport, nodes (both Maya + // and USD) are being added (e.g. the proxy shape itself), but + // there is no stage yet, and there is no way to detect that a + // proxy shape import command is under way. PPT, 28-Sep-2018. + UsdStageWeakPtr stage = getStage(fItem->path()); + if (stage) + { + fUsdRootPrim = stage->GetPrimAtPath(SdfPath("/")); + } + } + return fUsdRootPrim; +} + +//------------------------------------------------------------------------------ +// Ufe::Hierarchy overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItem::Ptr ProxyShapeHierarchy::sceneItem() const +{ + return fItem; +} + +bool ProxyShapeHierarchy::hasChildren() const +{ + const UsdPrim& rootPrim = getUsdRootPrim(); + if (!rootPrim.IsValid()) + return false; + return !rootPrim.GetChildren().empty(); +} + +Ufe::SceneItemList ProxyShapeHierarchy::children() const +{ + // Return children of the USD root. + const UsdPrim& rootPrim = getUsdRootPrim(); + if (!rootPrim.IsValid()) + return Ufe::SceneItemList(); + + auto usdChildren = rootPrim.GetChildren(); + auto parentPath = fItem->path(); + + // We must create selection items for our children. These will have as + // path the path of the proxy shape, with a single path segment of a + // single component appended to it. + Ufe::SceneItemList children; + for (const auto& child : usdChildren) + { + children.push_back(UsdSceneItem::create(parentPath + Ufe::PathSegment( + Ufe::PathComponent(child.GetName().GetString()), g_USDRtid, '/'), child)); + } + return children; +} + +Ufe::SceneItem::Ptr ProxyShapeHierarchy::parent() const +{ + return fMayaHierarchy->parent(); +} + +Ufe::AppendedChild ProxyShapeHierarchy::appendChild(const Ufe::SceneItem::Ptr& child) +{ + throw std::runtime_error("ProxyShapeHierarchy::appendChild() not implemented"); +} + +#ifdef UFE_V2_FEATURES_AVAILABLE +Ufe::SceneItem::Ptr ProxyShapeHierarchy::createGroup(const Ufe::PathComponent& name) const +{ + throw std::runtime_error("ProxyShapeHierarchy::createGroup() not implemented"); +} + +Ufe::Group ProxyShapeHierarchy::createGroupCmd(const Ufe::PathComponent& name) const +{ + throw std::runtime_error("ProxyShapeHierarchy::createGroupCmd not implemented"); +} +#endif + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/ProxyShapeHierarchy.h b/lib/ufe/ProxyShapeHierarchy.h new file mode 100644 index 0000000000..9f0a2ef061 --- /dev/null +++ b/lib/ufe/ProxyShapeHierarchy.h @@ -0,0 +1,80 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/hierarchy.h" +#include "ufe/hierarchyHandler.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief USD gateway node hierarchy interface. +/*! + This class defines a hierarchy interface for a single kind of Maya node, + the USD gateway node. This node is special in that its parent is a Maya + node, but its children are children of the USD root prim. + */ +class MAYAUSD_CORE_PUBLIC ProxyShapeHierarchy : public Ufe::Hierarchy +{ +public: + typedef std::shared_ptr Ptr; + + ProxyShapeHierarchy(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler); + ~ProxyShapeHierarchy() override; + + // Delete the copy/move constructors assignment operators. + ProxyShapeHierarchy(const ProxyShapeHierarchy&) = delete; + ProxyShapeHierarchy& operator=(const ProxyShapeHierarchy&) = delete; + ProxyShapeHierarchy(ProxyShapeHierarchy&&) = delete; + ProxyShapeHierarchy& operator=(ProxyShapeHierarchy&&) = delete; + + //! Create a ProxyShapeHierarchy from a UFE hierarchy handler. + static ProxyShapeHierarchy::Ptr create(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler); + + void setItem(const Ufe::SceneItem::Ptr& item); + + // Ufe::Hierarchy overrides + Ufe::SceneItem::Ptr sceneItem() const override; + bool hasChildren() const override; + Ufe::SceneItemList children() const override; + Ufe::SceneItem::Ptr parent() const override; + Ufe::AppendedChild appendChild(const Ufe::SceneItem::Ptr& child) override; + +#ifdef UFE_V2_FEATURES_AVAILABLE + Ufe::SceneItem::Ptr createGroup(const Ufe::PathComponent& name) const override; + Ufe::Group createGroupCmd(const Ufe::PathComponent& name) const override; +#endif + +private: + const UsdPrim& getUsdRootPrim() const; + +private: + Ufe::SceneItem::Ptr fItem; + Hierarchy::Ptr fMayaHierarchy; + Ufe::HierarchyHandler::Ptr fMayaHierarchyHandler; + + // The root prim is initialized on first use and therefore mutable. + mutable UsdPrim fUsdRootPrim; +}; // ProxyShapeHierarchy + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/ProxyShapeHierarchyHandler.cpp b/lib/ufe/ProxyShapeHierarchyHandler.cpp new file mode 100644 index 0000000000..2728396f43 --- /dev/null +++ b/lib/ufe/ProxyShapeHierarchyHandler.cpp @@ -0,0 +1,63 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "ProxyShapeHierarchyHandler.h" +#include "Utils.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +ProxyShapeHierarchyHandler::ProxyShapeHierarchyHandler(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler) + : Ufe::HierarchyHandler() + , fMayaHierarchyHandler(mayaHierarchyHandler) +{ + fProxyShapeHierarchy = ProxyShapeHierarchy::create(mayaHierarchyHandler); +} + +ProxyShapeHierarchyHandler::~ProxyShapeHierarchyHandler() +{ +} + +/*static*/ +ProxyShapeHierarchyHandler::Ptr ProxyShapeHierarchyHandler::create(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler) +{ + return std::make_shared(mayaHierarchyHandler); +} + +//------------------------------------------------------------------------------ +// Ufe::HierarchyHandler overrides +//------------------------------------------------------------------------------ + +Ufe::Hierarchy::Ptr ProxyShapeHierarchyHandler::hierarchy(const Ufe::SceneItem::Ptr& item) const +{ + if (isAGatewayType(item->nodeType())) + { + fProxyShapeHierarchy->setItem(item); + return fProxyShapeHierarchy; + } + else + { + return fMayaHierarchyHandler->hierarchy(item); + } +} + +Ufe::SceneItem::Ptr ProxyShapeHierarchyHandler::createItem(const Ufe::Path& path) const +{ + return fMayaHierarchyHandler->createItem(path); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/ProxyShapeHierarchyHandler.h b/lib/ufe/ProxyShapeHierarchyHandler.h new file mode 100644 index 0000000000..47689ae95b --- /dev/null +++ b/lib/ufe/ProxyShapeHierarchyHandler.h @@ -0,0 +1,69 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "ufe/hierarchyHandler.h" +#include "ufe/ProxyShapeHierarchy.h" + +//PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Maya run-time hierarchy handler with support for USD gateway node. +/*! + This hierarchy handler is NOT a USD run-time hierarchy handler: it is a + Maya run-time hierarchy handler. It decorates the standard Maya run-time + hierarchy handler and replaces it, providing special behavior only if the + requested hierarchy interface is for the Maya to USD gateway node. In that + case, it returns a special ProxyShapeHierarchy interface object, which + knows how to handle USD children of the Maya ProxyShapeHierarchy node. + + For all other Maya nodes, this hierarchy handler simply delegates the work + to the standard Maya hierarchy handler it decorates, which returns a + standard Maya hierarchy interface object. + */ +class MAYAUSD_CORE_PUBLIC ProxyShapeHierarchyHandler : public Ufe::HierarchyHandler +{ +public: + typedef std::shared_ptr Ptr; + + ProxyShapeHierarchyHandler(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler); + ~ProxyShapeHierarchyHandler() override; + + // Delete the copy/move constructors assignment operators. + ProxyShapeHierarchyHandler(const ProxyShapeHierarchyHandler&) = delete; + ProxyShapeHierarchyHandler& operator=(const ProxyShapeHierarchyHandler&) = delete; + ProxyShapeHierarchyHandler(ProxyShapeHierarchyHandler&&) = delete; + ProxyShapeHierarchyHandler& operator=(ProxyShapeHierarchyHandler&&) = delete; + + //! Create a ProxyShapeHierarchyHandler from a UFE hierarchy handler. + static ProxyShapeHierarchyHandler::Ptr create(Ufe::HierarchyHandler::Ptr mayaHierarchyHandler); + + // Ufe::HierarchyHandler overrides + Ufe::Hierarchy::Ptr hierarchy(const Ufe::SceneItem::Ptr& item) const override; + Ufe::SceneItem::Ptr createItem(const Ufe::Path& path) const override; + +private: + Ufe::HierarchyHandler::Ptr fMayaHierarchyHandler; + ProxyShapeHierarchy::Ptr fProxyShapeHierarchy; + +}; // ProxyShapeHierarchyHandler + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/StagesSubject.cpp b/lib/ufe/StagesSubject.cpp new file mode 100644 index 0000000000..87a52b4d27 --- /dev/null +++ b/lib/ufe/StagesSubject.cpp @@ -0,0 +1,217 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "StagesSubject.h" +#include "Utils.h" +#include "UsdStageMap.h" +#include "ProxyShapeHandler.h" +#include "private/InPathChange.h" + +#include "ufe/path.h" +#include "ufe/hierarchy.h" +#include "ufe/scene.h" +#include "ufe/sceneNotification.h" +#include "ufe/transform3d.h" + +#include "maya/MSceneMessage.h" +#include "maya/MMessage.h" + +#include + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Global variables & macros +//------------------------------------------------------------------------------ +extern UsdStageMap g_StageMap; +extern Ufe::Rtid g_USDRtid; + +//------------------------------------------------------------------------------ +// StagesSubject +//------------------------------------------------------------------------------ + +StagesSubject::StagesSubject() +{ + // Workaround to MAYA-65920: at startup, MSceneMessage.kAfterNew file + // callback is incorrectly called by Maya before the + // MSceneMessage.kBeforeNew file callback, which should be illegal. + // Detect this and ignore illegal calls to after new file callbacks. + // PPT, 19-Jan-16. + fBeforeNewCallback = false; + + MStatus res; + fCbIds.append(MSceneMessage::addCallback( + MSceneMessage::kBeforeNew, beforeNewCallback, this, &res)); + CHECK_MSTATUS(res); + fCbIds.append(MSceneMessage::addCallback( + MSceneMessage::kBeforeOpen, beforeOpenCallback, this, &res)); + CHECK_MSTATUS(res); + fCbIds.append(MSceneMessage::addCallback( + MSceneMessage::kAfterOpen, afterOpenCallback, this, &res)); + CHECK_MSTATUS(res); + fCbIds.append(MSceneMessage::addCallback( + MSceneMessage::kAfterNew, afterNewCallback, this, &res)); + CHECK_MSTATUS(res); + + TfWeakPtr me(this); + TfNotice::Register(me, &StagesSubject::onStageSet); +} + +StagesSubject::~StagesSubject() +{ + MMessage::removeCallbacks(fCbIds); + fCbIds.clear(); +} + +/*static*/ +StagesSubject::Ptr StagesSubject::create() +{ + return TfCreateWeakPtr(new StagesSubject); +} + +bool StagesSubject::beforeNewCallback() const +{ + return fBeforeNewCallback; +} + +void StagesSubject::beforeNewCallback(bool b) +{ + fBeforeNewCallback = b; +} + +/*static*/ +void StagesSubject::beforeNewCallback(void* clientData) +{ + StagesSubject* ss = static_cast(clientData); + ss->beforeNewCallback(true); +} + +/*static*/ +void StagesSubject::beforeOpenCallback(void* clientData) +{ + //StagesSubject* ss = static_cast(clientData); + StagesSubject::beforeNewCallback(clientData); +} + +/*static*/ +void StagesSubject::afterNewCallback(void* clientData) +{ + StagesSubject* ss = static_cast(clientData); + + // Workaround to MAYA-65920: detect and avoid illegal callback sequence. + if (!ss->beforeNewCallback()) + return; + + ss->beforeNewCallback(false); + StagesSubject::afterOpenCallback(clientData); +} + +/*static*/ +void StagesSubject::afterOpenCallback(void* clientData) +{ + StagesSubject* ss = static_cast(clientData); + ss->afterOpen(); +} + +void StagesSubject::afterOpen() +{ + // Observe stage changes, for all stages. Return listener object can + // optionally be used to call Revoke() to remove observation, but must + // keep reference to it, otherwise its reference count is immediately + // decremented, falls to zero, and no observation occurs. + + // Ideally, we would observe the data model only if there are observers, + // to minimize cost of observation. However, since observation is + // frequent, we won't implement this for now. PPT, 22-Dec-2017. + std::for_each(std::begin(fStageListeners), std::end(fStageListeners), + [](StageListenerMap::value_type element) { TfNotice::Revoke(element.second); } ); + + StagesSubject::Ptr me(this); + for (auto stage : ProxyShapeHandler::getAllStages()) + { + fStageListeners[stage] = TfNotice::Register( + me, &StagesSubject::stageChanged, stage); + } + + // Set up our stage to AL_usdmaya_ProxyShape UFE path (and reverse) + // mapping. We do this with the following steps: + // - get all proxyShape nodes in the scene. + // - get their AL Python wrapper + // - get their Dag paths + // - convert the Dag paths to UFE paths. + g_StageMap.clear(); + auto proxyShapeNames = ProxyShapeHandler::getAllNames(); + for (const auto& psn : proxyShapeNames) + { + MDagPath dag = nameToDagPath(psn); + Ufe::Path ufePath = dagPathToUfe(dag); + auto stage = ProxyShapeHandler::dagPathToStage(psn); + g_StageMap.addItem(ufePath, stage); + } +} + +void StagesSubject::stageChanged(UsdNotice::ObjectsChanged const& notice, UsdStageWeakPtr const& sender) +{ + // If the stage path has not been initialized yet, do nothing + if (stagePath(sender).empty()) + return; + + auto stage = notice.GetStage(); + for (const auto& changedPath : notice.GetResyncedPaths()) + { + const std::string& usdPrimPathStr = changedPath.GetPrimPath().GetString(); + Ufe::Path ufePath = stagePath(sender) + Ufe::PathSegment(usdPrimPathStr, g_USDRtid, '/'); + auto prim = stage->GetPrimAtPath(changedPath); + // Changed paths could be xformOps. + // These are considered as invalid null prims + if (prim.IsValid() && !InPathChange::inPathChange()) + { + if (prim.IsActive()) + { + auto notification = Ufe::ObjectAdd(Ufe::Hierarchy::createItem(ufePath)); + Ufe::Scene::notifyObjectAdd(notification); + } + else + { + auto notification = Ufe::ObjectPostDelete(Ufe::Hierarchy::createItem(ufePath)); + Ufe::Scene::notifyObjectDelete(notification); + } + } + } + + for (const auto& changedPath : notice.GetChangedInfoOnlyPaths()) + { + auto usdPrimPathStr = changedPath.GetPrimPath().GetString(); + auto ufePath = stagePath(sender) + Ufe::PathSegment(usdPrimPathStr, g_USDRtid, '/'); + // We need to determine if the change is a Transform3d change. + // We must at least pick up xformOp:translate, xformOp:rotateXYZ, + // and xformOp:scale. + static std::string xformOp(".xformOp:"); + if (changedPath.GetElementString().substr(0, xformOp.length()) == xformOp) + { + Ufe::Transform3d::notify(ufePath); + } + } +} + +void StagesSubject::onStageSet(const UsdMayaProxyStageSetNotice& notice) +{ + afterOpen(); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/StagesSubject.h b/lib/ufe/StagesSubject.h new file mode 100644 index 0000000000..42da829f5b --- /dev/null +++ b/lib/ufe/StagesSubject.h @@ -0,0 +1,90 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" +#include "../listeners/proxyShapeNotice.h" + +#include "pxr/base/tf/weakBase.h" +#include "pxr/usd/usd/stage.h" +#include "pxr/base/tf/hash.h" +#include "pxr/base/tf/notice.h" +#include "pxr/usd/usd/notice.h" + +#include "maya/MCallbackIdArray.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Subject class to observe Maya scene. +/*! + This class observes Maya file open, to register a USD observer on each + stage the Maya scene contains. This USD observer translates USD + notifications into UFE notifications. + */ +class MAYAUSD_CORE_PUBLIC StagesSubject : public TfWeakBase +{ +public: + typedef TfWeakPtr Ptr; + + //! Constructor + StagesSubject(); + + //! Destructor + ~StagesSubject(); + + //! Create the StagesSubject. + static StagesSubject::Ptr create(); + + // Delete the copy/move constructors assignment operators. + StagesSubject(const StagesSubject&) = delete; + StagesSubject& operator=(const StagesSubject&) = delete; + StagesSubject(StagesSubject&&) = delete; + StagesSubject& operator=(StagesSubject&&) = delete; + + bool beforeNewCallback() const; + void beforeNewCallback(bool b); + + void afterOpen(); + +private: + // Maya scene message callbacks + static void beforeNewCallback(void* clientData); + static void beforeOpenCallback(void* clientData); + static void afterNewCallback(void* clientData); + static void afterOpenCallback(void* clientData); + + //! Call the stageChanged() methods on stage observers. + void stageChanged(UsdNotice::ObjectsChanged const& notice, UsdStageWeakPtr const& sender); + +private: + // Notice listener method for proxy stage set + void onStageSet(const UsdMayaProxyStageSetNotice& notice); + + // Map of per-stage listeners, indexed by stage. + typedef TfHashMap StageListenerMap; + StageListenerMap fStageListeners; + + bool fBeforeNewCallback = false; + + MCallbackIdArray fCbIds; + +}; // StagesSubject + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdAttribute.cpp b/lib/ufe/UsdAttribute.cpp new file mode 100644 index 0000000000..c41566ce41 --- /dev/null +++ b/lib/ufe/UsdAttribute.cpp @@ -0,0 +1,489 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdAttribute.h" + +#include "pxr/base/tf/token.h" +#include "pxr/base/vt/value.h" +#include "pxr/usd/usd/schemaRegistry.h" +#include "pxr/usd/sdf/attributeSpec.h" + +// We unconditionally want the UFE asserts here (even in release builds). +// The UFE_ASSERT_MSG has a built-in throw which we want to use for error handling. +#define UFE_ENABLE_ASSERTS +#include + +#include +#include +#include + +// Note: normally we would use this using directive, but here we cannot because +// our class is called UsdAttribute which is exactly the same as the one +// in USD. +// PXR_NAMESPACE_USING_DIRECTIVE + +static constexpr char kErrorMsgFailedSet[] = "Failed to set USD attribute with new value"; +static constexpr char kErrorMsgFailedConvertToString[] = "Could not convert the attribute to a string"; +static constexpr char kErrorMsgInvalidType[] = "USD attribute does not match created attribute class type"; +static constexpr char kErrorMsgEnumNoValue[] = "Enum string attribute has no value"; + +//------------------------------------------------------------------------------ +// Helper functions +//------------------------------------------------------------------------------ + +namespace +{ + +std::string getUsdAttributeValueAsString(const PXR_NS::UsdAttribute& attr) +{ + if (!attr.HasValue()) return ""; + + PXR_NS::VtValue v; + if (attr.Get(&v)) + { + if (v.CanCast()) + { + PXR_NS::VtValue v_str = v.Cast(); + return v_str.Get(); + } + + std::ostringstream os; + os << v; + return os.str(); + } + + UFE_ASSERT_MSG(false, kErrorMsgFailedConvertToString); + return ""; +} + +template +U getUsdAttributeVectorAsUfe(const PXR_NS::UsdAttribute& attr) +{ + if (!attr.HasValue()) return U(); + + PXR_NS::VtValue vt; + if (attr.Get(&vt) && vt.IsHolding()) + { + T gfVec = vt.UncheckedGet(); + U ret(gfVec[0], gfVec[1], gfVec[2]); + return ret; + } + + UFE_ASSERT_MSG(false, kErrorMsgInvalidType); + return U(); +} + +template +void setUsdAttributeVectorFromUfe(PXR_NS::UsdAttribute& attr, const U& value) +{ + T vec; + UFE_ASSERT_MSG(attr.Get(&vec), kErrorMsgInvalidType); + vec.Set(value.x(), value.y(), value.z()); + bool b = attr.Set(vec); + UFE_ASSERT_MSG(b, kErrorMsgFailedSet); +} + +} // end namespace + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// UsdAttribute: +//------------------------------------------------------------------------------ + +UsdAttribute::UsdAttribute(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) + : fUsdAttr(usdAttr) +{ + fPrim = item->prim(); +} + +UsdAttribute::~UsdAttribute() +{ +} + +bool UsdAttribute::hasValue() const +{ + return fUsdAttr.HasValue(); +} + +std::string UsdAttribute::name() const +{ + // Should be the same as the name we were created with. + return fUsdAttr.GetName().GetString(); +} + +std::string UsdAttribute::documentation() const +{ + return fUsdAttr.GetDocumentation(); +} + +std::string UsdAttribute::string() const +{ + return getUsdAttributeValueAsString(fUsdAttr); +} + +//------------------------------------------------------------------------------ +// UsdAttributeGeneric: +//------------------------------------------------------------------------------ + +UsdAttributeGeneric::UsdAttributeGeneric(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) + : Ufe::AttributeGeneric(item) + , UsdAttribute(item, usdAttr) +{ +} + +/*static*/ +UsdAttributeGeneric::Ptr UsdAttributeGeneric::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeGeneric - Ufe::AttributeGeneric overrides +//------------------------------------------------------------------------------ + +std::string UsdAttributeGeneric::nativeType() const +{ + return fUsdAttr.GetTypeName().GetType().GetTypeName(); +} + +//------------------------------------------------------------------------------ +// UsdAttributeEnumString: +//------------------------------------------------------------------------------ + +UsdAttributeEnumString::UsdAttributeEnumString(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) + : Ufe::AttributeEnumString(item) + , UsdAttribute(item, usdAttr) +{ +} + +/*static*/ +UsdAttributeEnumString::Ptr UsdAttributeEnumString::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeEnumString - Ufe::AttributeEnumString overrides +//------------------------------------------------------------------------------ + +std::string UsdAttributeEnumString::get() const +{ + UFE_ASSERT_MSG(hasValue(), kErrorMsgEnumNoValue); + PXR_NS::VtValue vt; + if (fUsdAttr.Get(&vt) && vt.IsHolding()) + { + TfToken tok = vt.UncheckedGet(); + return tok.GetString(); + } + + UFE_ASSERT_MSG(false, kErrorMsgInvalidType); + return ""; +} + +void UsdAttributeEnumString::set(const std::string& value) +{ + PXR_NS::TfToken dummy; + UFE_ASSERT_MSG(fUsdAttr.Get(&dummy), kErrorMsgInvalidType); + PXR_NS::TfToken tok(value); + bool b = fUsdAttr.Set(tok); + UFE_ASSERT_MSG(b, kErrorMsgFailedSet); +} + +Ufe::AttributeEnumString::EnumValues UsdAttributeEnumString::getEnumValues() const +{ + PXR_NS::TfToken tk(name()); + auto attrDefn = PXR_NS::UsdSchemaRegistry::GetAttributeDefinition(fPrim.GetTypeName(), tk); + if (attrDefn && attrDefn->HasAllowedTokens()) + { + auto tokenArray = attrDefn->GetAllowedTokens(); + std::vector tokenVec(tokenArray.begin(), tokenArray.end()); + EnumValues tokens = PXR_NS::TfToStringVector(tokenVec); + return tokens; + } + + return EnumValues(); +} + +//------------------------------------------------------------------------------ +// TypedUsdAttribute: +//------------------------------------------------------------------------------ + +template +TypedUsdAttribute::TypedUsdAttribute(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) + : Ufe::TypedAttribute(item) + , UsdAttribute(item, usdAttr) +{ +} + +//------------------------------------------------------------------------------ +// TypedUsdAttribute - Ufe::TypedAttribute overrides +//------------------------------------------------------------------------------ + +template<> +std::string TypedUsdAttribute::get() const +{ + if (!hasValue()) return ""; + + PXR_NS::VtValue vt; + if (fUsdAttr.Get(&vt)) + { + // The USDAttribute can be holding either TfToken or string. + if (vt.IsHolding()) + { + TfToken tok = vt.UncheckedGet(); + return tok.GetString(); + } + else if (vt.IsHolding()) + { + return vt.UncheckedGet(); + } + } + + UFE_ASSERT_MSG(false, kErrorMsgInvalidType); + return ""; +} + +template <> +void TypedUsdAttribute::set(const std::string& value) +{ + // We need to figure out if the USDAttribute is holding a TfToken or string. + const PXR_NS::SdfValueTypeName typeName = fUsdAttr.GetTypeName(); + if (typeName.GetHash() == SdfValueTypeNames->String.GetHash()) + { + std::string dummy; + UFE_ASSERT_MSG(fUsdAttr.Get(&dummy), kErrorMsgInvalidType); + bool b = fUsdAttr.Set(value); + UFE_ASSERT_MSG(b, kErrorMsgFailedSet); + return; + } + else if (typeName.GetHash() == SdfValueTypeNames->Token.GetHash()) + { + PXR_NS::TfToken dummy; + UFE_ASSERT_MSG(fUsdAttr.Get(&dummy), kErrorMsgInvalidType); + PXR_NS::TfToken tok(value); + bool b = fUsdAttr.Set(tok); + UFE_ASSERT_MSG(b, kErrorMsgFailedSet); + return; + } + + // If we get here it means the USDAttribute type wasn't TfToken or string. + UFE_ASSERT_MSG(false, kErrorMsgInvalidType); +} + +template<> +Ufe::Color3f TypedUsdAttribute::get() const +{ + return getUsdAttributeVectorAsUfe(fUsdAttr); +} + +// Note: cannot use setUsdAttributeVectorFromUfe since it relies on x/y/z +template<> +void TypedUsdAttribute::set(const Ufe::Color3f& value) +{ + GfVec3f vec; + UFE_ASSERT_MSG(fUsdAttr.Get(&vec), kErrorMsgInvalidType); + vec.Set(value.r(), value.g(), value.b()); + bool b = fUsdAttr.Set(vec); + UFE_ASSERT_MSG(b, kErrorMsgFailedSet); +} + +template<> +Ufe::Vector3i TypedUsdAttribute::get() const +{ + return getUsdAttributeVectorAsUfe(fUsdAttr); +} + +template<> +void TypedUsdAttribute::set(const Ufe::Vector3i& value) +{ + setUsdAttributeVectorFromUfe(fUsdAttr, value); +} + +template<> +Ufe::Vector3f TypedUsdAttribute::get() const +{ + return getUsdAttributeVectorAsUfe(fUsdAttr); +} + +template<> +void TypedUsdAttribute::set(const Ufe::Vector3f& value) +{ + setUsdAttributeVectorFromUfe(fUsdAttr, value); +} + +template<> +Ufe::Vector3d TypedUsdAttribute::get() const +{ + return getUsdAttributeVectorAsUfe(fUsdAttr); +} + +template<> +void TypedUsdAttribute::set(const Ufe::Vector3d& value) +{ + setUsdAttributeVectorFromUfe(fUsdAttr, value); +} + +template +T TypedUsdAttribute::get() const +{ + if (!hasValue()) return T(); + + PXR_NS::VtValue vt; + if (fUsdAttr.Get(&vt) && vt.IsHolding()) + { + return vt.UncheckedGet(); + } + + UFE_ASSERT_MSG(false, kErrorMsgInvalidType); + return T(); +} + +template +void TypedUsdAttribute::set(const T& value) +{ + T dummy; + UFE_ASSERT_MSG(fUsdAttr.Get(&dummy), kErrorMsgInvalidType); + bool b = fUsdAttr.Set(value); + UFE_ASSERT_MSG(b, kErrorMsgFailedSet); +} + +//------------------------------------------------------------------------------ +// UsdAttributeBool: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeBool::Ptr UsdAttributeBool::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeInt: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeInt::Ptr UsdAttributeInt::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeFloat: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeFloat::Ptr UsdAttributeFloat::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeDouble: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeDouble::Ptr UsdAttributeDouble::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeString: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeString::Ptr UsdAttributeString::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeColorFloat3: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeColorFloat3::Ptr UsdAttributeColorFloat3::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeInt3: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeInt3::Ptr UsdAttributeInt3::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeFloat3: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeFloat3::Ptr UsdAttributeFloat3::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +//------------------------------------------------------------------------------ +// UsdAttributeDouble3: +//------------------------------------------------------------------------------ + +/*static*/ +UsdAttributeDouble3::Ptr UsdAttributeDouble3::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr) +{ + auto attr = std::make_shared(item, usdAttr); + return attr; +} + +#if 0 +// Note: if we were to implement generic attribute setting (via string) this +// would be the way it could be done. +bool UsdAttribute::setValue(const std::string& value) +{ + // Put the input string into a VtValue so we can cast it to the proper type. + PXR_NS::VtValue val(value.c_str()); + + // Attempt to cast the value to what we want. Get a default value for this + // attribute's type name. + PXR_NS::VtValue defVal = fUsdAttr.GetTypeName().GetDefaultValue(); + + // Attempt to cast the given string to the default value's type. + // If casting fails, attempt to continue with the given value. + PXR_NS::VtValue cast = PXR_NS::VtValue::CastToTypeOf(val, defVal); + if (!cast.IsEmpty()) + cast.Swap(val); + + return fUsdAttr.Set(val); +} +#endif + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdAttribute.h b/lib/ufe/UsdAttribute.h new file mode 100644 index 0000000000..005a226338 --- /dev/null +++ b/lib/ufe/UsdAttribute.h @@ -0,0 +1,215 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "UsdSceneItem.h" + +#include "ufe/attribute.h" + +#include "pxr/usd/usd/prim.h" +#include "pxr/usd/usd/attribute.h" + +// Ufe::Attribute overrides (minus the type method) +#define UFE_ATTRIBUTE_OVERRIDES \ + bool hasValue() const override { return UsdAttribute::hasValue(); } \ + std::string name() const override { return UsdAttribute::name(); } \ + std::string documentation() const override { return UsdAttribute::documentation(); } \ + std::string string() const override { return UsdAttribute::string(); } + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Internal helper class to implement the pure virtual methods from Ufe::Attribute. +class UsdAttribute +{ +public: + UsdAttribute(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); + ~UsdAttribute(); + + // Ufe::Attribute override methods that we've mimic'd here. + bool hasValue() const; + std::string name() const; + std::string documentation() const; + std::string string() const; + +public: + PXR_NS::UsdPrim fPrim; + PXR_NS::UsdAttribute fUsdAttr; +}; // UsdAttribute + +//! \brief Interface for USD attributes which don't match any defined type. +class UsdAttributeGeneric : public Ufe::AttributeGeneric, private UsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + UsdAttributeGeneric(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); + + //! Create a UsdAttributeGeneric. + static UsdAttributeGeneric::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); + + // Ufe::Attribute overrides + UFE_ATTRIBUTE_OVERRIDES + + // Ufe::AttributeGeneric overrides + std::string nativeType() const override; +}; // UsdAttributeGeneric + +//! \brief Interface for USD token attributes. +class UsdAttributeEnumString : public Ufe::AttributeEnumString, private UsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + UsdAttributeEnumString(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); + + //! Create a UsdAttributeEnumString. + static UsdAttributeEnumString::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); + + // Ufe::Attribute overrides + UFE_ATTRIBUTE_OVERRIDES + + // Ufe::AttributeEnumString overrides + std::string get() const override; + void set(const std::string& value) override; + EnumValues getEnumValues() const override; +}; // UsdAttributeEnumString + +//! \brief Internal helper template class to implement the get/set methods from Ufe::TypeAttribute. +template +class TypedUsdAttribute : public Ufe::TypedAttribute, private UsdAttribute +{ +public: + TypedUsdAttribute(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); + + // Ufe::Attribute overrides + UFE_ATTRIBUTE_OVERRIDES + + // Ufe::TypedAttribute overrides + T get() const override; + void set(const T& value) override; +}; // TypedUsdAttribute + +//! \brief Interface for USD bool attributes. +class UsdAttributeBool : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeBool. + static UsdAttributeBool::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeBool + +//! \brief Interface for USD int attributes. +class UsdAttributeInt : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeInt. + static UsdAttributeInt::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeInt + +//! \brief Interface for USD float attributes. +class UsdAttributeFloat : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeFloat. + static UsdAttributeFloat::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeFloat + +//! \brief Interface for USD double attributes. +class UsdAttributeDouble : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeDouble. + static UsdAttributeDouble::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeDouble + +//! \brief Interface for USD string/token attributes. +class UsdAttributeString : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeString. + static UsdAttributeString::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeString + +//! \brief Interface for USD RGB color (float) attributes. +class UsdAttributeColorFloat3 : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeColorFloat3. + static UsdAttributeColorFloat3::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeColorFloat3 + +//! \brief Interface for USD Vector3i (int) attributes. +class UsdAttributeInt3 : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeInt3. + static UsdAttributeInt3::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeInt3 + +//! \brief Interface for USD Vector3f (float) attributes. +class UsdAttributeFloat3 : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeFloat3. + static UsdAttributeFloat3::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeFloat3 + +//! \brief Interface for USD Vector3d (double) attributes. +class UsdAttributeDouble3 : public TypedUsdAttribute +{ +public: + typedef std::shared_ptr Ptr; + + using TypedUsdAttribute::TypedUsdAttribute; + + //! Create a UsdAttributeDouble3. + static UsdAttributeDouble3::Ptr create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAttribute& usdAttr); +}; // UsdAttributeDouble3 + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdAttributes.cpp b/lib/ufe/UsdAttributes.cpp new file mode 100644 index 0000000000..fd9e817fb1 --- /dev/null +++ b/lib/ufe/UsdAttributes.cpp @@ -0,0 +1,249 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdAttributes.h" + +#include + +#include "pxr/base/tf/token.h" +#include "pxr/usd/usd/schemaRegistry.h" +#include "pxr/usd/sdf/attributeSpec.h" + +// Note: normally we would use this using directive, but here we cannot because +// one of our classes is called UsdAttribute which is exactly the same as +// the one in USD. +// PXR_NAMESPACE_USING_DIRECTIVE + +static constexpr char kErrorMsgUnknown[] = "Unknown UFE attribute type encountered"; +static constexpr char kErrorMsgInvalidAttribute[] = "Invalid USDAttribute!"; + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdAttributes::UsdAttributes(const UsdSceneItem::Ptr& item) + : Ufe::Attributes() + , fItem(item) +{ + fPrim = item->prim(); +} + +UsdAttributes::~UsdAttributes() +{ +} + +/*static*/ +UsdAttributes::Ptr UsdAttributes::create(const UsdSceneItem::Ptr& item) +{ + auto attrs = std::make_shared(item); + return attrs; +} + +//------------------------------------------------------------------------------ +// Ufe::Attributes overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItem::Ptr UsdAttributes::sceneItem() const +{ + return fItem; +} + +Ufe::Attribute::Type UsdAttributes::attributeType(const std::string& name) +{ + PXR_NS::TfToken tok(name); + PXR_NS::UsdAttribute usdAttr = fPrim.GetAttribute(tok); + return getUfeTypeForAttribute(usdAttr); +} + +Ufe::Attribute::Ptr UsdAttributes::attribute(const std::string& name) +{ + // If we've already created an attribute for this name, just return it. + auto iter = fAttributes.find(name); + if (iter != std::end(fAttributes)) + return iter->second; + + // No attribute for the input name was found -> create one. + PXR_NS::TfToken tok(name); + PXR_NS::UsdAttribute usdAttr = fPrim.GetAttribute(tok); + Ufe::Attribute::Type newAttrType = getUfeTypeForAttribute(usdAttr); + Ufe::Attribute::Ptr newAttr; + + if (newAttrType == Ufe::Attribute::kBool) { + newAttr = UsdAttributeBool::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kInt) { + newAttr = UsdAttributeInt::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kFloat) { + newAttr = UsdAttributeFloat::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kDouble) { + newAttr = UsdAttributeDouble::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kString) { + newAttr = UsdAttributeString::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kColorFloat3) { + newAttr = UsdAttributeColorFloat3::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kEnumString) { + newAttr = UsdAttributeEnumString::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kInt3) { + newAttr = UsdAttributeInt3::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kFloat3) { + newAttr = UsdAttributeFloat3::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kDouble3) { + newAttr = UsdAttributeDouble3::create(fItem, usdAttr); + } + else if (newAttrType == Ufe::Attribute::kGeneric) { + newAttr = UsdAttributeGeneric::create(fItem, usdAttr); + } + else { + UFE_ASSERT_MSG(false, kErrorMsgUnknown); + } + fAttributes[name] = newAttr; + return newAttr; +} + +std::vector UsdAttributes::attributeNames() const +{ + std::vector names; + for (const auto& attr : fPrim.GetAttributes()) + { + names.push_back(attr.GetName()); + } + return names; +} + +bool UsdAttributes::hasAttribute(const std::string& name) const +{ + TfToken tkName(name); + return fPrim.HasAttribute(tkName); +} + +Ufe::Attribute::Type UsdAttributes::getUfeTypeForAttribute(const PXR_NS::UsdAttribute& usdAttr) const +{ + // Map the USD type into UFE type. + static const std::unordered_map sUsdTypeToUfe + { + {SdfValueTypeNames->Bool.GetHash(), Ufe::Attribute::kBool}, // bool +// {SdfValueTypeNames->UChar.GetHash(), Ufe::Attribute::kUnknown}, // uint8_t + {SdfValueTypeNames->Int.GetHash(), Ufe::Attribute::kInt}, // int32_t +// {SdfValueTypeNames->UInt.GetHash(), Ufe::Attribute::kUnknown}, // uint32_t +// {SdfValueTypeNames->Int64.GetHash(), Ufe::Attribute::kInt}, // int64_t +// {SdfValueTypeNames->UInt64.GetHash(), Ufe::Attribute::kUnknown}, // uint64_t +// {SdfValueTypeNames->Half.GetHash(), Ufe::Attribute::kUnknown}, // GfHalf + {SdfValueTypeNames->Float.GetHash(), Ufe::Attribute::kFloat}, // float + {SdfValueTypeNames->Double.GetHash(), Ufe::Attribute::kDouble}, // double + {SdfValueTypeNames->String.GetHash(), Ufe::Attribute::kString}, // std::string + {SdfValueTypeNames->Token.GetHash(), Ufe::Attribute::kEnumString}, // TfToken +// {SdfValueTypeNames->Asset.GetHash(), Ufe::Attribute::kUnknown}, // SdfAssetPath +// {SdfValueTypeNames->Int2.GetHash(), Ufe::Attribute::kInt2}, // GfVec2i +// {SdfValueTypeNames->Half2.GetHash(), Ufe::Attribute::kUnknown}, // GfVec2h +// {SdfValueTypeNames->Float2.GetHash(), Ufe::Attribute::kFloat2}, // GfVec2f +// {SdfValueTypeNames->Double2.GetHash(), Ufe::Attribute::kDouble2}, // GfVec2d + {SdfValueTypeNames->Int3.GetHash(), Ufe::Attribute::kInt3}, // GfVec3i +// {SdfValueTypeNames->Half3.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3h + {SdfValueTypeNames->Float3.GetHash(), Ufe::Attribute::kFloat3}, // GfVec3f + {SdfValueTypeNames->Double3.GetHash(), Ufe::Attribute::kDouble3}, // GfVec3d +// {SdfValueTypeNames->Int4.GetHash(), Ufe::Attribute::kInt4}, // GfVec4i +// {SdfValueTypeNames->Half4.GetHash(), Ufe::Attribute::kUnknown}, // GfVec4h +// {SdfValueTypeNames->Float4.GetHash(), Ufe::Attribute::kFloat4}, // GfVec4f +// {SdfValueTypeNames->Double4.GetHash(), Ufe::Attribute::kDouble4}, // GfVec4d +// {SdfValueTypeNames->Point3h.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3h +// {SdfValueTypeNames->Point3f.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3f +// {SdfValueTypeNames->Point3d.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3d +// {SdfValueTypeNames->Vector3h.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3h +// {SdfValueTypeNames->Vector3f.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3f +// {SdfValueTypeNames->Vector3d.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3d +// {SdfValueTypeNames->Normal3h.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3h +// {SdfValueTypeNames->Normal3f.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3f +// {SdfValueTypeNames->Normal3d.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3d +// {SdfValueTypeNames->Color3h.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3h + {SdfValueTypeNames->Color3f.GetHash(), Ufe::Attribute::kColorFloat3}, // GfVec3f + {SdfValueTypeNames->Color3d.GetHash(), Ufe::Attribute::kColorFloat3}, // GfVec3d +// {SdfValueTypeNames->Color4h.GetHash(), Ufe::Attribute::kUnknown}, // GfVec4h +// {SdfValueTypeNames->Color4f.GetHash(), Ufe::Attribute::kUnknown}, // GfVec4f +// {SdfValueTypeNames->Color4d.GetHash(), Ufe::Attribute::kUnknown}, // GfVec4d +// {SdfValueTypeNames->Quath.GetHash(), Ufe::Attribute::kUnknown}, // GfQuath +// {SdfValueTypeNames->Quatf.GetHash(), Ufe::Attribute::kUnknown}, // GfQuatf +// {SdfValueTypeNames->Quatd.GetHash(), Ufe::Attribute::kUnknown}, // GfQuatd +// {SdfValueTypeNames->Matrix2d.GetHash(), Ufe::Attribute::kUnknown}, // GfMatrix2d +// {SdfValueTypeNames->Matrix3d.GetHash(), Ufe::Attribute::kUnknown}, // GfMatrix3d +// {SdfValueTypeNames->Matrix4d.GetHash(), Ufe::Attribute::kUnknown}, // GfMatrix4d +// {SdfValueTypeNames->Frame4d.GetHash(), Ufe::Attribute::kUnknown}, // GfMatrix4d +// {SdfValueTypeNames->TexCoord2f.GetHash(), Ufe::Attribute::kUnknown}, // GfVec2f +// {SdfValueTypeNames->TexCoord2d.GetHash(), Ufe::Attribute::kUnknown}, // GfVec2d +// {SdfValueTypeNames->TexCoord2h.GetHash(), Ufe::Attribute::kUnknown}, // GfVec2h +// {SdfValueTypeNames->TexCoord3f.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3f +// {SdfValueTypeNames->TexCoord3d.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3d +// {SdfValueTypeNames->TexCoord3h.GetHash(), Ufe::Attribute::kUnknown}, // GfVec3h +// To add? +// SdfValueTypeName BoolArray; +// SdfValueTypeName UCharArray, IntArray, UIntArray, Int64Array, UInt64Array; +// SdfValueTypeName HalfArray, FloatArray, DoubleArray; +// SdfValueTypeName StringArray, TokenArray, AssetArray; +// SdfValueTypeName Int2Array, Int3Array, Int4Array; +// SdfValueTypeName Half2Array, Half3Array, Half4Array; +// SdfValueTypeName Float2Array, Float3Array, Float4Array; +// SdfValueTypeName Double2Array, Double3Array, Double4Array; +// SdfValueTypeName Point3hArray, Point3fArray, Point3dArray; +// SdfValueTypeName Vector3hArray, Vector3fArray, Vector3dArray; +// SdfValueTypeName Normal3hArray, Normal3fArray, Normal3dArray; +// SdfValueTypeName Color3hArray, Color3fArray, Color3dArray; +// SdfValueTypeName Color4hArray, Color4fArray, Color4dArray; +// SdfValueTypeName QuathArray, QuatfArray, QuatdArray; +// SdfValueTypeName Matrix2dArray, Matrix3dArray, Matrix4dArray; +// SdfValueTypeName Frame4dArray; +// SdfValueTypeName TexCoord2hArray, TexCoord2fArray, TexCoord2dArray; +// SdfValueTypeName TexCoord3hArray, TexCoord3fArray, TexCoord3dArray; + }; + + if (usdAttr.IsValid()) + { + const PXR_NS::SdfValueTypeName typeName = usdAttr.GetTypeName(); + const auto iter = sUsdTypeToUfe.find(typeName.GetHash()); + + // ** TEMP - for debugging purposes only + // std::string cppName = typeName.GetCPPTypeName(); + + if (iter != sUsdTypeToUfe.end()) + { + // Special case for TfToken -> Enum. If it doesn't have any allowed + // tokens, then use String instead. + if (iter->second == Ufe::Attribute::kEnumString) + { + auto attrDefn = PXR_NS::UsdSchemaRegistry::GetAttributeDefinition(fPrim.GetTypeName(), usdAttr.GetName()); + if (!attrDefn || !attrDefn->HasAllowedTokens()) + return Ufe::Attribute::kString; + } + + return iter->second; + } + + // We use the generic type to show a Usd attribute's value and native type. + return Ufe::Attribute::kGeneric; + } + + UFE_ASSERT_MSG(false, kErrorMsgInvalidAttribute); + return Ufe::Attribute::kInvalid; +} + + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdAttributes.h b/lib/ufe/UsdAttributes.h new file mode 100644 index 0000000000..c16cefb827 --- /dev/null +++ b/lib/ufe/UsdAttributes.h @@ -0,0 +1,70 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" +#include "UsdAttribute.h" + +#include "ufe/attributes.h" + +#include "pxr/usd/usd/prim.h" + +#include + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface for USD Attributes. +class UsdAttributes : public Ufe::Attributes +{ +public: + typedef std::shared_ptr Ptr; + + UsdAttributes(const UsdSceneItem::Ptr& item); + ~UsdAttributes() override; + + // Delete the copy/move constructors assignment operators. + UsdAttributes(const UsdAttributes&) = delete; + UsdAttributes& operator=(const UsdAttributes&) = delete; + UsdAttributes(UsdAttributes&&) = delete; + UsdAttributes& operator=(UsdAttributes&&) = delete; + + //! Create a UsdAttributes. + static UsdAttributes::Ptr create(const UsdSceneItem::Ptr& item); + + // Ufe::Attributes overrides + Ufe::SceneItem::Ptr sceneItem() const override; + Ufe::Attribute::Type attributeType(const std::string& name) override; + Ufe::Attribute::Ptr attribute(const std::string& name) override; + std::vector attributeNames() const override; + bool hasAttribute(const std::string& name) const override; + +private: + Ufe::Attribute::Type getUfeTypeForAttribute(const PXR_NS::UsdAttribute& usdAttr) const; + +private: + UsdSceneItem::Ptr fItem; + PXR_NS::UsdPrim fPrim; + + typedef std::unordered_map AttributeMap; + AttributeMap fAttributes; + +}; // UsdAttributes + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdAttributesHandler.cpp b/lib/ufe/UsdAttributesHandler.cpp new file mode 100644 index 0000000000..0366ba1971 --- /dev/null +++ b/lib/ufe/UsdAttributesHandler.cpp @@ -0,0 +1,53 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdAttributesHandler.h" +#include "UsdSceneItem.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdAttributesHandler::UsdAttributesHandler() + : Ufe::AttributesHandler() +{ +} + +UsdAttributesHandler::~UsdAttributesHandler() +{ +} + +/*static*/ +UsdAttributesHandler::Ptr UsdAttributesHandler::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// Ufe::AttributesHandler overrides +//------------------------------------------------------------------------------ + +Ufe::Attributes::Ptr UsdAttributesHandler::attributes(const Ufe::SceneItem::Ptr& item) const +{ + UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); +#if !defined(NDEBUG) + assert(usdItem); +#endif + auto usdAttributes = UsdAttributes::create(usdItem); + return usdAttributes; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdAttributesHandler.h b/lib/ufe/UsdAttributesHandler.h new file mode 100644 index 0000000000..36fc2961a5 --- /dev/null +++ b/lib/ufe/UsdAttributesHandler.h @@ -0,0 +1,53 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdAttributes.h" + +#include "ufe/attributesHandler.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface to create the USD Attributes interface objects. +class UsdAttributesHandler : public Ufe::AttributesHandler +{ +public: + typedef std::shared_ptr Ptr; + + UsdAttributesHandler(); + ~UsdAttributesHandler(); + + // Delete the copy/move constructors assignment operators. + UsdAttributesHandler(const UsdAttributesHandler&) = delete; + UsdAttributesHandler& operator=(const UsdAttributesHandler&) = delete; + UsdAttributesHandler(UsdAttributesHandler&&) = delete; + UsdAttributesHandler& operator=(UsdAttributesHandler&&) = delete; + + //! Create a UsdAttributesHandler. + static UsdAttributesHandler::Ptr create(); + + // Ufe::AttributesHandler overrides + Ufe::Attributes::Ptr attributes(const Ufe::SceneItem::Ptr& item) const override; + +private: + +}; // UsdAttributesHandler + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdHierarchy.cpp b/lib/ufe/UsdHierarchy.cpp new file mode 100644 index 0000000000..243fb7a065 --- /dev/null +++ b/lib/ufe/UsdHierarchy.cpp @@ -0,0 +1,183 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdHierarchy.h" +#include "UsdUndoCreateGroupCommand.h" +#include "private/Utils.h" +#include "Utils.h" +#include "private/InPathChange.h" + +#include "ufe/sceneNotification.h" +#include "ufe/scene.h" + +#include "pxr/usd/usd/prim.h" +#include "pxr/usd/usd/stage.h" +#include "pxr/usd/sdf/layer.h" +#include "pxr/usd/sdf/copyUtils.h" +#include "pxr/usd/usdGeom/xform.h" +#include "pxr/base/tf/stringUtils.h" + +#include +#include + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdHierarchy::UsdHierarchy() + : Ufe::Hierarchy() +{ +} + +UsdHierarchy::~UsdHierarchy() +{ +} + +/*static*/ +UsdHierarchy::Ptr UsdHierarchy::create() +{ + return std::make_shared(); +} + +void UsdHierarchy::setItem(UsdSceneItem::Ptr item) +{ + fPrim = item->prim(); + fItem = item; +} + +const Ufe::Path& UsdHierarchy::path() const +{ + return fItem->path(); +} + +UsdSceneItem::Ptr UsdHierarchy::usdSceneItem() const +{ + return fItem; +} + +//------------------------------------------------------------------------------ +// Ufe::Hierarchy overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItem::Ptr UsdHierarchy::sceneItem() const +{ + return fItem; +} + +bool UsdHierarchy::hasChildren() const +{ + return !fPrim.GetChildren().empty(); +} + +Ufe::SceneItemList UsdHierarchy::children() const +{ + // Return USD children only, i.e. children within this run-time. + Ufe::SceneItemList children; + for (auto child : fPrim.GetChildren()) + { + children.push_back(UsdSceneItem::create(fItem->path() + child.GetName(), child)); + } + return children; +} + +Ufe::SceneItem::Ptr UsdHierarchy::parent() const +{ + return UsdSceneItem::create(fItem->path().pop(), fPrim.GetParent()); +} + +Ufe::AppendedChild UsdHierarchy::appendChild(const Ufe::SceneItem::Ptr& child) +{ + auto usdChild = std::dynamic_pointer_cast(child); +#if !defined(NDEBUG) + assert(usdChild); +#endif + + // First, check if we need to rename the child. + std::string childName = uniqueChildName(sceneItem(), child->path()); + + // Set up all paths to perform the reparent. + auto prim = usdChild->prim(); + auto stage = prim.GetStage(); + auto ufeSrcPath = usdChild->path(); + auto usdSrcPath = prim.GetPath(); + auto ufeDstPath = fItem->path() + childName; + auto usdDstPath = fPrim.GetPath().AppendChild(TfToken(childName)); + SdfLayerHandle layer = defPrimSpecLayer(prim); + if (!layer) { + std::string err = TfStringPrintf("No prim found at %s", usdSrcPath.GetString().c_str()); + throw std::runtime_error(err.c_str()); + } + + // In USD, reparent is implemented like rename, using copy to + // destination, then remove from source. + // See UsdUndoRenameCommand._rename comments for details. + InPathChange pc; + + auto status = SdfCopySpec(layer, usdSrcPath, layer, usdDstPath); + if (!status) { + std::string err = TfStringPrintf("Appending child %s to parent %s failed.", + ufeSrcPath.string().c_str(), fItem->path().string().c_str()); + throw std::runtime_error(err.c_str()); + } + + stage->RemovePrim(usdSrcPath); + auto ufeDstItem = UsdSceneItem::create(ufeDstPath, ufePathToPrim(ufeDstPath)); + auto notification = Ufe::ObjectReparent(ufeDstItem, ufeSrcPath); + Ufe::Scene::notifyObjectPathChange(notification); + + // FIXME No idea how to get the child prim index yet. PPT, 16-Aug-2018. + return Ufe::AppendedChild(ufeDstItem, ufeSrcPath, 0); +} + +#ifdef UFE_V2_FEATURES_AVAILABLE +// Create a transform. +Ufe::SceneItem::Ptr UsdHierarchy::createGroup(const Ufe::PathComponent& name) const +{ + // According to Pixar, the following is more efficient when creating + // multiple transforms, because of the use of ChangeBlock(): + // with Sdf.ChangeBlock(): + // primSpec = Sdf.CreatePrimInLayer(layer, usdPath) + // primSpec.specifier = Sdf.SpecifierDef + // primSpec.typeName = 'Xform' + + // Rename the new group for uniqueness, if needed. + Ufe::Path newPath = fItem->path() + name; + auto childName = uniqueChildName(sceneItem(), newPath); + + // Next, get the stage corresponding to the new path. + auto segments = newPath.getSegments(); + TEST_USD_PATH(segments, newPath); + auto dagSegment = segments[0]; + auto stage = getStage(Ufe::Path(dagSegment)); + + // Build the corresponding USD path and create the USD group prim. + auto usdPath = fItem->prim().GetPath().AppendChild(TfToken(childName)); + auto prim = UsdGeomXform::Define(stage, usdPath).GetPrim(); + + // Create a UFE scene item from the prim. + auto ufeChildPath = fItem->path() + childName; + return UsdSceneItem::create(ufeChildPath, prim); +} + +Ufe::Group UsdHierarchy::createGroupCmd(const Ufe::PathComponent& name) const +{ + auto createGroupCmd = UsdUndoCreateGroupCommand::create(fItem, name); + createGroupCmd->execute(); + return Ufe::Group(createGroupCmd->group(), createGroupCmd); +} +#endif + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdHierarchy.h b/lib/ufe/UsdHierarchy.h new file mode 100644 index 0000000000..8b6ff1c6fe --- /dev/null +++ b/lib/ufe/UsdHierarchy.h @@ -0,0 +1,73 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" +#include "UsdSceneItem.h" + +#include "ufe/hierarchy.h" +#include "ufe/path.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief USD run-time hierarchy interface +/*! + This class implements the hierarchy interface for normal USD prims, using + standard USD calls to obtain a prim's parent and children. +*/ +class MAYAUSD_CORE_PUBLIC UsdHierarchy : public Ufe::Hierarchy +{ +public: + typedef std::shared_ptr Ptr; + + UsdHierarchy(); + ~UsdHierarchy() override; + + // Delete the copy/move constructors assignment operators. + UsdHierarchy(const UsdHierarchy&) = delete; + UsdHierarchy& operator=(const UsdHierarchy&) = delete; + UsdHierarchy(UsdHierarchy&&) = delete; + UsdHierarchy& operator=(UsdHierarchy&&) = delete; + + //! Create a UsdHierarchy. + static UsdHierarchy::Ptr create(); + + void setItem(UsdSceneItem::Ptr item); + const Ufe::Path& path() const; + + UsdSceneItem::Ptr usdSceneItem() const; + + // Ufe::Hierarchy overrides + Ufe::SceneItem::Ptr sceneItem() const override; + bool hasChildren() const override; + Ufe::SceneItemList children() const override; + Ufe::SceneItem::Ptr parent() const override; + Ufe::AppendedChild appendChild(const Ufe::SceneItem::Ptr& child) override; + +#ifdef UFE_V2_FEATURES_AVAILABLE + Ufe::SceneItem::Ptr createGroup(const Ufe::PathComponent& name) const override; + Ufe::Group createGroupCmd(const Ufe::PathComponent& name) const override; +#endif + +private: + UsdSceneItem::Ptr fItem; + UsdPrim fPrim; + +}; // UsdHierarchy + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdHierarchyHandler.cpp b/lib/ufe/UsdHierarchyHandler.cpp new file mode 100644 index 0000000000..240ed3ff94 --- /dev/null +++ b/lib/ufe/UsdHierarchyHandler.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdHierarchyHandler.h" +#include "UsdSceneItem.h" +#include "Utils.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdHierarchyHandler::UsdHierarchyHandler() + : Ufe::HierarchyHandler() +{ + fUsdRootChildHierarchy = UsdRootChildHierarchy::create(); + fUsdHierarchy = UsdHierarchy::create(); +} + +UsdHierarchyHandler::~UsdHierarchyHandler() +{ +} + +/*static*/ +UsdHierarchyHandler::Ptr UsdHierarchyHandler::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// UsdHierarchyHandler overrides +//------------------------------------------------------------------------------ + +Ufe::Hierarchy::Ptr UsdHierarchyHandler::hierarchy(const Ufe::SceneItem::Ptr& item) const +{ + UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); + if(isRootChild(usdItem->path())) + { + fUsdRootChildHierarchy->setItem(usdItem); + return fUsdRootChildHierarchy; + } + else + { + fUsdHierarchy->setItem(usdItem); + return fUsdHierarchy; + } +} + +Ufe::SceneItem::Ptr UsdHierarchyHandler::createItem(const Ufe::Path& path) const +{ + const UsdPrim prim = ufePathToPrim(path); + return prim.IsValid() ? UsdSceneItem::create(path, prim) : nullptr; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdHierarchyHandler.h b/lib/ufe/UsdHierarchyHandler.h new file mode 100644 index 0000000000..3850417b2b --- /dev/null +++ b/lib/ufe/UsdHierarchyHandler.h @@ -0,0 +1,66 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdHierarchy.h" +#include "UsdRootChildHierarchy.h" + +#include "ufe/hierarchyHandler.h" + +//PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief USD run-time hierarchy handler. +/*! + This hierarchy handler is the standard USD run-time hierarchy handler. Its + only special behavior is to return a UsdRootChildHierarchy interface object + if it is asked for a hierarchy interface for a child of the USD root prim. + These prims are special because we define their parent to be the Maya USD + gateway node, which the UsdRootChildHierarchy interface implements. + */ +class MAYAUSD_CORE_PUBLIC UsdHierarchyHandler : public Ufe::HierarchyHandler +{ +public: + typedef std::shared_ptr Ptr; + + UsdHierarchyHandler(); + ~UsdHierarchyHandler() override; + + // Delete the copy/move constructors assignment operators. + UsdHierarchyHandler(const UsdHierarchyHandler&) = delete; + UsdHierarchyHandler& operator=(const UsdHierarchyHandler&) = delete; + UsdHierarchyHandler(UsdHierarchyHandler&&) = delete; + UsdHierarchyHandler& operator=(UsdHierarchyHandler&&) = delete; + + //! Create a UsdHierarchyHandler. + static UsdHierarchyHandler::Ptr create(); + + // UsdHierarchyHandler overrides + Ufe::Hierarchy::Ptr hierarchy(const Ufe::SceneItem::Ptr& item) const override; + Ufe::SceneItem::Ptr createItem(const Ufe::Path& path) const override; + +private: + UsdRootChildHierarchy::Ptr fUsdRootChildHierarchy; + UsdHierarchy::Ptr fUsdHierarchy; + +}; // UsdHierarchyHandler + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdRootChildHierarchy.cpp b/lib/ufe/UsdRootChildHierarchy.cpp new file mode 100644 index 0000000000..ef4c260ecb --- /dev/null +++ b/lib/ufe/UsdRootChildHierarchy.cpp @@ -0,0 +1,68 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdRootChildHierarchy.h" + +#include "ufe/runTimeMgr.h" + +#include + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Global variables +//------------------------------------------------------------------------------ +extern Ufe::Rtid g_MayaRtid; +const std::string kNotGatewayNodePath = "Tail of path %s is not a gateway node."; + +UsdRootChildHierarchy::UsdRootChildHierarchy() + : UsdHierarchy() +{ +} + +UsdRootChildHierarchy::~UsdRootChildHierarchy() +{ +} + +/*static*/ +UsdRootChildHierarchy::Ptr UsdRootChildHierarchy::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// Ufe::Hierarchy overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItem::Ptr UsdRootChildHierarchy::parent() const +{ + // If we're a child of the root, our parent node in the path is a Maya + // node. Ask the Maya hierarchy interface to create a selection item + // for that path. + auto parentPath = path().pop(); +#if !defined(NDEBUG) + assert(parentPath.runTimeId() == g_MayaRtid); +#endif + if (parentPath.runTimeId() != g_MayaRtid) + TF_WARN(kNotGatewayNodePath.c_str(), path().string().c_str()); + + auto mayaHierarchyHandler = Ufe::RunTimeMgr::instance().hierarchyHandler(g_MayaRtid); + return mayaHierarchyHandler->createItem(parentPath); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdRootChildHierarchy.h b/lib/ufe/UsdRootChildHierarchy.h new file mode 100644 index 0000000000..ab63f2215c --- /dev/null +++ b/lib/ufe/UsdRootChildHierarchy.h @@ -0,0 +1,55 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdHierarchy.h" + +//PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief USD run-time hierarchy interface for children of the USD root prim. +/*! + This class modifies its base class implementation to return the Maya USD + gateway node as parent of USD prims that are children of the USD root prim. + */ +class MAYAUSD_CORE_PUBLIC UsdRootChildHierarchy : public UsdHierarchy +{ +public: + typedef std::shared_ptr Ptr; + + UsdRootChildHierarchy(); + ~UsdRootChildHierarchy() override; + + // Delete the copy/move constructors assignment operators. + UsdRootChildHierarchy(const UsdRootChildHierarchy&) = delete; + UsdRootChildHierarchy& operator=(const UsdRootChildHierarchy&) = delete; + UsdRootChildHierarchy(UsdRootChildHierarchy&&) = delete; + UsdRootChildHierarchy& operator=(UsdRootChildHierarchy&&) = delete; + + //! Create a UsdRootChildHierarchy. + static UsdRootChildHierarchy::Ptr create(); + + // Ufe::Hierarchy overrides + Ufe::SceneItem::Ptr parent() const override; + +}; // UsdRootChildHierarchy + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdRotatePivotTranslateUndoableCommand.cpp b/lib/ufe/UsdRotatePivotTranslateUndoableCommand.cpp new file mode 100644 index 0000000000..68eb56bb72 --- /dev/null +++ b/lib/ufe/UsdRotatePivotTranslateUndoableCommand.cpp @@ -0,0 +1,77 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdRotatePivotTranslateUndoableCommand.h" +#include "private/Utils.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdRotatePivotTranslateUndoableCommand::UsdRotatePivotTranslateUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) + : Ufe::TranslateUndoableCommand(item) + , fPrim(prim) + , fPath(ufePath) + , fNoPivotOp(false) +{ + // Prim does not have a pivot translate attribute + const TfToken xpivot("xformOp:translate:pivot"); + if (!fPrim.HasAttribute(xpivot)) + { + fNoPivotOp = true; + // Add an empty pivot translate. + rotatePivotTranslateOp(fPrim, fPath, 0, 0, 0); + } + + fPivotAttrib = fPrim.GetAttribute(xpivot); + fPivotAttrib.Get(&fPrevPivotValue); +} + +UsdRotatePivotTranslateUndoableCommand::~UsdRotatePivotTranslateUndoableCommand() +{ +} + +/*static*/ +UsdRotatePivotTranslateUndoableCommand::Ptr UsdRotatePivotTranslateUndoableCommand::create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) +{ + return std::make_shared(prim, ufePath, item); +} + +void UsdRotatePivotTranslateUndoableCommand::undo() +{ + fPivotAttrib.Set(fPrevPivotValue); + // Todo : We would want to remove the xformOp + // (SD-06/07/2018) Haven't found a clean way to do it - would need to investigate +} + +void UsdRotatePivotTranslateUndoableCommand::redo() +{ + // No-op, use move to translate the rotate pivot of the object. + // The Maya move command directly invokes our translate() method in its + // redoIt(), which is invoked both for the inital move and the redo. +} + +//------------------------------------------------------------------------------ +// Ufe::TranslateUndoableCommand overrides +//------------------------------------------------------------------------------ + +bool UsdRotatePivotTranslateUndoableCommand::translate(double x, double y, double z) +{ + rotatePivotTranslateOp(fPrim, fPath, x, y, z); + return true; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdRotatePivotTranslateUndoableCommand.h b/lib/ufe/UsdRotatePivotTranslateUndoableCommand.h new file mode 100644 index 0000000000..0c323848ef --- /dev/null +++ b/lib/ufe/UsdRotatePivotTranslateUndoableCommand.h @@ -0,0 +1,65 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "ufe/transform3dUndoableCommands.h" + +#include "pxr/usd/usd/attribute.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Absolute translation command of the given prim's rotate pivot. +/*! + Ability to perform undo to restore the original pivot value. + */ +class MAYAUSD_CORE_PUBLIC UsdRotatePivotTranslateUndoableCommand : public Ufe::TranslateUndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdRotatePivotTranslateUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + ~UsdRotatePivotTranslateUndoableCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdRotatePivotTranslateUndoableCommand(const UsdRotatePivotTranslateUndoableCommand&) = delete; + UsdRotatePivotTranslateUndoableCommand& operator=(const UsdRotatePivotTranslateUndoableCommand&) = delete; + UsdRotatePivotTranslateUndoableCommand(UsdRotatePivotTranslateUndoableCommand&&) = delete; + UsdRotatePivotTranslateUndoableCommand& operator=(UsdRotatePivotTranslateUndoableCommand&&) = delete; + + //! Create a UsdRotatePivotTranslateUndoableCommand from a USD prim, UFE path and UFE scene item. + static UsdRotatePivotTranslateUndoableCommand::Ptr create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + + // Ufe::TranslateUndoableCommand overrides + void undo() override; + void redo() override; + bool translate(double x, double y, double z) override; + +private: + UsdPrim fPrim; + UsdAttribute fPivotAttrib; + GfVec3f fPrevPivotValue; + Ufe::Path fPath; + bool fNoPivotOp; + +}; // UsdRotatePivotTranslateUndoableCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdRotateUndoableCommand.cpp b/lib/ufe/UsdRotateUndoableCommand.cpp new file mode 100644 index 0000000000..ea5663d074 --- /dev/null +++ b/lib/ufe/UsdRotateUndoableCommand.cpp @@ -0,0 +1,102 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdRotateUndoableCommand.h" +#include "private/Utils.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdRotateUndoableCommand::UsdRotateUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) + : Ufe::RotateUndoableCommand(item) + , fPrim(prim) + , fPath(ufePath) + , fNoRotateOp(false) +{ + // Since we want to change xformOp:rotateXYZ, and we need to store the prevRotate for + // undo purpose, we need to make sure we convert it to common API xformOps (In case we have + // rotateX, rotateY or rotateZ ops) + try { + convertToCompatibleCommonAPI(prim); + } + catch (...) { + // Since Maya cannot catch this error at this moment, store it until we actually rotate + fFailedInit = std::current_exception(); // capture + return; + } + + // Prim does not have a rotateXYZ attribute + const TfToken xrot("xformOp:rotateXYZ"); + if (!fPrim.HasAttribute(xrot)) + { + rotateOp(fPrim, fPath, 0, 0, 0); // Add an empty rotate + fNoRotateOp = true; + } + + fRotateAttrib = fPrim.GetAttribute(xrot); + fRotateAttrib.Get(&fPrevRotateValue); +} + +UsdRotateUndoableCommand::~UsdRotateUndoableCommand() +{ +} + +/*static*/ +UsdRotateUndoableCommand::Ptr UsdRotateUndoableCommand::create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) +{ + return std::make_shared(prim, ufePath, item); +} + +void UsdRotateUndoableCommand::undo() +{ + // Check if initialization went ok. + if (!fFailedInit) + { + fRotateAttrib.Set(fPrevRotateValue); + } + // Todo : We would want to remove the xformOp + // (SD-06/07/2018) Haven't found a clean way to do it - would need to investigate +} + +void UsdRotateUndoableCommand::redo() +{ + perform(); +} + +void UsdRotateUndoableCommand::perform() +{ + // No-op, use rotate to move the object + // The Maya rotate command directly invokes our rotate() method in its + // redoIt(), which is invoked both for the inital rotate and the redo. +} + +//------------------------------------------------------------------------------ +// Ufe::RotateUndoableCommand overrides +//------------------------------------------------------------------------------ + +bool UsdRotateUndoableCommand::rotate(double x, double y, double z) +{ + // Fail early - Initialization did not go as expected. + if (fFailedInit) + { + std::rethrow_exception(fFailedInit); + } + rotateOp(fPrim, fPath, x, y, z); + return true; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdRotateUndoableCommand.h b/lib/ufe/UsdRotateUndoableCommand.h new file mode 100644 index 0000000000..90c32cbd1c --- /dev/null +++ b/lib/ufe/UsdRotateUndoableCommand.h @@ -0,0 +1,71 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "ufe/transform3dUndoableCommands.h" + +#include "pxr/usd/usd/attribute.h" + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Absolute rotation command of the given prim. +/*! + Ability to perform undo to restore the original rotation value. + As of 06/07/2018, redo is a no op as Maya re-does the operation for redo + */ +class MAYAUSD_CORE_PUBLIC UsdRotateUndoableCommand : public Ufe::RotateUndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdRotateUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + ~UsdRotateUndoableCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdRotateUndoableCommand(const UsdRotateUndoableCommand&) = delete; + UsdRotateUndoableCommand& operator=(const UsdRotateUndoableCommand&) = delete; + UsdRotateUndoableCommand(UsdRotateUndoableCommand&&) = delete; + UsdRotateUndoableCommand& operator=(UsdRotateUndoableCommand&&) = delete; + + //! Create a UsdRotateUndoableCommand from a USD prim, UFE path and UFE scene item. + static UsdRotateUndoableCommand::Ptr create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + + // Ufe::RotateUndoableCommand overrides + void undo() override; + void redo() override; + bool rotate(double x, double y, double z) override; + +private: + void perform(); + +private: + UsdPrim fPrim; + Ufe::Path fPath; + UsdAttribute fRotateAttrib; + GfVec3f fPrevRotateValue; + std::exception_ptr fFailedInit; + bool fNoRotateOp; +}; // UsdRotateUndoableCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdScaleUndoableCommand.cpp b/lib/ufe/UsdScaleUndoableCommand.cpp new file mode 100644 index 0000000000..ee4f93156f --- /dev/null +++ b/lib/ufe/UsdScaleUndoableCommand.cpp @@ -0,0 +1,82 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdScaleUndoableCommand.h" +#include "private/Utils.h" +#include "Utils.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdScaleUndoableCommand::UsdScaleUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) + : Ufe::ScaleUndoableCommand(item) + , fPrim(prim) + , fPath(ufePath) + , fNoScaleOp(false) +{ + // Prim does not have a scale attribute + const TfToken xscale("xformOp:scale"); + if (!fPrim.HasAttribute(xscale)) + { + fNoScaleOp = true; + scaleOp(fPrim, fPath, 1, 1, 1); // Add a neutral scale xformOp. + } + + fScaleAttrib = fPrim.GetAttribute(xscale); + fScaleAttrib.Get(&fPrevScaleValue); +} + +UsdScaleUndoableCommand::~UsdScaleUndoableCommand() +{ +} + +/*static*/ +UsdScaleUndoableCommand::Ptr UsdScaleUndoableCommand::create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) +{ + return std::make_shared(prim, ufePath, item); +} + +void UsdScaleUndoableCommand::undo() +{ + fScaleAttrib.Set(fPrevScaleValue); + // Todo : We would want to remove the xformOp + // (SD-06/07/2018) Haven't found a clean way to do it - would need to investigate +} + +void UsdScaleUndoableCommand::redo() +{ + perform(); +} + +void UsdScaleUndoableCommand::perform() +{ + // No-op, use scale to scale the object. + // The Maya scale command directly invokes our scale() method in its + // redoIt(), which is invoked both for the inital scale and the redo. +} + +//------------------------------------------------------------------------------ +// Ufe::ScaleUndoableCommand overrides +//------------------------------------------------------------------------------ + +bool UsdScaleUndoableCommand::scale(double x, double y, double z) +{ + scaleOp(fPrim, fPath, x, y, z); + return true; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdScaleUndoableCommand.h b/lib/ufe/UsdScaleUndoableCommand.h new file mode 100644 index 0000000000..d24f430fd0 --- /dev/null +++ b/lib/ufe/UsdScaleUndoableCommand.h @@ -0,0 +1,68 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "ufe/transform3dUndoableCommands.h" + +#include "pxr/usd/usd/attribute.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Absolute scale command of the given prim. +/*! + Ability to perform undo to restore the original scale value. + As of 06/07/2018, redo is a no op as Maya re-does the operation for redo + */ +class MAYAUSD_CORE_PUBLIC UsdScaleUndoableCommand : public Ufe::ScaleUndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdScaleUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + ~UsdScaleUndoableCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdScaleUndoableCommand(const UsdScaleUndoableCommand&) = delete; + UsdScaleUndoableCommand& operator=(const UsdScaleUndoableCommand&) = delete; + UsdScaleUndoableCommand(UsdScaleUndoableCommand&&) = delete; + UsdScaleUndoableCommand& operator=(UsdScaleUndoableCommand&&) = delete; + + //! Create a UsdScaleUndoableCommand from a USD prim, UFE path and UFE scene item. + static UsdScaleUndoableCommand::Ptr create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + + // Ufe::ScaleUndoableCommand overrides + void undo() override; + void redo() override; + bool scale(double x, double y, double z) override; + +private: + void perform(); + +private: + UsdPrim fPrim; + UsdAttribute fScaleAttrib; + GfVec3f fPrevScaleValue; + Ufe::Path fPath; + bool fNoScaleOp; +}; // UsdScaleUndoableCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdSceneItem.cpp b/lib/ufe/UsdSceneItem.cpp new file mode 100644 index 0000000000..a75bbf1658 --- /dev/null +++ b/lib/ufe/UsdSceneItem.cpp @@ -0,0 +1,53 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdSceneItem.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdSceneItem::UsdSceneItem(const Ufe::Path& path, const UsdPrim& prim) + : Ufe::SceneItem(path) + , fPrim(prim) +{ +} + +UsdSceneItem::~UsdSceneItem() +{ +} + +/*static*/ +UsdSceneItem::Ptr UsdSceneItem::create(const Ufe::Path& path, const UsdPrim& prim) +{ + return std::make_shared(path, prim); +} + +const UsdPrim& UsdSceneItem::prim() const +{ + return fPrim; +} + +//------------------------------------------------------------------------------ +// Ufe::SceneItem overrides +//------------------------------------------------------------------------------ + +std::string UsdSceneItem::nodeType() const +{ + return fPrim.GetTypeName(); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdSceneItem.h b/lib/ufe/UsdSceneItem.h new file mode 100644 index 0000000000..3730373993 --- /dev/null +++ b/lib/ufe/UsdSceneItem.h @@ -0,0 +1,57 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "ufe/sceneItem.h" + +#include "pxr/usd/usd/prim.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief USD run-time scene item interface +class MAYAUSD_CORE_PUBLIC UsdSceneItem : public Ufe::SceneItem +{ +public: + typedef std::shared_ptr Ptr; + + UsdSceneItem(const Ufe::Path& path, const UsdPrim& prim); + ~UsdSceneItem() override; + + // Delete the copy/move constructors assignment operators. + UsdSceneItem(const UsdSceneItem&) = delete; + UsdSceneItem& operator=(const UsdSceneItem&) = delete; + UsdSceneItem(UsdSceneItem&&) = delete; + UsdSceneItem& operator=(UsdSceneItem&&) = delete; + + //! Create a UsdSceneItem from a UFE path and a USD prim. + static UsdSceneItem::Ptr create(const Ufe::Path& path, const UsdPrim& prim); + + const UsdPrim& prim() const; + + // Ufe::SceneItem overrides + std::string nodeType() const override; + +private: + UsdPrim fPrim; +}; // UsdSceneItem + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdSceneItemOps.cpp b/lib/ufe/UsdSceneItemOps.cpp new file mode 100644 index 0000000000..60b7895f47 --- /dev/null +++ b/lib/ufe/UsdSceneItemOps.cpp @@ -0,0 +1,110 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdSceneItemOps.h" +#include "UsdUndoDeleteCommand.h" +#include "UsdUndoDuplicateCommand.h" +#include "UsdUndoRenameCommand.h" +#include "Utils.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdSceneItemOps::UsdSceneItemOps() + : Ufe::SceneItemOps() +{ +} + +UsdSceneItemOps::~UsdSceneItemOps() +{ +} + +/*static*/ +UsdSceneItemOps::Ptr UsdSceneItemOps::create() +{ + return std::make_shared(); +} + +void UsdSceneItemOps::setItem(const UsdSceneItem::Ptr& item) +{ + fPrim = item->prim(); + fItem = item; +} + +const Ufe::Path& UsdSceneItemOps::path() const +{ + return fItem->path(); +} + +//------------------------------------------------------------------------------ +// Ufe::SceneItemOps overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItem::Ptr UsdSceneItemOps::sceneItem() const +{ + return fItem; +} + +Ufe::UndoableCommand::Ptr UsdSceneItemOps::deleteItemCmd() +{ + auto deleteCmd = UsdUndoDeleteCommand::create(fPrim); + deleteCmd->execute(); + return deleteCmd; +} + +bool UsdSceneItemOps::deleteItem() +{ + return fPrim.SetActive(false); +} + +Ufe::Duplicate UsdSceneItemOps::duplicateItemCmd() +{ + auto duplicateCmd = UsdUndoDuplicateCommand::create(fPrim, fItem->path()); + duplicateCmd->execute(); + auto item = createSiblingSceneItem(path(), duplicateCmd->usdDstPath().GetElementString()); + return Ufe::Duplicate(item, duplicateCmd); +} + +Ufe::SceneItem::Ptr UsdSceneItemOps::duplicateItem() +{ + SdfPath usdDstPath; + SdfLayerHandle layer; + UsdUndoDuplicateCommand::primInfo(fPrim, usdDstPath, layer); + bool status = UsdUndoDuplicateCommand::duplicate(layer, fPrim.GetPath(), usdDstPath); + + // The duplicate is a sibling of the source. + if (status) + return createSiblingSceneItem(path(), usdDstPath.GetElementString()); + + return nullptr; +} + +Ufe::SceneItem::Ptr UsdSceneItemOps::renameItem(const Ufe::PathComponent& newName) +{ + auto renameCmd = UsdUndoRenameCommand::create(fItem, newName); + renameCmd->execute(); + return renameCmd->renamedItem(); +} + +Ufe::Rename UsdSceneItemOps::renameItemCmd(const Ufe::PathComponent& newName) +{ + auto renameCmd = UsdUndoRenameCommand::create(fItem, newName); + renameCmd->execute(); + return Ufe::Rename(renameCmd->renamedItem(), renameCmd); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdSceneItemOps.h b/lib/ufe/UsdSceneItemOps.h new file mode 100644 index 0000000000..cc22dd8177 --- /dev/null +++ b/lib/ufe/UsdSceneItemOps.h @@ -0,0 +1,69 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/path.h" +#include "ufe/sceneItemOps.h" + +#include "pxr/usd/usd/prim.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface for scene item operations. +class MAYAUSD_CORE_PUBLIC UsdSceneItemOps : public Ufe::SceneItemOps +{ +public: + typedef std::shared_ptr Ptr; + + UsdSceneItemOps(); + ~UsdSceneItemOps() override; + + // Delete the copy/move constructors assignment operators. + UsdSceneItemOps(const UsdSceneItemOps&) = delete; + UsdSceneItemOps& operator=(const UsdSceneItemOps&) = delete; + UsdSceneItemOps(UsdSceneItemOps&&) = delete; + UsdSceneItemOps& operator=(UsdSceneItemOps&&) = delete; + + //! Create a UsdSceneItemOps. + static UsdSceneItemOps::Ptr create(); + + void setItem(const UsdSceneItem::Ptr& item); + const Ufe::Path& path() const; + + // Ufe::SceneItemOps overrides + Ufe::SceneItem::Ptr sceneItem() const override; + Ufe::UndoableCommand::Ptr deleteItemCmd() override; + bool deleteItem() override; + Ufe::Duplicate duplicateItemCmd() override; + Ufe::SceneItem::Ptr duplicateItem() override; + Ufe::Rename renameItemCmd(const Ufe::PathComponent& newName) override; + Ufe::SceneItem::Ptr renameItem(const Ufe::PathComponent& newName) override; + +private: + UsdSceneItem::Ptr fItem; + UsdPrim fPrim; + +}; // UsdSceneItemOps + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdSceneItemOpsHandler.cpp b/lib/ufe/UsdSceneItemOpsHandler.cpp new file mode 100644 index 0000000000..f305034fd8 --- /dev/null +++ b/lib/ufe/UsdSceneItemOpsHandler.cpp @@ -0,0 +1,53 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdSceneItemOpsHandler.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdSceneItemOpsHandler::UsdSceneItemOpsHandler() + : Ufe::SceneItemOpsHandler() +{ + fUsdSceneItemOps = UsdSceneItemOps::create(); +} + +UsdSceneItemOpsHandler::~UsdSceneItemOpsHandler() +{ +} + +/*static*/ +UsdSceneItemOpsHandler::Ptr UsdSceneItemOpsHandler::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// Ufe::SceneItemOpsHandler overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItemOps::Ptr UsdSceneItemOpsHandler::sceneItemOps(const Ufe::SceneItem::Ptr& item) const +{ + UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); +#if !defined(NDEBUG) + assert(usdItem); +#endif + fUsdSceneItemOps->setItem(usdItem); + return fUsdSceneItemOps; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdSceneItemOpsHandler.h b/lib/ufe/UsdSceneItemOpsHandler.h new file mode 100644 index 0000000000..53ffaec2c4 --- /dev/null +++ b/lib/ufe/UsdSceneItemOpsHandler.h @@ -0,0 +1,56 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItemOps.h" + +#include "ufe/sceneItemOpsHandler.h" + +//PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface to create a UsdSceneItemOps interface object. +class MAYAUSD_CORE_PUBLIC UsdSceneItemOpsHandler : public Ufe::SceneItemOpsHandler +{ +public: + typedef std::shared_ptr Ptr; + + UsdSceneItemOpsHandler(); + ~UsdSceneItemOpsHandler() override; + + // Delete the copy/move constructors assignment operators. + UsdSceneItemOpsHandler(const UsdSceneItemOpsHandler&) = delete; + UsdSceneItemOpsHandler& operator=(const UsdSceneItemOpsHandler&) = delete; + UsdSceneItemOpsHandler(UsdSceneItemOpsHandler&&) = delete; + UsdSceneItemOpsHandler& operator=(UsdSceneItemOpsHandler&&) = delete; + + //! Create a UsdSceneItemOpsHandler. + static UsdSceneItemOpsHandler::Ptr create(); + + // Ufe::SceneItemOpsHandler overrides + Ufe::SceneItemOps::Ptr sceneItemOps(const Ufe::SceneItem::Ptr& item) const override; + +private: + UsdSceneItemOps::Ptr fUsdSceneItemOps; + +}; // UsdSceneItemOpsHandler + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdStageMap.cpp b/lib/ufe/UsdStageMap.cpp new file mode 100644 index 0000000000..91a0ff14f7 --- /dev/null +++ b/lib/ufe/UsdStageMap.cpp @@ -0,0 +1,63 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdStageMap.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Global variables +//------------------------------------------------------------------------------ + +UsdStageMap g_StageMap; + +//------------------------------------------------------------------------------ +// UsdStageMap +//------------------------------------------------------------------------------ + +void UsdStageMap::addItem(const Ufe::Path& path, UsdStageWeakPtr stage) +{ + fPathToStage[path] = stage; + fStageToPath[stage] = path; +} + +UsdStageWeakPtr UsdStageMap::stage(const Ufe::Path& path) const +{ + // A stage is bound to a single Dag proxy shape. + auto iter = fPathToStage.find(path); + if (iter != std::end(fPathToStage)) + return iter->second; + return nullptr; +} + +Ufe::Path UsdStageMap::path(UsdStageWeakPtr stage) const +{ + // A stage is bound to a single Dag proxy shape. + auto iter = fStageToPath.find(stage); + if (iter != std::end(fStageToPath)) + return iter->second; + return Ufe::Path(); +} + +void UsdStageMap::clear() +{ + fPathToStage.clear(); + fStageToPath.clear(); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdStageMap.h b/lib/ufe/UsdStageMap.h new file mode 100644 index 0000000000..0688da448c --- /dev/null +++ b/lib/ufe/UsdStageMap.h @@ -0,0 +1,73 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "ufe/path.h" + +#include "pxr/usd/usd/stage.h" +#include "pxr/base/tf/hash.h" + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief USD Stage Map +/*! + Map of AL_usdmaya_ProxyShape UFE path to corresponding stage. + + Map of stage to corresponding AL_usdmaya_ProxyShape UFE path. Ideally, we + would support dynamically computing the path for the AL_usdmaya_ProxyShape + node, but we assume here it will not be reparented. We will also assume that + a USD stage will not be instanced (even though nothing in the data model + prevents it). +*/ +class MAYAUSD_CORE_PUBLIC UsdStageMap +{ +public: + UsdStageMap() = default; + ~UsdStageMap() = default; + + // Delete the copy/move constructors assignment operators. + UsdStageMap(const UsdStageMap&) = delete; + UsdStageMap& operator=(const UsdStageMap&) = delete; + UsdStageMap(UsdStageMap&&) = delete; + UsdStageMap& operator=(UsdStageMap&&) = delete; + + //!Add the input Ufe path and USD Stage to the map. + void addItem(const Ufe::Path& path, UsdStageWeakPtr stage); + + //! Get USD stage corresponding to argument Maya Dag path. + UsdStageWeakPtr stage(const Ufe::Path& path) const; + + //! Return the ProxyShape node UFE path for the argument stage. + Ufe::Path path(UsdStageWeakPtr stage) const; + + void clear(); + +private: + // We keep two maps for fast lookup when there are many proxy shapes. + std::unordered_map fPathToStage; + TfHashMap fStageToPath; + +}; // UsdStageMap + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdTransform3d.cpp b/lib/ufe/UsdTransform3d.cpp new file mode 100644 index 0000000000..1f96ebd332 --- /dev/null +++ b/lib/ufe/UsdTransform3d.cpp @@ -0,0 +1,196 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdTransform3d.h" +#include "UsdTranslateUndoableCommand.h" +#include "UsdRotateUndoableCommand.h" +#include "UsdScaleUndoableCommand.h" +#include "UsdRotatePivotTranslateUndoableCommand.h" +#include "private/Utils.h" + +#include "pxr/usd/usdGeom/xformCache.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +namespace { + Ufe::Matrix4d convertFromUsd(const GfMatrix4d& matrix) + { + double m[4][4]; + matrix.Get(m); + Ufe::Matrix4d uMat; + uMat.matrix = {{ {{m[0][0], m[0][1], m[0][2], m[0][3]}}, + {{m[1][0], m[1][1], m[1][2], m[1][3]}}, + {{m[2][0], m[2][1], m[2][2], m[2][3]}}, + {{m[3][0], m[3][1], m[3][2], m[3][3]}} }}; + return uMat; + } + + Ufe::Matrix4d primToUfeXform(const UsdPrim& prim) + { + UsdGeomXformCache xformCache; + GfMatrix4d usdMatrix = xformCache.GetLocalToWorldTransform(prim); + Ufe::Matrix4d xform = convertFromUsd(usdMatrix); + return xform; + } + + Ufe::Matrix4d primToUfeExclusiveXform(const UsdPrim& prim) + { + UsdGeomXformCache xformCache; + GfMatrix4d usdMatrix = xformCache.GetParentToWorldTransform(prim); + Ufe::Matrix4d xform = convertFromUsd(usdMatrix); + return xform; + } +} + +UsdTransform3d::UsdTransform3d() + : Transform3d() +{ +} + +UsdTransform3d::~UsdTransform3d() +{ +} + +/*static*/ +UsdTransform3d::Ptr UsdTransform3d::create() +{ + return std::make_shared(); +} + +void UsdTransform3d::setItem(const UsdSceneItem::Ptr& item) +{ + fPrim = item->prim(); + fItem = item; +} + +//------------------------------------------------------------------------------ +// Ufe::Transform3d overrides +//------------------------------------------------------------------------------ + +const Ufe::Path& UsdTransform3d::path() const +{ + return fItem->path(); +} + +Ufe::SceneItem::Ptr UsdTransform3d::sceneItem() const +{ + return fItem; +} + +Ufe::TranslateUndoableCommand::Ptr UsdTransform3d::translateCmd() +{ + auto translateCmd = UsdTranslateUndoableCommand::create(fPrim, fItem->path(), fItem); + return translateCmd; +} + +void UsdTransform3d::translate(double x, double y, double z) +{ + translateOp(fPrim, fItem->path(), x, y, z); +} + +Ufe::Vector3d UsdTransform3d::translation() const +{ + double x{0}, y{0}, z{0}; + const TfToken xlate("xformOp:translate"); + if (fPrim.HasAttribute(xlate)) + { + // Initially, attribute can be created, but have no value. + GfVec3d v; + if (fPrim.GetAttribute(xlate).Get(&v)) + { + x = v[0]; y = v[1]; z = v[2]; + } + } + return Ufe::Vector3d(x, y, z); +} + +Ufe::RotateUndoableCommand::Ptr UsdTransform3d::rotateCmd() +{ + auto rotateCmd = UsdRotateUndoableCommand::create(fPrim, fItem->path(), fItem); + return rotateCmd; +} + +void UsdTransform3d::rotate(double x, double y, double z) +{ + rotateOp(fPrim, fItem->path(), x, y, z); +} + +Ufe::ScaleUndoableCommand::Ptr UsdTransform3d::scaleCmd() +{ + auto scaleCmd = UsdScaleUndoableCommand::create(fPrim, fItem->path(), fItem); + return scaleCmd; +} + +void UsdTransform3d::scale(double x, double y, double z) +{ + scaleOp(fPrim, fItem->path(), x, y, z); +} + +Ufe::TranslateUndoableCommand::Ptr UsdTransform3d::rotatePivotTranslateCmd() +{ + auto translateCmd = UsdRotatePivotTranslateUndoableCommand::create(fPrim, fItem->path(), fItem); + return translateCmd; +} + +void UsdTransform3d::rotatePivotTranslate(double x, double y, double z) +{ + rotatePivotTranslateOp(fPrim, fItem->path(), x, y, z); +} + +Ufe::Vector3d UsdTransform3d::rotatePivot() const +{ + double x{0}, y{0}, z{0}; + const TfToken xpivot("xformOp:translate:pivot"); + if (fPrim.HasAttribute(xpivot)) + { + // Initially, attribute can be created, but have no value. + GfVec3f v; + if (fPrim.GetAttribute(xpivot).Get(&v)) + { + x = v[0]; y = v[1]; z = v[2]; + } + } + return Ufe::Vector3d(x, y, z); +} + +Ufe::TranslateUndoableCommand::Ptr UsdTransform3d::scalePivotTranslateCmd() +{ + throw std::runtime_error("UsdTransform3d::scalePivotTranslateCmd() not implemented"); +} + +void UsdTransform3d::scalePivotTranslate(double x, double y, double z) +{ + return rotatePivotTranslate(x, y, z); +} + +Ufe::Vector3d UsdTransform3d::scalePivot() const +{ + return rotatePivot(); +} + +Ufe::Matrix4d UsdTransform3d::segmentInclusiveMatrix() const +{ + return primToUfeXform(fPrim); +} + +Ufe::Matrix4d UsdTransform3d::segmentExclusiveMatrix() const +{ + return primToUfeExclusiveXform(fPrim); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdTransform3d.h b/lib/ufe/UsdTransform3d.h new file mode 100644 index 0000000000..928891c55c --- /dev/null +++ b/lib/ufe/UsdTransform3d.h @@ -0,0 +1,78 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/path.h" +#include "ufe/transform3d.h" + +#include "pxr/usd/usd/prim.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface to transform objects in 3D. +class MAYAUSD_CORE_PUBLIC UsdTransform3d : public Ufe::Transform3d +{ +public: + typedef std::shared_ptr Ptr; + + UsdTransform3d(); + ~UsdTransform3d() override; + + // Delete the copy/move constructors assignment operators. + UsdTransform3d(const UsdTransform3d&) = delete; + UsdTransform3d& operator=(const UsdTransform3d&) = delete; + UsdTransform3d(UsdTransform3d&&) = delete; + UsdTransform3d& operator=(UsdTransform3d&&) = delete; + + //! Create a UsdTransform3d. + static UsdTransform3d::Ptr create(); + + void setItem(const UsdSceneItem::Ptr& item); + + // Ufe::Transform3d overrides + const Ufe::Path& path() const override; + Ufe::SceneItem::Ptr sceneItem() const override; + Ufe::TranslateUndoableCommand::Ptr translateCmd() override; + void translate(double x, double y, double z) override; + Ufe::Vector3d translation() const override; + Ufe::RotateUndoableCommand::Ptr rotateCmd() override; + void rotate(double x, double y, double z) override; + Ufe::ScaleUndoableCommand::Ptr scaleCmd() override; + void scale(double x, double y, double z) override; + Ufe::TranslateUndoableCommand::Ptr rotatePivotTranslateCmd() override; + void rotatePivotTranslate(double x, double y, double z) override; + Ufe::Vector3d rotatePivot() const override; + Ufe::TranslateUndoableCommand::Ptr scalePivotTranslateCmd() override; + void scalePivotTranslate(double x, double y, double z) override; + Ufe::Vector3d scalePivot() const override; + Ufe::Matrix4d segmentInclusiveMatrix() const override; + Ufe::Matrix4d segmentExclusiveMatrix() const override; + +private: + UsdSceneItem::Ptr fItem; + UsdPrim fPrim; + +}; // UsdTransform3d + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdTransform3dHandler.cpp b/lib/ufe/UsdTransform3dHandler.cpp new file mode 100644 index 0000000000..a47957f456 --- /dev/null +++ b/lib/ufe/UsdTransform3dHandler.cpp @@ -0,0 +1,54 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdTransform3dHandler.h" +#include "UsdSceneItem.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdTransform3dHandler::UsdTransform3dHandler() + : Ufe::Transform3dHandler() +{ + fUsdTransform3d = UsdTransform3d::create(); +} + +UsdTransform3dHandler::~UsdTransform3dHandler() +{ +} + +/*static*/ +UsdTransform3dHandler::Ptr UsdTransform3dHandler::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// Ufe::Transform3dHandler overrides +//------------------------------------------------------------------------------ + +Ufe::Transform3d::Ptr UsdTransform3dHandler::transform3d(const Ufe::SceneItem::Ptr& item) const +{ + UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); +#if !defined(NDEBUG) + assert(usdItem); +#endif + fUsdTransform3d->setItem(usdItem); + return fUsdTransform3d; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdTransform3dHandler.h b/lib/ufe/UsdTransform3dHandler.h new file mode 100644 index 0000000000..de5a6b5152 --- /dev/null +++ b/lib/ufe/UsdTransform3dHandler.h @@ -0,0 +1,56 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdTransform3d.h" + +#include "ufe/transform3dHandler.h" + +//PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Interface to create a UsdTransform3d interface object. +class MAYAUSD_CORE_PUBLIC UsdTransform3dHandler : public Ufe::Transform3dHandler +{ +public: + typedef std::shared_ptr Ptr; + + UsdTransform3dHandler(); + ~UsdTransform3dHandler(); + + // Delete the copy/move constructors assignment operators. + UsdTransform3dHandler(const UsdTransform3dHandler&) = delete; + UsdTransform3dHandler& operator=(const UsdTransform3dHandler&) = delete; + UsdTransform3dHandler(UsdTransform3dHandler&&) = delete; + UsdTransform3dHandler& operator=(UsdTransform3dHandler&&) = delete; + + //! Create a UsdTransform3dHandler. + static UsdTransform3dHandler::Ptr create(); + + // Ufe::Transform3dHandler overrides + Ufe::Transform3d::Ptr transform3d(const Ufe::SceneItem::Ptr& item) const override; + +private: + UsdTransform3d::Ptr fUsdTransform3d; + +}; // UsdTransform3dHandler + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdTranslateUndoableCommand.cpp b/lib/ufe/UsdTranslateUndoableCommand.cpp new file mode 100644 index 0000000000..7ba15e63ce --- /dev/null +++ b/lib/ufe/UsdTranslateUndoableCommand.cpp @@ -0,0 +1,82 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdTranslateUndoableCommand.h" +#include "private/Utils.h" +#include "Utils.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdTranslateUndoableCommand::UsdTranslateUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) + : Ufe::TranslateUndoableCommand(item) + , fPrim(prim) + , fPath(ufePath) + , fNoTranslateOp(false) +{ + // Prim does not have a translate attribute + const TfToken xlate("xformOp:translate"); + if (!fPrim.HasAttribute(xlate)) + { + fNoTranslateOp = true; + translateOp(fPrim, fPath, 0, 0, 0); // Add an empty translate + } + + fTranslateAttrib = fPrim.GetAttribute(xlate); + fTranslateAttrib.Get(&fPrevTranslateValue); +} + +UsdTranslateUndoableCommand::~UsdTranslateUndoableCommand() +{ +} + +/*static*/ +UsdTranslateUndoableCommand::Ptr UsdTranslateUndoableCommand::create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item) +{ + return std::make_shared(prim, ufePath, item); +} + +void UsdTranslateUndoableCommand::undo() +{ + fTranslateAttrib.Set(fPrevTranslateValue); + // Todo : We would want to remove the xformOp + // (SD-06/07/2018) Haven't found a clean way to do it - would need to investigate +} + +void UsdTranslateUndoableCommand::redo() +{ + perform(); +} + +void UsdTranslateUndoableCommand::perform() +{ + // No-op, use translate to move the object. + // The Maya move command directly invokes our translate() method in its + // redoIt(), which is invoked both for the inital move and the redo. +} + +//------------------------------------------------------------------------------ +// Ufe::TranslateUndoableCommand overrides +//------------------------------------------------------------------------------ + +bool UsdTranslateUndoableCommand::translate(double x, double y, double z) +{ + translateOp(fPrim, fPath, x, y, z); + return true; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdTranslateUndoableCommand.h b/lib/ufe/UsdTranslateUndoableCommand.h new file mode 100644 index 0000000000..6d311d832f --- /dev/null +++ b/lib/ufe/UsdTranslateUndoableCommand.h @@ -0,0 +1,68 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "ufe/transform3dUndoableCommands.h" + +#include "pxr/usd/usd/attribute.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Translation command of the given prim. +/*! + Ability to perform undo to restore the original translate value. + As of 06/07/2018, redo is a no op as Maya re-does the operation for redo + */ +class MAYAUSD_CORE_PUBLIC UsdTranslateUndoableCommand : public Ufe::TranslateUndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdTranslateUndoableCommand(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + ~UsdTranslateUndoableCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdTranslateUndoableCommand(const UsdTranslateUndoableCommand&) = delete; + UsdTranslateUndoableCommand& operator=(const UsdTranslateUndoableCommand&) = delete; + UsdTranslateUndoableCommand(UsdTranslateUndoableCommand&&) = delete; + UsdTranslateUndoableCommand& operator=(UsdTranslateUndoableCommand&&) = delete; + + //! Create a UsdTranslateUndoableCommand from a USD prim, UFE path and UFE scene item. + static UsdTranslateUndoableCommand::Ptr create(const UsdPrim& prim, const Ufe::Path& ufePath, const Ufe::SceneItem::Ptr& item); + + // Ufe::TranslateUndoableCommand overrides + void undo() override; + void redo() override; + bool translate(double x, double y, double z) override; + +private: + void perform(); + +private: + UsdPrim fPrim; + UsdAttribute fTranslateAttrib; + GfVec3d fPrevTranslateValue; + Ufe::Path fPath; + bool fNoTranslateOp; +}; // UsdTranslateUndoableCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoCreateGroupCommand.cpp b/lib/ufe/UsdUndoCreateGroupCommand.cpp new file mode 100644 index 0000000000..edc611e98b --- /dev/null +++ b/lib/ufe/UsdUndoCreateGroupCommand.cpp @@ -0,0 +1,80 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdUndoCreateGroupCommand.h" + +#include "ufe/scene.h" +#include "ufe/hierarchy.h" +#include "ufe/sceneNotification.h" + +#include "pxr/usd/usd/prim.h" +#include "pxr/usd/usd/stage.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdUndoCreateGroupCommand::UsdUndoCreateGroupCommand(const UsdSceneItem::Ptr& parentItem, const Ufe::PathComponent& name) + : Ufe::UndoableCommand() + , fParentItem(parentItem) + , fName(name) +{ +} + +UsdUndoCreateGroupCommand::~UsdUndoCreateGroupCommand() +{ +} + +/*static*/ +UsdUndoCreateGroupCommand::Ptr UsdUndoCreateGroupCommand::create(const UsdSceneItem::Ptr& parentItem, const Ufe::PathComponent& name) +{ + return std::make_shared(parentItem, name); +} + +Ufe::SceneItem::Ptr UsdUndoCreateGroupCommand::group() const +{ + return fGroup; +} + +//------------------------------------------------------------------------------ +// UsdUndoCreateGroupCommand overrides +//------------------------------------------------------------------------------ + +void UsdUndoCreateGroupCommand::undo() +{ + if (!fGroup) return; + + // See UsdUndoDuplicateCommand.undo() comments. + auto notification = Ufe::ObjectPreDelete(fGroup); + Ufe::Scene::notifyObjectDelete(notification); + + auto prim = fGroup->prim(); + auto stage = prim.GetStage(); + auto usdPath = prim.GetPath(); + stage->RemovePrim(usdPath); + + fGroup.reset(); +} + +void UsdUndoCreateGroupCommand::redo() +{ + auto hierarchy = Ufe::Hierarchy::hierarchy(fParentItem); + // See MAYA-92264: redo doesn't work. PPT, 19-Nov-2018. + Ufe::SceneItem::Ptr group = hierarchy->createGroup(fName); + fGroup = std::dynamic_pointer_cast(group); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoCreateGroupCommand.h b/lib/ufe/UsdUndoCreateGroupCommand.h new file mode 100644 index 0000000000..e0499a8d54 --- /dev/null +++ b/lib/ufe/UsdUndoCreateGroupCommand.h @@ -0,0 +1,62 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/undoableCommand.h" +#include "ufe/pathComponent.h" + +//PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief UsdUndoCreateGroupCommand +class MAYAUSD_CORE_PUBLIC UsdUndoCreateGroupCommand : public Ufe::UndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdUndoCreateGroupCommand(const UsdSceneItem::Ptr& parentItem, const Ufe::PathComponent& name); + ~UsdUndoCreateGroupCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdUndoCreateGroupCommand(const UsdUndoCreateGroupCommand&) = delete; + UsdUndoCreateGroupCommand& operator=(const UsdUndoCreateGroupCommand&) = delete; + UsdUndoCreateGroupCommand(UsdUndoCreateGroupCommand&&) = delete; + UsdUndoCreateGroupCommand& operator=(UsdUndoCreateGroupCommand&&) = delete; + + //! Create a UsdUndoCreateGroupCommand from a USD scene item and a UFE path component. + static UsdUndoCreateGroupCommand::Ptr create(const UsdSceneItem::Ptr& parentItem, const Ufe::PathComponent& name); + + Ufe::SceneItem::Ptr group() const; + + // UsdUndoCreateGroupCommand overrides + void undo() override; + void redo() override; + +private: + UsdSceneItem::Ptr fParentItem; + Ufe::PathComponent fName; + UsdSceneItem::Ptr fGroup; + +}; // UsdUndoCreateGroupCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoDeleteCommand.cpp b/lib/ufe/UsdUndoDeleteCommand.cpp new file mode 100644 index 0000000000..f8835070dc --- /dev/null +++ b/lib/ufe/UsdUndoDeleteCommand.cpp @@ -0,0 +1,58 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdUndoDeleteCommand.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdUndoDeleteCommand::UsdUndoDeleteCommand(const UsdPrim& prim) + : Ufe::UndoableCommand() + , fPrim(prim) +{ +} + +UsdUndoDeleteCommand::~UsdUndoDeleteCommand() +{ +} + +/*static*/ +UsdUndoDeleteCommand::Ptr UsdUndoDeleteCommand::create(const UsdPrim& prim) +{ + return std::make_shared(prim); +} + +void UsdUndoDeleteCommand::perform(bool state) +{ + fPrim.SetActive(state); +} + +//------------------------------------------------------------------------------ +// UsdUndoDeleteCommand overrides +//------------------------------------------------------------------------------ + +void UsdUndoDeleteCommand::undo() +{ + perform(true); +} + +void UsdUndoDeleteCommand::redo() +{ + perform(false); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoDeleteCommand.h b/lib/ufe/UsdUndoDeleteCommand.h new file mode 100644 index 0000000000..a81dc1f580 --- /dev/null +++ b/lib/ufe/UsdUndoDeleteCommand.h @@ -0,0 +1,62 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/undoableCommand.h" + +#include "pxr/usd/usd/prim.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief UsdUndoDeleteCommand +class MAYAUSD_CORE_PUBLIC UsdUndoDeleteCommand : public Ufe::UndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdUndoDeleteCommand(const UsdPrim& prim); + ~UsdUndoDeleteCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdUndoDeleteCommand(const UsdUndoDeleteCommand&) = delete; + UsdUndoDeleteCommand& operator=(const UsdUndoDeleteCommand&) = delete; + UsdUndoDeleteCommand(UsdUndoDeleteCommand&&) = delete; + UsdUndoDeleteCommand& operator=(UsdUndoDeleteCommand&&) = delete; + + //! Create a UsdUndoDeleteCommand from a USD prim. + static UsdUndoDeleteCommand::Ptr create(const UsdPrim& prim); + + // UsdUndoDeleteCommand overrides + void undo() override; + void redo() override; + +private: + void perform(bool state); + +private: + UsdPrim fPrim; + +}; // UsdUndoDeleteCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoDuplicateCommand.cpp b/lib/ufe/UsdUndoDuplicateCommand.cpp new file mode 100644 index 0000000000..5e141969dd --- /dev/null +++ b/lib/ufe/UsdUndoDuplicateCommand.cpp @@ -0,0 +1,124 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdUndoDuplicateCommand.h" +#include "Utils.h" + +#include "ufe/scene.h" +#include "ufe/sceneNotification.h" +#include "ufe/log.h" + +#include "pxr/usd/usd/stage.h" +#include "pxr/usd/sdf/copyUtils.h" +#include "pxr/base/tf/token.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdUndoDuplicateCommand::UsdUndoDuplicateCommand(const UsdPrim& srcPrim, const Ufe::Path& ufeSrcPath) + : Ufe::UndoableCommand() + , fSrcPrim(srcPrim) + , fUfeSrcPath(ufeSrcPath) +{ + fStage = fSrcPrim.GetStage(); + primInfo(srcPrim, fUsdDstPath, fLayer); +} + +UsdUndoDuplicateCommand::~UsdUndoDuplicateCommand() +{ +} + +/*static*/ +UsdUndoDuplicateCommand::Ptr UsdUndoDuplicateCommand::create(const UsdPrim& srcPrim, const Ufe::Path& ufeSrcPath) +{ + return std::make_shared(srcPrim, ufeSrcPath); +} + +const SdfPath& UsdUndoDuplicateCommand::usdDstPath() const +{ + return fUsdDstPath; +} + +/*static*/ +void UsdUndoDuplicateCommand::primInfo(const UsdPrim& srcPrim, SdfPath& usdDstPath, SdfLayerHandle& srcLayer) +{ + auto parent = srcPrim.GetParent(); + TfToken::HashSet childrenNames; + for(auto child : parent.GetChildren()) + { + childrenNames.insert(child.GetName()); + } + + // Find a unique name for the destination. If the source name already + // has a numerical suffix, increment it, otherwise append "1" to it. + auto dstName = uniqueName(childrenNames, srcPrim.GetName()); + usdDstPath = parent.GetPath().AppendChild(TfToken(dstName)); + + // Iterate over the layer stack, starting at the highest-priority layer. + // The source layer is the one in which there exists a def primSpec, not + // an over. An alternative would have beeen to call Sdf.CopySpec for + // each layer in which there is an over or a def, until we reach the + // layer with a def primSpec. This would preserve the visual appearance + // of the duplicate. PPT, 12-Jun-2018. + srcLayer = defPrimSpecLayer(srcPrim); + if (!srcLayer) { + std::string err = TfStringPrintf("No prim found at %s", srcPrim.GetPath().GetString().c_str()); + throw std::runtime_error(err.c_str()); + } +} + +/*static*/ +bool UsdUndoDuplicateCommand::duplicate(const SdfLayerHandle& layer, const SdfPath& usdSrcPath, const SdfPath& usdDstPath) +{ + // We use the source layer as the destination. An alternate workflow + // would be the edit target layer be the destination: + // layer = self._stage.GetEditTarget().GetLayer() + return SdfCopySpec(layer, usdSrcPath, layer, usdDstPath); +} + +//------------------------------------------------------------------------------ +// UsdUndoDuplicateCommand overrides +//------------------------------------------------------------------------------ + +void UsdUndoDuplicateCommand::undo() +{ + // USD sends a ResyncedPaths notification after the prim is removed, but + // at that point the prim is no longer valid, and thus a UFE post delete + // notification is no longer possible. To respect UFE object delete + // notification semantics, which require the object to be alive when + // the notification is sent, we send a pre delete notification here. + Ufe::ObjectPreDelete notification(createSiblingSceneItem( + fUfeSrcPath, fUsdDstPath.GetElementString())); + Ufe::Scene::notifyObjectDelete(notification); + + fStage->RemovePrim(fUsdDstPath); +} + +void UsdUndoDuplicateCommand::redo() +{ + // MAYA-92264: Pixar bug prevents redo from working. Try again with USD + // version 0.8.5 or later. PPT, 28-May-2018. + try { + duplicate(fLayer, fSrcPrim.GetPath(), fUsdDstPath); + } + catch (const std::exception& e) { + UFE_LOG(e.what()); + throw; // re-throw the same exception + } +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoDuplicateCommand.h b/lib/ufe/UsdUndoDuplicateCommand.h new file mode 100644 index 0000000000..fb0ac02593 --- /dev/null +++ b/lib/ufe/UsdUndoDuplicateCommand.h @@ -0,0 +1,77 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/path.h" +#include "ufe/undoableCommand.h" + +#include "pxr/usd/sdf/path.h" +#include "pxr/usd/usd/prim.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief UsdUndoDuplicateCommand +class MAYAUSD_CORE_PUBLIC UsdUndoDuplicateCommand : public Ufe::UndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdUndoDuplicateCommand(const UsdPrim& srcPrim, const Ufe::Path& ufeSrcPath); + ~UsdUndoDuplicateCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdUndoDuplicateCommand(const UsdUndoDuplicateCommand&) = delete; + UsdUndoDuplicateCommand& operator=(const UsdUndoDuplicateCommand&) = delete; + UsdUndoDuplicateCommand(UsdUndoDuplicateCommand&&) = delete; + UsdUndoDuplicateCommand& operator=(UsdUndoDuplicateCommand&&) = delete; + + //! Create a UsdUndoDuplicateCommand from a USD prim and UFE path. + static UsdUndoDuplicateCommand::Ptr create(const UsdPrim& srcPrim, const Ufe::Path& ufeSrcPath); + + const SdfPath& usdDstPath() const; + + //! Return the USD destination path and layer. + static void primInfo(const UsdPrim& srcPrim, SdfPath& usdDstPath, SdfLayerHandle& srcLayer); + + //! Duplicate the prim hierarchy at usdSrcPath. + //! \return True for success. + static bool duplicate(const SdfLayerHandle& layer, const SdfPath& usdSrcPath, const SdfPath& usdDstPath); + + //! Return the USD destination path and layer. + static void primInfo(); + + // UsdUndoDuplicateCommand overrides + void undo() override; + void redo() override; + +private: + UsdPrim fSrcPrim; + UsdStageWeakPtr fStage; + SdfLayerHandle fLayer; + Ufe::Path fUfeSrcPath; + SdfPath fUsdDstPath; + +}; // UsdUndoDuplicateCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoRenameCommand.cpp b/lib/ufe/UsdUndoRenameCommand.cpp new file mode 100644 index 0000000000..b7791732cb --- /dev/null +++ b/lib/ufe/UsdUndoRenameCommand.cpp @@ -0,0 +1,118 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "UsdUndoRenameCommand.h" +#include "Utils.h" +#include "private/InPathChange.h" + +#include "ufe/scene.h" +#include "ufe/sceneNotification.h" +#include "ufe/log.h" + +#include "pxr/usd/usd/stage.h" +#include "pxr/usd/sdf/copyUtils.h" +#include "pxr/base/tf/token.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +UsdUndoRenameCommand::UsdUndoRenameCommand(const UsdSceneItem::Ptr& srcItem, const Ufe::PathComponent& newName) + : Ufe::UndoableCommand() +{ + const UsdPrim& prim = srcItem->prim(); + fStage = prim.GetStage(); + fUfeSrcPath = srcItem->path(); + fUsdSrcPath = prim.GetPath(); + // Every call to rename() (through execute(), undo() or redo()) removes + // a prim, which becomes expired. Since USD UFE scene items contain a + // prim, we must recreate them after every call to rename. + fUsdDstPath = prim.GetParent().GetPath().AppendChild(TfToken(newName.string())); + fLayer = defPrimSpecLayer(prim); + if (fLayer) { + std::string err = TfStringPrintf("No prim found at %s", prim.GetPath().GetString().c_str()); + throw std::runtime_error(err.c_str()); + } +} + +UsdUndoRenameCommand::~UsdUndoRenameCommand() +{ +} + +/*static*/ +UsdUndoRenameCommand::Ptr UsdUndoRenameCommand::create(const UsdSceneItem::Ptr& srcItem, const Ufe::PathComponent& newName) +{ + return std::make_shared(srcItem, newName); +} + +UsdSceneItem::Ptr UsdUndoRenameCommand::renamedItem() const +{ + return fUfeDstItem; +} + +bool UsdUndoRenameCommand::rename(SdfLayerHandle layer, const Ufe::Path& ufeSrcPath, const SdfPath& usdSrcPath, const SdfPath& usdDstPath) +{ + InPathChange pc; + return internalRename(layer, ufeSrcPath, usdSrcPath, usdDstPath); +} + +bool UsdUndoRenameCommand::internalRename(SdfLayerHandle layer, const Ufe::Path& ufeSrcPath, const SdfPath& usdSrcPath, const SdfPath& usdDstPath) +{ + // We use the source layer as the destination. An alternate workflow + // would be the edit target layer be the destination: + // layer = self._stage.GetEditTarget().GetLayer() + bool status = SdfCopySpec(layer, usdSrcPath, layer, usdDstPath); + if (status) + { + fStage->RemovePrim(usdSrcPath); + // The renamed scene item is a "sibling" of its original name. + fUfeDstItem = createSiblingSceneItem( + ufeSrcPath, usdDstPath.GetElementString()); + // USD sends two ResyncedPaths() notifications, one for the CopySpec + // call, the other for the RemovePrim call (new name added, old name + // removed). Unfortunately, the rename semantics are lost: there is + // no notion that the two notifications belong to the same atomic + // change. Provide a single Rename notification ourselves here. + Ufe::ObjectRename notification(fUfeDstItem, ufeSrcPath); + Ufe::Scene::notifyObjectPathChange(notification); + } + + return status; +} + +//------------------------------------------------------------------------------ +// UsdUndoRenameCommand overrides +//------------------------------------------------------------------------------ + +void UsdUndoRenameCommand::undo() +{ + // MAYA-92264: Pixar bug prevents undo from working. Try again with USD + // version 0.8.5 or later. PPT, 7-Jul-2018. + try { + rename(fLayer, fUfeDstItem->path(), fUsdDstPath, fUsdSrcPath); + } + catch (const std::exception& e) { + UFE_LOG(e.what()); + throw; // re-throw the same exception + } +} + +void UsdUndoRenameCommand::redo() +{ + rename(fLayer, fUfeSrcPath, fUsdSrcPath, fUsdDstPath); +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/UsdUndoRenameCommand.h b/lib/ufe/UsdUndoRenameCommand.h new file mode 100644 index 0000000000..652ae19aba --- /dev/null +++ b/lib/ufe/UsdUndoRenameCommand.h @@ -0,0 +1,75 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/path.h" +#include "ufe/pathComponent.h" +#include "ufe/undoableCommand.h" + +#include "pxr/usd/sdf/path.h" +#include "pxr/usd/usd/prim.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief UsdUndoRenameCommand +class MAYAUSD_CORE_PUBLIC UsdUndoRenameCommand : public Ufe::UndoableCommand +{ +public: + typedef std::shared_ptr Ptr; + + UsdUndoRenameCommand(const UsdSceneItem::Ptr& srcItem, const Ufe::PathComponent& newName); + ~UsdUndoRenameCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdUndoRenameCommand(const UsdUndoRenameCommand&) = delete; + UsdUndoRenameCommand& operator=(const UsdUndoRenameCommand&) = delete; + UsdUndoRenameCommand(UsdUndoRenameCommand&&) = delete; + UsdUndoRenameCommand& operator=(UsdUndoRenameCommand&&) = delete; + + //! Create a UsdUndoRenameCommand from a USD scene item and UFE pathcomponent. + static UsdUndoRenameCommand::Ptr create(const UsdSceneItem::Ptr& srcItem, const Ufe::PathComponent& newName); + + UsdSceneItem::Ptr renamedItem() const; + + //! Rename the prim hierarchy at usdSrcPath to usdDstPath. + bool rename(SdfLayerHandle layer, const Ufe::Path& ufeSrcPath, const SdfPath& usdSrcPath, const SdfPath& usdDstPath); + + // UsdUndoRenameCommand overrides + void undo() override; + void redo() override; + +private: + bool internalRename(SdfLayerHandle layer, const Ufe::Path& ufeSrcPath, const SdfPath& usdSrcPath, const SdfPath& usdDstPath); + +private: + UsdStageWeakPtr fStage; + SdfLayerHandle fLayer; + Ufe::Path fUfeSrcPath; + SdfPath fUsdSrcPath; + UsdSceneItem::Ptr fUfeDstItem; + SdfPath fUsdDstPath; + +}; // UsdUndoRenameCommand + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/Utils.cpp b/lib/ufe/Utils.cpp new file mode 100644 index 0000000000..95e800a03e --- /dev/null +++ b/lib/ufe/Utils.cpp @@ -0,0 +1,222 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "Utils.h" +#include "private/Utils.h" +#include "UsdStageMap.h" +#include "ProxyShapeHandler.h" + +#include "pxr/base/tf/hashset.h" +#include "pxr/usd/usd/stage.h" + +#include "maya/MGlobal.h" +#include "maya/MSelectionList.h" + +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +#ifndef MAYA_MSTRINGARRAY_ITERATOR_CATEGORY +// MStringArray::Iterator is not standard-compliant in Maya 2019, needs the +// following workaround. Fixed in Maya 2020. PPT, 20-Jun-2019. +namespace std +{ + template<> + struct iterator_traits { + typedef std::bidirectional_iterator_tag iterator_category; + }; +} +#endif + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Global variables & macros +//------------------------------------------------------------------------------ + +extern UsdStageMap g_StageMap; +extern Ufe::Rtid g_MayaRtid; + +// Cache of Maya node types we've queried before for inheritance from the +// gateway node type. +std::unordered_map g_GatewayType; + +//------------------------------------------------------------------------------ +// Utility Functions +//------------------------------------------------------------------------------ + +UsdStageWeakPtr getStage(const Ufe::Path& path) +{ + return g_StageMap.stage(path); +} + +Ufe::Path stagePath(UsdStageWeakPtr stage) +{ + return g_StageMap.path(stage); +} + +UsdPrim ufePathToPrim(const Ufe::Path& path) +{ + // Assume that there are only two segments in the path, the first a Maya + // Dag path segment to the proxy shape, which identifies the stage, and + // the second the USD segment. + const Ufe::Path::Segments& segments = path.getSegments(); + TEST_USD_PATH(segments, path); + + UsdPrim prim; + if (auto stage = getStage(Ufe::Path(segments[0]))) + { + prim = stage->GetPrimAtPath(SdfPath(segments[1].string())); + } + return prim; +} + +bool isRootChild(const Ufe::Path& path) +{ + auto segments = path.getSegments(); + TEST_USD_PATH(segments, path); + return(segments[1].size() == 1); +} + +SdfLayerHandle defPrimSpecLayer(const UsdPrim& prim) +{ + // Iterate over the layer stack, starting at the highest-priority layer. + // The source layer is the one in which there exists a def primSpec, not + // an over. + + SdfLayerHandle defLayer; + auto layerStack = prim.GetStage()->GetLayerStack(); + for (auto layer : layerStack) + { + auto primSpec = layer->GetPrimAtPath(prim.GetPath()); + if (primSpec && (primSpec->GetSpecifier() == SdfSpecifierDef)) + { + defLayer = layer; + break; + } + } + return defLayer; +} + +UsdSceneItem::Ptr createSiblingSceneItem(const Ufe::Path& ufeSrcPath, const std::string& siblingName) +{ + auto ufeSiblingPath = ufeSrcPath.sibling(Ufe::PathComponent(siblingName)); + return UsdSceneItem::create(ufeSiblingPath, ufePathToPrim(ufeSiblingPath)); +} + +std::string uniqueName(const TfToken::HashSet& existingNames, std::string srcName) +{ + // Compiled regular expression to find a numerical suffix to a path component. + // It searches for any number of characters followed by a single non-numeric, + // then one or more digits at end of string. + std::regex re("(.*)([^0-9])([0-9]+)$"); + std::string base{srcName}; + int suffix{1}; + std::smatch match; + if (std::regex_match(srcName, match, re)) + { + base = match[1].str() + match[2].str(); + suffix = std::stoi(match[3].str()) + 1; + } + std::string dstName = base + std::to_string(suffix); + while (existingNames.count(TfToken(dstName)) > 0) + { + dstName = base + std::to_string(++suffix); + } + return dstName; +} + +std::string uniqueChildName(const Ufe::SceneItem::Ptr& parent, const Ufe::Path& childPath) +{ + auto usdParent = std::dynamic_pointer_cast(parent); +#if !defined(NDEBUG) + assert(usdParent); +#endif + if (!usdParent) return ""; + + TfToken::HashSet childrenNames; + for (auto child : usdParent->prim().GetChildren()) + { + childrenNames.insert(child.GetName()); + } + std::string childName = childPath.back().string(); + if (childrenNames.find(TfToken(childName)) != childrenNames.end()) + { + childName = uniqueName(childrenNames, childName); + } + return childName; +} + +bool isAGatewayType(const std::string& mayaNodeType) +{ + // If we've seen this node type before, return the cached value. + auto iter = g_GatewayType.find(mayaNodeType); + if (iter != std::end(g_GatewayType)) + { + return iter->second; + } + + // Note: we are calling the MEL interpreter to determine the inherited types, + // but we are then caching the result. So MEL will only be called once + // for each node type. + // Not seen before, so ask Maya. + // When the inherited flag is used, the command returns a string array containing + // the names of all the base node types inherited by the the given node. + MString cmd; + MStringArray inherited; + bool isInherited = false; + cmd.format("nodeType -inherited -isTypeName ^1s", mayaNodeType.c_str()); + if (MS::kSuccess == MGlobal::executeCommand(cmd, inherited)) + { + MString gatewayNodeType(ProxyShapeHandler::gatewayNodeType().c_str()); + auto iter2 = std::find(inherited.begin(), inherited.end(), gatewayNodeType); + isInherited = (iter2 != inherited.end()); + g_GatewayType[mayaNodeType] = isInherited; + } + return isInherited; +} + +Ufe::Path dagPathToUfe(const MDagPath& dagPath) +{ + // This function can only create UFE Maya scene items with a single + // segment, as it is only given a Dag path as input. + return Ufe::Path(dagPathToPathSegment(dagPath)); +} + +Ufe::PathSegment dagPathToPathSegment(const MDagPath& dagPath) +{ + std::string fullPathName = dagPath.fullPathName().asChar(); + return Ufe::PathSegment("world" + fullPathName, g_MayaRtid, '|'); +} + +MDagPath nameToDagPath(const std::string& name) +{ + MSelectionList selection; + selection.add(MString(name.c_str())); + MDagPath dag; + MStatus status = selection.getDagPath(0, dag); + CHECK_MSTATUS(status); + return dag; +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/Utils.h b/lib/ufe/Utils.h new file mode 100644 index 0000000000..e55bbce04c --- /dev/null +++ b/lib/ufe/Utils.h @@ -0,0 +1,85 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../base/api.h" + +#include "UsdSceneItem.h" + +#include "ufe/path.h" +#include "ufe/pathSegment.h" + +#include "pxr/usd/usd/prim.h" +#include "pxr/usd/sdf/layer.h" +#include "pxr/base/tf/token.h" + +#include "maya/MDagPath.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Helper functions +//------------------------------------------------------------------------------ + +//! Get USD stage corresponding to argument Maya Dag path. +MAYAUSD_CORE_PUBLIC +UsdStageWeakPtr getStage(const Ufe::Path& path); + +//! Return the ProxyShape node UFE path for the argument stage. +MAYAUSD_CORE_PUBLIC +Ufe::Path stagePath(UsdStageWeakPtr stage); + +//! Return the USD prim corresponding to the argument UFE path. +MAYAUSD_CORE_PUBLIC +UsdPrim ufePathToPrim(const Ufe::Path& path); + +MAYAUSD_CORE_PUBLIC +bool isRootChild(const Ufe::Path& path); + +//! Return the highest-priority layer where the prim has a def primSpec. +MAYAUSD_CORE_PUBLIC +SdfLayerHandle defPrimSpecLayer(const UsdPrim& prim); + +MAYAUSD_CORE_PUBLIC +UsdSceneItem::Ptr createSiblingSceneItem(const Ufe::Path& ufeSrcPath, const std::string& siblingName); + +//! Split the source name into a base name and a numerical suffix (set to +//! 1 if absent). Increment the numerical suffix until name is unique. +MAYAUSD_CORE_PUBLIC +std::string uniqueName(const TfToken::HashSet& existingNames, std::string srcName); + +//! Return a unique child name. Parent must be a UsdSceneItem. +MAYAUSD_CORE_PUBLIC +std::string uniqueChildName(const Ufe::SceneItem::Ptr& parent, const Ufe::Path& childPath); + +//! Return if a Maya node type is derived from the gateway node type. +MAYAUSD_CORE_PUBLIC +bool isAGatewayType(const std::string& mayaNodeType); + +MAYAUSD_CORE_PUBLIC +Ufe::Path dagPathToUfe(const MDagPath& dagPath); + +MAYAUSD_CORE_PUBLIC +Ufe::PathSegment dagPathToPathSegment(const MDagPath& dagPath); + +MAYAUSD_CORE_PUBLIC +MDagPath nameToDagPath(const std::string& name); + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/__init__.py b/lib/ufe/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ufe/private/InPathChange.h b/lib/ufe/private/InPathChange.h new file mode 100644 index 0000000000..bd9e1d173f --- /dev/null +++ b/lib/ufe/private/InPathChange.h @@ -0,0 +1,43 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../../base/api.h" + +MAYAUSD_NS_DEF { +namespace ufe { + +//! \brief Helper class to scope when we are in a path change operation. +class InPathChange +{ +public: + InPathChange() { fInPathChange = true; } + ~InPathChange() { fInPathChange = false; } + + // Delete the copy/move constructors assignment operators. + InPathChange(const InPathChange&) = delete; + InPathChange& operator=(const InPathChange&) = delete; + InPathChange(InPathChange&&) = delete; + InPathChange& operator=(InPathChange&&) = delete; + + static bool inPathChange() { return fInPathChange; } + +private: + static bool fInPathChange; +}; + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/private/Utils.cpp b/lib/ufe/private/Utils.cpp new file mode 100644 index 0000000000..cc75e87bde --- /dev/null +++ b/lib/ufe/private/Utils.cpp @@ -0,0 +1,214 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "Utils.h" + +#include "ufe/log.h" + +#include "pxr/base/tf/stringUtils.h" +#include "pxr/usd/usdGeom/xformable.h" + +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Private helper functions +//------------------------------------------------------------------------------ + +UsdGeomXformCommonAPI convertToCompatibleCommonAPI(const UsdPrim& prim) +{ + // As we are using USD's XformCommonAPI which supports only the following xformOps : + // ["xformOp:translate", "xformOp:translate:pivot", "xformOp:rotateXYZ", "xformOp:scale", "!invert!xformOp:translate:pivot"] + // We are extending the supported xform Operations with : + // ["xformOp:rotateX", "xformOp:rotateY", "xformOp:rotateZ"] + // Where we convert these into xformOp:rotateXYZ. + + static TfToken rotX("xformOp:rotateX"); + static TfToken rotY("xformOp:rotateY"); + static TfToken rotZ("xformOp:rotateZ"); + static TfToken rotXYZ("xformOp:rotateXYZ"); + static TfToken scale("xformOp:scale"); + static TfToken trans("xformOp:translate"); + static TfToken pivot("xformOp:translate:pivot"); + static TfToken notPivot("!invert!xformOp:translate:pivot"); + + auto xformable = UsdGeomXformable(prim); + bool resetsXformStack; + auto xformOps = xformable.GetOrderedXformOps(&resetsXformStack); + xformable.ClearXformOpOrder(); + auto primXform = UsdGeomXformCommonAPI(prim); + for (const auto& op : xformOps) + { + auto opName = op.GetOpName(); + + // RotateX, RotateY, RotateZ + if ((opName == rotX) || (opName == rotY) || (opName == rotZ)) + { + float retValue; + if (op.Get(&retValue)) + { + if (opName == rotX) + primXform.SetRotate(GfVec3f(retValue, 0, 0)); + else if (opName == rotY) + primXform.SetRotate(GfVec3f(0, retValue, 0)); + else if (opName == rotZ) + primXform.SetRotate(GfVec3f(0, 0, retValue)); + } + } + // RotateXYZ + else if (opName == rotXYZ) + { + GfVec3f retValue; + if (op.Get(&retValue)) + { + primXform.SetRotate(retValue); + } + } + // Scale + else if (opName == scale) + { + GfVec3f retValue; + if (op.Get(&retValue)) + { + primXform.SetScale(retValue); + } + } + // Translate + else if (opName == trans) + { + GfVec3d retValue; + if (op.Get(&retValue)) + { + primXform.SetTranslate(retValue); + } + } + // Scale / rotate pivot + else if (opName == pivot) + { + GfVec3f retValue; + if (op.Get(&retValue)) + { + primXform.SetPivot(retValue); + } + } + // Scale / rotate pivot inverse + else if (opName == notPivot) + { + // automatically added, nothing to do. + } + // Not compatible + else + { + // Restore old + xformable.SetXformOpOrder(xformOps); + std::string err = TfStringPrintf("Incompatible xform op %s:", op.GetOpName().GetString().c_str()); + throw std::runtime_error(err.c_str()); + } + } + return primXform; +} + +//------------------------------------------------------------------------------ +// Operations: translate, rotate, scale, pivot +//------------------------------------------------------------------------------ + +void translateOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z) +{ + auto primXform = UsdGeomXformCommonAPI(prim); + if (!primXform.SetTranslate(GfVec3d(x, y, z))) + { + // This could mean that we have an incompatible xformOp in the stack + try { + primXform = convertToCompatibleCommonAPI(prim); + if (!primXform.SetTranslate(GfVec3d(x,y,z))) + throw std::runtime_error("Unable to SetTranslate after conversion to CommonAPI."); + } + catch (const std::exception& e) { + std::string err = TfStringPrintf("Failed to translate prim %s - %s", + path.string().c_str(), e.what()); + UFE_LOG(err.c_str()); + throw; + } + } +} + +void rotateOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z) +{ + auto primXform = UsdGeomXformCommonAPI(prim); + if (!primXform.SetRotate(GfVec3f(x, y, z))) + { + // This could mean that we have an incompatible xformOp in the stack + try { + primXform = convertToCompatibleCommonAPI(prim); + if (!primXform.SetRotate(GfVec3f(x, y, z))) + throw std::runtime_error("Unable to SetRotate after conversion to CommonAPI."); + } + catch (const std::exception& e) { + std::string err = TfStringPrintf("Failed to rotate prim %s - %s", + path.string().c_str(), e.what()); + UFE_LOG(err.c_str()); + throw; + } + } +} + +void scaleOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z) +{ + auto primXform = UsdGeomXformCommonAPI(prim); + if (!primXform.SetScale(GfVec3f(x, y, z))) + { + // This could mean that we have an incompatible xformOp in the stack + try { + primXform = convertToCompatibleCommonAPI(prim); + if (!primXform.SetScale(GfVec3f(x, y, z))) + throw std::runtime_error("Unable to SetScale after conversion to CommonAPI."); + } + catch (const std::exception& e) { + std::string err = TfStringPrintf("Failed to scale prim %s - %s", + path.string().c_str(), e.what()); + UFE_LOG(err.c_str()); + throw; + } + } +} + +void rotatePivotTranslateOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z) +{ + auto primXform = UsdGeomXformCommonAPI(prim); + if (!primXform.SetPivot(GfVec3f(x, y, z))) + { + // This could mean that we have an incompatible xformOp in the stack + try { + primXform = convertToCompatibleCommonAPI(prim); + if (!primXform.SetPivot(GfVec3f(x, y, z))) + throw std::runtime_error("Unable to SetPivot after conversion to CommonAPI."); + } + catch (const std::exception& e) { + std::string err = TfStringPrintf("Failed to set pivot for prim %s - %s", + path.string().c_str(), e.what()); + UFE_LOG(err.c_str()); + throw; + } + } +} + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/private/Utils.h b/lib/ufe/private/Utils.h new file mode 100644 index 0000000000..65b1f6c5db --- /dev/null +++ b/lib/ufe/private/Utils.h @@ -0,0 +1,71 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "../../base/api.h" + +#include "ufe/path.h" + +#include "pxr/usd/usd/prim.h" +#include "pxr/base/tf/token.h" +#include "pxr/usd/usdGeom/xformCommonAPI.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { +namespace ufe { + +//------------------------------------------------------------------------------ +// Private globals and macros +//------------------------------------------------------------------------------ +const std::string kIllegalUSDPath = "Illegal USD run-time path %s."; + +#if !defined(NDEBUG) + #define TEST_USD_PATH(SEG, PATH) \ + assert(SEG.size() == 2); \ + if (SEG.size() != 2) \ + TF_WARN(kIllegalUSDPath.c_str(), PATH.string().c_str()); +#else + #define TEST_USD_PATH(SEG, PATH) \ + if (SEG.size() != 2) \ + TF_WARN(kIllegalUSDPath.c_str(), PATH.string().c_str()); +#endif + +//------------------------------------------------------------------------------ +// Private helper functions +//------------------------------------------------------------------------------ + +//! Extended support for the xform operations. +UsdGeomXformCommonAPI convertToCompatibleCommonAPI(const UsdPrim& prim); + +//------------------------------------------------------------------------------ +// Operations: translate, rotate, scale, pivot +//------------------------------------------------------------------------------ + +//! Absolute translation of the given prim. +void translateOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z); + +//! Absolute rotation (degrees) of the given prim. +void rotateOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z); + +//! Absolute scale of the given prim. +void scaleOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z); + +//! Absolute translation of the given prim's pivot point. +void rotatePivotTranslateOp(const UsdPrim& prim, const Ufe::Path& path, double x, double y, double z); + +} // namespace ufe +} // namespace MayaUsd diff --git a/lib/ufe/wrapUsdSceneItem.cpp b/lib/ufe/wrapUsdSceneItem.cpp new file mode 100644 index 0000000000..53b470d4ba --- /dev/null +++ b/lib/ufe/wrapUsdSceneItem.cpp @@ -0,0 +1,195 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + + +// ***** NOTE ***** +// +// This is a WIP that is currently not used (as it doesn't work). We need to +// figure out how to create python bindings for our UsdSceneItem such that it +// derives from the Ufe::SceneItem python bindings. +// + + +#include "UsdSceneItem.h" + +#if 1 + +#include + +using namespace boost::python; +using namespace MayaUsd::ufe; + +struct ufe_SceneItem +{ + static void RegisterConversions() + { + // From Python + converter::registry::push_back( + &convertible, + &construct, + type_id()); + } + + // Determine if obj_ptr can be converted into a Ufe::SceneItem + static void* convertible(PyObject* obj_ptr) + { + // Can always put a python object into Ufe::SceneItem + if (0 == std::strcmp(obj_ptr->ob_type->tp_name, "ufe.PyUfe.SceneItem")) + return obj_ptr; + + // This causes infinite loop +// return extract(obj_ptr).check() ? obj_ptr : nullptr; + + // This causes infinite loop +// extract extractor(obj_ptr); +// return extractor.check() ? obj_ptr : nullptr; + } + + // Convert obj_ptr into a SceneItem + static void construct( + PyObject* obj_ptr, + converter::rvalue_from_python_stage1_data* data) + { + void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; + + // In-place construct the new SceneItem using the data extracted from the python object. + extract item(obj_ptr); + + // TEST + //Ufe::SceneItem::Ptr* ptr = static_cast(data->convertible); + + new (storage) Ufe::SceneItem::Ptr(item()); + data->convertible = storage; + + +// // Grab pointer to memory into which to construct the new Ufe::SceneItem. +// void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; +// +// // In-place construct the new SceneItem using the data extracted from the python object. +// Ufe::SceneItem::Ptr item = extract(obj_ptr); + +// void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; +// new (storage)Map(); +// data->convertible = storage; + +// void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; +// Usd_PyPrimRange pyIter = extract(obj_ptr); +// new (storage) UsdPrimRange(pyIter._rng); +// data->convertible = storage; + } +// static void +// _construct(PyObject *obj_ptr, +// converter::rvalue_from_python_stage1_data *data) { +// void *storage = +// ((converter::rvalue_from_python_storage*)data) +// ->storage.bytes; +// // Make a TfPyObjWrapper holding the Python object. +// new (storage) TfPyObjWrapper(object(borrowed(obj_ptr))); +// data->convertible = storage; +// } +}; + +UsdPrim primFromSceneItem(const Ufe::SceneItem::Ptr& item) +{ + UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); + if (usdItem) + return usdItem->prim(); + + return UsdPrim(); +} + +BOOST_PYTHON_MODULE(ufe) +{ +// class_, boost::noncopyable >("UsdSceneItem", init()) +// import mayaUsd.ufe +// # Error: RuntimeError: file line 1: extension class wrapper for base class class Ufe_v0::SceneItem has not been created yet # + + class_("UsdSceneItem", init()) +// print dir(mayaUsd.ufe) +// ['UsdSceneItem', '__doc__', '__file__', '__name__', '__package__', 'primFromSceneItem'] + .def("create", &UsdSceneItem::create) + .staticmethod("create") + .def("prim", &UsdSceneItem::prim, return_value_policy()) + .def("nodeType", &UsdSceneItem::nodeType) + .def("runTimeId", &UsdSceneItem::runTimeId) + .def("path", &UsdSceneItem::path, return_value_policy()) + .def("nodeType", &UsdSceneItem::nodeType) + .def("isProperty", &UsdSceneItem::isProperty) + ; +// # Error: ArgumentError: file line 4: Python argument types in +// UsdSceneItem.__init__(UsdSceneItem, ufe.PyUfe.Path, Prim) +// did not match C++ signature: +// __init__(struct _object * __ptr64, class Ufe_v0::Path, class pxrInternal_v0_19__pxrReserved__::UsdPrim) # + + + def("primFromSceneItem", primFromSceneItem); +// # Error: ArgumentError: file line 1: Python argument types in +// mayaUsd.ufe.primFromSceneItem(ufe.PyUfe.SceneItem) +// did not match C++ signature: +// primFromSceneItem(class std::shared_ptr) # + + // Register the from-python converter + ufe_SceneItem::RegisterConversions(); +} + +#else + +#include + +static PyObject* helloworld(PyObject* self) { + return Py_BuildValue("s", "Hello, Python extensions!!"); +} + +static char helloworld_docs[] = + "helloworld( ): Any message you want to put here!!\n"; + +static PyObject *primFromSceneItem(PyObject *self, PyObject *args) +{ + PyObject* sceneItem = nullptr; + + if (PyArg_ParseTuple(args, "O", &sceneItem)) + { +// if (PyObject_TypeCheck(sceneItem, &MPyMObject_Type::typeObj())) + if (0 == std::strcmp(Py_TYPE(sceneItem)->tp_name, "ufe.PyUfe.SceneItem")) + { + Ufe::SceneItem::Ptr* item = (Ufe::SceneItem::Ptr*)sceneItem; + if (item) + { + Ufe::Path path = (*item)->path(); + std::string p = path.string(); + } + } + } + + /* Do something interesting here. */ + Py_RETURN_NONE; +} + +static PyMethodDef helloworld_funcs[] = { + {"helloworld", (PyCFunction)helloworld, METH_NOARGS, helloworld_docs}, + {"primFromSceneItem", (PyCFunction)primFromSceneItem, METH_VARARGS, NULL }, + {NULL} +}; + + +PyMODINIT_FUNC initufe() +{ + Py_InitModule3("ufe", helloworld_funcs, + "Extension module example!"); +} + +#endif diff --git a/lib/ufe/wrapUtils.cpp b/lib/ufe/wrapUtils.cpp new file mode 100644 index 0000000000..bd04e9b7c9 --- /dev/null +++ b/lib/ufe/wrapUtils.cpp @@ -0,0 +1,71 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include + +#include "UsdSceneItem.h" +#include "Utils.h" + +#include "ufe/runTimeMgr.h" +#include "ufe/rtid.h" + +using namespace MayaUsd; +using namespace boost::python; + +UsdPrim getPrimFromRawItem(uint64_t rawItem) +{ + Ufe::SceneItem* item = reinterpret_cast(rawItem); + ufe::UsdSceneItem* usdItem = dynamic_cast(item); + if (nullptr != usdItem) + { + return usdItem->prim(); + } + return UsdPrim(); +} + +#ifdef UFE_V2_FEATURES_AVAILABLE +std::string getNodeNameFromRawItem(uint64_t rawItem) +{ + std::string name; + Ufe::SceneItem* item = reinterpret_cast(rawItem); + if (nullptr != item) + name = item->nodeName(); + return name; +} +#endif + +std::string getNodeTypeFromRawItem(uint64_t rawItem) +{ + std::string type; + Ufe::SceneItem* item = reinterpret_cast(rawItem); + if (nullptr != item) + { + // Prepend the name of the runtime manager of this item to the type. + type = Ufe::RunTimeMgr::instance().getName(item->runTimeId()) + item->nodeType(); + } + return type; +} + +BOOST_PYTHON_MODULE(ufe) +{ + def("getPrimFromRawItem", getPrimFromRawItem); + + #ifdef UFE_V2_FEATURES_AVAILABLE + def("getNodeNameFromRawItem", getNodeNameFromRawItem); + #endif + + def("getNodeTypeFromRawItem", getNodeTypeFromRawItem); +} diff --git a/plugin/al/CMakeLists.txt b/plugin/al/CMakeLists.txt index 87d783a0a3..bee1c428fb 100644 --- a/plugin/al/CMakeLists.txt +++ b/plugin/al/CMakeLists.txt @@ -17,24 +17,18 @@ option(BUILD_USDMAYA_SCHEMAS "Build optional schemas." ON) option(BUILD_USDMAYA_TRANSLATORS "Build optional translators." ON) option(SKIP_USDMAYA_TESTS "Build tests" OFF) -if ("${MAYA_DEVKIT_LOCATION}" STREQUAL "") - set(CMAKE_WANT_UFE_BUILD OFF) -else() - set(CMAKE_WANT_UFE_BUILD ON) -endif() - - # to get PYTHON_EXECUTABLE find_package(PythonInterp) if(CMAKE_WANT_UFE_BUILD) - add_definitions(-DWANT_UFE_BUILD) - - find_package(UFE REQUIRED) - include_directories(${UFE_INCLUDE_DIR}) - - message("UFE Build Enabled") - message("Using UFE version : ${UFE_VERSION}") + find_package(UFE QUIET) + if(UFE_FOUND) + message(STATUS "Building with UFE ${UFE_VERSION} features enabled.") + include_directories(${UFE_INCLUDE_DIR}) + add_definitions(-DWANT_UFE_BUILD) + else() + message(STATUS "UFE not found. UFE features will be disabled.") + endif() endif() # FindBoost is particularly buggy, and doesn't like custom boost locations. diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h b/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h index f2e479db7c..07b6893169 100644 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h +++ b/plugin/al/lib/AL_USDMaya/AL/usdmaya/PluginRegister.h @@ -61,6 +61,10 @@ #include +#if defined(WANT_UFE_BUILD) +#include +#endif + PXR_NAMESPACE_USING_DIRECTIVE namespace AL { @@ -291,6 +295,13 @@ MStatus registerPlugin(AFnPlugin& plugin) AL_REGISTER_SHAPE_NODE(plugin, AL::usdmaya::nodes::ProxyShape, AL::usdmaya::nodes::ProxyShapeUI, AL::usdmaya::nodes::ProxyDrawOverride); } +#if defined(WANT_UFE_BUILD) + status = MayaUsd::ufe::initialize(); + if (!status) { + status.perror("Unable to initialize ufe."); + } +#endif + AL_REGISTER_TRANSFORM_NODE(plugin, AL::usdmaya::nodes::Transform, AL::usdmaya::nodes::TransformationMatrix); AL_REGISTER_DEPEND_NODE(plugin, AL::usdmaya::nodes::RendererManager); AL_REGISTER_DEPEND_NODE(plugin, AL::usdmaya::nodes::Layer); @@ -353,6 +364,11 @@ MStatus unregisterPlugin(AFnPlugin& plugin) { MStatus status; +#if defined(WANT_UFE_BUILD) + status = MayaUsd::ufe::finalize(); + CHECK_MSTATUS(status); +#endif + // gpuCachePluginMain used as an example. if (MGlobal::kInteractive == MGlobal::mayaState()) { MString cmd = "deleteSelectTypeItem(\"Surface\",\""; diff --git a/plugin/al/lib/AL_USDMaya/CMakeLists.txt b/plugin/al/lib/AL_USDMaya/CMakeLists.txt index b72840d31f..169f060ac4 100644 --- a/plugin/al/lib/AL_USDMaya/CMakeLists.txt +++ b/plugin/al/lib/AL_USDMaya/CMakeLists.txt @@ -215,10 +215,13 @@ target_link_libraries(${LIBRARY_NAME} ${MAYA_OpenMayaUI_LIBRARY} ${MAYA_OpenMaya_LIBRARY} ${MAYA_OpenMayaRender_LIBRARY} - ${UFE_LIBRARY} mayaUsd ) +if(UFE_FOUND) + target_link_libraries(${LIBRARY_NAME} ${UFE_LIBRARY}) +endif() + if(NEED_BOOST_FILESYSTEM) target_link_libraries(${LIBRARY_NAME} ${Boost_FILESYSTEM_LIBRARY} diff --git a/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt b/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt index 71fb1267db..fcab7a0b20 100644 --- a/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt +++ b/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt @@ -32,6 +32,21 @@ pxr_plugin(${PXR_PACKAGE} DISABLE_PRECOMPILED_HEADERS ) +if(CMAKE_WANT_UFE_BUILD) + find_package(UFE QUIET) + if(UFE_FOUND) + message(STATUS "Building with UFE ${UFE_VERSION} features enabled.") + include_directories(${UFE_INCLUDE_DIR}) + add_definitions(-DWANT_UFE_BUILD) + else() + message(STATUS "UFE not found. UFE features will be disabled.") + endif() +endif() + +if (UFE_FOUND) + target_link_libraries(${PXR_PACKAGE} ${UFE_LIBRARY}) +endif() + pxr_test_scripts( testenv/testPxrUsdAlembicChaser.py ) diff --git a/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp b/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp index 5a187d02e9..14233ff218 100644 --- a/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp +++ b/plugin/pxr/maya/plugin/pxrUsd/plugin.cpp @@ -45,6 +45,9 @@ #include #include +#if defined(WANT_UFE_BUILD) +#include +#endif PXR_NAMESPACE_USING_DIRECTIVE @@ -59,6 +62,13 @@ initializePlugin(MObject obj) MStatus status; MFnPlugin plugin(obj, "Pixar", "1.0", "Any"); +#if defined(WANT_UFE_BUILD) + status = MayaUsd::ufe::initialize(); + if (!status) { + status.perror("Unable to initialize ufe."); + } +#endif + status = MayaUsdProxyShapePlugin::initialize(plugin); CHECK_MSTATUS(status); @@ -227,6 +237,11 @@ uninitializePlugin(MObject obj) MStatus status; MFnPlugin plugin(obj); +#if defined(WANT_UFE_BUILD) + status = MayaUsd::ufe::finalize(); + CHECK_MSTATUS(status); +#endif + status = plugin.deregisterCommand("usdImport"); if (!status) { status.perror("deregisterCommand usdImport"); From c217793790c365eb28e6d272f23fa5045e0e7d4b Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Sun, 27 Oct 2019 15:17:47 -0400 Subject: [PATCH 05/15] Add test build stage, adsk plugin and UFE plugin tests --- CMakeLists.txt | 16 + build.py | 43 +- .../CMakeLists_googletest_download.txt.in | 0 .../CMakeLists_googletest_src.txt.in | 0 cmake/Googletest.cmake | 82 ++++ cmake/jinja.cmake | 53 +++ cmake/modules/FindMaya.cmake | 48 +- cmake/utils.cmake | 182 +++++++- doc/DEVELOPER.md | 2 + doc/build.md | 5 +- plugin/adsk/CMakeLists.txt | 9 + plugin/adsk/plugin/CMakeLists.txt | 76 ++++ plugin/adsk/plugin/ProxyShape.cpp | 64 +++ plugin/adsk/plugin/ProxyShape.h | 54 +++ plugin/adsk/plugin/base/api.h | 48 ++ plugin/adsk/plugin/plugin.cpp | 87 ++++ plugin/al/CMakeLists.txt | 84 ---- .../lib/AL_USDMaya/AL/usdmaya/StageData.cpp | 81 ---- .../al/lib/AL_USDMaya/AL/usdmaya/StageData.h | 77 ---- .../usdmaya/nodes/test_ProxyUsdGeomCamera.cpp | 2 +- .../usd/transaction/tests/testTransaction.pyc | Bin 0 -> 7286 bytes .../tests/testTransactionManager.pyc | Bin 0 -> 2293 bytes test/lib/ufe/CMakeLists.txt | 115 +++++ .../StandaloneScene/Assembly_room_set.usda | 409 ++++++++++++++++++ .../ballset/StandaloneScene/Ball.maya.usd | Bin 0 -> 31653 bytes .../StandaloneScene/Ball.shadingVariants.usda | 317 ++++++++++++++ .../ballset/StandaloneScene/Ball.usd | 21 + .../ballset/StandaloneScene/Room_set.usd | Bin 0 -> 8058 bytes .../ballset/StandaloneScene/edits.usda | 405 +++++++++++++++++ .../ballset/StandaloneScene/tex/ball1.jpg | Bin 0 -> 5101 bytes .../ballset/StandaloneScene/tex/ball1.tex | Bin 0 -> 202932 bytes .../ballset/StandaloneScene/tex/ball4.jpg | Bin 0 -> 5872 bytes .../ballset/StandaloneScene/tex/ball4.tex | Bin 0 -> 223576 bytes .../ballset/StandaloneScene/tex/ball8.jpg | Bin 0 -> 6800 bytes .../ballset/StandaloneScene/tex/ball8.tex | Bin 0 -> 150914 bytes .../ballset/StandaloneScene/tex/ball9.jpg | Bin 0 -> 7803 bytes .../ballset/StandaloneScene/tex/ball9.tex | Bin 0 -> 237074 bytes .../ballset/StandaloneScene/tex/ballCue.jpg | Bin 0 -> 1434 bytes .../ballset/StandaloneScene/tex/ballCue.tex | Bin 0 -> 6262 bytes .../ballset/StandaloneScene/tex/makeTex | 7 + .../ballset/StandaloneScene/top_layer.ma | 185 ++++++++ .../ballset/StandaloneScene/top_layer.usda | 9 + .../ufe/test-samples/cylinder/cylinder.usda | 25 ++ .../ufe/test-samples/cylinder/usdCylinder.ma | 180 ++++++++ .../ufe/test-samples/twoSpheres/sphere.usda | 20 + .../ufe/test-samples/twoSpheres/twoSpheres.ma | 198 +++++++++ test/lib/ufe/testAttribute.py | 344 +++++++++++++++ test/lib/ufe/testAttributes.py | 69 +++ test/lib/ufe/testDeleteCmd.py | 140 ++++++ test/lib/ufe/testDuplicateCmd.py | 163 +++++++ test/lib/ufe/testGroupCmd.py | 91 ++++ test/lib/ufe/testMatrices.py | 152 +++++++ test/lib/ufe/testMayaPickwalk.py | 218 ++++++++++ test/lib/ufe/testMoveCmd.py | 271 ++++++++++++ test/lib/ufe/testRotateCmd.py | 284 ++++++++++++ test/lib/ufe/testRotatePivot.py | 143 ++++++ test/lib/ufe/testScaleCmd.py | 279 ++++++++++++ test/lib/ufe/testTRSBase.py | 102 +++++ test/lib/ufe/testTransform3dTranslate.py | 240 ++++++++++ test/lib/ufe/ufeScripts/__init__.py | 0 test/lib/ufe/ufeScripts/ufeSelectCmd.py | 302 +++++++++++++ .../ufe/ufeTestPlugins/ufeTestCmdsPlugin.py | 53 +++ test/lib/ufe/ufeTestUtils/__init__.py | 0 test/lib/ufe/ufeTestUtils/mayaUtils.py | 114 +++++ test/lib/ufe/ufeTestUtils/ufeUtils.py | 53 +++ test/lib/ufe/ufeTestUtils/usdUtils.py | 41 ++ 66 files changed, 5714 insertions(+), 249 deletions(-) rename {plugin/al => cmake}/CMakeLists_googletest_download.txt.in (100%) rename {plugin/al => cmake}/CMakeLists_googletest_src.txt.in (100%) create mode 100644 cmake/Googletest.cmake create mode 100644 cmake/jinja.cmake create mode 100644 plugin/adsk/CMakeLists.txt create mode 100644 plugin/adsk/plugin/CMakeLists.txt create mode 100644 plugin/adsk/plugin/ProxyShape.cpp create mode 100644 plugin/adsk/plugin/ProxyShape.h create mode 100644 plugin/adsk/plugin/base/api.h create mode 100644 plugin/adsk/plugin/plugin.cpp delete mode 100644 plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.cpp delete mode 100644 plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.h create mode 100644 plugin/al/usdtransaction/AL/usd/transaction/tests/testTransaction.pyc create mode 100644 plugin/al/usdtransaction/AL/usd/transaction/tests/testTransactionManager.pyc create mode 100644 test/lib/ufe/CMakeLists.txt create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/Assembly_room_set.usda create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.maya.usd create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.shadingVariants.usda create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.usd create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/Room_set.usd create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/edits.usda create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball1.jpg create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball1.tex create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball4.jpg create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball4.tex create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball8.jpg create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball8.tex create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball9.jpg create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball9.tex create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ballCue.jpg create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ballCue.tex create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/tex/makeTex create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/top_layer.ma create mode 100644 test/lib/ufe/test-samples/ballset/StandaloneScene/top_layer.usda create mode 100644 test/lib/ufe/test-samples/cylinder/cylinder.usda create mode 100644 test/lib/ufe/test-samples/cylinder/usdCylinder.ma create mode 100644 test/lib/ufe/test-samples/twoSpheres/sphere.usda create mode 100644 test/lib/ufe/test-samples/twoSpheres/twoSpheres.ma create mode 100644 test/lib/ufe/testAttribute.py create mode 100644 test/lib/ufe/testAttributes.py create mode 100644 test/lib/ufe/testDeleteCmd.py create mode 100644 test/lib/ufe/testDuplicateCmd.py create mode 100644 test/lib/ufe/testGroupCmd.py create mode 100644 test/lib/ufe/testMatrices.py create mode 100644 test/lib/ufe/testMayaPickwalk.py create mode 100644 test/lib/ufe/testMoveCmd.py create mode 100644 test/lib/ufe/testRotateCmd.py create mode 100644 test/lib/ufe/testRotatePivot.py create mode 100644 test/lib/ufe/testScaleCmd.py create mode 100644 test/lib/ufe/testTRSBase.py create mode 100644 test/lib/ufe/testTransform3dTranslate.py create mode 100644 test/lib/ufe/ufeScripts/__init__.py create mode 100644 test/lib/ufe/ufeScripts/ufeSelectCmd.py create mode 100644 test/lib/ufe/ufeTestPlugins/ufeTestCmdsPlugin.py create mode 100644 test/lib/ufe/ufeTestUtils/__init__.py create mode 100644 test/lib/ufe/ufeTestUtils/mayaUtils.py create mode 100644 test/lib/ufe/ufeTestUtils/ufeUtils.py create mode 100644 test/lib/ufe/ufeTestUtils/usdUtils.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 49770f2037..8e97372ee3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,10 @@ project(maya-usd) # Define common build variables #============================================================================== option(BUILD_MAYAUSD_LIBRARY "Build Core USD libraries." ON) +option(BUILD_ADSK_PLUGIN "Build Autodesk USD plugin." ON) option(BUILD_PXR_PLUGIN "Build the Pixar USD plugin and libraries." ON) option(BUILD_AL_PLUGIN "Build the Animal Logic USD plugin and libraries." ON) +option(BUILD_TESTS "Build tests." ON) option(WANT_USD_RELATIVE_PATH "Use relative rpaths for USD libraries" OFF) option(CMAKE_WANT_UFE_BUILD "Enable building with UFE (if found)." ON) @@ -96,6 +98,17 @@ endif() string(REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +#============================================================================== +# Tests +#============================================================================== +if (BUILD_TESTS) + include(cmake/Googletest.cmake) + fetch_googletest() + + enable_testing() + add_subdirectory(test/lib/ufe) +endif() + #============================================================================== # CORE #============================================================================== @@ -122,6 +135,9 @@ if (BUILD_AL_PLUGIN) endif() endif() +if (BUILD_ADSK_PLUGIN) + add_subdirectory(plugin/adsk) +endif() #============================================================================== # Install mayaUSD.mod diff --git a/build.py b/build.py index 97a36f9667..2143d9937a 100644 --- a/build.py +++ b/build.py @@ -306,6 +306,24 @@ def RunCMake(context, extraArgs=None, stages=None): installArg=installArg, multiproc=FormatMultiProcs(context.numJobs, generator))) +def RunCTest(context, extraArgs=None): + buildDir = context.buildDir + variant = BuildVariant(context) + numJobs = context.numJobs + + with CurrentWorkingDirectory(buildDir): + Run(context, + 'ctest ' + '--output-on-failure ' + '--timeout 300 ' + '-j {numJobs} ' + '-C {variant} ' + '{extraArgs} ' + .format(numJobs=numJobs, + variant=variant, + extraArgs=(" ".join(extraArgs) if extraArgs else ""))) + + def BuildAndInstall(context, buildArgs, stages): with CurrentWorkingDirectory(context.mayaUsdSrcDir): extraArgs = [] @@ -343,6 +361,10 @@ def BuildAndInstall(context, buildArgs, stages): sys.exit(1) Print("""Success MayaUSD build and install !!!!""") +def RunTests(context,extraArgs): + RunCTest(context,extraArgs) + Print("""Success running MayaUSD tests !!!!""") + ############################################################ # ArgumentParser parser = argparse.ArgumentParser( @@ -390,8 +412,11 @@ def BuildAndInstall(context, buildArgs, stages): parser.add_argument("--build-args", type=str, nargs="*", default=[], help=("Comma-separated list of arguments passed into CMake when building libraries")) +parser.add_argument("--ctest-args", type=str, nargs="*", default=[], + help=("Comma-separated list of arguments passed into CTest.(e.g -VV, --output-on-failure)")) + parser.add_argument("--stages", type=str, nargs="*", default=['clean','configure','build','install'], - help=("Comma-separated list of stages to execute.( possible stages: clean, configure, build, install)")) + help=("Comma-separated list of stages to execute.(possible stages: clean, configure, build, install, test)")) parser.add_argument("-j", "--jobs", type=int, default=GetCPUCount(), help=("Number of build jobs to run in parallel. " @@ -466,6 +491,12 @@ def __init__(self, args): for arg in argList.split(","): self.stagesArgs.append(arg) + # CTest arguments + self.ctestArgs = list() + for argList in args.ctest_args: + for arg in argList.split(","): + self.ctestArgs.append(arg) + # Redirect output stream to file self.redirectOutstreamFile = args.redirect_outstream_file try: @@ -497,6 +528,10 @@ def __init__(self, args): summaryMsg += """ Stages arguments {stagesArgs}""" + if context.ctestArgs: + summaryMsg += """ + CTest arguments {ctestArgs}""" + summaryMsg = summaryMsg.format( mayaUsdSrcDir=context.mayaUsdSrcDir, workspaceDir=context.workspaceDir, @@ -505,6 +540,7 @@ def __init__(self, args): logFileLocation=context.logFileLocation, buildArgs=context.buildArgs, stagesArgs=context.stagesArgs, + ctestArgs=context.ctestArgs, buildVariant=BuildVariant(context), cmakeGenerator=("Default" if not context.cmakeGenerator else context.cmakeGenerator) @@ -515,3 +551,8 @@ def __init__(self, args): # BuildAndInstall if any(stage in ['clean', 'configure', 'build', 'install'] for stage in context.stagesArgs): BuildAndInstall(context, context.buildArgs, context.stagesArgs) + + # Run Tests + if 'test' in context.stagesArgs: + RunTests(context, context.ctestArgs) + diff --git a/plugin/al/CMakeLists_googletest_download.txt.in b/cmake/CMakeLists_googletest_download.txt.in similarity index 100% rename from plugin/al/CMakeLists_googletest_download.txt.in rename to cmake/CMakeLists_googletest_download.txt.in diff --git a/plugin/al/CMakeLists_googletest_src.txt.in b/cmake/CMakeLists_googletest_src.txt.in similarity index 100% rename from plugin/al/CMakeLists_googletest_src.txt.in rename to cmake/CMakeLists_googletest_src.txt.in diff --git a/cmake/Googletest.cmake b/cmake/Googletest.cmake new file mode 100644 index 0000000000..ae9f4addb6 --- /dev/null +++ b/cmake/Googletest.cmake @@ -0,0 +1,82 @@ +macro(fetch_googletest) + + if (NOT GTEST_FOUND) + # First see if we can find a gtest that was downloaded and built. + if (NOT GOOGLETEST_BUILD_ROOT) + set(GOOGLETEST_BUILD_ROOT ${CMAKE_CURRENT_BINARY_DIR}) + endif() + if (NOT GTEST_ROOT) + set(GTEST_ROOT "${GOOGLETEST_BUILD_ROOT}/googletest-install") + endif() + find_package(GTest QUIET) + # At this point GTEST_FOUND is set to True in Release but False in Debug. + endif() + + if (NOT GTEST_FOUND) + #====================================================================== + # Download and unpack googletest at configure time. Adapted from + # + # https://github.com/abseil/googletest/blob/master/googletest/README.md + # + # PPT, 22-Nov-2018. + + # Immediately convert CMAKE_MAKE_PROGRAM to forward slashes (if required). + # Attempting to do so in execute_process fails with string invalid escape + # sequence parsing errors. PPT, 22-Nov-2018. + file(TO_CMAKE_PATH ${CMAKE_MAKE_PROGRAM} CMAKE_MAKE_PROGRAM) + if (GOOGLETEST_SRC_DIR) + configure_file(cmake/CMakeLists_googletest_src.txt.in ${GOOGLETEST_BUILD_ROOT}/googletest-config/CMakeLists.txt) + else() + configure_file(cmake/CMakeLists_googletest_download.txt.in ${GOOGLETEST_BUILD_ROOT}/googletest-config/CMakeLists.txt) + endif() + + message(STATUS "========== Installing GoogleTest... ==========") + set(FORCE_SHARED_CRT "") + if(IS_WINDOWS) + set(FORCE_SHARED_CRT -DFORCE_SHARED_CRT=OFF) + endif() + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM} . ${FORCE_SHARED_CRT} + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_BUILD_ROOT}/googletest-config ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + + execute_process(COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} + RESULT_VARIABLE result + WORKING_DIRECTORY ${GOOGLETEST_BUILD_ROOT}/googletest-config ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() + message(STATUS "========== ... GoogleTest installed. ==========") + + set(GTEST_ROOT ${GOOGLETEST_BUILD_ROOT}/googletest-install CACHE path "GoogleTest installation root") + endif() + + # https://gitlab.kitware.com/cmake/cmake/issues/17799 + # FindGtest is buggy when dealing with Debug build. + if (CMAKE_BUILD_TYPE MATCHES Debug AND GTEST_FOUND MATCHES FALSE) + message("Setting GTest libraries with debug...") + + if (GTEST_LIBRARY_DEBUG MATCHES GTEST_LIBRARY_DEBUG-NOTFOUND) + set(gtest_library "") + set(gtest_main_library "") + if(WIN32) + set(gtest_library lib/gtestd.lib) + set(gtest_main_library lib/gtest_maind.lib) + else() + set(gtest_library lib64/libgtestd.a) + set(gtest_main_library lib64/libgtest_maind.a) + endif() + set(GTEST_INCLUDE_DIRS ${GOOGLETEST_BUILD_ROOT}/googletest-install/include) + set(GTEST_LIBRARY_DEBUG ${GOOGLETEST_BUILD_ROOT}/googletest-install/${gtest_library}) + set(GTEST_MAIN_LIBRARY_DEBUG ${GOOGLETEST_BUILD_ROOT}/googletest-install/${gtest_main_library}) + endif() + + set(GTEST_LIBRARY ${GTEST_LIBRARY_DEBUG}) + set(GTEST_LIBRARIES ${GTEST_LIBRARY}) + set(GTEST_MAIN_LIBRARY ${GTEST_MAIN_LIBRARY_DEBUG}) + set(GTEST_MAIN_LIBRARIES ${GTEST_MAIN_LIBRARY}) + endif() + +endmacro() diff --git a/cmake/jinja.cmake b/cmake/jinja.cmake new file mode 100644 index 0000000000..c72af00259 --- /dev/null +++ b/cmake/jinja.cmake @@ -0,0 +1,53 @@ +#- +# ======================================================================= +# Copyright 2018 Autodesk, Inc. All rights reserved. +# +# This computer source code and related instructions and comments are the +# unpublished confidential and proprietary information of Autodesk, Inc. +# and are protected under applicable copyright and trade secret law. They +# may not be disclosed to, copied or used by any third party without the +# prior written consent of Autodesk, Inc. +# ======================================================================= +#+ + +#------------------------------------------------------------------------------ +# +# Gets the Jinja2 and the dependant MarkupSafe python libraries from +# artifactory and set them up. +# +function(init_markupsafe) + + mayaUsd_find_python_module(markupsafe) + + if (NOT MARKUPSAFE_FOUND) + if (NOT MARKUPSAFE_LOCATION) + message(FATAL_ERROR "MARKUPSAFE_LOCATION not set") + endif() + + set(MARKUPSAFE_ROOT "${MARKUPSAFE_LOCATION}/src") + + # Add MarkupSafe to the python path so that Jinja2 can run properly. + mayaUsd_append_path_to_env_var("PYTHONPATH" "${MARKUPSAFE_ROOT}") + endif() + +endfunction() + +function(init_jinja) + + mayaUsd_find_python_module(jinja2) + + if (NOT JINJA2_FOUND) + if (NOT JINJA_LOCATION) + message(FATAL_ERROR "JINJA_LOCATION not set") + endif() + + set(JINJA_ROOT "${JINJA_LOCATION}") + + # Add Jinja2 to the python path so that usdGenSchemas can run properly. + mayaUsd_append_path_to_env_var("PYTHONPATH" "${JINJA_ROOT}") + endif() + +endfunction() + +init_markupsafe() +init_jinja() diff --git a/cmake/modules/FindMaya.cmake b/cmake/modules/FindMaya.cmake index 716201d571..83cab0a6b7 100644 --- a/cmake/modules/FindMaya.cmake +++ b/cmake/modules/FindMaya.cmake @@ -26,7 +26,49 @@ # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) -if(APPLE) +#============================================================================= +# Macro for setting up typical plugin properties. These include: +# - OS-specific plugin suffix (.mll, .so, .bundle) +# - Removal of 'lib' prefix on osx/linux +# - OS-specific defines +# - Post-commnad for correcting Qt library linking on osx +# - Windows link flags for exporting initializePlugin/uninitializePlugin +macro(MAYA_SET_PLUGIN_PROPERTIES target) + set_target_properties(${target} PROPERTIES + SUFFIX ${MAYA_PLUGIN_SUFFIX}) + + set(_maya_DEFINES REQUIRE_IOSTREAM _BOOL) + + if(IS_MACOSX) + set(_maya_DEFINES "${_maya_DEFINES}" MAC_PLUGIN OSMac_ OSMac_MachO) + set_target_properties(${target} PROPERTIES + PREFIX "") + elseif(WIN32) + set(_maya_DEFINES "${_maya_DEFINES}" _AFXDLL _MBCS NT_PLUGIN) + set_target_properties( ${target} PROPERTIES + LINK_FLAGS "/export:initializePlugin /export:uninitializePlugin") + else() + set(_maya_DEFINES "${_maya_DEFINES}" LINUX LINUX_64) + set_target_properties( ${target} PROPERTIES + PREFIX "") + endif() + target_compile_definitions(${target} + PRIVATE + ${_maya_DEFINES} + ) + +endmacro(MAYA_SET_PLUGIN_PROPERTIES) +#============================================================================= + +if(IS_MACOSX) + set(MAYA_PLUGIN_SUFFIX ".bundle") +elseif(IS_WINDOWS) + set(MAYA_PLUGIN_SUFFIX ".mll") +else(IS_LINUX) + set(MAYA_PLUGIN_SUFFIX ".so") +endif() + +if(IS_MACOSX) # Note: according to official Autodesk sources (and how it sets up # MAYA_LOCATION itself), MAYA_LOCATION should include Maya.app/Contents # on MacOS - ie: @@ -62,7 +104,7 @@ if(APPLE) DOC "Maya's libraries path" ) -elseif(UNIX) +elseif(IS_LINUX) find_path(MAYA_BASE_DIR include/maya/MFn.h HINTS @@ -87,7 +129,7 @@ elseif(UNIX) DOC "Maya's libraries path" ) -elseif(WIN32) +elseif(IS_WINDOWS) find_path(MAYA_BASE_DIR include/maya/MFn.h HINTS diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 09a2dcc5b4..81e9af89dc 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -16,6 +16,17 @@ # ======================================================================= # +include(CMakeParseArguments) + +# The name of the operating system for which CMake is to build +if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(IS_WINDOWS TRUE) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(IS_LINUX TRUE) +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(IS_MACOSX TRUE) +endif() + # Appends a path to an environment variable. # Note: if you want to append multiple paths either call this multiple # times, or send in the paths with the proper platform separator. @@ -26,7 +37,7 @@ function(append_path_to_env_var envVar pathToAppend) file(TO_NATIVE_PATH "${pathToAppend}" nativePathToAppend) if(DEFINED ENV{${envVar}}) - if(WIN32) + if(IS_WINDOWS) set(newPath "$ENV{${envVar}};${nativePathToAppend}") else() set(newPath "$ENV{${envVar}}:${nativePathToAppend}") @@ -62,6 +73,76 @@ function(find_python_module module) endif(NOT ${module_found}) endfunction(find_python_module) +# Initialize a variable to accumulate an rpath. The origin is the +# RUNTIME DESTINATION of the target. If not absolute it's appended +# to CMAKE_INSTALL_PREFIX. +function(mayaUsd_init_rpath rpathRef origin) + if(NOT IS_ABSOLUTE ${origin}) + set(origin "${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX}/${origin}") + get_filename_component(origin "${origin}" REALPATH) + endif() + set(${rpathRef} "${origin}" PARENT_SCOPE) +endfunction() + +# Add a relative target path to the rpath. If target is absolute compute +# and add a relative path from the origin to the target. +function(mayaUsd_add_rpath rpathRef target) + if(IS_ABSOLUTE "${target}") + # init_rpath calls get_filename_component([...] REALPATH), which does + # symlink resolution, so we must do the same, otherwise relative path + # determination below will fail. + get_filename_component(target "${target}" REALPATH) + # Make target relative to $ORIGIN (which is the first element in + # rpath when initialized with _pxr_mayaUsd_init_rpath()). + list(GET ${rpathRef} 0 origin) + file(RELATIVE_PATH + target + "${origin}" + "${target}" + ) + if("x${target}" STREQUAL "x") + set(target ".") + endif() + endif() + file(TO_CMAKE_PATH "${target}" target) + set(new_rpath "${${rpathRef}}") + list(APPEND new_rpath "$ORIGIN/${target}") + set(${rpathRef} "${new_rpath}" PARENT_SCOPE) +endfunction() + +function(mayaUsd_install_rpath rpathRef NAME) + # Get and remove the origin. + list(GET ${rpathRef} 0 origin) + set(rpath ${${rpathRef}}) + list(REMOVE_AT rpath 0) + + # Canonicalize and uniquify paths. + set(final "") + foreach(path ${rpath}) + # Replace $ORIGIN with @loader_path + if(IS_MACOSX) + if("${path}/" MATCHES "^[$]ORIGIN/") + # Replace with origin path. + string(REPLACE "$ORIGIN/" "@loader_path/" path "${path}/") + endif() + endif() + + # Strip trailing slashes. + string(REGEX REPLACE "/+$" "" path "${path}") + + # Ignore paths we already have. + if (NOT ";${final};" MATCHES ";${path};") + list(APPEND final "${path}") + endif() + endforeach() + + set_target_properties(${NAME} + PROPERTIES + INSTALL_RPATH_USE_LINK_PATH TRUE + INSTALL_RPATH "${final}" + ) +endfunction() + function(mayaUsd_promoteMayaUsdHeader) set(srcFile ${CMAKE_CURRENT_SOURCE_DIR}/base/mayaUsd.h.src) set(dstFile ${CMAKE_BINARY_DIR}/include/mayaUsd/mayaUsd.h) @@ -90,3 +171,102 @@ function(mayaUsd_promoteHeaderList) endif() endforeach() endfunction() + +function(mayaUsd_get_unittest_target unittest_target unittest_basename) + get_filename_component(unittest_name ${unittest_basename} NAME_WE) + set(${unittest_target} "${unittest_name}" PARENT_SCOPE) +endfunction() + +# +# mayaUsd_copyFiles( +# [DESTINATION ] +# [FILES ]) +# +# DESTINATION - destination where files will be copied into. +# FILES - list of files to copy +# +function(mayaUsd_copyFiles target) + cmake_parse_arguments(PREFIX + "TARGET" + "DESTINATION" + "FILES" + ${ARGN} + ) + + if(PREFIX_DESTINATION) + set(destination ${PREFIX_DESTINATION}) + else() + message(FATAL_ERROR "DESTINATION keyword is not specified.") + endif() + + if(PREFIX_FILES) + set(srcFiles ${PREFIX_FILES}) + else() + message(FATAL_ERROR "FILES keyword is not specified.") + endif() + + foreach(file ${srcFiles}) + get_filename_component(input_file "${file}" ABSOLUTE) + get_filename_component(output_file "${destination}/${file}" ABSOLUTE) + + add_custom_command( + TARGET ${target} + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${input_file} ${output_file} + DEPENDS "${srcFiles}" + COMMENT "copying file from ${input_file} to ${output_file}" + ) + endforeach() +endfunction() + +# +# mayaUsd_copyDirectory( +# [DESTINATION ] +# [DIRECTORY ]) +# +# DESTINATION - destination where directory will be copied into. +# DIRECTORY - directory to be copied. +# +function(mayaUsd_copyDirectory target) + cmake_parse_arguments(PREFIX + "TARGET" + "DESTINATION" + "DIRECTORY" + ${ARGN} + ) + + if(PREFIX_DESTINATION) + set(destination ${PREFIX_DESTINATION}) + endif() + if(NOT PREFIX_DESTINATION) + message(FATAL_ERROR "DESTINATION keyword is not specified.") + endif() + + if(PREFIX_DIRECTORY) + set(directory ${PREFIX_DIRECTORY}) + get_filename_component(directory "${directory}" ABSOLUTE) + get_filename_component(dir_name "${directory}" NAME) + endif() + if(NOT PREFIX_DIRECTORY) + message(FATAL_ERROR "DIRECTORY keyword is not specified.") + endif() + + # figure out files in directories by traversing all the subdirectories + # relative to directory + file(GLOB_RECURSE srcFiles RELATIVE ${directory} ${directory}/*) + + foreach(file ${srcFiles}) + get_filename_component(input_file "${dir_name}/${file}" ABSOLUTE) + get_filename_component(output_file "${destination}/${dir_name}/${file}" ABSOLUTE) + + add_custom_command( + TARGET ${target} + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${input_file} ${output_file} + DEPENDS "${input_file}" + ) + + endforeach() +endfunction() \ No newline at end of file diff --git a/doc/DEVELOPER.md b/doc/DEVELOPER.md index ad39f08056..19b8afd2d4 100644 --- a/doc/DEVELOPER.md +++ b/doc/DEVELOPER.md @@ -3,6 +3,7 @@ | Location | Description | | ------------- | --------------- | | lib | The libraries that all other plugins depend on. Will contain common utilities and features. | +| plugin/adsk | The Autodesk Maya plugin | | plugin/pxr | The Pixar Maya plugin | | plugin/al | The Animal Logic Maya plugin | @@ -13,4 +14,5 @@ | ------------------- | ----------------- | ------------------- | | commit/tag | 1d08054 (>19.07) | 631a2911 (>0.34.0) | + \ No newline at end of file diff --git a/doc/build.md b/doc/build.md index 2af754e588..77b5e9091e 100644 --- a/doc/build.md +++ b/doc/build.md @@ -82,8 +82,11 @@ C:\> python build.py --maya-location "C:\Program Files\Autodesk\maya2019" --pxru Name | Description | Default --- | --- | --- -BUILD_AL_PLUGIN | Builds the Animal Logic USD plugin and libraries. | ON +BUILD_MAYAUSD_LIBRARY | Build Core USD libraries. | ON +BUILD_ADSK_PLUGIN | Build Autodesk USD plugin. | ON BUILD_PXR_PLUGIN | Builds the Pixar USD plugin and libraries. | ON +BUILD_AL_PLUGIN | Builds the Animal Logic USD plugin and libraries. | ON +BUILD_TESTS | Build tests. | ON # Building USD diff --git a/plugin/adsk/CMakeLists.txt b/plugin/adsk/CMakeLists.txt new file mode 100644 index 0000000000..63fa811c9d --- /dev/null +++ b/plugin/adsk/CMakeLists.txt @@ -0,0 +1,9 @@ +message("========== ADSK USD Plugin ==========") + +set(INSTALL_DIR_SUFFIX plugin/adsk) + +#============================================================================== +# Packages +#============================================================================== +add_subdirectory(plugin) + diff --git a/plugin/adsk/plugin/CMakeLists.txt b/plugin/adsk/plugin/CMakeLists.txt new file mode 100644 index 0000000000..e3a79fd92d --- /dev/null +++ b/plugin/adsk/plugin/CMakeLists.txt @@ -0,0 +1,76 @@ +set(PLUGIN_PACKAGE mayaUsdPlugin) + +if(CMAKE_WANT_UFE_BUILD) + find_package(UFE QUIET) + if(UFE_FOUND) + message(STATUS "Building with UFE ${UFE_VERSION} features enabled.") + include_directories(${UFE_INCLUDE_DIR}) + add_definitions(-DWANT_UFE_BUILD) + else() + message(STATUS "UFE not found. UFE features will be disabled.") + endif() +endif() + +add_library(${PLUGIN_PACKAGE} MODULE + plugin.cpp + ProxyShape.cpp +) + +target_compile_definitions(${PLUGIN_PACKAGE} + PRIVATE + MAYAUSD_PLUGIN_EXPORT +) + +target_include_directories(${PLUGIN_PACKAGE} + PRIVATE + ${MAYAUSD_INCLUDE_DIR} + ${PXR_INCLUDE_DIRS} +) + +target_link_libraries(${PLUGIN_PACKAGE} + PRIVATE + ${MAYA_LIBRARIES} + mayaUsd + ar + gf + kind + plug + sdf + tf + usd + usdGeom + usdUtils +) + +MAYA_SET_PLUGIN_PROPERTIES(${PLUGIN_PACKAGE}) + +# rpath setup +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "plugin") + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_install_rpath(rpath ${PLUGIN_PACKAGE}) +endif() + +if(IS_LINUX) + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib64") + endif() +endif() + +install(TARGETS + ${PLUGIN_PACKAGE} + LIBRARY + DESTINATION ${INSTALL_DIR_SUFFIX}/plugin + RUNTIME + DESTINATION ${INSTALL_DIR_SUFFIX}/plugin +) + +if(IS_WINDOWS) + install(FILES $ DESTINATION ${INSTALL_DIR_SUFFIX}/plugin OPTIONAL) +endif() + diff --git a/plugin/adsk/plugin/ProxyShape.cpp b/plugin/adsk/plugin/ProxyShape.cpp new file mode 100644 index 0000000000..bb1ec6489d --- /dev/null +++ b/plugin/adsk/plugin/ProxyShape.cpp @@ -0,0 +1,64 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "ProxyShape.h" + +#include + +MAYAUSD_NS_DEF { + +// ======================================================== + +const MTypeId MAYAUSD_PROXYSHAPE_ID (0x58000095); + +const MTypeId ProxyShape::typeId(MayaUsd::MAYAUSD_PROXYSHAPE_ID); +const MString ProxyShape::typeName("mayaUsdProxyShape"); + +/* static */ +void* +ProxyShape::creator() +{ + return new ProxyShape(); +} + +/* static */ +MStatus +ProxyShape::initialize() +{ + MStatus retValue = inheritAttributesFrom(MayaUsdProxyShapeBase::typeName); + CHECK_MSTATUS_AND_RETURN_IT(retValue); + + return retValue; +} + +ProxyShape::ProxyShape() : MayaUsdProxyShapeBase() +{ + TfRegistryManager::GetInstance().SubscribeTo(); +} + +/* virtual */ +ProxyShape::~ProxyShape() +{ + // + // empty + // +} + +void ProxyShape::postConstructor() +{ + ParentClass::postConstructor(); +} + +} // MayaUsd diff --git a/plugin/adsk/plugin/ProxyShape.h b/plugin/adsk/plugin/ProxyShape.h new file mode 100644 index 0000000000..e4d58925dc --- /dev/null +++ b/plugin/adsk/plugin/ProxyShape.h @@ -0,0 +1,54 @@ +// +// Copyright 2016 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#pragma once + +#include "base/api.h" + +#include "mayaUsd/nodes/proxyShapeBase.h" + +#include "pxr/pxr.h" + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_NS_DEF { + +class ProxyShape : public MayaUsdProxyShapeBase +{ + public: + typedef MayaUsdProxyShapeBase ParentClass; + + MAYAUSD_PLUGIN_PUBLIC + static const MTypeId typeId; + MAYAUSD_PLUGIN_PUBLIC + static const MString typeName; + + MAYAUSD_PLUGIN_PUBLIC + static void* creator(); + + MAYAUSD_PLUGIN_PUBLIC + static MStatus initialize(); + + void postConstructor() override; + + private: + ProxyShape(); + + ProxyShape(const ProxyShape&); + ~ProxyShape() override; + ProxyShape& operator=(const ProxyShape&); +}; + +} // MayaUsd diff --git a/plugin/adsk/plugin/base/api.h b/plugin/adsk/plugin/base/api.h new file mode 100644 index 0000000000..dc445c2cc1 --- /dev/null +++ b/plugin/adsk/plugin/base/api.h @@ -0,0 +1,48 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if defined _WIN32 || defined __CYGWIN__ + + // We need a different export symbol for the plugin because when we are building + // the actual Maya plugin we must 'export' the two functions for plugin load/unload. + // And then we won't set the core export because we need to 'import' the symbols. + #ifdef MAYAUSD_PLUGIN_EXPORT + #ifdef __GNUC__ + #define MAYAUSD_PLUGIN_PUBLIC __attribute__ ((dllexport)) + #else + #define MAYAUSD_PLUGIN_PUBLIC __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define MAYAUSD_PLUGIN_PUBLIC __attribute__ ((dllimport)) + #else + #define MAYAUSD_PLUGIN_PUBLIC __declspec(dllimport) + #endif + #endif + #define MAYAUSD_PLUGIN_LOCAL +#else + #if __GNUC__ >= 4 + #define MAYAUSD_PLUGIN_PUBLIC __attribute__ ((visibility ("default"))) + #define MAYAUSD_PLUGIN_LOCAL __attribute__ ((visibility ("hidden"))) +#else + #define MAYAUSD_PLUGIN_PUBLIC + #define MAYAUSD_PLUGIN_LOCAL +#endif +#endif + +// Convenience symbol versioning include: because api.h is widely +// included, this reduces the need to explicitly include mayaUsd.h. +#include "mayaUsd/mayaUsd.h" diff --git a/plugin/adsk/plugin/plugin.cpp b/plugin/adsk/plugin/plugin.cpp new file mode 100644 index 0000000000..675bfb78fa --- /dev/null +++ b/plugin/adsk/plugin/plugin.cpp @@ -0,0 +1,87 @@ +// +// Copyright 2019 Autodesk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "base/api.h" +#include "ProxyShape.h" + +#include +#include +#include +#include + +#if defined(WANT_UFE_BUILD) +#include +#endif + +#include +#include +#include + +#include "pxr/base/tf/envSetting.h" +#include "pxr/base/plug/plugin.h" +#include "pxr/base/plug/registry.h" + +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +MAYAUSD_PLUGIN_PUBLIC +MStatus initializePlugin(MObject obj) +{ + MStatus status; + MFnPlugin plugin(obj, "Autodesk", "1.0", "Any"); + + status = MayaUsdProxyShapePlugin::initialize(plugin); + CHECK_MSTATUS(status); + +#if defined(WANT_UFE_BUILD) + status = MayaUsd::ufe::initialize(); + if (!status) { + status.perror("mayaUsdPlugin: unable to initialize ufe."); + } +#endif + + status = plugin.registerShape( + MayaUsd::ProxyShape::typeName, + MayaUsd::ProxyShape::typeId, + MayaUsd::ProxyShape::creator, + MayaUsd::ProxyShape::initialize, + nullptr, + MayaUsdProxyShapePlugin::getProxyShapeClassification()); + CHECK_MSTATUS(status); + + return status; +} + +MAYAUSD_PLUGIN_PUBLIC +MStatus uninitializePlugin(MObject obj) +{ + MFnPlugin plugin(obj); + MStatus status; + + status = plugin.deregisterNode(MayaUsd::ProxyShape::typeId); + CHECK_MSTATUS(status); + + status = MayaUsdProxyShapePlugin::finalize(plugin); + CHECK_MSTATUS(status); + +#if defined(WANT_UFE_BUILD) + status = MayaUsd::ufe::finalize(); + CHECK_MSTATUS(status); +#endif + + return status; +} diff --git a/plugin/al/CMakeLists.txt b/plugin/al/CMakeLists.txt index bee1c428fb..789d048e2f 100644 --- a/plugin/al/CMakeLists.txt +++ b/plugin/al/CMakeLists.txt @@ -50,90 +50,6 @@ find_package(Boost COMPONENTS REQUIRED ) -if(NOT SKIP_USDMAYA_TESTS) - if (NOT GTEST_FOUND) - # First see if we can find a gtest that was downloaded and built. - if (NOT GOOGLETEST_BUILD_ROOT) - set(GOOGLETEST_BUILD_ROOT ${CMAKE_CURRENT_BINARY_DIR}) - endif() - if (NOT GTEST_ROOT) - set(GTEST_ROOT "${GOOGLETEST_BUILD_ROOT}/googletest-install") - endif() - find_package(GTest QUIET) - # At this point GTEST_FOUND is set to True in Release but False in Debug. - endif() - - enable_testing() - if (NOT GTEST_FOUND) - #====================================================================== - # Download and unpack googletest at configure time. Adapted from - # - # https://github.com/abseil/googletest/blob/master/googletest/README.md - # - # PPT, 22-Nov-2018. - - # Immediately convert CMAKE_MAKE_PROGRAM to forward slashes (if required). - # Attempting to do so in execute_process fails with string invalid escape - # sequence parsing errors. PPT, 22-Nov-2018. - file(TO_CMAKE_PATH ${CMAKE_MAKE_PROGRAM} CMAKE_MAKE_PROGRAM) - - if (GOOGLETEST_SRC_DIR) - configure_file(CMakeLists_googletest_src.txt.in ${GOOGLETEST_BUILD_ROOT}/googletest-config/CMakeLists.txt) - else() - configure_file(CMakeLists_googletest_download.txt.in ${GOOGLETEST_BUILD_ROOT}/googletest-config/CMakeLists.txt) - endif() - - message(STATUS "========== Installing GoogleTest... ==========") - set(FORCE_SHARED_CRT "") - if(MSVC) - set(FORCE_SHARED_CRT -DFORCE_SHARED_CRT=OFF) - endif() - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM} . ${FORCE_SHARED_CRT} - RESULT_VARIABLE result - WORKING_DIRECTORY ${GOOGLETEST_BUILD_ROOT}/googletest-config ) - if(result) - message(FATAL_ERROR "CMake step for googletest failed: ${result}") - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} - RESULT_VARIABLE result - WORKING_DIRECTORY ${GOOGLETEST_BUILD_ROOT}/googletest-config ) - if(result) - message(FATAL_ERROR "Build step for googletest failed: ${result}") - endif() - message(STATUS "========== ... GoogleTest installed. ==========") - - set(GTEST_ROOT ${GOOGLETEST_BUILD_ROOT}/googletest-install CACHE path "GoogleTest installation root") - #====================================================================== - endif() - - # https://gitlab.kitware.com/cmake/cmake/issues/17799 - # FindGtest is buggy when dealing with Debug build. - if (CMAKE_BUILD_TYPE MATCHES Debug AND GTEST_FOUND MATCHES FALSE) - message("Setting GTest libraries with debug...") - - if (GTEST_LIBRARY_DEBUG MATCHES GTEST_LIBRARY_DEBUG-NOTFOUND) - set(gtest_library "") - set(gtest_main_library "") - if(WIN32) - set(gtest_library lib/gtestd.lib) - set(gtest_main_library lib/gtest_maind.lib) - else() - set(gtest_library lib64/libgtestd.a) - set(gtest_main_library lib64/libgtest_maind.a) - endif() - set(GTEST_INCLUDE_DIRS ${GOOGLETEST_BUILD_ROOT}/googletest-install/include) - set(GTEST_LIBRARY_DEBUG ${GOOGLETEST_BUILD_ROOT}/googletest-install/${gtest_library}) - set(GTEST_MAIN_LIBRARY_DEBUG ${GOOGLETEST_BUILD_ROOT}/googletest-install/${gtest_main_library}) - endif() - - set(GTEST_LIBRARY ${GTEST_LIBRARY_DEBUG}) - set(GTEST_LIBRARIES ${GTEST_LIBRARY}) - set(GTEST_MAIN_LIBRARY ${GTEST_MAIN_LIBRARY_DEBUG}) - set(GTEST_MAIN_LIBRARIES ${GTEST_MAIN_LIBRARY}) - endif() -endif() - set(AL_USDMAYA_LOCATION_NAME "AL_USDMAYA_LOCATION" CACHE diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.cpp b/plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.cpp deleted file mode 100644 index b4149f9cf2..0000000000 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// -// Copyright 2017 Animal Logic -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#include "AL/usdmaya/StageData.h" -#include "AL/usdmaya/TypeIDs.h" -#include "AL/usdmaya/DebugCodes.h" -#include "AL/maya/event/MayaEventManager.h" - -namespace AL { -namespace usdmaya { - -const MTypeId StageData::kTypeId(AL_USDMAYA_STAGEDATA); -const MString StageData::kName("AL_usdmaya_StageData"); - - -//---------------------------------------------------------------------------------------------------------------------- -static void _cleanUp(void* gdPtr) -{ - StageData *gd = (StageData*)gdPtr; - gd->stage = UsdStageRefPtr(); -} - -//---------------------------------------------------------------------------------------------------------------------- -void* StageData::creator() -{ - return new StageData; -} - -//---------------------------------------------------------------------------------------------------------------------- -void StageData::copy(const MPxData& data) -{ - const StageData* stageData = dynamic_cast(&data); - if(stageData) - { - stage = stageData->stage; - primPath = stageData->primPath; - } -} - -//---------------------------------------------------------------------------------------------------------------------- -StageData::StageData() -{ - m_exitCallbackId = AL::maya::event::MayaEventManager::instance().registerCallback(_cleanUp, "MayaExiting", "DestroyStageDataOnExit", 0x10000, this); - TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("StageData::StageData() created: %p\n", this); -} - -//---------------------------------------------------------------------------------------------------------------------- -StageData::~StageData() -{ - AL::maya::event::MayaEventManager::instance().unregisterCallback(m_exitCallbackId); - TF_DEBUG(ALUSDMAYA_EVALUATION).Msg("StageData::StageData() deleted: %p\n", this); -} - - -//---------------------------------------------------------------------------------------------------------------------- -MTypeId StageData::typeId() const -{ - return kTypeId; -} - -//---------------------------------------------------------------------------------------------------------------------- -MString StageData::name() const -{ - return kName; -} -} -//---------------------------------------------------------------------------------------------------------------------- -} // al -//---------------------------------------------------------------------------------------------------------------------- diff --git a/plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.h b/plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.h deleted file mode 100644 index 337c0a421f..0000000000 --- a/plugin/al/lib/AL_USDMaya/AL/usdmaya/StageData.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// Copyright 2017 Animal Logic -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#pragma once - -#include "AL/usdmaya/Api.h" - -#include "AL/event/EventHandler.h" - -#include "maya/MPxGeometryData.h" - -#include "pxr/usd/usd/stage.h" - -PXR_NAMESPACE_USING_DIRECTIVE - -namespace AL { -namespace usdmaya { - -//---------------------------------------------------------------------------------------------------------------------- -/// \brief This code is effectively copied from the pixar plugin. It's just used to pass the usd stage through the DG -/// \ingroup usdmaya -//---------------------------------------------------------------------------------------------------------------------- -class StageData - : public MPxGeometryData -{ -public: - - /// \brief ctor - StageData(); - - /// \brief dtor - ~StageData(); - - /// \brief creates an instance of this data object - AL_USDMAYA_PUBLIC - static void* creator(); - - /// \brief copy the input stage data into this node - /// \param aDatum the data to copy - void copy(const MPxData& aDatum) override; - - /// the type id of the stage data - AL_USDMAYA_PUBLIC - static const MTypeId kTypeId; - - /// the type name of the stage data - AL_USDMAYA_PUBLIC - static const MString kName; - - /// the stage passed through the DG - UsdStageWeakPtr stage; - - /// the prim path root - SdfPath primPath; - -private: - MTypeId typeId() const override; - MString name() const override; - AL::event::CallbackId m_exitCallbackId; -}; - -//---------------------------------------------------------------------------------------------------------------------- -} // usdmaya -} // AL -//---------------------------------------------------------------------------------------------------------------------- diff --git a/plugin/al/plugin/AL_USDMayaTestPlugin/AL/usdmaya/nodes/test_ProxyUsdGeomCamera.cpp b/plugin/al/plugin/AL_USDMayaTestPlugin/AL/usdmaya/nodes/test_ProxyUsdGeomCamera.cpp index 82a9387e52..2644d3dc67 100644 --- a/plugin/al/plugin/AL_USDMayaTestPlugin/AL/usdmaya/nodes/test_ProxyUsdGeomCamera.cpp +++ b/plugin/al/plugin/AL_USDMayaTestPlugin/AL/usdmaya/nodes/test_ProxyUsdGeomCamera.cpp @@ -37,7 +37,7 @@ #include "pxr/usd/usdGeom/xform.h" #include "AL/usdmaya/TypeIDs.h" -#include +#include #include using AL::usdmaya::nodes::ProxyShape; diff --git a/plugin/al/usdtransaction/AL/usd/transaction/tests/testTransaction.pyc b/plugin/al/usdtransaction/AL/usd/transaction/tests/testTransaction.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c25f4b14819859c36fd97dcd72eb93f8635a6b6f GIT binary patch literal 7286 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 b{z1kZ(&Oy#pU{WIP>wf6OYAu9?vD5gh{Vz4 literal 0 HcmV?d00001 diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt new file mode 100644 index 0000000000..dfab9e3880 --- /dev/null +++ b/test/lib/ufe/CMakeLists.txt @@ -0,0 +1,115 @@ +# +# Copyright 2019 Autodesk +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set(TARGET_NAME UFE_TEST) + +# unit test scripts. Note that testTRSBase.py is not a test case, but rather +# a module providing a base class for other tests. +set(test_script_files + testDeleteCmd.py + testMatrices.py + testMayaPickwalk.py + testRotatePivot.py +) + +set(test_support_files + testTRSBase.py +) + +if(CMAKE_UFE_V2_FEATURES_AVAILABLE) + list(APPEND test_script_files + testGroupCmd.py + testAttribute.py + testAttributes.py + # The following files test UFE_V1 interfaces, and therefore should not + # depend on UFE_V2. However, the test code relies on capability to + # retrieve a USD prim from a UFE scene item, which in turn depends on + # functionality that is only available in UFE_V2. Therefore, run the + # tests only if UFE_V2 is available. PPT, 11-Oct-2019. + # + # MAYA-101385: until Maya switches back to UFE_V2, fail, so commented + # out. PPT, 11-Oct-2019. + # + # testDuplicateCmd.py + # testMoveCmd.py + # testRotateCmd.py + # testScaleCmd.py + # testTransform3dTranslate.py + ) +endif() + +# copy ufe tests to ${CMAKE_CURRENT_BINARY_DIR} and run them from there +add_custom_target(${TARGET_NAME} ALL) + +mayaUsd_copyDirectory(${TARGET_NAME} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + DIRECTORY test-samples) +mayaUsd_copyDirectory(${TARGET_NAME} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + DIRECTORY ufeScripts) +mayaUsd_copyDirectory(${TARGET_NAME} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + DIRECTORY ufeTestUtils) +mayaUsd_copyDirectory(${TARGET_NAME} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + DIRECTORY ufeTestPlugins) +mayaUsd_copyFiles(${TARGET_NAME} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + FILES ${test_script_files}) +mayaUsd_copyFiles(${TARGET_NAME} DESTINATION ${CMAKE_CURRENT_BINARY_DIR} + FILES ${test_support_files}) + +# unit tests +set(pythonPath + "${CMAKE_INSTALL_PREFIX}/lib/python" + "$ENV{PYTHONPATH}" +) + +set(mayaPluginPath + "${CMAKE_INSTALL_PREFIX}/plugin/adsk/plugin" + "${CMAKE_CURRENT_BINARY_DIR}/ufeTestPlugins" +) + +set(path + "${CMAKE_INSTALL_PREFIX}/lib" + "${MAYA_LOCATION}/bin" + "$ENV{PATH}" +) + +if(IS_WINDOWS) + string(REPLACE ";" "\;" pythonPath "${pythonPath}") + string(REPLACE ";" "\;" path "${path}") + string(REPLACE ";" "\;" mayaPluginPath "${mayaPluginPath}") +else() + separate_arguments(pythonPath NATIVE_COMMAND "${pythonPath}") + separate_arguments(path NATIVE_COMMAND "${path}") + separate_arguments(mayaPluginPath NATIVE_COMMAND "${mayaPluginPath}") + + string(REPLACE "\;" ":" pythonPath "${pythonPath}") + string(REPLACE "\;" ":" path "${path}") + string(REPLACE "\;" ":" mayaPluginPath "${mayaPluginPath}") +endif() + +foreach(script ${test_script_files}) + mayaUsd_get_unittest_target(target ${script}) + add_test( + NAME ${target} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${MAYA_PY_EXECUTABLE} -c "from unittest import main;import maya.standalone;maya.standalone.initialize(name='python');import ${target};main(module=${target});maya.standalone.uninitialize()" + ) + set_property(TEST ${target} APPEND PROPERTY ENVIRONMENT + "PYTHONPATH=${pythonPath}" + "PATH=${path}" + "MAYA_PLUG_IN_PATH=${mayaPluginPath}" + "PXR_PLUGINPATH_NAME=${CMAKE_INSTALL_PREFIX}/lib/usd" + "MAYA_NO_STANDALONE_ATEXIT=1" + ) +endforeach() diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/Assembly_room_set.usda b/test/lib/ufe/test-samples/ballset/StandaloneScene/Assembly_room_set.usda new file mode 100644 index 0000000000..a59cc55178 --- /dev/null +++ b/test/lib/ufe/test-samples/ballset/StandaloneScene/Assembly_room_set.usda @@ -0,0 +1,409 @@ +#usda 1.0 +( + defaultPrim = "Room_set" + subLayers = [ + @edits.usda@ + ] + upAxis = "Y" +) + +def Xform "Room_set" ( + assetInfo = { + asset identifier = @Room_set.usd@ + string name = "Room_set" + } + kind = "assembly" +) +{ + def Xform "Props" ( + kind = "group" + ) + { + def "Ball_1" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_2" + } + ) + { + double3 xformOp:translate = (-5, 2.8575, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_2" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_3" + } + ) + { + double3 xformOp:translate = (-0.0506648173719331, 2.8575, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_3" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_4" + } + ) + { + double3 xformOp:translate = (-0.0506648173719331, 2.8575, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_4" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_5" + } + ) + { + double3 xformOp:translate = (4.89867036525613, 2.8575, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_5" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_6" + } + ) + { + double3 xformOp:translate = (4.89867036525613, 2.8575, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_6" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_7" + } + ) + { + double3 xformOp:translate = (4.89867036525613, 2.8575, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_7" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_8" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, -8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_8" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_9" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_9" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_10" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_10" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_11" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, 8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_11" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_12" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, -11.43) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_12" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_13" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_13" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_14" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_14" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_1" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_15" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_2" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, 11.43) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_16" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_3" + } + ) + { + double3 xformOp:translate = (-2.1425, 7.14375, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_17" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_4" + } + ) + { + double3 xformOp:translate = (2.80683518262807, 7.14375, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_18" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_5" + } + ) + { + double3 xformOp:translate = (2.80683518262807, 7.14375, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_19" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_6" + } + ) + { + double3 xformOp:translate = (7.75617036525613, 7.14375, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_20" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_7" + } + ) + { + double3 xformOp:translate = (7.75617036525613, 7.14375, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_21" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_8" + } + ) + { + double3 xformOp:translate = (7.75617036525613, 7.14375, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_22" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_9" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, -8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_23" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_10" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_24" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_11" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_25" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_12" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, 8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_26" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_13" + } + ) + { + double3 xformOp:translate = (0.715, 11.43, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_27" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_14" + } + ) + { + double3 xformOp:translate = (5.66433518262807, 11.43, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_28" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_1" + } + ) + { + double3 xformOp:translate = (5.66433518262807, 11.43, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_29" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_2" + } + ) + { + double3 xformOp:translate = (10.6136703652561, 11.43, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_30" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_3" + } + ) + { + double3 xformOp:translate = (10.6136703652561, 11.43, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_31" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_4" + } + ) + { + double3 xformOp:translate = (10.6136703652561, 11.43, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_32" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_5" + } + ) + { + double3 xformOp:translate = (3.5725, 15.71625, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_33" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_6" + } + ) + { + double3 xformOp:translate = (8.52183518262807, 15.71625, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_34" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_7" + } + ) + { + double3 xformOp:translate = (8.52183518262807, 15.71625, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_35" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_8" + } + ) + { + double3 xformOp:translate = (6.43, 20.0025, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + } +} + + diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.maya.usd b/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.maya.usd new file mode 100644 index 0000000000000000000000000000000000000000..09497ee1ff270c6965d898fe3735e3a586bbd55c GIT binary patch literal 31653 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 AL;wH) literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.shadingVariants.usda b/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.shadingVariants.usda new file mode 100644 index 0000000000..d9ba3ed9a1 --- /dev/null +++ b/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.shadingVariants.usda @@ -0,0 +1,317 @@ +#usda 1.0 +( + defaultPrim = "Ball" +) + +over "Ball" ( + variants = { + string shadingVariant = "Cue" + } + add variantSets = "shadingVariant" +) +{ + over "mesh" + { + } + + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + } + } + } + variantSet "shadingVariant" = { + "Ball_1" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball1.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(1, 0.850422, 0.0241323)] + } + + } + "Ball_10" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball10.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.0170208, 0.0444973, 0.363132)] + } + + } + "Ball_11" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball11.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.947959, 0.0329563, 0.0134364)] + } + + } + "Ball_12" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball12.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.0473661, 0.0167832, 0.133209)] + } + + } + "Ball_13" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball13.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.956527, 0.215727, 0.0241323)] + } + + } + "Ball_14" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball14.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.0013732, 0.0494753, 0.0429018)] + } + + } + "Ball_15" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball15.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.333438, 0.0019764, 0.00550657)] + } + + } + "Ball_2" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball2.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.0170208, 0.0444973, 0.363132)] + } + + } + "Ball_3" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball3.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.947959, 0.0329563, 0.0134364)] + } + + } + "Ball_4" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball4.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.0473661, 0.0167832, 0.133209)] + } + + } + "Ball_5" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball5.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.956527, 0.215727, 0.0241323)] + } + + } + "Ball_6" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball6.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.0013732, 0.0494753, 0.0429018)] + } + + } + "Ball_7" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball7.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.333438, 0.0019764, 0.00550657)] + } + + } + "Ball_8" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball8.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.00977218, 0.00908114, 0.00778151)] + } + + } + "Ball_9" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ball9.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(1, 0.850422, 0.0241323)] + } + + } + "Cue" { + over "Looks" + { + over "BallLook" + { + over "BallTexture" + { + string filename = "./tex/ballCue.tex" + } + } + } + + over "mesh" + { + color3f[] primvars:displayColor = [(0.991221, 0.982484, 0.743576)] + } + + } + } +} + diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.usd b/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.usd new file mode 100644 index 0000000000..b52b0f26c0 --- /dev/null +++ b/test/lib/ufe/test-samples/ballset/StandaloneScene/Ball.usd @@ -0,0 +1,21 @@ +#usda 1.0 +( + defaultPrim = "Ball" + upAxis = "Y" +) + +def Xform "Ball" ( + assetInfo = { + asset identifier = @Ball/Ball.usd@ + string name = "Ball" + } + kind = "component" + add references = [ + @./Ball.shadingVariants.usda@, + @./Ball.maya.usd@ + ] +) +{ +} + + diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/Room_set.usd b/test/lib/ufe/test-samples/ballset/StandaloneScene/Room_set.usd new file mode 100644 index 0000000000000000000000000000000000000000..3942423402e2ddaa3690e7d3c8281e45c82e2b60 GIT binary patch literal 8058 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 literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/edits.usda b/test/lib/ufe/test-samples/ballset/StandaloneScene/edits.usda new file mode 100644 index 0000000000..f8bc9ff130 --- /dev/null +++ b/test/lib/ufe/test-samples/ballset/StandaloneScene/edits.usda @@ -0,0 +1,405 @@ +#usda 1.0 +( + defaultPrim = "Room_set" + upAxis = "Y" +) + +def Xform "Room_set" ( + assetInfo = { + asset identifier = @Room_set.usd@ + string name = "Room_set" + } + kind = "assembly" +) +{ + def Xform "Props" ( + kind = "group" + ) + { + def "Ball_1" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_2" + } + ) + { + double3 xformOp:translate = (-5, 2.8575, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_2" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_3" + } + ) + { + double3 xformOp:translate = (-0.0506648173719331, 2.8575, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_3" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_4" + } + ) + { + double3 xformOp:translate = (-0.0506648173719331, 2.8575, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_4" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_5" + } + ) + { + double3 xformOp:translate = (4.89867036525613, 2.8575, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_5" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_6" + } + ) + { + double3 xformOp:translate = (4.89867036525613, 2.8575, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_6" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_7" + } + ) + { + double3 xformOp:translate = (4.89867036525613, 2.8575, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_7" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_8" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, -8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_8" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_9" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_9" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_10" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_10" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_11" + } + ) + { + double3 xformOp:translate = (9.8480055478842, 2.8575, 8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_11" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_12" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, -11.43) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_12" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_13" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_13" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_14" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_14" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_1" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_15" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_2" + } + ) + { + double3 xformOp:translate = (14.7973407305123, 2.8575, 11.43) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_16" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_3" + } + ) + { + double3 xformOp:translate = (-2.1425, 7.14375, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_17" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_4" + } + ) + { + double3 xformOp:translate = (2.80683518262807, 7.14375, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_18" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_5" + } + ) + { + double3 xformOp:translate = (2.80683518262807, 7.14375, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_19" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_6" + } + ) + { + double3 xformOp:translate = (7.75617036525613, 7.14375, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_20" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_7" + } + ) + { + double3 xformOp:translate = (7.75617036525613, 7.14375, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_21" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_8" + } + ) + { + double3 xformOp:translate = (7.75617036525613, 7.14375, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_22" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_9" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, -8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_23" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_10" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_24" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_11" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_25" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_12" + } + ) + { + double3 xformOp:translate = (12.7055055478842, 7.14375, 8.5725) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_26" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_13" + } + ) + { + double3 xformOp:translate = (0.715, 11.43, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_27" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_14" + } + ) + { + double3 xformOp:translate = (27.155909710631434, 28.129659947096272, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_28" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_1" + } + ) + { + double3 xformOp:translate = (5.66433518262807, 11.43, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_29" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_2" + } + ) + { + double3 xformOp:translate = (10.6136703652561, 11.43, -5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_30" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_3" + } + ) + { + double3 xformOp:translate = (10.6136703652561, 11.43, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_31" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_4" + } + ) + { + double3 xformOp:translate = (10.6136703652561, 11.43, 5.715) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_32" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_5" + } + ) + { + double3 xformOp:translate = (3.5725, 15.71625, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_33" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_6" + } + ) + { + double3 xformOp:translate = (8.52183518262807, 15.71625, -2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_34" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_7" + } + ) + { + double3 xformOp:translate = (8.52183518262807, 15.71625, 2.8575) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def "Ball_35" ( + add references = @./Ball.usd@ + variants = { + string shadingVariant = "Ball_8" + } + ) + { + double3 xformOp:translate = (6.43, 20.0025, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + } +} + diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball1.jpg b/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f9579793a999cfc740d916e4c0bd90e6b0735928 GIT binary patch literal 5101 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 JvO<^L{x>4ANu>Y) literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball1.tex b/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball1.tex new file mode 100644 index 0000000000000000000000000000000000000000..309c7fa328766ace225951863b8d40534ed8e015 GIT binary patch literal 202932 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
*_M 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%0A9*{&g_qm1n|4w^}rRjOk1 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 KI5Mx-9UTFP5?QGL literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball4.jpg b/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8fc7c6eddccfd5ad55ef84215eab24e493ae7eb2 GIT binary patch literal 5872 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 literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball4.tex b/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball4.tex new file mode 100644 index 0000000000000000000000000000000000000000..cb16fbed57d1de52d5711c6d7d4910fb573a2fa2 GIT binary patch literal 223576 zcmeFaXIoRz*X}!4I-!LsH3g6+B2{TZC`u6!J0fZ*3MwK3DpFVIRf+{vgixf32#8XJ z1dv`86%jEg(mRMW@$g*d&DrnHi@o==|NHVAWM$3FagY0Vj}dcoMezD40>A=Hf|iUh zH#XGB!KT6zFUlKf;xIpc@8oVm^no8oDr7EdlcJlm`L&#*&E-N`pYj)@3Qo%<9~$uz zy%%^%Gp=)t?{?p;vHtyTSH5V8`WTsvBg4lZ9hZ7&6>@BnZy#p36(^gLX^ub-gc2l(?m_p$&~5FZHYh z#CJB2sA<%s9ouEyKB}%MG32Ftyqme;$u0547zxAwKK{RN|KH*H_tX7%^ZdIw|COzO zRrjB0_$S%^H!99ks|Ya&Fu%>s<8T;=ZnQ0!M=T^jq)BF?YvWBTxp31Fl?jP+>hdWE zhl^KZOLr-!bUAowqdv;*OX|wAxinG~B$M1T?tOasb%@2&w)`@@m64Za;t9nMqE9a- zp2;{V`(Xop?&-PI(--$D?$7xk|N2(!r4ue|SmEliG_nf&Vi@bvVpP1GFvYVNSd zhe4v85J#O;$-IADx12(gquOVmm5V=9TO5|wxpJcCvO5l672Pd+>WInvbH5fgSJGXI zK9Ak79UStyUiociMa#+*d%o%W{I5qm2g~ojXF#z{=ZS8|A%|p|2m9+9mYQd^S_25c_5$00jdY# zzp+CN*!l}t?wS@Ah~b;%gYZ6Wx5U4wTEn^IdE`3hwIKUB+ODrFrlEjF>(?TRVS|Ex zmXZksPHCqW=OP&7%~blvg1Pa7up`02Tqo4#mcrGH9Y={C^qz3^LC(vnxZ zFxA6-zt=5@T zjqRYVHso$<4eNEc$Ntf{rZ!Y-aMWu(M14<;Pd!V?-UpB(zC6b2n3stYE}h*yNikBNF%Dc+O@epb&$}`OH`@1XKx>PsuF{51*=nm4By* z|M?qGd3M%Ed~*laRy`=X!1?OWU!R6^FUfjor#O+dqeWDutHuh!!LHr}`}_78KCE+d zr#E`AXHGCj84sVB0qoNp_Dx%R*=}?A>%y(aXWtz5D*L>ALn2bP;YvgM_x0-9(XQdQ zPtUqby6p?9`}*FoIOTDR_|5QbdOl;#zT&eRMkvk54ud~9twNDHTDbT?|(dvh-RI4O1uB~i>;dJoqz)L*?T zk8l*-u}uwx@sTOVhD<`HHL(Kb(t;YUPsuds${!Eg@iy-9OTqG)QpygQ3+m)Rl4zKu!h|nP4W!)Zgs;a-vNb~fQ+==> z-SYseXhWYMty6`M7i{&rk=O*`a%t$l^DzGl4*dJ}|Cy@)nWm~-5R<8C@urF6CjINS z-nnWI`P8wlooe?#1i>{!vp%+Wq?Rxcbs>@KrvA^+x(l>VH4iS5Qft3uipB{_tsQyC zT3(McL!R;s9{E17!N&Y3jh%WYpb>zU4-!dHaD9sko~uQj#1_Ja%ExQ`}OnRXuQqE{mR{l`IZ_c zv>BwmdulXfUq?(GsP1+W>|q_=Kmx;SM>cC}ik*}?U-iV))|YWsBSv3$UHQ!Y;?8@w zAeCYn%Spp=>6I1gt}k!+4-};yMv`ba98?j1hD9>S(|r(E(R=v^GCg-{B@i%aVpxc6 z%a4NDT6pNql>xQ6*FBGecv*?>YnaR#uSO(9hGp>xW&#lh!8F1$i{D~NyrL))EoyV^ z%M8Lmu6G3uN3fPRNMoNj5eVA24ljyki$K5(6+Gh5oOl)ZZ!A0$>YD(1R&V|ZpsR-d z2EpzLeqRT|t*XBk?MT_pL|~ZHTd~|xHD&4?)^=UxgS?@ojTNal$ zisF&JU#bsV+>x(+Va(s5I)CWq`{(nVCT)ALOZrgW_bXQ1MTD1vE5#mp5SM(Wvo@}z zXX$kZH5xqlS=G)!fs(6J$yj;BA+vYS7aq$88FkuoNbC&ORcip2y24PC$0mkt7G)b9 ze2_-KH>8dAbI352`~!q@5lAU;DDF&KzpAGR^HG#F4kDXj*$506n^+Ar>Xm-DyG_CH z)V}oG-g&=vxyk{bbkoZDfONJ}`A$GpZ>v=$JhmBWQ;u5kCr0Ft1`-cuGzAdLCYA?O zXd*ndZ){@sZq`4SxfoXB32mGyZJrqQ2d2HFo)QO#J8rg9&3L{aot=Jwgqf7BfOOMo zmB4iD&xi7if57hloJ8Y4_vb&x{dryN;EtL>M7YEC ziYQs59ZA7M9@^&CI@%AyE-F53#RLmv(nm`xXRJ##b3!lgtQxPa=PmusM<$$K=9bc7)UqXK2Nq zB>svEb5bM3KCtG#-)^Wbnt-{cp4RTF=BpGo6p|V~sW6alU7A%CJn>Xqac@1gEQw0} zAD>7I#fb{5~Wmh78`Wv!jcMd<$^}C zrHeo@a_gGj-R^OIF(Dp<16c%8PB9E6fk=VGICi%ZLXOv$2UYh-7U(#9Bw{D+{6$jwCk3SW~8&nLJthU6} zQH;SL`x94oG^bbPNA=e}#NY!lO_#`z+^o-CDtOoJT{V$*SABmCb<8ex<-r1jbxmpG zN&DJT+5K^!+s}6`OtjqhESGrJF!QtL?)VK46~SNYo3~eFK1HvzC7%nrwYD0xGp21T z&_HXe<*M)MTF_Y!|Lz+LvjFje-)Lsv9__8bG^YG{NQURVM{s&GgI|UfxshF~9kDio zg-F2mS)fJ8~_Rng00`vwO| zOu-lGyO}Cfb+c=V0{b)sBC*6`hL(}41+S7sKWl2_Co!8U!cU0h4>tMo%3A8{0dO23 z5Rf6R&U|0#hV!Bx%(ogO5TzU>=A5aN`QC``uC`+D?}tse8EK z!_1fmSavo z6FSJ95ZXvt(tCc>Xg%2U+gxt@2!@9bk7mtB%Hy_bdMBtNf%Fw*?$zC>HFT*U6xpEg zz~YpV&YEBCdA0|`-(E=&S)vDc0ycz#0Pq|8!N#DTb@c#QRRx4c>oW$YVq^NXcP)Nt zral0N&Peg#hF$Vk(R8Z9xIdjOg~=xsWt+UnFk2TK;JB>Ln9EdtCX$eEHV*j0C^!=V zd6%f^X)y7fQSt(aD?;S4zH>7ikrvc4JsgaK(j&geBS@wX$Ag}d`=cdgsaiY`DI&WN z2O{`pF$uZZ=I?uvbh$NS>j0&a89~XGV+a4U2t!TK4fT(7K(%q5H zV>@35do9it&SdI8l4@QR=Egu~mMDD&8R1WCW@FF*+dAY2vF%cjQf6vOX~m0vWpcZ5 zNnNQHFX=13L|8;gIGmEA+@0RRx9-o%4=o5U;R#_PK*1D$%ezD~Ag^+~O(bm}TOQ|| zmfNBsjv2O9Ldi~sy1vzvFJq^XBW-Xfb&gE@r=0wZEd{tWW2H3b(v)3Dx=S9wVi7); zm?tn*6lji0HH!2SA^-ev= z@u;Jc+FH={2q0?Cp)?~}f4S9;eIE4>rnKY;!0SFzQ2HJX{F3TYHS0x2JTeS&%?HSA zt%$F#erXZKI8L+!+ZzMp%>;uanKB`a*7ZI{*t`U$r3v4zj*1~M`POd}O?ylvF%%V2 zw(O&m50F}B)~`x(#PKmqnx04=ve6nP5kSnnJ&%ga%+n7_rE2X(rRM?!m%_Bf@l>b5 zDdESrCZ#+Tz^{zyslMiW`nb?~TqK$lP%MukMNoOT4W60I4Cz{)*PVNN+k8tNrfT8+ zKA1-h2GEC7!UYfvg^eG%41hz?U08G1Jlvcv-s5t7MRE35;7w9{b)WhhqNb8U*dqG8 z&-#dNTAbhn<2de+1B`pV_1Fy6i%3{J?HyUnaC=lo;K-c?fc z?yVj~n`gX4R*#a<+C{*$J%Fc~Y-SoSo{7A@0c3s|7isV3HE+Q|PN_HPWMN=xJCCH< zD8;vK?x(uS<7f*5JCCe5o7qpOh0~4iCcl#VLjJ1|~gftTKHR*myPS^c-^HbfoMc|q`F#4}DMZ~p^V44Psi437NSnW%VJ*^K=7A^!MZ z)4X|zsppr?;MYedl;aOeAQ4noerg^dHVg~xc7c=*X|(4pn*`Lt2&8GxDA6g!I+l>L z1yilDj5y-`1JoBW}UVSK8Di|25x=cWLkRT>8 z8)ga|I}+gkplHkTi>;19a{s!7W8t~j-tk_92*kmn;r0x#gXZ}KQh0#Xr!QIF7Yc#( zTfs+3h)3EO+qCjV`*4(pj6i(?`XU2|F+Rp;@l zC5AjLe!h44fEndU>9RJn3Ww>xKtntR+_Aj3_+Ll#%m_#^ZVnrH3L&VfD4N<)jhv?6 zx&Aux<*&Z+_LC8Z#Ck}JCphAUJ>;AGi_Doo8ExZ;HC=nFYQjHQx})LT$e>(lRj;5 zUZeAyjAYl22k06eIFSGOL3CSUrlN9%JP)Du3b`G!bC)aR>5v_PyY}4{yeXHKcAnmPC4dtWr>s z4w?7kO_1M@0WXEQck|~^acCog3oEOb|h317lMCnFu1Z1;6@l<8OUE8n+M# zkZJ!8NE&4q-P^>pkpF`m-js%i@9YZe`MP>K78zgig#hQ_OJ*pR>dERUtyMjck9X+q zWJ`!OD!@7?Iz`j{u8d;(^H7fx>EXgGPjOdg11TTG&ArmBqQ$^^mV#U%(?tyo$9vKZHnq)jgUrd~i)Ill{ znPLdTPyxEXME*&VkqD`IfQx(&Vl7M3^F$#>vBUbe!ke5-E8L6Jdy|yNJF*+SqTZP z2xpMR=##s$#h%b4@Z=;MP$D_J13*@cbyLV8jWj%dKoxk&*1!k}h%zog#v!bIfx|}$ zwi(2Ng(w##pWCR(38D-&F!b-z4kLgeD>{5x*dbpObyRMWm|$G%y8YnLCSrJ1g1!YY zBuE{P{8>yv$V0+mCE+O-pdQCD*hqTY6nz~q_(kZn9o-?KQkRGX&ni<`HsUyv&Cfj8 zMS_|TR6VibbAptz=1d&~xfV~RUi`7_O<&)UVK4IHgQ#Nk(J_rj=57ZlBP1#NF2zUb zOnJ!Antzx0~vRO9X!P95wJcL00IsHf?(SGzL1P}lh^f#vtuD2 zmH|X3_fU~*f6|W4;^juue$8JUo$yd9OpSJx8Nz?fl;7sQ%Yp2z*UndY@-~DjD0@z z>QQZ4)!FAkHEzc))1dXufI8`(<$=2VAc8XD2gr8b0|l_*YM?=sK+a#ye1*Y}GUj4E zZ+9w)ZU$;FRS1pgJ;8TCTDW29)l_VB42&oqG_-A zrn&sFL0cRJ;Ck}t^|I|UaYp#(p4N*&G82?Tehm-FsV5#gk}qLWIYc$W#&GV9Oe& zbdqTMvmv_%U+ z-W4G0iVL<~_#l#Bq)f<@W{$#hkI5!QXb6_M49X=WhQPJzrJQ6ryT6zcmiD4xn|kiV z%3K<{B-*=BBOC-A8LOQ0+IeP{9{bxXSR>K(i45V3v&+*>PKXM9_k(Y(rRhU);YL8ABDpsk-$C3VH~G=J6FG+a@6Px~D2E!grdj1>(zKAKR3_L|*SC zCcm9y{1jPn`g$f{3K~RIENwTY*mBkJ{U8BHbi$py<9Hl`ZT^zNWBoIh`AbBX&0UJq z_|r!{EFxNdK}-`UzGNhnYK9^Gm>7_Uns%S@;WRdhMAjvrv~6COyHyyt^Hm=rnuEN$ zeVw4?Op4Yy$=pzjUTQpDM~NEAOEQs3F>x`|J-omfT5xGE88;Zj?eQnUxiUri$hdxT z1+O8SfCZ56?g*W3wOs#16Nh!3?#<|

K=s5)jAk5LQUVvb%{$2`AWmoFom~aKXYT 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

8Y6RM?lph@6B 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�{{~*jrq1u_jeBDnmPwY6>Ji=&K;&Vx6 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 Q7=-@3@Sj>G69|3e9~M?@xc~qF literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball8.tex b/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball8.tex new file mode 100644 index 0000000000000000000000000000000000000000..bf65e2eabb965a8694d9264f2deff11944c30a9f GIT binary patch literal 150914 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

J}%~DOlmYs(79G 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 QP=Qx7#iFUlKW5VZ0eI{AYXATM literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball9.jpg b/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball9.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b7d1850cdd05bfe9a9250358c75ebdba69e2858e GIT binary patch literal 7803 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 R3hnEW`QMSP2Mb1P`8P!nyZZnD literal 0 HcmV?d00001 diff --git a/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball9.tex b/test/lib/ufe/test-samples/ballset/StandaloneScene/tex/ball9.tex new file mode 100644 index 0000000000000000000000000000000000000000..fd0b374f2b546c08e4e480f0959a07a37ab03707 GIT binary patch literal 237074 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 Date: Mon, 28 Oct 2019 11:41:41 -0400 Subject: [PATCH 06/15] Add CMake changes --- CMakeLists.txt | 4 +- cmake/usd.cmake | 6 +- cmake/utils.cmake | 6 +- lib/CMakeLists.txt | 5 + mayaUSD.mod.template | 10 +- plugin/al/CMakeLists.txt | 2 + plugin/al/lib/AL_USDMaya/CMakeLists.txt | 5 - .../al/mayatest/AL/maya/test/CMakeLists.txt | 2 - plugin/al/mayautils/AL/maya/CMakeLists.txt | 2 - .../al/plugin/AL_USDMayaPlugin/CMakeLists.txt | 6 +- .../AL_USDMayaTestPlugin/CMakeLists.txt | 4 +- .../AL/usd/schemas/maya/CMakeLists.txt | 18 +- .../AL/usd/schemas/mayatest/CMakeLists.txt | 5 - plugin/al/translators/CMakeLists.txt | 20 +- .../AL/usdmaya/utils/CMakeLists.txt | 2 - plugin/al/usdtransaction/CMakeLists.txt | 2 - .../al/usdutils/AL/usd/utils/CMakeLists.txt | 2 - plugin/al/utils/AL/CMakeLists.txt | 2 - plugin/pxr/cmake/macros/Private.cmake | 14 +- plugin/pxr/cmake/macros/Public.cmake | 31 +- plugin/pxr/maya/CMakeLists.txt | 2 +- plugin/pxr/maya/lib/usdMaya/CMakeLists.txt | 434 ++++++++++-------- plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt | 9 +- .../pxrUsdPreviewSurface/CMakeLists.txt | 9 +- .../plugin/pxrUsdTranslators/CMakeLists.txt | 23 +- 25 files changed, 349 insertions(+), 276 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e97372ee3..e32d9921ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,9 +47,7 @@ if (BUILD_MAYAUSD_LIBRARY) endif() if (APPLE) - set(OSX_ARCHITECTURES "x86_64") - add_definitions(-DOSMac_ -DMAC_PLUGIN) - add_definitions(-D_BOOL -DREQUIRE_IOSTREAM) + set(CMAKE_OSX_ARCHITECTURES "x86_64") endif() if (DEFINED PYTHON_INCLUDE_DIR AND DEFINED PYTHON_LIBRARIES AND DEFINED PYTHON_EXECUTABLE) diff --git a/cmake/usd.cmake b/cmake/usd.cmake index 6d80eda691..8390ac3480 100644 --- a/cmake/usd.cmake +++ b/cmake/usd.cmake @@ -1,6 +1,6 @@ # # ======================================================================= -# Copyright 2019 Autodesk, Inc. All rights reserved. +# Copyright 2019 Autodesk # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,9 +19,9 @@ function(init_usd) # Adjust PYTHONPATH, PATH - append_path_to_env_var("PYTHONPATH" "${PXR_USD_LOCATION}/lib/python") + mayaUsd_append_path_to_env_var("PYTHONPATH" "${PXR_USD_LOCATION}/lib/python") if(WIN32) - append_path_to_env_var("PATH" "${PXR_USD_LOCATION}/bin;${PXR_USD_LOCATION}/lib") + mayaUsd_append_path_to_env_var("PATH" "${PXR_USD_LOCATION}/bin;${PXR_USD_LOCATION}/lib") endif() endfunction() diff --git a/cmake/utils.cmake b/cmake/utils.cmake index 81e9af89dc..4b987d6efe 100644 --- a/cmake/utils.cmake +++ b/cmake/utils.cmake @@ -34,7 +34,7 @@ endif() # envVar The environment variable to modify # pathToAppend The path to append # -function(append_path_to_env_var envVar pathToAppend) +function(mayaUsd_append_path_to_env_var envVar pathToAppend) file(TO_NATIVE_PATH "${pathToAppend}" nativePathToAppend) if(DEFINED ENV{${envVar}}) if(IS_WINDOWS) @@ -54,7 +54,7 @@ endfunction() # # module The python module to find # -function(find_python_module module) +function(mayaUsd_find_python_module module) string(TOUPPER ${module} module_upper) set(module_found "${module_upper}_FOUND") if(NOT ${module_found}) @@ -71,7 +71,7 @@ function(find_python_module module) "Location of Python module ${module}") endif(NOT _${module}_status) endif(NOT ${module_found}) -endfunction(find_python_module) +endfunction(mayaUsd_find_python_module) # Initialize a variable to accumulate an rpath. The origin is the # RUNTIME DESTINATION of the target. If not absolute it's appended diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d53168838e..cf886c389a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -380,6 +380,11 @@ install(FILES ${mayaUsdShaderFragments_xmls} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/usd/mayaUsd_ShaderFragments/resources ) +#install top level plugInfo.json that includes the configured plugInfo.json +install(CODE + "file(WRITE \"${CMAKE_INSTALL_PREFIX}/lib/usd/plugInfo.json\" \"{\n \\\"Includes\\\": [ \\\"*/resources/\\\" ]\n}\")" +) + if(UFE_FOUND) # UFE Python Bindings set(UFE_PYTHON_LIBRARY_NAME ufe) diff --git a/mayaUSD.mod.template b/mayaUSD.mod.template index 2efc2e1b59..e356af9ef8 100644 --- a/mayaUSD.mod.template +++ b/mayaUSD.mod.template @@ -11,8 +11,16 @@ MAYA_SCRIPT_PATH+:=maya/lib/usd/usdMaya/resources MAYA_PLUG_IN_PATH+:=maya/plugin PXR_PLUGINPATH_NAME+:=lib/usd ++ MayaUSD_LIB ${MAYAUSD_VERSION} ${CMAKE_INSTALL_PREFIX} +PATH+:=lib +PYTHONPATH+:=lib/python +PXR_PLUGINPATH_NAME+:=lib/usd +VP2_RENDER_DELEGATE_PROXY=1 + + AL_USDMaya ${AL_USDMAYA_VERSION} ${CMAKE_INSTALL_PREFIX}/plugin/al PYTHONPATH+:=lib/python PATH+:=lib MAYA_PLUG_IN_PATH+:=plugin -PXR_PLUGINPATH_NAME+:=lib/usd \ No newline at end of file +PXR_PLUGINPATH_NAME+:=lib/usd +PXR_PLUGINPATH_NAME+:=plugin +MAYA_WANT_UFE_SELECTION=1 diff --git a/plugin/al/CMakeLists.txt b/plugin/al/CMakeLists.txt index 789d048e2f..aff32e13f2 100644 --- a/plugin/al/CMakeLists.txt +++ b/plugin/al/CMakeLists.txt @@ -57,6 +57,8 @@ set(AL_USDMAYA_LOCATION_NAME "Name of the environment variable used to store AL_USDMaya installation location" ) +set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) + # Build all the utils set(EVENTS_INCLUDE_LOCATION ${CMAKE_CURRENT_LIST_DIR}/utils) set(USDUTILS_INCLUDE_LOCATION ${CMAKE_CURRENT_LIST_DIR}/usdutils) diff --git a/plugin/al/lib/AL_USDMaya/CMakeLists.txt b/plugin/al/lib/AL_USDMaya/CMakeLists.txt index 169f060ac4..3f98bdf2f8 100644 --- a/plugin/al/lib/AL_USDMaya/CMakeLists.txt +++ b/plugin/al/lib/AL_USDMaya/CMakeLists.txt @@ -1,7 +1,5 @@ set(arDirPath AL/usdmaya) -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set(LIBRARY_NAME AL_USDMaya) list(APPEND AL_usdmaya_headers @@ -164,9 +162,6 @@ target_compile_definitions(${LIBRARY_NAME} ${_macDef} AL_MAYA_MACROS_EXPORT AL_USDMAYA_EXPORT - MFB_PACKAGE_NAME="${LIBRARY_NAME}" - MFB_ALT_PACKAGE_NAME="${LIBRARY_NAME}" - MFB_PACKAGE_MODULE=usdmaya AL_USDMAYA_LOCATION_NAME="${AL_USDMAYA_LOCATION_NAME}" ) diff --git a/plugin/al/mayatest/AL/maya/test/CMakeLists.txt b/plugin/al/mayatest/AL/maya/test/CMakeLists.txt index d9fe6a6c26..9e5dfe1521 100755 --- a/plugin/al/mayatest/AL/maya/test/CMakeLists.txt +++ b/plugin/al/mayatest/AL/maya/test/CMakeLists.txt @@ -4,8 +4,6 @@ #################################################################################################### find_package(GTest REQUIRED) -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set(MAYA_TEST_LIBRARY_NAME "AL_MayaTest") set(MAYA_TEST_LIBRARY_LOCATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/plugin/al/mayautils/AL/maya/CMakeLists.txt b/plugin/al/mayautils/AL/maya/CMakeLists.txt index 2fe0f9d74c..ddb3326018 100755 --- a/plugin/al/mayautils/AL/maya/CMakeLists.txt +++ b/plugin/al/mayautils/AL/maya/CMakeLists.txt @@ -3,8 +3,6 @@ # Setup #################################################################################################### -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set(MAYAUTILS_LIBRARY_NAME "AL_MayaUtils") set(MAYAUTILS_LIBRARY_LOCATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt b/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt index 43e6cc4bf4..947af2cec4 100644 --- a/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt +++ b/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt @@ -58,10 +58,10 @@ endif() install(TARGETS ${PXR_PACKAGE} LIBRARY - DESTINATION ${INSTALL_DIR_SUFFIX}/plugin + DESTINATION ${CMAKE_INSTALL_PREFIX}/plugin RUNTIME - DESTINATION ${INSTALL_DIR_SUFFIX}/plugin + DESTINATION ${CMAKE_INSTALL_PREFIX}/plugin ) if(IS_WINDOWS) - install(FILES $ DESTINATION ${INSTALL_DIR_SUFFIX}/plugin OPTIONAL) + install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX}/plugin OPTIONAL) endif() diff --git a/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt b/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt index 9c419be7fb..3a21ad1b20 100644 --- a/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt +++ b/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt @@ -108,9 +108,9 @@ endif() install(TARGETS ${PXR_PACKAGE} LIBRARY - DESTINATION ${INSTALL_DIR_SUFFIX}/plugin + DESTINATION ${CMAKE_INSTALL_PREFIX}/plugin RUNTIME - DESTINATION ${INSTALL_DIR_SUFFIX}/plugin + DESTINATION ${CMAKE_INSTALL_PREFIX}/plugin ) add_test( diff --git a/plugin/al/schemas/AL/usd/schemas/maya/CMakeLists.txt b/plugin/al/schemas/AL/usd/schemas/maya/CMakeLists.txt index 7455ac1124..4012ae0618 100644 --- a/plugin/al/schemas/AL/usd/schemas/maya/CMakeLists.txt +++ b/plugin/al/schemas/AL/usd/schemas/maya/CMakeLists.txt @@ -1,6 +1,3 @@ - -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set( resources_install_path ${CMAKE_INSTALL_PREFIX}/lib/usd/AL_USDMayaSchemas/resources @@ -39,7 +36,7 @@ configure_file ( execute_process( COMMAND - python + ${PYTHON_EXECUTABLE} ${USD_GENSCHEMA} ${CMAKE_CURRENT_BINARY_DIR}/schema.usda . @@ -67,9 +64,6 @@ add_library(AL_USDMayaSchemas target_compile_definitions(AL_USDMayaSchemas PRIVATE - MFB_PACKAGE_NAME=AL_USDMayaSchemas - MFB_ALT_PACKAGE_NAME=AL_USDMayaSchemas - MFB_PACKAGE_MODULE=AL.usd.schemas.maya AL_USDMAYASCHEMAS_EXPORTS ) @@ -81,7 +75,7 @@ target_include_directories(AL_USDMayaSchemas # Hamed 2019 # https://stackoverflow.com/questions/25617839/undefined-reference-to-symbol-pthread-key-deleteglibc-2-2-5 set(PTHREAD_LINK "") -if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") +if(IS_LINUX) set(PTHREAD_LINK -lpthread -lm) endif() @@ -94,7 +88,7 @@ install( RUNTIME DESTINATION ${library_install_path} ) -if(MSVC) +if(IS_WINDOWS) install(FILES $ DESTINATION ${library_install_path} OPTIONAL) endif() @@ -123,7 +117,7 @@ set_target_properties(_AL_USDMayaSchemas PREFIX "" ) -if(MSVC) +if(IS_WINDOWS) set_target_properties(_AL_USDMayaSchemas PROPERTIES SUFFIX ".pyd" @@ -145,7 +139,7 @@ install(TARGETS _AL_USDMayaSchemas # configure_file has a nice feature where it will copy the __init__ file over when it gets modified, unlike file(COPY ...) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/__init__.py ${CMAKE_BINARY_DIR}/AL/usd/schemas/maya/__init__.py COPYONLY) -install(CODE "execute_process(COMMAND python -m compileall ${CMAKE_BINARY_DIR}/AL/usd/schemas/maya/__init__.py )") +install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -m compileall ${CMAKE_BINARY_DIR}/AL/usd/schemas/maya/__init__.py )") string(REPLACE "/" ";" folderHierarchy "AL/usd/schemas/maya") @@ -180,7 +174,7 @@ install( ) install(CODE "message(STATUS \"POST INSTALL: Compiling python/pyc for ${CMAKE_INSTALL_PREFIX}/lib/python ... \")") -install(CODE "execute_process(COMMAND python -m compileall ${CMAKE_INSTALL_PREFIX}/lib/python )") +install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -m compileall ${CMAKE_INSTALL_PREFIX}/lib/python )") #################################################################################################### # Install public headers diff --git a/plugin/al/schemas/AL/usd/schemas/mayatest/CMakeLists.txt b/plugin/al/schemas/AL/usd/schemas/mayatest/CMakeLists.txt index 14753885cc..6870198691 100644 --- a/plugin/al/schemas/AL/usd/schemas/mayatest/CMakeLists.txt +++ b/plugin/al/schemas/AL/usd/schemas/mayatest/CMakeLists.txt @@ -1,5 +1,3 @@ -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set( resources_install_path ${CMAKE_INSTALL_PREFIX}/lib/usd/AL_USDMayaSchemasTest/resources @@ -64,9 +62,6 @@ add_library(AL_USDMayaSchemasTest target_compile_definitions(AL_USDMayaSchemasTest PRIVATE - MFB_PACKAGE_NAME=AL_USDMayaSchemasTest - MFB_ALT_PACKAGE_NAME=AL_USDMayaSchemasTest - MFB_PACKAGE_MODULE=AL.usd.schemas.mayatest AL_USDMAYASCHEMASTEST_EXPORTS ) diff --git a/plugin/al/translators/CMakeLists.txt b/plugin/al/translators/CMakeLists.txt index 56be512e5b..bb8acd25cc 100644 --- a/plugin/al/translators/CMakeLists.txt +++ b/plugin/al/translators/CMakeLists.txt @@ -1,7 +1,5 @@ list(APPEND DEPENDANT_LIBRARIES AL_USDMaya AL_USDMayaSchemas mayaUsd gf plug tf) -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set(DIRECTORY_PATH AL/usdmaya/fileio/translators) configure_file ( @@ -22,15 +20,23 @@ add_library(${TRANSLATORS_PACKAGE} DirectionalLight.cpp ) +if(IS_MACOSX) + set(_macDef OSMac_) +endif() + +# HS OCT 2019, do we really need to define +# AL_MAYA_MACROS_EXPORT for this target? +if(NOT IS_WINDOWS) + set_target_properties(${TRANSLATORS_PACKAGE} + PROPERTIES COMPILE_DEFINITIONS + "AL_MAYA_MACROS_EXPORT;${_macDef}" + ) +endif() + set(arg_PUBLIC_HEADER_FILES Camera.h ) -target_compile_definitions(${TRANSLATORS_PACKAGE} - PRIVATE - AL_MAYA_MACROS_EXPORT -) - # Copy the plugin metadata file in a hierarchy replicating the install hierarchy. # The file will be used by the unittests. get_target_property(LIBRARY_LOCATION diff --git a/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt b/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt index b4d164d692..1455a6ba7a 100755 --- a/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt +++ b/plugin/al/usdmayautils/AL/usdmaya/utils/CMakeLists.txt @@ -3,8 +3,6 @@ # Setup #################################################################################################### -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set(USDMAYA_UTILS_LIBRARY_NAME "AL_USDMayaUtils") set(MAYA_UTILS_LIBRARY_LOCATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/plugin/al/usdtransaction/CMakeLists.txt b/plugin/al/usdtransaction/CMakeLists.txt index 3ce0e4f049..dd753f9e20 100755 --- a/plugin/al/usdtransaction/CMakeLists.txt +++ b/plugin/al/usdtransaction/CMakeLists.txt @@ -1,5 +1,3 @@ -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - add_subdirectory(AL/usd/transaction) set(LIBRARY_NAME AL_USDTransaction) diff --git a/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt b/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt index 1c9046300e..ee78fcbfaa 100755 --- a/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt +++ b/plugin/al/usdutils/AL/usd/utils/CMakeLists.txt @@ -3,8 +3,6 @@ # Setup #################################################################################################### -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set(USDUTILS_LIBRARY_NAME "AL_USDUtils") set(USDUTILS_LIBRARY_LOCATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/plugin/al/utils/AL/CMakeLists.txt b/plugin/al/utils/AL/CMakeLists.txt index be18cb82b2..6f0236ee8f 100755 --- a/plugin/al/utils/AL/CMakeLists.txt +++ b/plugin/al/utils/AL/CMakeLists.txt @@ -2,8 +2,6 @@ # Setup #################################################################################################### -set(CMAKE_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX} ) - set(EVENTS_LIBRARY_NAME "AL_EventSystem") set(EVENTS_LIBRARY_LOCATION ${CMAKE_INSTALL_PREFIX}/lib) diff --git a/plugin/pxr/cmake/macros/Private.cmake b/plugin/pxr/cmake/macros/Private.cmake index 1567325c51..1e095ec7a9 100644 --- a/plugin/pxr/cmake/macros/Private.cmake +++ b/plugin/pxr/cmake/macros/Private.cmake @@ -438,7 +438,7 @@ function(_pxr_enable_precompiled_header TARGET_NAME) # Additional compile flags to use precompiled header. This will be set(compile_flags "") - if(MSVC) + if(IS_WINDOWS) # Build with precompiled header (/Yu, /Fp) and automatically # include the header (/FI). set(compile_flags "/Yu\"${rel_output_header_path}\" /FI\"${rel_output_header_path}\" /Fp\"${abs_precompiled_path}\"") @@ -450,7 +450,7 @@ function(_pxr_enable_precompiled_header TARGET_NAME) # Use FALSE if we have an external precompiled header we can use. if(TRUE) - if(MSVC) + if(IS_WINDOWS) # Copy the header to precompile. add_custom_command( OUTPUT "${abs_output_header_path}" @@ -817,7 +817,7 @@ function(_pxr_target_link_libraries NAME) if(";${PXR_STATIC_LIBS};" MATCHES ";${lib};") # The library is explicitly static. list(APPEND final ${lib}) - elseif(MSVC) + elseif(IS_WINDOWS) # The syntax here is -WHOLEARCHIVE[:lib] but CMake will # treat that as a link flag and not "see" the library. # As a result it won't replace a target with the path @@ -958,16 +958,18 @@ function(_pxr_python_module NAME) PROPERTIES SUFFIX ".pyd" ) - elseif(APPLE) + elseif(IS_MACOSX) # Python modules must be suffixed with .so on Mac. set_target_properties(${LIBRARY_NAME} PROPERTIES SUFFIX ".so" ) + set(_macDef OSMac_) endif() target_compile_definitions(${LIBRARY_NAME} PRIVATE + ${_macDef} MFB_PACKAGE_NAME=${PXR_PACKAGE} MFB_ALT_PACKAGE_NAME=${PXR_PACKAGE} MFB_PACKAGE_MODULE=${pyModuleName} @@ -1213,11 +1215,15 @@ function(_pxr_library NAME) if(TARGET shared_libs) set(pythonModulesEnabled "PXR_PYTHON_MODULES_ENABLED=1") endif() + if(IS_MACOSX) + set(_macDef OSMac_) + endif() target_compile_definitions(${NAME} PUBLIC ${pythonEnabled} ${apiPublic} PRIVATE + ${_macDef} MFB_PACKAGE_NAME=${PXR_PACKAGE} MFB_ALT_PACKAGE_NAME=${PXR_PACKAGE} MFB_PACKAGE_MODULE=${pythonModuleName} diff --git a/plugin/pxr/cmake/macros/Public.cmake b/plugin/pxr/cmake/macros/Public.cmake index b5414bd25a..548da5d5d1 100644 --- a/plugin/pxr/cmake/macros/Public.cmake +++ b/plugin/pxr/cmake/macros/Public.cmake @@ -108,7 +108,7 @@ function(pxr_library NAME) if(args_MAYA_PLUGIN) if (WIN32) set(suffix ".mll") - elseif(APPLE) + elseif(IS_MACOSX) set(suffix ".bundle") endif() endif() @@ -425,7 +425,7 @@ function(pxr_register_test TEST_NAME) # This harness is a filter which allows us to manipulate the test run, # e.g. by changing the environment, changing the expected return code, etc. - set(testWrapperCmd ${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/testWrapper.py --verbose) + set(testWrapperCmd ${PROJECT_SOURCE_DIR}/plugin/pxr/cmake/macros/testWrapper.py --verbose) if (bt_STDOUT_REDIRECT) set(testWrapperCmd ${testWrapperCmd} --stdout-redirect=${bt_STDOUT_REDIRECT}) @@ -456,9 +456,9 @@ function(pxr_register_test TEST_NAME) # assume the testenv has the same name as the test but allow it to be # overridden by specifying TESTENV. if (bt_TESTENV) - set(testenvDir ${CMAKE_INSTALL_PREFIX}/tests/ctest/${bt_TESTENV}) + set(testenvDir ${CMAKE_INSTALL_PREFIX}/plugin/pxr/tests/ctest/${bt_TESTENV}) else() - set(testenvDir ${CMAKE_INSTALL_PREFIX}/tests/ctest/${TEST_NAME}) + set(testenvDir ${CMAKE_INSTALL_PREFIX}/plugin/pxr/tests/ctest/${TEST_NAME}) endif() set(testWrapperCmd ${testWrapperCmd} --testenv-dir=${testenvDir}) @@ -515,27 +515,24 @@ function(pxr_register_test TEST_NAME) endforeach() endif() - # If we're building static libraries, the C++ tests that link against - # these libraries will look for resource files in the "usd" subdirectory - # relative to where the tests are installed. However, the build installs - # these files in the "lib" directory where the libraries are installed. + # Look for resource files in the "usd" subdirectory relative to the + # "lib" directory where the libraries are installed. # # We don't want to copy these resource files for each test, so instead # we set the PXR_PLUGINPATH_NAME env var to point to the "lib/usd" # directory where these files are installed. - if (NOT TARGET shared_libs) - set(_plugSearchPathEnvName "PXR_PLUGINPATH_NAME") - if (PXR_OVERRIDE_PLUGINPATH_NAME) - set(_plugSearchPathEnvName ${PXR_OVERRIDE_PLUGINPATH_NAME}) - endif() - - set(testWrapperCmd ${testWrapperCmd} --env-var=${_plugSearchPathEnvName}=${CMAKE_INSTALL_PREFIX}/lib/usd) + set(_plugSearchPathEnvName "PXR_PLUGINPATH_NAME") + if (PXR_OVERRIDE_PLUGINPATH_NAME) + set(_plugSearchPathEnvName ${PXR_OVERRIDE_PLUGINPATH_NAME}) endif() + set(testWrapperCmd ${testWrapperCmd} --env-var=${_plugSearchPathEnvName}=${CMAKE_INSTALL_PREFIX}/lib/usd) + set(testWrapperCmd ${testWrapperCmd} --env-var=PATH=${PXR_USD_LOCATION}/lib) + # Ensure that Python imports the Python files built by this build. # On Windows convert backslash to slash and don't change semicolons # to colons. - set(_testPythonPath "${CMAKE_INSTALL_PREFIX}/lib/python;$ENV{PYTHONPATH}") + set(_testPythonPath "${CMAKE_INSTALL_PREFIX}/lib/python;${CMAKE_INSTALL_PREFIX}/plugin/pxr/lib/python;$ENV{PYTHONPATH}") if(WIN32) string(REGEX REPLACE "\\\\" "/" _testPythonPath "${_testPythonPath}") else() @@ -732,7 +729,7 @@ function(pxr_toplevel_epilogue) # that we carefully avoid adding the usd_m target itself by using # TARGET_FILE. Linking the usd_m target would link usd_m and # everything it links to. - if(MSVC) + if(IS_WINDOWS) target_link_libraries(usd_ms PRIVATE -WHOLEARCHIVE:$ diff --git a/plugin/pxr/maya/CMakeLists.txt b/plugin/pxr/maya/CMakeLists.txt index 0834a07c9b..b805f2379c 100644 --- a/plugin/pxr/maya/CMakeLists.txt +++ b/plugin/pxr/maya/CMakeLists.txt @@ -1,5 +1,5 @@ set(PXR_INSTALL_SUBDIR "maya") - + pxr_add_extra_plugins(plugin) add_subdirectory(lib) diff --git a/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt b/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt index e849c03cad..773a2c3d51 100644 --- a/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt +++ b/plugin/pxr/maya/lib/usdMaya/CMakeLists.txt @@ -224,14 +224,22 @@ pxr_install_test_dir( SRC testenv/PointBasedDeformerNodeTest DEST testPointBasedDeformerNode ) +set(TEST_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/plugin/pxr") + +# MAYA-96273 (closed as by design) says that to obtain correct mayapy exit +# codes starting with Maya 2018.4, the MAYA_NO_STANDALONE_ATEXIT environment +# variable must be defined. Otherwise, mayapy unconditionally exits with 0 +# (success), which completely masks test failures. + pxr_register_test(testPointBasedDeformerNode CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testPointBasedDeformerNode" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testPointBasedDeformerNode" TESTENV testPointBasedDeformerNode ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -241,12 +249,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportAsClip CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportAsClip" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportAsClip" TESTENV testUsdExportAsClip ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -256,12 +265,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportAssembly CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportAssembly" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportAssembly" TESTENV testUsdExportAssembly ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -271,12 +281,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportAssemblyEdits CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportAssemblyEdits" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportAssemblyEdits" TESTENV testUsdExportAssemblyEdits ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -286,12 +297,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportCamera CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportCamera" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportCamera" TESTENV testUsdExportCamera ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -301,12 +313,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportColorSets CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportColorSets" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportColorSets" TESTENV testUsdExportColorSets ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -316,12 +329,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportConnected CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportConnected" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportConnected" TESTENV testUsdExportConnected ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -332,12 +346,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportDisplayColor CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportDisplayColor" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportDisplayColor" TESTENV testUsdExportDisplayColor ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -347,12 +362,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportEulerFilter CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportEulerFilter" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportEulerFilter" TESTENV testUsdExportEulerFilter ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -362,12 +378,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportFilterTypes CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportFilterTypes" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportFilterTypes" TESTENV testUsdExportFilterTypes ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -377,12 +394,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportFrameOffset CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportFrameOffset" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportFrameOffset" TESTENV testUsdExportFrameOffset ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -392,12 +410,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportInstances CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportInstances" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportInstances" TESTENV testUsdExportInstances ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -407,12 +426,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportLocator CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportLocator" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportLocator" TESTENV testUsdExportLocator ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -422,12 +442,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportMesh CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportMesh" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportMesh" TESTENV testUsdExportMesh ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -437,22 +458,24 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportNurbsCurve CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportNurbsCurve" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportNurbsCurve" TESTENV testUsdExportNurbsCurve ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) pxr_register_test(testUsdExportOpenLayer CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportOpenLayer" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportOpenLayer" ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -462,12 +485,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportOverImport CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportOverImport" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportOverImport" TESTENV testUsdExportOverImport ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -477,12 +501,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportPackage CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportPackage" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportPackage" TESTENV testUsdExportPackage ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -492,12 +517,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportParentScope CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportParentScope" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportParentScope" TESTENV testUsdExportParentScope ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -507,12 +533,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportParticles CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportParticles" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportParticles" TESTENV testUsdExportParticles ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -522,12 +549,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportPointInstancer CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportPointInstancer" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportPointInstancer" TESTENV testUsdExportPointInstancer ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -537,12 +565,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportPref CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportPref" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportPref" TESTENV testUsdExportPref ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -552,12 +581,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportRenderLayerMode CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportRenderLayerMode" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportRenderLayerMode" TESTENV testUsdExportRenderLayerMode ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile MAYA_ENABLE_LEGACY_RENDER_LAYERS=1 ) @@ -570,12 +600,13 @@ pxr_register_test(testUsdExportRenderLayerMode # ) # pxr_register_test(testUsdExportRfMLight # CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} -# COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportRfMLight" +# COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportRfMLight" # TESTENV testUsdExportRfMLight # ENV -# MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin -# MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources +# MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin +# MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources # MAYA_DISABLE_CIP=1 +# MAYA_NO_STANDALONE_ATEXIT=1 # MAYA_APP_DIR=/maya_profile # ) @@ -585,12 +616,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportSelection CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportSelection" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportSelection" TESTENV testUsdExportSelection ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -600,12 +632,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportShadingInstanced CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportShadingInstanced" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportShadingInstanced" TESTENV testUsdExportShadingInstanced ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -615,12 +648,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportShadingModeDisplayColor CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportShadingModeDisplayColor" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportShadingModeDisplayColor" TESTENV testUsdExportShadingModeDisplayColor ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -630,12 +664,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportShadingModePxrRis CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportShadingModePxrRis" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportShadingModePxrRis" TESTENV testUsdExportShadingModePxrRis ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -645,23 +680,25 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportSkeleton CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportSkeleton" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportSkeleton" TESTENV testUsdExportSkeleton ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) pxr_register_test(testUsdExportStripNamespaces CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportStripNamespaces" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportStripNamespaces" TESTENV testUsdExportStripNamespaces ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -671,13 +708,14 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportUVSets CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportUVSets" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportUVSets" TESTENV testUsdExportUVSets ENV PIXMAYA_WRITE_UV_AS_FLOAT2=0 - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -687,13 +725,14 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportUVSetsFloat CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportUVSets" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportUVSets" TESTENV testUsdExportUVSetsFloat ENV PIXMAYA_WRITE_UV_AS_FLOAT2=1 - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -703,13 +742,14 @@ pxr_install_test_dir( ) pxr_register_test(testUsdExportVisibilityDefault CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdExportVisibilityDefault" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdExportVisibilityDefault" TESTENV testUsdExportVisibilityDefault ENV PIXMAYA_WRITE_UV_AS_FLOAT2=1 - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -719,12 +759,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportAsAssemblies CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportAsAssemblies" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportAsAssemblies" TESTENV testUsdImportAsAssemblies ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -734,12 +775,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportCamera CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportCamera" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportCamera" TESTENV testUsdImportCamera ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -749,12 +791,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportColorSets CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportColorSets" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportColorSets" TESTENV testUsdImportColorSets ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -764,12 +807,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportFrameRange CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportFrameRange" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportFrameRange" TESTENV testUsdImportFrameRange ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -779,12 +823,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportMesh CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportMesh" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportMesh" TESTENV testUsdImportMesh ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -794,12 +839,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportNestedAssemblyAnimation CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportNestedAssemblyAnimation" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportNestedAssemblyAnimation" TESTENV testUsdImportNestedAssemblyAnimation ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -811,12 +857,13 @@ pxr_register_test(testUsdImportNestedAssemblyAnimation # ) # pxr_register_test(testUsdImportRfMLight # CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} -# COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportRfMLight" +# COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportRfMLight" # TESTENV testUsdImportRfMLight # ENV -# MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin -# MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources +# MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin +# MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources # MAYA_DISABLE_CIP=1 +# MAYA_NO_STANDALONE_ATEXIT=1 # MAYA_APP_DIR=/maya_profile # ) @@ -826,12 +873,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportSessionLayer CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportSessionLayer" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportSessionLayer" TESTENV testUsdImportSessionLayer ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -841,12 +889,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportShadingModeDisplayColor CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportShadingModeDisplayColor" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportShadingModeDisplayColor" TESTENV testUsdImportShadingModeDisplayColor ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -856,12 +905,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportShadingModePxrRis CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportShadingModePxrRis" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportShadingModePxrRis" TESTENV testUsdImportShadingModePxrRis ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -871,12 +921,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportSkeleton CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportSkeleton" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportSkeleton" TESTENV testUsdImportSkeleton ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -886,12 +937,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdTranslateTypelessDefs CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdTranslateTypelessDefs" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdTranslateTypelessDefs" TESTENV testUsdTranslateTypelessDefs ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -901,13 +953,14 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportUVSets CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportUVSets" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportUVSets" TESTENV testUsdImportUVSets ENV PIXMAYA_READ_FLOAT2_AS_UV=0 - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -917,13 +970,14 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportUVSetsFloat CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportUVSets" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportUVSets" TESTENV testUsdImportUVSetsFloat ENV PIXMAYA_READ_FLOAT2_AS_UV=1 - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -933,12 +987,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdImportXforms CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdImportXforms" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdImportXforms" TESTENV testUsdImportXforms ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -948,32 +1003,35 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaAppDir CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaAppDir" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaAppDir" TESTENV testUsdMayaAppDir ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) pxr_register_test(testUsdMayaBlockSceneModificationContext CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaBlockSceneModificationContext" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaBlockSceneModificationContext" ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) pxr_register_test(testUsdMayaDiagnosticDelegate CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaDiagnosticDelegate" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaDiagnosticDelegate" ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -983,12 +1041,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaGetVariantSetSelections CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaGetVariantSetSelections" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaGetVariantSetSelections" TESTENV testUsdMayaGetVariantSetSelections ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -998,12 +1057,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaModelKindProcessor CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaModelKindProcessor" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaModelKindProcessor" TESTENV testUsdMayaModelKindProcessor ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -1013,12 +1073,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaProxyShape CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaProxyShape" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaProxyShape" TESTENV testUsdMayaProxyShape ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -1028,12 +1089,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaReferenceAssemblyEdits CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaReferenceAssemblyEdits" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaReferenceAssemblyEdits" TESTENV testUsdMayaReferenceAssemblyEdits ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -1043,12 +1105,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaUserExportedAttributes CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaUserExportedAttributes" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaUserExportedAttributes" TESTENV testUsdMayaUserExportedAttributes ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -1058,12 +1121,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaAdaptorGeom CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaAdaptorGeom" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaAdaptorGeom" TESTENV testUsdMayaAdaptorGeom ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -1073,12 +1137,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdMayaAdaptorMetadata CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaAdaptorMetadata" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaAdaptorMetadata" TESTENV testUsdMayaAdaptorMetadata ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -1088,12 +1153,13 @@ pxr_install_test_dir( ) pxr_register_test(testUsdReferenceAssemblyChangeRepresentations CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdReferenceAssemblyChangeRepresentations" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdReferenceAssemblyChangeRepresentations" TESTENV testUsdReferenceAssemblyChangeRepresentations ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -1103,22 +1169,24 @@ pxr_install_test_dir( ) pxr_register_test(testUsdReferenceAssemblySelection CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdReferenceAssemblySelection" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdReferenceAssemblySelection" TESTENV testUsdReferenceAssemblySelection ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) pxr_register_test(testUsdMayaXformStack CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testUsdMayaXformStack" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testUsdMayaXformStack" TESTENV testUsdMayaXformStack ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) diff --git a/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt b/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt index fcab7a0b20..04b88e0bab 100644 --- a/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt +++ b/plugin/pxr/maya/plugin/pxrUsd/CMakeLists.txt @@ -55,13 +55,16 @@ pxr_install_test_dir( SRC testenv/AlembicChaser DEST testPxrUsdAlembicChaser ) +set(TEST_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/plugin/pxr") + pxr_register_test(testPxrUsdAlembicChaser CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testPxrUsdAlembicChaser" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testPxrUsdAlembicChaser" TESTENV testPxrUsdAlembicChaser ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) diff --git a/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/CMakeLists.txt b/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/CMakeLists.txt index ed763d7562..4d6e4b5222 100644 --- a/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/CMakeLists.txt +++ b/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/CMakeLists.txt @@ -76,13 +76,16 @@ pxr_install_test_dir( SRC testenv/PxrUsdPreviewSurfaceExportTest DEST testPxrUsdPreviewSurfaceExport ) +set(TEST_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/plugin/pxr") + pxr_register_test(testPxrUsdPreviewSurfaceExport CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testPxrUsdPreviewSurfaceExport" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testPxrUsdPreviewSurfaceExport" TESTENV testPxrUsdPreviewSurfaceExport ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources:${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin/pxrUsdPreviewSurface/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources:${TEST_INSTALL_PREFIX}/maya/plugin/pxrUsdPreviewSurface/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) diff --git a/plugin/pxr/maya/plugin/pxrUsdTranslators/CMakeLists.txt b/plugin/pxr/maya/plugin/pxrUsdTranslators/CMakeLists.txt index 422925789e..ebb8e8af78 100644 --- a/plugin/pxr/maya/plugin/pxrUsdTranslators/CMakeLists.txt +++ b/plugin/pxr/maya/plugin/pxrUsdTranslators/CMakeLists.txt @@ -69,13 +69,16 @@ pxr_test_scripts( testenv/testPxrUsdTranslatorsStroke.py ) +set(TEST_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/plugin/pxr") + pxr_register_test(testPxrUsdTranslators CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testPxrUsdTranslators" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testPxrUsdTranslators" ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -85,12 +88,13 @@ pxr_install_test_dir( ) pxr_register_test(testPxrUsdTranslatorsScope CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testPxrUsdTranslatorsScope" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testPxrUsdTranslatorsScope" TESTENV testPxrUsdTranslatorsScope ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) @@ -100,11 +104,12 @@ pxr_install_test_dir( ) pxr_register_test(testPxrUsdTranslatorsStroke CUSTOM_PYTHON ${MAYA_PY_EXECUTABLE} - COMMAND "${CMAKE_INSTALL_PREFIX}/tests/testPxrUsdTranslatorsStroke" + COMMAND "${TEST_INSTALL_PREFIX}/tests/testPxrUsdTranslatorsStroke" TESTENV testPxrUsdTranslatorsStroke ENV - MAYA_PLUG_IN_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin - MAYA_SCRIPT_PATH=${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib/usd/usdMaya/resources + MAYA_PLUG_IN_PATH=${TEST_INSTALL_PREFIX}/maya/plugin + MAYA_SCRIPT_PATH=${TEST_INSTALL_PREFIX}/maya/lib/usd/usdMaya/resources MAYA_DISABLE_CIP=1 + MAYA_NO_STANDALONE_ATEXIT=1 MAYA_APP_DIR=/maya_profile ) From f424a5f8808a3467d2f5c95ae55b9b92a66d35be Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Mon, 28 Oct 2019 12:14:18 -0400 Subject: [PATCH 07/15] Refactor rpaths --- cmake/modules/FindMayaUSD.cmake | 114 ---------------- lib/CMakeLists.txt | 6 + .../al/plugin/AL_USDMayaPlugin/CMakeLists.txt | 42 +++--- .../AL_USDMayaTestPlugin/CMakeLists.txt | 39 +++--- plugin/al/translators/CMakeLists.txt | 27 ++-- .../pxrUsdTranslators/CMakeLists.txt | 41 +++--- plugin/pxr/cmake/macros/Private.cmake | 126 +++++++----------- plugin/pxr/cmake/macros/Public.cmake | 22 +-- 8 files changed, 148 insertions(+), 269 deletions(-) delete mode 100644 cmake/modules/FindMayaUSD.cmake diff --git a/cmake/modules/FindMayaUSD.cmake b/cmake/modules/FindMayaUSD.cmake deleted file mode 100644 index cdbdff6fe5..0000000000 --- a/cmake/modules/FindMayaUSD.cmake +++ /dev/null @@ -1,114 +0,0 @@ -# - MayaUSD finder module -# This module searches for a valid MayaUsd installation. -# It searches for MayaUSD's libraries and include header files -# -# Variables that will be defined: -# MAYAUSD_FOUND Defined if a MAYAUSD installation has been detected -# MAYAUSD_LIBRARY Path to MAYAUSD library -# MAYAUSD_INCLUDE_DIR Path to the MAYAUSD's include directories -# - -if(APPLE) - find_path(MAYAUSD_LIBRARY_DIR - libmayaUsd.dylib - HINTS - "${MAYAUSD_LIB_ROOT}" - "$ENV{MAYAUSD_LIB_ROOT}" - "${MAYA_DEVKIT_LOCATION}" - "${MAYA_LOCATION}" - "$ENV{MAYA_LOCATION}" - "${MAYA_BASE_DIR}" - PATH_SUFFIXES - devkit/mayaUsd/lib - lib/ - DOC - "MayaUSD's libraries path" - ) -elseif(UNIX) - find_path(MAYAUSD_LIBRARY_DIR - libmayaUsd.so - HINTS - "${MAYAUSD_LIB_ROOT}" - "$ENV{MAYAUSD_LIB_ROOT}" - "${MAYA_DEVKIT_LOCATION}" - "${MAYA_LOCATION}" - "$ENV{MAYA_LOCATION}" - "${MAYA_BASE_DIR}" - PATH_SUFFIXES - devkit/mayaUsd/lib - lib/ - DOC - "MayaUSD's libraries path" - ) -elseif(WIN32) - find_path(MAYAUSD_LIBRARY_DIR - mayaUsd.lib - HINTS - "${MAYAUSD_LIB_ROOT}" - "$ENV{MAYAUSD_LIB_ROOT}" - "${MAYA_DEVKIT_LOCATION}" - "${MAYA_LOCATION}" - "$ENV{MAYA_LOCATION}" - "${MAYA_BASE_DIR}" - PATH_SUFFIXES - devkit/mayaUsd/lib - lib/ - DOC - "MayaUSD's libraries path" - ) -endif() - -find_path(MAYAUSD_INCLUDE_DIR - mayaUsd/mayaUsd.h - HINTS - "${MAYAUSD_INCLUDE_ROOT}" - "$ENV{MAYAUSD_INCLUDE_ROOT}" - "${MAYA_DEVKIT_LOCATION}" - "${MAYA_LOCATION}" - "$ENV{MAYA_LOCATION}" - "${MAYA_BASE_DIR}" - PATH_SUFFIXES - devkit/mayaUsd/include - include/ - DOC - "MayaUSD's headers path" -) - -# Use find_library to account for platform-specific library name prefixes -# (e.g. lib) and suffixes (e.g. .lib, .so, .dylib). -foreach(MAYAUSD_LIB mayaUsd) - - find_library(MAYAUSD_LIBRARY - NAMES - ${MAYAUSD_LIB} - PATHS - ${MAYAUSD_LIBRARY_DIR} - NO_DEFAULT_PATH - ) - - if (MAYAUSD_LIBRARY) - list(APPEND MAYAUSD_LIBRARIES ${MAYAUSD_LIBRARY}) - endif() - -endforeach(MAYAUSD_LIB) - -# If we weren't passed in the MayaUsd version info, read it from the header file. -if (NOT DEFINED MAYAUSD_MAJOR_VERSION) - file(READ ${MAYAUSD_INCLUDE_DIR}/mayaUsd/mayaUsd.h MAYAUSD_MAIN_HEADER) - string(REGEX REPLACE ".*\#define MAYAUSD_MAJOR_VERSION[ ]+([0-9]+).*" "\\1" MAYAUSD_MAJOR_VERSION ${MAYAUSD_MAIN_HEADER}) - string(REGEX REPLACE ".*\#define MAYAUSD_MINOR_VERSION[ ]+([0-9]+).*" "\\1" MAYAUSD_MINOR_VERSION ${MAYAUSD_MAIN_HEADER}) - string(REGEX REPLACE ".*\#define MAYAUSD_PATCH_LEVEL[ ]+([0-9]+).*" "\\1" MAYAUSD_PATCH_LEVEL ${MAYAUSD_MAIN_HEADER}) -endif() -set(MAYAUSD_VERSION "${MAYAUSD_MAJOR_VERSION}.${MAYAUSD_MINOR_VERSION}.${MAYAUSD_PATCH_LEVEL}") - -# handle the QUIETLY and REQUIRED arguments and set MAYAUSD_FOUND to TRUE if -# all listed variables are TRUE -include(FindPackageHandleStandardArgs) - -find_package_handle_standard_args(MAYAUSD - REQUIRED_VARS - MAYAUSD_INCLUDE_DIR - MAYAUSD_LIBRARIES - VERSION_VAR - MAYAUSD_VERSION -) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index cf886c389a..df8f43d537 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -461,3 +461,9 @@ endif() if(IS_MACOSX) set(_macDef OSMac_) endif() + +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "lib") + mayaUsd_add_rpath(rpath "../../..") + mayaUsd_install_rpath(rpath ${PYTHON_LIBRARY_NAME}) +endif() \ No newline at end of file diff --git a/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt b/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt index 947af2cec4..b743d6ecaf 100644 --- a/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt +++ b/plugin/al/plugin/AL_USDMayaPlugin/CMakeLists.txt @@ -30,29 +30,27 @@ target_include_directories(${PXR_PACKAGE} "../../lib/AL_USDMaya" ) -if(MSVC) - set_target_properties(${PXR_PACKAGE} PROPERTIES SUFFIX ".mll") -elseif(APPLE) - set_target_properties(${PXR_PACKAGE} - PROPERTIES - PREFIX "" - SUFFIX ".bundle" - MACOSX_RPATH TRUE - INSTALL_RPATH_USE_LINK_PATH TRUE - INSTALL_RPATH "@loader_path/../lib;@loader_path/../../pxr/lib;@loader_path/../../pxr/maya/lib;${PXR_USD_LOCATION}/lib" - ) -else() - # This puts the paths into RUNPATH instead of RPATH. - # They are the same except that RPATH is searched before LD_LIBRARY_PATH whereas RUNPATH is searched after. - # Using RUNPATH will give users the ability to override a shared library. - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--enable-new-dtags") +MAYA_SET_PLUGIN_PROPERTIES(${PXR_PACKAGE}) - set_target_properties(${PXR_PACKAGE} - PROPERTIES - PREFIX "" - INSTALL_RPATH_USE_LINK_PATH TRUE - INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/../../pxr/lib:$ORIGIN/../../pxr/maya/lib:${PXR_USD_LOCATION}/lib:${PXR_USD_LOCATION}/lib64" - ) +# rpath setup +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "plugin") + mayaUsd_add_rpath(rpath "../lib") + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/plugin/pxr/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib") + mayaUsd_install_rpath(rpath ${PXR_PACKAGE}) +endif() + +if(IS_LINUX) + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib64") + endif() endif() install(TARGETS diff --git a/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt b/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt index 3a21ad1b20..2bd7cd7843 100644 --- a/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt +++ b/plugin/al/plugin/AL_USDMayaTestPlugin/CMakeLists.txt @@ -85,24 +85,27 @@ PRIVATE ${MAYATEST_INCLUDE_LOCATION} ) -if(MSVC) - set_target_properties(${PXR_PACKAGE} PROPERTIES SUFFIX ".mll") -elseif(APPLE) - set_target_properties(${PXR_PACKAGE} - PROPERTIES - PREFIX "" - SUFFIX ".bundle" - MACOSX_RPATH TRUE - INSTALL_RPATH_USE_LINK_PATH TRUE - INSTALL_RPATH "@loader_path/../lib;@loader_path/../../pxr/lib;@loader_path/../../pxr/maya/lib;${PXR_USD_LOCATION}/lib" - ) -else() - set_target_properties(${PXR_PACKAGE} - PROPERTIES - PREFIX "" - INSTALL_RPATH_USE_LINK_PATH TRUE - INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/../../pxr/lib:$ORIGIN/../../pxr/maya/lib:${PXR_USD_LOCATION}/lib:${PXR_USD_LOCATION}/lib64" - ) +MAYA_SET_PLUGIN_PROPERTIES(${PXR_PACKAGE}) + +# rpath setup +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "plugin") + mayaUsd_add_rpath(rpath "../lib") + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/plugin/pxr/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib") + mayaUsd_install_rpath(rpath ${PXR_PACKAGE}) +endif() + +if(IS_LINUX) + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib64") + endif() endif() install(TARGETS diff --git a/plugin/al/translators/CMakeLists.txt b/plugin/al/translators/CMakeLists.txt index bb8acd25cc..7adea48356 100644 --- a/plugin/al/translators/CMakeLists.txt +++ b/plugin/al/translators/CMakeLists.txt @@ -68,15 +68,26 @@ target_include_directories( ../lib/AL_USDMaya ) -set_target_properties(${TRANSLATORS_PACKAGE} - PROPERTIES COMPILE_DEFINITIONS - "MFB_TRANSLATORS_PACKAGE=${TRANSLATORS_PACKAGE};MFB_ALT_TRANSLATORS_PACKAGE=${TRANSLATORS_PACKAGE}" -) +# rpath setup +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "plugin") + mayaUsd_add_rpath(rpath "../lib") + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_install_rpath(rpath ${TRANSLATORS_PACKAGE}) +endif() -set_target_properties(${TRANSLATORS_PACKAGE} - PROPERTIES COMPILE_DEFINITIONS - "MFB_TRANSLATORS_PACKAGE=${TRANSLATORS_PACKAGE};MFB_ALT_TRANSLATORS_PACKAGE=${TRANSLATORS_PACKAGE}" - ) +if (IS_LINUX) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--enable-new-dtags" PARENT_SCOPE) + + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib64") + endif() +endif() target_link_libraries(${TRANSLATORS_PACKAGE} ${DEPENDANT_LIBRARIES}) diff --git a/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt b/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt index 58743f15da..67d49cd1f4 100644 --- a/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt +++ b/plugin/al/translators/pxrUsdTranslators/CMakeLists.txt @@ -30,24 +30,29 @@ add_library(${PXR_TRANSLATORS_PACKAGE} plugin.cpp ) -if(MSVC) - set_target_properties(${PXR_TRANSLATORS_PACKAGE} PROPERTIES SUFFIX ".mll") -elseif(APPLE) - set_target_properties(${PXR_TRANSLATORS_PACKAGE} - PROPERTIES - PREFIX "" - SUFFIX ".bundle" - MACOSX_RPATH TRUE - INSTALL_RPATH_USE_LINK_PATH TRUE - INSTALL_RPATH "@loader_path/../lib;@loader_path/../../pxr/lib;@loader_path/../../pxr/maya/lib;${PXR_USD_LOCATION}/lib" - ) -else() - set_target_properties(${PXR_TRANSLATORS_PACKAGE} - PROPERTIES - PREFIX "" - INSTALL_RPATH_USE_LINK_PATH TRUE - INSTALL_RPATH "$ORIGIN/../lib:$ORIGIN/../../pxr/lib:$ORIGIN/../../pxr/maya/lib:${PXR_USD_LOCATION}/lib:${PXR_USD_LOCATION}/lib64" - ) +MAYA_SET_PLUGIN_PROPERTIES(${PXR_TRANSLATORS_PACKAGE}) + +# rpath setup +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "plugin") + mayaUsd_add_rpath(rpath "../lib") + + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/plugin/pxr/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib") + mayaUsd_install_rpath(rpath ${PXR_TRANSLATORS_PACKAGE}) +endif() + +if (IS_LINUX) + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../USD/lib64") + endif() endif() target_include_directories( diff --git a/plugin/pxr/cmake/macros/Private.cmake b/plugin/pxr/cmake/macros/Private.cmake index 1e095ec7a9..05e2d6db5d 100644 --- a/plugin/pxr/cmake/macros/Private.cmake +++ b/plugin/pxr/cmake/macros/Private.cmake @@ -570,72 +570,6 @@ function(_pxr_enable_precompiled_header TARGET_NAME) endforeach() endfunction() -# Initialize a variable to accumulate an rpath. The origin is the -# RUNTIME DESTINATION of the target. If not absolute it's appended -# to CMAKE_INSTALL_PREFIX. -function(_pxr_init_rpath rpathRef origin) - if(NOT IS_ABSOLUTE ${origin}) - set(origin "${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX}/${origin}") - get_filename_component(origin "${origin}" REALPATH) - endif() - set(${rpathRef} "${origin}" PARENT_SCOPE) -endfunction() - -# Add a relative target path to the rpath. If target is absolute compute -# and add a relative path from the origin to the target. -function(_pxr_add_rpath rpathRef target) - if(IS_ABSOLUTE "${target}") - # Make target relative to $ORIGIN (which is the first element in - # rpath when initialized with _pxr_init_rpath()). - list(GET ${rpathRef} 0 origin) - file(RELATIVE_PATH - target - "${origin}" - "${target}" - ) - if("x${target}" STREQUAL "x") - set(target ".") - endif() - endif() - file(TO_CMAKE_PATH "${target}" target) - set(new_rpath "${${rpathRef}}") - list(APPEND new_rpath "$ORIGIN/${target}") - set(${rpathRef} "${new_rpath}" PARENT_SCOPE) -endfunction() - -function(_pxr_install_rpath rpathRef NAME) - # Get and remove the origin. - list(GET ${rpathRef} 0 origin) - set(rpath ${${rpathRef}}) - list(REMOVE_AT rpath 0) - - # Canonicalize and uniquify paths. - set(final "") - foreach(path ${rpath}) - # Replace $ORIGIN with @loader_path - if(APPLE) - if("${path}/" MATCHES "^[$]ORIGIN/") - # Replace with origin path. - string(REPLACE "$ORIGIN/" "@loader_path/" path "${path}/") - endif() - endif() - - # Strip trailing slashes. - string(REGEX REPLACE "/+$" "" path "${path}") - - # Ignore paths we already have. - if (NOT ";${final};" MATCHES ";${path};") - list(APPEND final "${path}") - endif() - endforeach() - - set_target_properties(${NAME} - PROPERTIES - INSTALL_RPATH_USE_LINK_PATH TRUE - INSTALL_RPATH "${final}" - ) -endfunction() - # Split the library (target) names in libs into internal-to-the-monolithic- # library and external-of-it lists. function(_pxr_split_libraries libs internal_result external_result) @@ -939,12 +873,32 @@ function(_pxr_python_module NAME) set(libInstallPrefix "lib/python/pxr/${pyModuleName}") # Python modules need to be able to access their corresponding - # wrapped library and the install lib directory. - _pxr_init_rpath(rpath "${libInstallPrefix}") - _pxr_add_rpath(rpath + # wrapped library and the install lib directory. The wrapped library + # is the C++ library (e.g. libusdMaya.so on Linux) for which we're + # providing Python wrappers (e.g. _usdMaya.so on Linux). + # Do not prepend INSTALL_DIR_SUFFIX, init_rpath does this for relative + # paths. + mayaUsd_init_rpath(rpath "${libInstallPrefix}") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${args_WRAPPED_LIB_INSTALL_PREFIX}") - _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") - _pxr_install_rpath(rpath ${LIBRARY_NAME}) + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + + # Add path for usd core + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + + if(IS_LINUX) + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../../USD/lib64") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib64") + endif() + endif() + + mayaUsd_install_rpath(rpath ${LIBRARY_NAME}) _get_folder("_python" folder) set_target_properties(${LIBRARY_NAME} @@ -1270,13 +1224,29 @@ function(_pxr_library NAME) # XXX -- May want some plugins to be baked into monolithic. _pxr_target_link_libraries(${NAME} ${args_LIBRARIES}) - # Rpath has libraries under the USD/third party prefix and the install prefix. - # The former is for helper libraries for a third party application and - # the latter for core USD libraries. - _pxr_init_rpath(rpath "${libInstallPrefix}") - _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX}/${PXR_INSTALL_SUBDIR}/lib") - _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX}/lib") - _pxr_install_rpath(rpath ${NAME}) + mayaUsd_init_rpath(rpath "${libInstallPrefix}") + # Add path for Pixar-specific Maya shared libraries. As of 1-Aug-2019, + # this is only the usdMaya shared library. + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${INSTALL_DIR_SUFFIX}/${PXR_INSTALL_SUBDIR}/lib") + # Add path for common mayaUsd shared libraries. As of 1-Aug-2019, this is + # only the mayaUsd shared library. + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../../USD/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + + if(IS_LINUX) + if(WANT_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../../../USD/lib64") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib64") + endif() + endif() + + mayaUsd_install_rpath(rpath ${NAME}) # # Set up the install. diff --git a/plugin/pxr/cmake/macros/Public.cmake b/plugin/pxr/cmake/macros/Public.cmake index 548da5d5d1..552f234fa3 100644 --- a/plugin/pxr/cmake/macros/Public.cmake +++ b/plugin/pxr/cmake/macros/Public.cmake @@ -157,7 +157,7 @@ function(pxr_library NAME) if(PXR_ENABLE_PYTHON_SUPPORT AND (args_PYMODULE_CPPFILES OR args_PYMODULE_FILES OR args_PYSIDE_UI_FILES)) _pxr_python_module( ${NAME} - WRAPPED_LIB_INSTALL_PREFIX "${libInstallPrefix}" + WRAPPED_LIB_INSTALL_PREFIX "plugin/pxr/${libInstallPrefix}" PYTHON_FILES ${args_PYMODULE_FILES} PYSIDE_UI_FILES ${args_PYSIDE_UI_FILES} CPPFILES ${args_PYMODULE_CPPFILES} @@ -261,9 +261,9 @@ function(pxr_build_test_shared_lib LIBRARY_NAME) # Find libraries under the install prefix, which has the core USD # libraries. - _pxr_init_rpath(rpath "tests/lib") - _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") - _pxr_install_rpath(rpath ${LIBRARY_NAME}) + mayaUsd_init_rpath(rpath "tests/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_install_rpath(rpath ${LIBRARY_NAME}) if (NOT bt_SOURCE_DIR) set(bt_SOURCE_DIR testenv) @@ -341,9 +341,9 @@ function(pxr_build_test TEST_NAME) # Find libraries under the install prefix, which has the core USD # libraries. - _pxr_init_rpath(rpath "tests") - _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") - _pxr_install_rpath(rpath ${TEST_NAME}) + mayaUsd_init_rpath(rpath "tests") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_install_rpath(rpath ${TEST_NAME}) # XXX -- We shouldn't have to install to run tests. install(TARGETS ${TEST_NAME} @@ -774,10 +774,10 @@ function(pxr_toplevel_epilogue) ${PXR_THREAD_LIBS} ) - _pxr_init_rpath(rpath "${libInstallPrefix}") - _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${PXR_INSTALL_SUBDIR}/lib") - _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") - _pxr_install_rpath(rpath usd_ms) + mayaUsd_init_rpath(rpath "${libInstallPrefix}") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${PXR_INSTALL_SUBDIR}/lib") + mayaUsd_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib") + mayaUsd_install_rpath(rpath usd_ms) endif() # Setup the plugins in the top epilogue to ensure that everybody has had a From 77425bc485a870c41a934f57d14ab76579105863 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Mon, 28 Oct 2019 13:55:30 -0400 Subject: [PATCH 08/15] Fix OSX build after rpath changes and enabling of AL tests build --- .gitignore | 5 ++++- lib/CMakeLists.txt | 6 ------ plugin/al/mayatest/AL/maya/test/CMakeLists.txt | 5 +++++ .../AL/maya/tests/mayaplugintest/CMakeLists.txt | 9 +++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 29b3afc258..62d3261474 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ *.cproject *.pydevproject *.settings/ +*.vs +CMakeSettings.json CMakeCache.txt CMakeLists.txt.user CMakeFiles/cmake.check_cache @@ -13,4 +15,5 @@ CMakeFiles/cmake.check_cache *.DS_Store *.xcuserstate *.pbxuser -*.swp \ No newline at end of file +*.swp +*.pyc \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index df8f43d537..cf886c389a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -461,9 +461,3 @@ endif() if(IS_MACOSX) set(_macDef OSMac_) endif() - -if(IS_MACOSX OR IS_LINUX) - mayaUsd_init_rpath(rpath "lib") - mayaUsd_add_rpath(rpath "../../..") - mayaUsd_install_rpath(rpath ${PYTHON_LIBRARY_NAME}) -endif() \ No newline at end of file diff --git a/plugin/al/mayatest/AL/maya/test/CMakeLists.txt b/plugin/al/mayatest/AL/maya/test/CMakeLists.txt index 9e5dfe1521..9e7a4bfc21 100755 --- a/plugin/al/mayatest/AL/maya/test/CMakeLists.txt +++ b/plugin/al/mayatest/AL/maya/test/CMakeLists.txt @@ -27,8 +27,13 @@ add_library(${MAYA_TEST_LIBRARY_NAME} ${maya_test_source} ) +if(IS_MACOSX) + set(_macDef OSMac_) +endif() + target_compile_definitions(${MAYA_TEST_LIBRARY_NAME} PRIVATE + ${_macDef} AL_MAYA_TEST_EXPORT ) diff --git a/plugin/al/mayautils/AL/maya/tests/mayaplugintest/CMakeLists.txt b/plugin/al/mayautils/AL/maya/tests/mayaplugintest/CMakeLists.txt index e16a952559..3691221786 100644 --- a/plugin/al/mayautils/AL/maya/tests/mayaplugintest/CMakeLists.txt +++ b/plugin/al/mayautils/AL/maya/tests/mayaplugintest/CMakeLists.txt @@ -22,6 +22,15 @@ add_library(${MAYAUTILS_TEST_LIBRARY_NAME} # Remove the lib prefix else Maya can't load the library SET_TARGET_PROPERTIES(${MAYAUTILS_TEST_LIBRARY_NAME} PROPERTIES PREFIX "") +if(IS_MACOSX) + set(_macDef OSMac_) +endif() + +target_compile_definitions(${MAYAUTILS_TEST_LIBRARY_NAME} + PRIVATE + ${_macDef} +) + target_link_libraries(${MAYAUTILS_TEST_LIBRARY_NAME} PRIVATE ${GTEST_LIBRARIES} From 4120aae35c0995241e7c901f34b24e40713b7c39 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Mon, 28 Oct 2019 15:24:27 -0400 Subject: [PATCH 09/15] Fix linux build --- plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt b/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt index 14a17921d5..993e62dfd4 100644 --- a/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt +++ b/plugin/al/schemas/AL/usd/schemas/maya/tests/CMakeLists.txt @@ -20,7 +20,6 @@ target_include_directories(testMayaSchemas target_link_libraries(testMayaSchemas AL_USDMayaSchemas - mayaUsd ${GTEST_LIBRARIES} sdf tf From f51d2f2ea28d6e7b23446bf1de599d5932d5c3b5 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Tue, 29 Oct 2019 11:28:02 -0400 Subject: [PATCH 10/15] Fix UFE crashes with AL regression tests --- lib/ufe/ProxyShapeHandler.cpp | 7 ++++++- lib/ufe/StagesSubject.cpp | 30 ++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/ufe/ProxyShapeHandler.cpp b/lib/ufe/ProxyShapeHandler.cpp index 8bf45bdfa4..a4272e23d3 100644 --- a/lib/ufe/ProxyShapeHandler.cpp +++ b/lib/ufe/ProxyShapeHandler.cpp @@ -60,7 +60,12 @@ std::vector ProxyShapeHandler::getAllNames() /*static*/ UsdStageWeakPtr ProxyShapeHandler::dagPathToStage(const std::string& dagPath) { - return UsdMayaQuery::GetPrim(dagPath).GetStage(); + UsdStageWeakPtr stagePtr; + + if (UsdPrim usdPrim = UsdMayaQuery::GetPrim(dagPath)) + stagePtr = usdPrim.GetStage(); + + return stagePtr; } /*static*/ diff --git a/lib/ufe/StagesSubject.cpp b/lib/ufe/StagesSubject.cpp index 87a52b4d27..12de877c64 100644 --- a/lib/ufe/StagesSubject.cpp +++ b/lib/ufe/StagesSubject.cpp @@ -180,16 +180,26 @@ void StagesSubject::stageChanged(UsdNotice::ObjectsChanged const& notice, UsdSta // These are considered as invalid null prims if (prim.IsValid() && !InPathChange::inPathChange()) { - if (prim.IsActive()) - { - auto notification = Ufe::ObjectAdd(Ufe::Hierarchy::createItem(ufePath)); - Ufe::Scene::notifyObjectAdd(notification); - } - else - { - auto notification = Ufe::ObjectPostDelete(Ufe::Hierarchy::createItem(ufePath)); - Ufe::Scene::notifyObjectDelete(notification); - } + auto sceneItem = Ufe::Hierarchy::createItem(ufePath); + + // AL LayerCommands.addSubLayer test will cause Maya to crash + // if we don't filter invalid sceneItems. This patch is provided + // to prevent crashes, but more investigation will have to be + // done to understand why ufePath in case of sub layer + // creation causes Ufe::Hierarchy::createItem to fail. + if (!sceneItem) + continue; + + if (prim.IsActive()) + { + auto notification = Ufe::ObjectAdd(sceneItem); + Ufe::Scene::notifyObjectAdd(notification); + } + else + { + auto notification = Ufe::ObjectPostDelete(sceneItem); + Ufe::Scene::notifyObjectDelete(notification); + } } } From a38b8455603e329d5bd09ebdb3ef9542ed1f6100 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Wed, 30 Oct 2019 11:16:20 -0400 Subject: [PATCH 11/15] Add new changes from refactoring_sandbox --- lib/CMakeLists.txt | 12 +-- lib/render/vp2RenderDelegate/material.cpp | 20 ++-- .../vp2ShaderFragments/FallbackCPVShader.xml | 12 +-- .../vp2ShaderFragments/FallbackShader.xml | 16 ++-- .../vp2ShaderFragments/Float4ToFloat3.xml | 47 ++++++++++ ...{float4ToFloat3.xml => Float4ToFloat4.xml} | 37 +++----- ...{float4ToFloat4.xml => Float4ToFloatW.xml} | 35 +++---- .../vp2ShaderFragments/Float4ToFloatX.xml | 47 ++++++++++ .../vp2ShaderFragments/Float4ToFloatY.xml | 47 ++++++++++ .../vp2ShaderFragments/Float4ToFloatZ.xml | 47 ++++++++++ .../vp2ShaderFragments/UsdPreviewSurface.xml | 8 +- .../UsdPrimvarReader_float.xml | 4 +- .../UsdPrimvarReader_float2.xml | 4 +- .../UsdPrimvarReader_float3.xml | 4 +- .../UsdPrimvarReader_float4.xml | 4 +- .../vp2ShaderFragments/float4ToFloatW.xml | 91 ------------------- .../vp2ShaderFragments/float4ToFloatX.xml | 91 ------------------- .../vp2ShaderFragments/float4ToFloatY.xml | 91 ------------------- .../vp2ShaderFragments/float4ToFloatZ.xml | 91 ------------------- .../vp2ShaderFragments/shaderFragments.cpp | 24 ++--- 20 files changed, 267 insertions(+), 465 deletions(-) create mode 100644 lib/render/vp2ShaderFragments/Float4ToFloat3.xml rename lib/render/vp2ShaderFragments/{float4ToFloat3.xml => Float4ToFloat4.xml} (60%) rename lib/render/vp2ShaderFragments/{float4ToFloat4.xml => Float4ToFloatW.xml} (52%) create mode 100644 lib/render/vp2ShaderFragments/Float4ToFloatX.xml create mode 100644 lib/render/vp2ShaderFragments/Float4ToFloatY.xml create mode 100644 lib/render/vp2ShaderFragments/Float4ToFloatZ.xml delete mode 100644 lib/render/vp2ShaderFragments/float4ToFloatW.xml delete mode 100644 lib/render/vp2ShaderFragments/float4ToFloatX.xml delete mode 100644 lib/render/vp2ShaderFragments/float4ToFloatY.xml delete mode 100644 lib/render/vp2ShaderFragments/float4ToFloatZ.xml diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index cf886c389a..2bf97ad6b3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -75,10 +75,10 @@ list(APPEND mayaUsd_src list(APPEND mayaUsdShaderFragments_xmls # Copied from pxrUsdPreviewSurface with change to support multiple lights - render/vp2ShaderFragments/float4ToFloatX.xml - render/vp2ShaderFragments/float4ToFloatY.xml - render/vp2ShaderFragments/float4ToFloatZ.xml - render/vp2ShaderFragments/float4ToFloatW.xml + render/vp2ShaderFragments/Float4ToFloatX.xml + render/vp2ShaderFragments/Float4ToFloatY.xml + render/vp2ShaderFragments/Float4ToFloatZ.xml + render/vp2ShaderFragments/Float4ToFloatW.xml render/vp2ShaderFragments/lightingContributions.xml render/vp2ShaderFragments/opacityToTransparency.xml render/vp2ShaderFragments/scaledDiffusePassThrough.xml @@ -89,8 +89,8 @@ list(APPEND mayaUsdShaderFragments_xmls # New fragments render/vp2ShaderFragments/FallbackCPVShader.xml render/vp2ShaderFragments/FallbackShader.xml - render/vp2ShaderFragments/float4ToFloat3.xml - render/vp2ShaderFragments/float4ToFloat4.xml + render/vp2ShaderFragments/Float4ToFloat3.xml + render/vp2ShaderFragments/Float4ToFloat4.xml render/vp2ShaderFragments/UsdPrimvarReader_float.xml render/vp2ShaderFragments/UsdPrimvarReader_float2.xml render/vp2ShaderFragments/UsdPrimvarReader_float3.xml diff --git a/lib/render/vp2RenderDelegate/material.cpp b/lib/render/vp2RenderDelegate/material.cpp index 8292ed3fe4..0671eec1ed 100644 --- a/lib/render/vp2RenderDelegate/material.cpp +++ b/lib/render/vp2RenderDelegate/material.cpp @@ -75,11 +75,11 @@ TF_DEFINE_PRIVATE_TOKENS( (z) (w) - (float4ToFloatX) - (float4ToFloatY) - (float4ToFloatZ) - (float4ToFloatW) - (float4ToFloat3) + (Float4ToFloatX) + (Float4ToFloatY) + (Float4ToFloatZ) + (Float4ToFloatW) + (Float4ToFloat3) ); //! Helper utility function to test whether a node is a UsdShade primvar reader. @@ -150,19 +150,19 @@ void _ApplyVP2Fixes(HdMaterialNetwork& outNet, const HdMaterialNetwork& inNet) TfToken passThroughId; if (rel.inputName == _tokens->rgb || rel.inputName == _tokens->xyz) { - passThroughId = _tokens->float4ToFloat3; + passThroughId = _tokens->Float4ToFloat3; } else if (rel.inputName == _tokens->r || rel.inputName == _tokens->x) { - passThroughId = _tokens->float4ToFloatX; + passThroughId = _tokens->Float4ToFloatX; } else if (rel.inputName == _tokens->g || rel.inputName == _tokens->y) { - passThroughId = _tokens->float4ToFloatY; + passThroughId = _tokens->Float4ToFloatY; } else if (rel.inputName == _tokens->b || rel.inputName == _tokens->z) { - passThroughId = _tokens->float4ToFloatZ; + passThroughId = _tokens->Float4ToFloatZ; } else if (rel.inputName == _tokens->a || rel.inputName == _tokens->w) { - passThroughId = _tokens->float4ToFloatW; + passThroughId = _tokens->Float4ToFloatW; } else { outNet.relationships.push_back(rel); diff --git a/lib/render/vp2ShaderFragments/FallbackCPVShader.xml b/lib/render/vp2ShaderFragments/FallbackCPVShader.xml index 3b28bfe9ca..6c3e59b467 100644 --- a/lib/render/vp2ShaderFragments/FallbackCPVShader.xml +++ b/lib/render/vp2ShaderFragments/FallbackCPVShader.xml @@ -2,16 +2,16 @@ - - + + - + - - - + + + diff --git a/lib/render/vp2ShaderFragments/FallbackShader.xml b/lib/render/vp2ShaderFragments/FallbackShader.xml index 9f55772de7..c3173cd742 100644 --- a/lib/render/vp2ShaderFragments/FallbackShader.xml +++ b/lib/render/vp2ShaderFragments/FallbackShader.xml @@ -2,16 +2,16 @@ - - - + + + - + - - - + + + @@ -48,7 +48,7 @@ - + diff --git a/lib/render/vp2ShaderFragments/Float4ToFloat3.xml b/lib/render/vp2ShaderFragments/Float4ToFloat3.xml new file mode 100644 index 0000000000..c839a11c00 --- /dev/null +++ b/lib/render/vp2ShaderFragments/Float4ToFloat3.xml @@ -0,0 +1,47 @@ + + + Extracts the XYZ component of a 4D float vector. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloat3.xml b/lib/render/vp2ShaderFragments/Float4ToFloat4.xml similarity index 60% rename from lib/render/vp2ShaderFragments/float4ToFloat3.xml rename to lib/render/vp2ShaderFragments/Float4ToFloat4.xml index f2f4a3463e..7279f9caa9 100644 --- a/lib/render/vp2ShaderFragments/float4ToFloat3.xml +++ b/lib/render/vp2ShaderFragments/Float4ToFloat4.xml @@ -1,58 +1,47 @@ - + - Extracts the XYZ component of a 4D float vector. + Passthrough of a 4D float vector. - + - + - + - + - - - - - - - \ No newline at end of file + diff --git a/lib/render/vp2ShaderFragments/float4ToFloat4.xml b/lib/render/vp2ShaderFragments/Float4ToFloatW.xml similarity index 52% rename from lib/render/vp2ShaderFragments/float4ToFloat4.xml rename to lib/render/vp2ShaderFragments/Float4ToFloatW.xml index d2c55c8509..22a2a2307e 100644 --- a/lib/render/vp2ShaderFragments/float4ToFloat4.xml +++ b/lib/render/vp2ShaderFragments/Float4ToFloatW.xml @@ -1,55 +1,44 @@ - + - Passthrough of a 4D float vector. + Extracts the W component of a 4D float vector. - + - + - + - + - - - - - - diff --git a/lib/render/vp2ShaderFragments/Float4ToFloatX.xml b/lib/render/vp2ShaderFragments/Float4ToFloatX.xml new file mode 100644 index 0000000000..2ad55df63e --- /dev/null +++ b/lib/render/vp2ShaderFragments/Float4ToFloatX.xml @@ -0,0 +1,47 @@ + + + Extracts the X component of a 4D float vector. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/Float4ToFloatY.xml b/lib/render/vp2ShaderFragments/Float4ToFloatY.xml new file mode 100644 index 0000000000..d9895ff573 --- /dev/null +++ b/lib/render/vp2ShaderFragments/Float4ToFloatY.xml @@ -0,0 +1,47 @@ + + + Extracts the Y component of a 4D float vector. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/Float4ToFloatZ.xml b/lib/render/vp2ShaderFragments/Float4ToFloatZ.xml new file mode 100644 index 0000000000..eff719b783 --- /dev/null +++ b/lib/render/vp2ShaderFragments/Float4ToFloatZ.xml @@ -0,0 +1,47 @@ + + + Extracts the Z component of a 4D float vector. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/render/vp2ShaderFragments/UsdPreviewSurface.xml b/lib/render/vp2ShaderFragments/UsdPreviewSurface.xml index f99998ed8a..68c6b2f228 100644 --- a/lib/render/vp2ShaderFragments/UsdPreviewSurface.xml +++ b/lib/render/vp2ShaderFragments/UsdPreviewSurface.xml @@ -5,10 +5,10 @@ - - - - + + + + diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml index bc00166e86..59ce5474ef 100644 --- a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float.xml @@ -13,9 +13,9 @@ diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml index 93d2e554de..55a404c58b 100644 --- a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float2.xml @@ -13,9 +13,9 @@ diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml index f58e0919f2..5d19d53735 100644 --- a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float3.xml @@ -13,9 +13,9 @@ diff --git a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml index 4d4bbfd3e4..55ba06bc35 100644 --- a/lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml +++ b/lib/render/vp2ShaderFragments/UsdPrimvarReader_float4.xml @@ -13,9 +13,9 @@ diff --git a/lib/render/vp2ShaderFragments/float4ToFloatW.xml b/lib/render/vp2ShaderFragments/float4ToFloatW.xml deleted file mode 100644 index 7c22175b03..0000000000 --- a/lib/render/vp2ShaderFragments/float4ToFloatW.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - Extracts the W component of a 4D float vector. - - - - - - - - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatW(float4 input) -{ - return input.w; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatW(float4 input) -{ - return input.w; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatW(float4 input) -{ - return input.w; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatW(float4 input) -{ - return input.w; -} - -]]> - - - - \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloatX.xml b/lib/render/vp2ShaderFragments/float4ToFloatX.xml deleted file mode 100644 index 2e41dddc20..0000000000 --- a/lib/render/vp2ShaderFragments/float4ToFloatX.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - Extracts the X component of a 4D float vector. - - - - - - - - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatX(float4 input) -{ - return input.x; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatX(float4 input) -{ - return input.x; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatX(float4 input) -{ - return input.x; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatX(float4 input) -{ - return input.x; -} - -]]> - - - - \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloatY.xml b/lib/render/vp2ShaderFragments/float4ToFloatY.xml deleted file mode 100644 index 9790797a08..0000000000 --- a/lib/render/vp2ShaderFragments/float4ToFloatY.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - Extracts the Y component of a 4D float vector. - - - - - - - - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatY(float4 input) -{ - return input.y; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatY(float4 input) -{ - return input.y; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatY(float4 input) -{ - return input.y; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatY(float4 input) -{ - return input.y; -} - -]]> - - - - \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/float4ToFloatZ.xml b/lib/render/vp2ShaderFragments/float4ToFloatZ.xml deleted file mode 100644 index ecb4b982d8..0000000000 --- a/lib/render/vp2ShaderFragments/float4ToFloatZ.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - Extracts the Z component of a 4D float vector. - - - - - - - - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatZ(float4 input) -{ - return input.z; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatZ(float4 input) -{ - return input.z; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatZ(float4 input) -{ - return input.z; -} - -]]> - - - - - - = 110) -#define float4 vec4 -#endif - -float -float4ToFloatZ(float4 input) -{ - return input.z; -} - -]]> - - - - \ No newline at end of file diff --git a/lib/render/vp2ShaderFragments/shaderFragments.cpp b/lib/render/vp2ShaderFragments/shaderFragments.cpp index f88d0d3732..1c70e14f0b 100644 --- a/lib/render/vp2ShaderFragments/shaderFragments.cpp +++ b/lib/render/vp2ShaderFragments/shaderFragments.cpp @@ -35,12 +35,12 @@ TF_DEFINE_PRIVATE_TOKENS( (FallbackCPVShader) (FallbackShader) - (float4ToFloatX) - (float4ToFloatY) - (float4ToFloatZ) - (float4ToFloatW) - (float4ToFloat3) - (float4ToFloat4) + (Float4ToFloatX) + (Float4ToFloatY) + (Float4ToFloatZ) + (Float4ToFloatW) + (Float4ToFloat3) + (Float4ToFloat4) (lightingContributions) (scaledDiffusePassThrough) @@ -67,12 +67,12 @@ static const TfTokenVector _FragmentNames = { _tokens->UsdPrimvarReader_float3, _tokens->UsdPrimvarReader_float4, - _tokens->float4ToFloatX, - _tokens->float4ToFloatY, - _tokens->float4ToFloatZ, - _tokens->float4ToFloatW, - _tokens->float4ToFloat3, - _tokens->float4ToFloat4, + _tokens->Float4ToFloatX, + _tokens->Float4ToFloatY, + _tokens->Float4ToFloatZ, + _tokens->Float4ToFloatW, + _tokens->Float4ToFloat3, + _tokens->Float4ToFloat4, _tokens->lightingContributions, _tokens->scaledDiffusePassThrough, From f9c76da931a883e362ac9001cc9c7d8e355122b7 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Mon, 11 Nov 2019 14:57:13 -0500 Subject: [PATCH 12/15] Get Pixar tests run on Windows --- build.py | 2 +- plugin/pxr/cmake/macros/Public.cmake | 14 +++++++--- plugin/pxr/cmake/macros/testWrapper.py | 6 ++-- .../usdMaya/testenv/testUsdMayaProxyShape.py | 28 ++++++++++--------- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/build.py b/build.py index 2143d9937a..0d6f6d141a 100644 --- a/build.py +++ b/build.py @@ -309,7 +309,7 @@ def RunCMake(context, extraArgs=None, stages=None): def RunCTest(context, extraArgs=None): buildDir = context.buildDir variant = BuildVariant(context) - numJobs = context.numJobs + numJobs = 1 with CurrentWorkingDirectory(buildDir): Run(context, diff --git a/plugin/pxr/cmake/macros/Public.cmake b/plugin/pxr/cmake/macros/Public.cmake index 552f234fa3..adf32b44c3 100644 --- a/plugin/pxr/cmake/macros/Public.cmake +++ b/plugin/pxr/cmake/macros/Public.cmake @@ -526,17 +526,21 @@ function(pxr_register_test TEST_NAME) set(_plugSearchPathEnvName ${PXR_OVERRIDE_PLUGINPATH_NAME}) endif() - set(testWrapperCmd ${testWrapperCmd} --env-var=${_plugSearchPathEnvName}=${CMAKE_INSTALL_PREFIX}/lib/usd) - set(testWrapperCmd ${testWrapperCmd} --env-var=PATH=${PXR_USD_LOCATION}/lib) - + set(_testPluginPath "${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/plugin;${CMAKE_INSTALL_PREFIX}/lib/usd") + set(_testPrePath "$ENV{PATH};${CMAKE_INSTALL_PREFIX}/plugin/pxr/maya/lib;${CMAKE_INSTALL_PREFIX}/lib") + # Ensure that Python imports the Python files built by this build. # On Windows convert backslash to slash and don't change semicolons # to colons. set(_testPythonPath "${CMAKE_INSTALL_PREFIX}/lib/python;${CMAKE_INSTALL_PREFIX}/plugin/pxr/lib/python;$ENV{PYTHONPATH}") if(WIN32) string(REGEX REPLACE "\\\\" "/" _testPythonPath "${_testPythonPath}") + string(REGEX REPLACE "\\\\" "/" _testPluginPath "${_testPluginPath}") + string(REGEX REPLACE "\\\\" "/" _testPrePath "${_testPrePath}") else() string(REPLACE ";" ":" _testPythonPath "${_testPythonPath}") + string(REPLACE ";" ":" _testPluginPath "${_testPluginPath}") + string(REPLACE ";" ":" _testPrePath "${_testPrePath}") endif() # Ensure we run with the appropriate python executable. @@ -551,7 +555,9 @@ function(pxr_register_test TEST_NAME) add_test( NAME ${TEST_NAME} COMMAND ${PYTHON_EXECUTABLE} ${testWrapperCmd} - "--env-var=PYTHONPATH=${_testPythonPath}" ${testCmd} + "--env-var=PYTHONPATH=${_testPythonPath}" + "--env-var=${_plugSearchPathEnvName}=${_testPluginPath}" + "--pre-path=${_testPrePath}" ${testCmd} ) # But in some cases, we need to pass cmake properties directly to cmake diff --git a/plugin/pxr/cmake/macros/testWrapper.py b/plugin/pxr/cmake/macros/testWrapper.py index ae8100beaa..2936383a1d 100644 --- a/plugin/pxr/cmake/macros/testWrapper.py +++ b/plugin/pxr/cmake/macros/testWrapper.py @@ -233,11 +233,11 @@ def _runCommand(raw_command, stdout_redir, stderr_redir, env, # Add any envvars specified with --env-var options into the environment env = os.environ.copy() for varStr in args.envVars: - if varStr == 'PATH': - sys.stderr.write("Error: use --pre-path or --post-path to edit PATH.") - sys.exit(1) try: k, v = varStr.split('=', 1) + if k == 'PATH': + sys.stderr.write("Error: use --pre-path or --post-path to edit PATH.") + sys.exit(1) v = v.replace('', testDir) env[k] = v except IndexError: diff --git a/plugin/pxr/maya/lib/usdMaya/testenv/testUsdMayaProxyShape.py b/plugin/pxr/maya/lib/usdMaya/testenv/testUsdMayaProxyShape.py index d533800c22..361a95be29 100644 --- a/plugin/pxr/maya/lib/usdMaya/testenv/testUsdMayaProxyShape.py +++ b/plugin/pxr/maya/lib/usdMaya/testenv/testUsdMayaProxyShape.py @@ -15,7 +15,7 @@ # limitations under the License. # -from pxr import Usd +from pxr import Usd, Tf from maya import cmds from maya import standalone @@ -42,21 +42,23 @@ def testProxyShapeBoundingBox(self): bboxSize = cmds.getAttr('Cube_usd.boundingBoxSize')[0] self.assertEqual(bboxSize, (1.0, 1.0, 1.0)) - # The proxy shape is imaged by the pxrHdImagingShape, which should be - # created by the proxy shape's postConstructor() method. Make sure the - # pxrHdImagingShape (and its parent transform) exist. - hdImagingTransformPath = '|HdImaging' - hdImagingShapePath = '%s|HdImagingShape' % hdImagingTransformPath + # The VP2 render delegate doesn't use additional proxy shape + if not Tf.GetEnvSetting('VP2_RENDER_DELEGATE_PROXY'): + # The proxy shape is imaged by the pxrHdImagingShape, which should be + # created by the proxy shape's postConstructor() method. Make sure the + # pxrHdImagingShape (and its parent transform) exist. + hdImagingTransformPath = '|HdImaging' + hdImagingShapePath = '%s|HdImagingShape' % hdImagingTransformPath - self.assertTrue(cmds.objExists(hdImagingTransformPath)) - self.assertEqual(cmds.nodeType(hdImagingTransformPath), 'transform') + self.assertTrue(cmds.objExists(hdImagingTransformPath)) + self.assertEqual(cmds.nodeType(hdImagingTransformPath), 'transform') - self.assertTrue(cmds.objExists(hdImagingShapePath)) - self.assertEqual(cmds.nodeType(hdImagingShapePath), 'pxrHdImagingShape') + self.assertTrue(cmds.objExists(hdImagingShapePath)) + self.assertEqual(cmds.nodeType(hdImagingShapePath), 'pxrHdImagingShape') - self.assertNotEqual( - cmds.ls(hdImagingTransformPath, uuid=True), - cmds.ls(hdImagingShapePath, uuid=True)) + self.assertNotEqual( + cmds.ls(hdImagingTransformPath, uuid=True), + cmds.ls(hdImagingShapePath, uuid=True)) # The pxrHdImagingShape and its parent transform are set so that they # do not write to the Maya scene file and are not exported by From 1585bcd82673a14d1e2507550d4c5dc8f6032d3e Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Mon, 11 Nov 2019 15:40:52 -0500 Subject: [PATCH 13/15] Address blockers for Pixar --- lib/CMakeLists.txt | 37 ----- lib/render/vp2RenderDelegate/mesh.cpp | 4 +- lib/ufe/wrapUsdSceneItem.cpp | 195 -------------------------- 3 files changed, 2 insertions(+), 234 deletions(-) delete mode 100644 lib/ufe/wrapUsdSceneItem.cpp diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2bf97ad6b3..df06d2a67f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -205,28 +205,6 @@ if(UFE_FOUND) endif() endif() -list(APPEND mayaUsdPxrUsdMayaGL_headers - render/pxrUsdMayaGL/batchRenderer.h - render/pxrUsdMayaGL/debugCodes.h - render/pxrUsdMayaGL/hdImagingShapeUI.h - render/pxrUsdMayaGL/hdImagingShapeDrawOverride.h - render/pxrUsdMayaGL/instancerImager.h - render/pxrUsdMayaGL/instancerShapeAdapter.h - render/pxrUsdMayaGL/proxyShapeUI.h - render/pxrUsdMayaGL/proxyDrawOverride.h - render/pxrUsdMayaGL/renderParams.h - render/pxrUsdMayaGL/sceneDelegate.h - render/pxrUsdMayaGL/shapeAdapter.h - render/pxrUsdMayaGL/softSelectHelper.h - render/pxrUsdMayaGL/usdProxyShapeAdapter.h - render/pxrUsdMayaGL/userData.h -) - -list(APPEND mayaUsdPxVP20_headers - render/px_vp20/utils.h - render/px_vp20/utils_legacy.h -) - add_library(${LIBRARY_NAME} SHARED ${mayaUsd_src} ) @@ -441,21 +419,6 @@ if(UFE_FOUND) DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/${PROJECT_NAME} ) - if (CMAKE_UFE_V2_FEATURES_AVAILABLE) - # Maya Attribute Editor python template files - install(FILES ae/mesh/ae_template.py - DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python/ufe_ae/usd/nodes/mesh - ) - # We need an empty __init__.py file in each subfolder so it is considered a python module. - set(_INIT_PY_DEST ${CMAKE_INSTALL_PREFIX}/lib/python/) - foreach(_SUBDIR ufe_ae usd nodes mesh) - string(APPEND _INIT_PY_DEST "/${_SUBDIR}") - install(FILES ae/__init__.py - DESTINATION ${_INIT_PY_DEST} - ) - endforeach() - endif() - endif() if(IS_MACOSX) diff --git a/lib/render/vp2RenderDelegate/mesh.cpp b/lib/render/vp2RenderDelegate/mesh.cpp index a317c813e8..b9f7e29f25 100644 --- a/lib/render/vp2RenderDelegate/mesh.cpp +++ b/lib/render/vp2RenderDelegate/mesh.cpp @@ -264,7 +264,7 @@ namespace { break; default: TF_CODING_ERROR("Invalid Hydra prim '%s': " - "unimplemented interpolation %zu for primvar %s", + "unimplemented interpolation %d for primvar %s", meshId.GetText(), (int)primvarInterp, primvarName.GetText()); break; } @@ -726,7 +726,7 @@ void HdVP2Mesh::_UpdateDrawItem( // Prepare normal buffer. if (drawItemData._normalsBuffer) { VtVec3fArray normals; - HdInterpolation interp; + HdInterpolation interp = HdInterpolationConstant; const auto it = _primvarSourceMap.find(HdTokens->normals); if (it != _primvarSourceMap.end()) { diff --git a/lib/ufe/wrapUsdSceneItem.cpp b/lib/ufe/wrapUsdSceneItem.cpp deleted file mode 100644 index 53b470d4ba..0000000000 --- a/lib/ufe/wrapUsdSceneItem.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// -// Copyright 2019 Autodesk -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - - - -// ***** NOTE ***** -// -// This is a WIP that is currently not used (as it doesn't work). We need to -// figure out how to create python bindings for our UsdSceneItem such that it -// derives from the Ufe::SceneItem python bindings. -// - - -#include "UsdSceneItem.h" - -#if 1 - -#include - -using namespace boost::python; -using namespace MayaUsd::ufe; - -struct ufe_SceneItem -{ - static void RegisterConversions() - { - // From Python - converter::registry::push_back( - &convertible, - &construct, - type_id()); - } - - // Determine if obj_ptr can be converted into a Ufe::SceneItem - static void* convertible(PyObject* obj_ptr) - { - // Can always put a python object into Ufe::SceneItem - if (0 == std::strcmp(obj_ptr->ob_type->tp_name, "ufe.PyUfe.SceneItem")) - return obj_ptr; - - // This causes infinite loop -// return extract(obj_ptr).check() ? obj_ptr : nullptr; - - // This causes infinite loop -// extract extractor(obj_ptr); -// return extractor.check() ? obj_ptr : nullptr; - } - - // Convert obj_ptr into a SceneItem - static void construct( - PyObject* obj_ptr, - converter::rvalue_from_python_stage1_data* data) - { - void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; - - // In-place construct the new SceneItem using the data extracted from the python object. - extract item(obj_ptr); - - // TEST - //Ufe::SceneItem::Ptr* ptr = static_cast(data->convertible); - - new (storage) Ufe::SceneItem::Ptr(item()); - data->convertible = storage; - - -// // Grab pointer to memory into which to construct the new Ufe::SceneItem. -// void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; -// -// // In-place construct the new SceneItem using the data extracted from the python object. -// Ufe::SceneItem::Ptr item = extract(obj_ptr); - -// void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; -// new (storage)Map(); -// data->convertible = storage; - -// void* storage = ((converter::rvalue_from_python_storage*)data)->storage.bytes; -// Usd_PyPrimRange pyIter = extract(obj_ptr); -// new (storage) UsdPrimRange(pyIter._rng); -// data->convertible = storage; - } -// static void -// _construct(PyObject *obj_ptr, -// converter::rvalue_from_python_stage1_data *data) { -// void *storage = -// ((converter::rvalue_from_python_storage*)data) -// ->storage.bytes; -// // Make a TfPyObjWrapper holding the Python object. -// new (storage) TfPyObjWrapper(object(borrowed(obj_ptr))); -// data->convertible = storage; -// } -}; - -UsdPrim primFromSceneItem(const Ufe::SceneItem::Ptr& item) -{ - UsdSceneItem::Ptr usdItem = std::dynamic_pointer_cast(item); - if (usdItem) - return usdItem->prim(); - - return UsdPrim(); -} - -BOOST_PYTHON_MODULE(ufe) -{ -// class_, boost::noncopyable >("UsdSceneItem", init()) -// import mayaUsd.ufe -// # Error: RuntimeError: file line 1: extension class wrapper for base class class Ufe_v0::SceneItem has not been created yet # - - class_("UsdSceneItem", init()) -// print dir(mayaUsd.ufe) -// ['UsdSceneItem', '__doc__', '__file__', '__name__', '__package__', 'primFromSceneItem'] - .def("create", &UsdSceneItem::create) - .staticmethod("create") - .def("prim", &UsdSceneItem::prim, return_value_policy()) - .def("nodeType", &UsdSceneItem::nodeType) - .def("runTimeId", &UsdSceneItem::runTimeId) - .def("path", &UsdSceneItem::path, return_value_policy()) - .def("nodeType", &UsdSceneItem::nodeType) - .def("isProperty", &UsdSceneItem::isProperty) - ; -// # Error: ArgumentError: file line 4: Python argument types in -// UsdSceneItem.__init__(UsdSceneItem, ufe.PyUfe.Path, Prim) -// did not match C++ signature: -// __init__(struct _object * __ptr64, class Ufe_v0::Path, class pxrInternal_v0_19__pxrReserved__::UsdPrim) # - - - def("primFromSceneItem", primFromSceneItem); -// # Error: ArgumentError: file line 1: Python argument types in -// mayaUsd.ufe.primFromSceneItem(ufe.PyUfe.SceneItem) -// did not match C++ signature: -// primFromSceneItem(class std::shared_ptr) # - - // Register the from-python converter - ufe_SceneItem::RegisterConversions(); -} - -#else - -#include - -static PyObject* helloworld(PyObject* self) { - return Py_BuildValue("s", "Hello, Python extensions!!"); -} - -static char helloworld_docs[] = - "helloworld( ): Any message you want to put here!!\n"; - -static PyObject *primFromSceneItem(PyObject *self, PyObject *args) -{ - PyObject* sceneItem = nullptr; - - if (PyArg_ParseTuple(args, "O", &sceneItem)) - { -// if (PyObject_TypeCheck(sceneItem, &MPyMObject_Type::typeObj())) - if (0 == std::strcmp(Py_TYPE(sceneItem)->tp_name, "ufe.PyUfe.SceneItem")) - { - Ufe::SceneItem::Ptr* item = (Ufe::SceneItem::Ptr*)sceneItem; - if (item) - { - Ufe::Path path = (*item)->path(); - std::string p = path.string(); - } - } - } - - /* Do something interesting here. */ - Py_RETURN_NONE; -} - -static PyMethodDef helloworld_funcs[] = { - {"helloworld", (PyCFunction)helloworld, METH_NOARGS, helloworld_docs}, - {"primFromSceneItem", (PyCFunction)primFromSceneItem, METH_VARARGS, NULL }, - {NULL} -}; - - -PyMODINIT_FUNC initufe() -{ - Py_InitModule3("ufe", helloworld_funcs, - "Extension module example!"); -} - -#endif From 0006397d674db249febb4fe5dd4a6d957123d412 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Mon, 11 Nov 2019 17:03:24 -0500 Subject: [PATCH 14/15] Add TODO --- build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/build.py b/build.py index 0d6f6d141a..2d4423e9b9 100644 --- a/build.py +++ b/build.py @@ -309,6 +309,7 @@ def RunCMake(context, extraArgs=None, stages=None): def RunCTest(context, extraArgs=None): buildDir = context.buildDir variant = BuildVariant(context) + #TODO we can't currently run tests in parallel, something to revisit. numJobs = 1 with CurrentWorkingDirectory(buildDir): From 6f34227d1b4e9e5dd143d0889376348e8257b1e3 Mon Sep 17 00:00:00 2001 From: Krystian Ligenza Date: Mon, 11 Nov 2019 18:35:56 -0500 Subject: [PATCH 15/15] Remove pyc files added by mistake --- .../AL/usd/transaction/tests/testTransaction.pyc | Bin 7286 -> 0 bytes .../transaction/tests/testTransactionManager.pyc | Bin 2293 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 plugin/al/usdtransaction/AL/usd/transaction/tests/testTransaction.pyc delete mode 100644 plugin/al/usdtransaction/AL/usd/transaction/tests/testTransactionManager.pyc diff --git a/plugin/al/usdtransaction/AL/usd/transaction/tests/testTransaction.pyc b/plugin/al/usdtransaction/AL/usd/transaction/tests/testTransaction.pyc deleted file mode 100644 index c25f4b14819859c36fd97dcd72eb93f8635a6b6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7286 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 b{z1kZ(&Oy#pU{WIP>wf6OYAu9?vD5gh{Vz4