From 68b861fe15cb0a27708481e5522245a78993de11 Mon Sep 17 00:00:00 2001 From: Aram Azhari Date: Mon, 9 Dec 2024 16:31:43 -0500 Subject: [PATCH] EMSUSD-1692 - Add LookdevXUsd Extension --- CMakeLists.txt | 36 +- cmake/modules/FindLookdevXUfe.cmake | 79 + doc/build.md | 2 + lib/CMakeLists.txt | 3 + lib/lookdevXUsd/CMakeLists.txt | 177 +++ lib/lookdevXUsd/Export.h | 27 + lib/lookdevXUsd/LookdevXUsd.cpp | 122 ++ lib/lookdevXUsd/LookdevXUsd.h | 22 + lib/lookdevXUsd/UsdCapabilityHandler.cpp | 31 + lib/lookdevXUsd/UsdCapabilityHandler.h | 34 + lib/lookdevXUsd/UsdClipboardHandler.cpp | 141 ++ lib/lookdevXUsd/UsdClipboardHandler.h | 47 + lib/lookdevXUsd/UsdComponentConnections.cpp | 44 + lib/lookdevXUsd/UsdComponentConnections.h | 49 + lib/lookdevXUsd/UsdConnectionCommands.cpp | 127 ++ lib/lookdevXUsd/UsdConnectionCommands.h | 121 ++ lib/lookdevXUsd/UsdDebugHandler.cpp | 64 + lib/lookdevXUsd/UsdDebugHandler.h | 60 + lib/lookdevXUsd/UsdDeleteCommand.cpp | 97 ++ lib/lookdevXUsd/UsdDeleteCommand.h | 52 + .../UsdExtendedAttributeHandler.cpp | 36 + lib/lookdevXUsd/UsdExtendedAttributeHandler.h | 47 + .../UsdExtendedConnectionHandler.cpp | 50 + .../UsdExtendedConnectionHandler.h | 59 + lib/lookdevXUsd/UsdFileHandler.cpp | 215 +++ lib/lookdevXUsd/UsdFileHandler.h | 47 + lib/lookdevXUsd/UsdHierarchy.cpp | 38 + lib/lookdevXUsd/UsdHierarchy.h | 126 ++ lib/lookdevXUsd/UsdHierarchyHandler.cpp | 56 + lib/lookdevXUsd/UsdHierarchyHandler.h | 65 + lib/lookdevXUsd/UsdLookdevHandler.cpp | 137 ++ lib/lookdevXUsd/UsdLookdevHandler.h | 54 + lib/lookdevXUsd/UsdMaterial.cpp | 131 ++ lib/lookdevXUsd/UsdMaterial.h | 53 + lib/lookdevXUsd/UsdMaterialCommands.cpp | 80 ++ lib/lookdevXUsd/UsdMaterialCommands.h | 54 + lib/lookdevXUsd/UsdMaterialHandler.cpp | 115 ++ lib/lookdevXUsd/UsdMaterialHandler.h | 63 + lib/lookdevXUsd/UsdMaterialValidator.cpp | 1269 +++++++++++++++++ lib/lookdevXUsd/UsdMaterialValidator.h | 106 ++ lib/lookdevXUsd/UsdSceneItemOps.cpp | 123 ++ lib/lookdevXUsd/UsdSceneItemOps.h | 62 + lib/lookdevXUsd/UsdSceneItemOpsHandler.cpp | 41 + lib/lookdevXUsd/UsdSceneItemOpsHandler.h | 46 + lib/lookdevXUsd/UsdSceneItemUI.cpp | 78 + lib/lookdevXUsd/UsdSceneItemUI.h | 59 + lib/lookdevXUsd/UsdSceneItemUIHandler.cpp | 39 + lib/lookdevXUsd/UsdSceneItemUIHandler.h | 48 + lib/lookdevXUsd/UsdSoloingHandler.cpp | 1025 +++++++++++++ lib/lookdevXUsd/UsdSoloingHandler.h | 58 + lib/lookdevXUsd/UsdUINodeGraphNode.cpp | 29 + lib/lookdevXUsd/UsdUINodeGraphNode.h | 110 ++ lib/lookdevXUsd/UsdUINodeGraphNodeHandler.cpp | 61 + lib/lookdevXUsd/UsdUINodeGraphNodeHandler.h | 55 + lib/lookdevXUsd/Utils.cpp | 85 ++ lib/lookdevXUsd/Utils.h | 42 + plugin/adsk/plugin/CMakeLists.txt | 6 + plugin/adsk/plugin/plugin.cpp | 6 + test/lib/ufe/CMakeLists.txt | 1 + test/lib/ufe/testAttribute.py | 3 + 60 files changed, 6106 insertions(+), 7 deletions(-) create mode 100644 cmake/modules/FindLookdevXUfe.cmake create mode 100644 lib/lookdevXUsd/CMakeLists.txt create mode 100644 lib/lookdevXUsd/Export.h create mode 100644 lib/lookdevXUsd/LookdevXUsd.cpp create mode 100644 lib/lookdevXUsd/LookdevXUsd.h create mode 100644 lib/lookdevXUsd/UsdCapabilityHandler.cpp create mode 100644 lib/lookdevXUsd/UsdCapabilityHandler.h create mode 100644 lib/lookdevXUsd/UsdClipboardHandler.cpp create mode 100644 lib/lookdevXUsd/UsdClipboardHandler.h create mode 100644 lib/lookdevXUsd/UsdComponentConnections.cpp create mode 100644 lib/lookdevXUsd/UsdComponentConnections.h create mode 100644 lib/lookdevXUsd/UsdConnectionCommands.cpp create mode 100644 lib/lookdevXUsd/UsdConnectionCommands.h create mode 100644 lib/lookdevXUsd/UsdDebugHandler.cpp create mode 100644 lib/lookdevXUsd/UsdDebugHandler.h create mode 100644 lib/lookdevXUsd/UsdDeleteCommand.cpp create mode 100644 lib/lookdevXUsd/UsdDeleteCommand.h create mode 100644 lib/lookdevXUsd/UsdExtendedAttributeHandler.cpp create mode 100644 lib/lookdevXUsd/UsdExtendedAttributeHandler.h create mode 100644 lib/lookdevXUsd/UsdExtendedConnectionHandler.cpp create mode 100644 lib/lookdevXUsd/UsdExtendedConnectionHandler.h create mode 100644 lib/lookdevXUsd/UsdFileHandler.cpp create mode 100644 lib/lookdevXUsd/UsdFileHandler.h create mode 100644 lib/lookdevXUsd/UsdHierarchy.cpp create mode 100644 lib/lookdevXUsd/UsdHierarchy.h create mode 100644 lib/lookdevXUsd/UsdHierarchyHandler.cpp create mode 100644 lib/lookdevXUsd/UsdHierarchyHandler.h create mode 100644 lib/lookdevXUsd/UsdLookdevHandler.cpp create mode 100644 lib/lookdevXUsd/UsdLookdevHandler.h create mode 100644 lib/lookdevXUsd/UsdMaterial.cpp create mode 100644 lib/lookdevXUsd/UsdMaterial.h create mode 100644 lib/lookdevXUsd/UsdMaterialCommands.cpp create mode 100644 lib/lookdevXUsd/UsdMaterialCommands.h create mode 100644 lib/lookdevXUsd/UsdMaterialHandler.cpp create mode 100644 lib/lookdevXUsd/UsdMaterialHandler.h create mode 100644 lib/lookdevXUsd/UsdMaterialValidator.cpp create mode 100644 lib/lookdevXUsd/UsdMaterialValidator.h create mode 100644 lib/lookdevXUsd/UsdSceneItemOps.cpp create mode 100644 lib/lookdevXUsd/UsdSceneItemOps.h create mode 100644 lib/lookdevXUsd/UsdSceneItemOpsHandler.cpp create mode 100644 lib/lookdevXUsd/UsdSceneItemOpsHandler.h create mode 100644 lib/lookdevXUsd/UsdSceneItemUI.cpp create mode 100644 lib/lookdevXUsd/UsdSceneItemUI.h create mode 100644 lib/lookdevXUsd/UsdSceneItemUIHandler.cpp create mode 100644 lib/lookdevXUsd/UsdSceneItemUIHandler.h create mode 100644 lib/lookdevXUsd/UsdSoloingHandler.cpp create mode 100644 lib/lookdevXUsd/UsdSoloingHandler.h create mode 100644 lib/lookdevXUsd/UsdUINodeGraphNode.cpp create mode 100644 lib/lookdevXUsd/UsdUINodeGraphNode.h create mode 100644 lib/lookdevXUsd/UsdUINodeGraphNodeHandler.cpp create mode 100644 lib/lookdevXUsd/UsdUINodeGraphNodeHandler.h create mode 100644 lib/lookdevXUsd/Utils.cpp create mode 100644 lib/lookdevXUsd/Utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c424718077..be25391bd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ project(maya-usd) #------------------------------------------------------------------------------ option(BUILD_MAYAUSD_LIBRARY "Build Core USD libraries." ON) option(BUILD_MAYAUSDAPI_LIBRARY "Build the mayaUsdAPI subset library that provides a stable versioned interface to mayaUsd for external plugins." ON) +option(BUILD_LOOKDEVXUSD_LIBRARY "Build LookdevXUsd library using LookdevXUfe." 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) @@ -128,6 +129,11 @@ if (BUILD_MAYAUSDAPI_LIBRARY) set(MAYAUSDAPI_VERSION "${MAYAUSDAPI_MAJOR_VERSION}.${MAYAUSDAPI_MINOR_VERSION}.${MAYAUSDAPI_PATCH_LEVEL}") endif() +if (BUILD_MAYAUSDAPI_LIBRARY) + if (NOT BUILD_MAYAUSD_LIBRARY) + message(FATAL_ERROR "Building mayaUsdAPI library requires MayaUsd core libraries to be built, please enable BUILD_MAYAUSD_LIBRARY.") + endif() +endif() if (DEFINED PYTHON_INCLUDE_DIR AND DEFINED PYTHON_LIBRARIES AND DEFINED Python_EXECUTABLE) SET(PYTHON_INCLUDE_DIRS "${PYTHON_INCLUDE_DIR}") @@ -226,6 +232,28 @@ if(BUILD_MAYAUSD_LIBRARY) endif() endif() +# Need LookdevXUfe/MayaUsdAPI when building LookdevXUsd. +if(BUILD_LOOKDEVXUSD_LIBRARY) + if(NOT BUILD_MAYAUSDAPI_LIBRARY) + message(FATAL_ERROR "Building LookdevXUsd requires MayaUsdAPI.") + endif() + + if(MAYA_APP_VERSION VERSION_GREATER 2025) + find_package(LookdevXUfe) # Optional component - if not found, disable LookdevXUsd. + else() + set(BUILD_LOOKDEVXUSD_LIBRARY OFF) + message(WARNING "Disabling LookdevXUsd: it is not supported by Maya ${MAYA_APP_VERSION}.") + endif() + if (LookdevXUfe_FOUND) + message(STATUS "Build LookdevXUsd with LookdevXUfe version: ${LookdevXUfe_VERSION}") + message(STATUS " LookdevXUfe include dir: ${LookdevXUfe_INCLUDE_DIR}") + message(STATUS " LookdevXUfe library : ${LookdevXUfe_LIBRARY}") + else() + set(BUILD_LOOKDEVXUSD_LIBRARY OFF) + message(WARNING "Disabling LookdevXUsd as LookdevXUfe was not found (in Maya devkit).") + endif() +endif() + if(BUILD_MAYAUSD_LIBRARY AND (USD_VERSION VERSION_GREATER_EQUAL "0.24.11")) # In USD v24.11 Pixar USD has completely removed Boost. # However MayaUsd is still using a few of the Boost components, so @@ -299,12 +327,6 @@ if (BUILD_PXR_PLUGIN OR BUILD_AL_PLUGIN OR BUILD_ADSK_PLUGIN) endif() endif() -if (BUILD_MAYAUSDAPI_LIBRARY) - if (NOT BUILD_MAYAUSD_LIBRARY) - message(FATAL_ERROR "Building mayaUsdAPI library requires MayaUsd core libraries to be built, please enable BUILD_MAYAUSD_LIBRARY.") - endif() -endif() - if (BUILD_PXR_PLUGIN) add_subdirectory(plugin/pxr) endif() @@ -371,6 +393,6 @@ endif() # Special file for handling boost vs pxr::boost #------------------------------------------------------------------------------ mayaUsd_promoteHeaderList(HEADERS pxr_python.h BASEDIR "NONE") -install(FILES ${CMAKE_BINARY_DIR}/include//pxr_python.h +install(FILES ${CMAKE_BINARY_DIR}/include/pxr_python.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include ) diff --git a/cmake/modules/FindLookdevXUfe.cmake b/cmake/modules/FindLookdevXUfe.cmake new file mode 100644 index 0000000000..3850c3efb6 --- /dev/null +++ b/cmake/modules/FindLookdevXUfe.cmake @@ -0,0 +1,79 @@ +# +# Simple module to find LookdevXUfe. +# +# This module searches for a valid LookdevXUfe installation in the Maya devkit. +# It searches for LookdevXUfe's libraries and include files. +# +# Variables that will be defined: +# LookdevXUfe_FOUND Defined if a LookdevXUfe installation has been detected +# LookdevXUfe_LIBRARY Path to LookdevXUfe library +# LookdevXUfe_INCLUDE_DIR Path to the LookdevXUfe include directory +# LookdevXUfe_VERSION LookdevXUfe version (major.minor.patch) from LookdevXUfe.h +# + +find_path(LookdevXUfe_INCLUDE_DIR + LookdevXUfe/LookdevXUfe.h + HINTS + $ENV{LOOKDEVXUFE_INCLUDE_ROOT} + ${LOOKDEVXUFE_INCLUDE_ROOT} + ${MAYA_DEVKIT_LOCATION} + $ENV{MAYA_DEVKIT_LOCATION} + ${MAYA_LOCATION} + $ENV{MAYA_LOCATION} + ${MAYA_BASE_DIR} + PATH_SUFFIXES + devkit/ufe/extensions/lookdevXUfe/include + include/ + DOC + "LookdevXUfe header path" +) + +# Get the LookdevXUfe_VERSION from LookdevXUfe.h +if(LookdevXUfe_INCLUDE_DIR AND EXISTS "${LookdevXUfe_INCLUDE_DIR}/LookdevXUfe/LookdevXUfe.h") + # Parse the file and get the three lines that have the version info. + file(STRINGS + "${LookdevXUfe_INCLUDE_DIR}/LookdevXUfe/LookdevXUfe.h" + _ldx_vers + REGEX "#define[ ]+(LOOKDEVXUFE_MAJOR_VERSION|LOOKDEVXUFE_MINOR_VERSION|LOOKDEVXUFE_PATCH_LEVEL)[ ]+[0-9]+$") + + # Then extract the number from each one. + foreach(_ldxufe_tmp ${_ldx_vers}) + if(_ldxufe_tmp MATCHES "#define[ ]+(LOOKDEVXUFE_MAJOR_VERSION|LOOKDEVXUFE_MINOR_VERSION|LOOKDEVXUFE_PATCH_LEVEL)[ ]+([0-9]+)$") + set(${CMAKE_MATCH_1} ${CMAKE_MATCH_2}) + endif() + endforeach() + set(LookdevXUfe_VERSION ${LOOKDEVXUFE_MAJOR_VERSION}.${LOOKDEVXUFE_MINOR_VERSION}.${LOOKDEVXUFE_PATCH_LEVEL}) +endif() + +find_library(LookdevXUfe_LIBRARY + NAMES + lookdevXUfe_${LOOKDEVXUFE_MAJOR_VERSION}_${LOOKDEVXUFE_MINOR_VERSION} + HINTS + $ENV{LOOKDEVXUFE_LIB_ROOT} + ${LOOKDEVXUFE_LIB_ROOT} + ${MAYA_DEVKIT_LOCATION} + $ENV{MAYA_DEVKIT_LOCATION} + ${MAYA_LOCATION} + $ENV{MAYA_LOCATION} + ${MAYA_BASE_DIR} + PATHS + ${UFE_LIBRARY_DIR} + PATH_SUFFIXES + devkit/ufe/extensions/lookdevXUfe/lib + lib/ + DOC + "LookdevXUfe library" + NO_DEFAULT_PATH +) + +# Handle the QUIETLY and REQUIRED arguments and set LookdevXUfe_FOUND to TRUE if +# all listed variables are TRUE. +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(LookdevXUfe + REQUIRED_VARS + LookdevXUfe_INCLUDE_DIR + LookdevXUfe_LIBRARY + VERSION_VAR + LookdevXUfe_VERSION +) diff --git a/doc/build.md b/doc/build.md index aaf91f7336..8a7f535539 100644 --- a/doc/build.md +++ b/doc/build.md @@ -120,6 +120,8 @@ c:\maya-usd> python build.py --maya-location "C:\Program Files\Autodesk\Maya2025 Name | Description | Default --- | --- | --- BUILD_MAYAUSD_LIBRARY | builds Core USD libraries. | ON +BUILD_MAYAUSDAPI_LIBRARY | Build the mayaUsdAPI subset library that provides a stable versioned interface to mayaUsd for external plugins. | ON +BUILD_LOOKDEVXUSD_LIBRARY | Build LookdevXUsd library using LookdevXUfe. | ON BUILD_ADSK_PLUGIN | builds 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 diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 946db5fb32..6a07ea92d2 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -6,3 +6,6 @@ endif() if (BUILD_MAYAUSDAPI_LIBRARY) add_subdirectory(mayaUsdAPI) endif() +if (BUILD_LOOKDEVXUSD_LIBRARY) + add_subdirectory(lookdevXUsd) +endif() diff --git a/lib/lookdevXUsd/CMakeLists.txt b/lib/lookdevXUsd/CMakeLists.txt new file mode 100644 index 0000000000..983d0e0d64 --- /dev/null +++ b/lib/lookdevXUsd/CMakeLists.txt @@ -0,0 +1,177 @@ +# +# Copyright 2024 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. +# + +project(lookdevXUsd) + +add_library(${PROJECT_NAME} SHARED) + +# ----------------------------------------------------------------------------- +# sources +# ----------------------------------------------------------------------------- +target_sources(${PROJECT_NAME} + PRIVATE + LookdevXUsd.cpp + UsdCapabilityHandler.cpp + UsdClipboardHandler.cpp + UsdComponentConnections.cpp + UsdConnectionCommands.cpp + UsdDebugHandler.cpp + UsdDeleteCommand.cpp + UsdExtendedAttributeHandler.cpp + UsdExtendedConnectionHandler.cpp + UsdFileHandler.cpp + UsdHierarchy.cpp + UsdHierarchyHandler.cpp + UsdLookdevHandler.cpp + UsdMaterial.cpp + UsdMaterialCommands.cpp + UsdMaterialHandler.cpp + UsdMaterialValidator.cpp + UsdSceneItemOps.cpp + UsdSceneItemOpsHandler.cpp + UsdSceneItemUI.cpp + UsdSceneItemUIHandler.cpp + UsdSoloingHandler.cpp + UsdUINodeGraphNode.cpp + UsdUINodeGraphNodeHandler.cpp + Utils.cpp +) + +set(HEADERS + Export.h + LookdevXUsd.h + UsdCapabilityHandler.h + UsdClipboardHandler.h + UsdComponentConnections.h + UsdConnectionCommands.h + UsdDebugHandler.h + UsdDeleteCommand.h + UsdExtendedAttributeHandler.h + UsdExtendedConnectionHandler.h + UsdFileHandler.h + UsdHierarchy.h + UsdHierarchyHandler.h + UsdLookdevHandler.h + UsdMaterial.h + UsdMaterialCommands.h + UsdMaterialHandler.h + UsdMaterialValidator.h + UsdSceneItemOps.h + UsdSceneItemOpsHandler.h + UsdSceneItemUI.h + UsdSceneItemUIHandler.h + UsdSoloingHandler.h + UsdUINodeGraphNode.h + UsdUINodeGraphNodeHandler.h + Utils.h +) + +# ----------------------------------------------------------------------------- +# Compiler configuration +# ----------------------------------------------------------------------------- +target_compile_definitions(${PROJECT_NAME} + PRIVATE + LOOKDEVX_USD_SHARED + PXR_VERSION=${PXR_VERSION} + MFB_PACKAGE_NAME="${PROJECT_NAME}" + MFB_ALT_PACKAGE_NAME="${PROJECT_NAME}" + MFB_PACKAGE_MODULE="${PROJECT_NAME}" + $<$:OSMac_> + $<$:LINUX> + # this flag is needed when building for Maya + $<$:WIN32> +) + +mayaUsd_compile_config(${PROJECT_NAME}) + +mayaUsd_promoteHeaderList( + HEADERS + ${HEADERS} + BASEDIR + ${PROJECT_NAME} +) + +# ----------------------------------------------------------------------------- +# include directories +# ----------------------------------------------------------------------------- +target_include_directories(${PROJECT_NAME} + PUBLIC + ${PXR_INCLUDE_DIRS} + ${UFE_INCLUDE_DIR} + ${LookdevXUfe_INCLUDE_DIR} + ${CMAKE_BINARY_DIR}/include +) + +# ----------------------------------------------------------------------------- +# link libraries +# ----------------------------------------------------------------------------- +target_link_libraries(${PROJECT_NAME} + PUBLIC + sdf + sdr + mayaUsdAPI + PRIVATE + usd + usdShade + usdUtils + usdUI + ${UFE_LIBRARY} + ${LookdevXUfe_LIBRARY} +) + +# ----------------------------------------------------------------------------- +# run-time search paths +# ----------------------------------------------------------------------------- +if(IS_MACOSX OR IS_LINUX) + mayaUsd_init_rpath(rpath "lib") + if(DEFINED MAYAUSD_TO_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../${MAYAUSD_TO_USD_RELATIVE_PATH}/lib") + elseif(DEFINED PXR_USD_LOCATION) + mayaUsd_add_rpath(rpath "${PXR_USD_LOCATION}/lib") + endif() + if (IS_LINUX AND DEFINED MAYAUSD_TO_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../${MAYAUSD_TO_USD_RELATIVE_PATH}/lib64") + endif() + if(IS_MACOSX AND DEFINED MAYAUSD_TO_USD_RELATIVE_PATH) + mayaUsd_add_rpath(rpath "../../../Maya.app/Contents/MacOS") + endif() + mayaUsd_install_rpath(rpath ${PROJECT_NAME}) +endif() + +# ----------------------------------------------------------------------------- +# install +# ----------------------------------------------------------------------------- + +# TODO - do we need to install the headers? +#install(FILES ${HEADERS} +# DESTINATION +# ${CMAKE_INSTALL_PREFIX}/include/${PROJECT_NAME} +#) + +install(TARGETS ${PROJECT_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() diff --git a/lib/lookdevXUsd/Export.h b/lib/lookdevXUsd/Export.h new file mode 100644 index 0000000000..8d0cff79be --- /dev/null +++ b/lib/lookdevXUsd/Export.h @@ -0,0 +1,27 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#ifndef LOOKDEVX_USD_EXPORT_H_ +#define LOOKDEVX_USD_EXPORT_H_ + +#if defined(_WIN32) +#if defined(LOOKDEVX_USD_SHARED) +#define LOOKDEVX_USD_EXPORT __declspec(dllexport) +#else +#define LOOKDEVX_USD_EXPORT __declspec(dllimport) +#endif +#elif defined(__GNUC__) +#define LOOKDEVX_USD_EXPORT __attribute__((visibility("default"))) +#else +#error "Unsupported platform." +#endif + +#endif diff --git a/lib/lookdevXUsd/LookdevXUsd.cpp b/lib/lookdevXUsd/LookdevXUsd.cpp new file mode 100644 index 0000000000..d859b3fa92 --- /dev/null +++ b/lib/lookdevXUsd/LookdevXUsd.cpp @@ -0,0 +1,122 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "LookdevXUsd.h" + +#include "UsdCapabilityHandler.h" +#include "UsdClipboardHandler.h" +#include "UsdDebugHandler.h" +#include "UsdExtendedAttributeHandler.h" +#include "UsdExtendedConnectionHandler.h" +#include "UsdFileHandler.h" +#include "UsdHierarchyHandler.h" +#include "UsdLookdevHandler.h" +#include "UsdMaterialHandler.h" +#include "UsdSceneItemUIHandler.h" +#include "UsdSceneItemOpsHandler.h" +#include "UsdSoloingHandler.h" +#include "UsdUINodeGraphNodeHandler.h" + +#include +#include +#include + +#include + +namespace +{ + +// Runtime ids (default 0 is invalid). +Ufe::Rtid mayaUsdRuntimeId = 0; +Ufe::SceneItemOpsHandler::Ptr mayaUsdSceneItemOpsHandler; + +} // namespace + +namespace LookdevXUsd +{ + +constexpr auto kMayaUsdRuntimeName = "USD"; + +void initialize() +{ + // New extension handlers. + try + { + mayaUsdRuntimeId = Ufe::RunTimeMgr::instance().getId(kMayaUsdRuntimeName); + } + catch (const std::exception&) + { + return; + } + + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdDebugHandler::id, UsdDebugHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdLookdevHandler::id, UsdLookdevHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdMaterialHandler::id, UsdMaterialHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdSoloingHandler::id, UsdSoloingHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdFileHandler::id, UsdFileHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdSceneItemUIHandler::id, + UsdSceneItemUIHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdExtendedAttributeHandler::id, + UsdExtendedAttributeHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdExtendedConnectionHandler::id, + UsdExtendedConnectionHandler::create()); + Ufe::RunTimeMgr::instance().registerHandler(mayaUsdRuntimeId, UsdCapabilityHandler::id, + UsdCapabilityHandler::create()); + + // Replacements/wrappers for existing handlers. + UsdUINodeGraphNodeHandler::registerHandler(mayaUsdRuntimeId); + UsdHierarchyHandler::registerHandler(mayaUsdRuntimeId); + UsdClipboardHandler::registerHandler(mayaUsdRuntimeId); + + mayaUsdSceneItemOpsHandler = Ufe::RunTimeMgr::instance().sceneItemOpsHandler(mayaUsdRuntimeId); + Ufe::RunTimeMgr::instance().setSceneItemOpsHandler( + mayaUsdRuntimeId, LookdevXUsd::UsdSceneItemOpsHandler::create(mayaUsdSceneItemOpsHandler)); + + // Force loading the Sdr library to preload the source of the NodeLibrary on the USD side. This will load the Arnold + // DLL if it is in the USD paths and initialize it for its nodes, which should result in a slight delay. + // + // Hopefully this will fix: + // - LOOKDEVX-2609: Module error when saving and reopening a scene file with USD data + // - The long library load wait when creating the first USD material or opening LookdevX on a USD tab + // + // Without re-causing: + // - LOOKDEVX-871: Arnold's Library isn't loaded while autoloading both LookdevX and Bifrost + // (should stay fixed since MAYA-130935 ensures we load LookdevX last in the bundle) + // + PXR_NS::SdrRegistry::GetInstance(); +} + +void uninitialize() +{ + // New extension handlers. + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdDebugHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdLookdevHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdMaterialHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdSoloingHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdFileHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdSceneItemUIHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdExtendedAttributeHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdExtendedConnectionHandler::id); + Ufe::RunTimeMgr::instance().unregisterHandler(mayaUsdRuntimeId, UsdCapabilityHandler::id); + + // Replacements/wrappers for existing handlers + UsdUINodeGraphNodeHandler::unregisterHandler(); + UsdHierarchyHandler::unregisterHandler(); + UsdClipboardHandler::unregisterHandler(); + + // Unregister the decorated MayaUsd handlers and restore the original ones. + auto& runTimeMgr = Ufe::RunTimeMgr::instance(); + if (runTimeMgr.hasId(mayaUsdRuntimeId)) { + runTimeMgr.setSceneItemOpsHandler(mayaUsdRuntimeId, mayaUsdSceneItemOpsHandler); + } + mayaUsdSceneItemOpsHandler.reset(); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/LookdevXUsd.h b/lib/lookdevXUsd/LookdevXUsd.h new file mode 100644 index 0000000000..687e6f2fb4 --- /dev/null +++ b/lib/lookdevXUsd/LookdevXUsd.h @@ -0,0 +1,22 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include "Export.h" + +namespace LookdevXUsd +{ + +LOOKDEVX_USD_EXPORT void initialize(); + +LOOKDEVX_USD_EXPORT void uninitialize(); + +} // namespace LookdevXUsd \ No newline at end of file diff --git a/lib/lookdevXUsd/UsdCapabilityHandler.cpp b/lib/lookdevXUsd/UsdCapabilityHandler.cpp new file mode 100644 index 0000000000..69917450ea --- /dev/null +++ b/lib/lookdevXUsd/UsdCapabilityHandler.cpp @@ -0,0 +1,31 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdCapabilityHandler.h" + +namespace LookdevXUsd +{ + +bool UsdCapabilityHandler::hasCapability(Capability capability) const +{ + switch (capability) + { + case Capability::kCanPromoteToMaterial: + case Capability::kCanPromoteInputAtTopLevel: + case Capability::kCanHaveNestedNodeGraphs: + return true; + case Capability::kCanUseCreateShaderCommandForComponentConnections: + return false; + } + return false; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdCapabilityHandler.h b/lib/lookdevXUsd/UsdCapabilityHandler.h new file mode 100644 index 0000000000..3294e165e0 --- /dev/null +++ b/lib/lookdevXUsd/UsdCapabilityHandler.h @@ -0,0 +1,34 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include + +namespace LookdevXUsd +{ + +/** + * @brief Provides an interface for the USD runtime specific capabilities. + */ +class UsdCapabilityHandler : public LookdevXUfe::CapabilityHandler +{ +public: + using Ptr = std::shared_ptr; + + static LookdevXUfe::CapabilityHandler::Ptr create() + { + return std::make_shared(); + } + + [[nodiscard]] bool hasCapability(Capability capability) const override; +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdClipboardHandler.cpp b/lib/lookdevXUsd/UsdClipboardHandler.cpp new file mode 100644 index 0000000000..9cbb9d0570 --- /dev/null +++ b/lib/lookdevXUsd/UsdClipboardHandler.cpp @@ -0,0 +1,141 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdClipboardHandler.h" + +#include +#include + +#include + +#include + +#include + +namespace +{ +bool isNodeGraph(const PXR_NS::UsdPrim& prim) +{ + return bool(PXR_NS::UsdShadeNodeGraph(prim)); +} + +bool isNonMaterial(const PXR_NS::UsdPrim& prim) +{ + return !PXR_NS::UsdShadeMaterial(prim); +} + +bool isMaterial(const PXR_NS::UsdPrim& prim) +{ + return bool(PXR_NS::UsdShadeMaterial(prim)); +} + +} // namespace + +namespace LookdevXUsd +{ +Ufe::ClipboardHandler::Ptr UsdClipboardHandler::m_wrappedClipboardHandler; +Ufe::Rtid UsdClipboardHandler::m_rtid; + +void UsdClipboardHandler::registerHandler(const Ufe::Rtid& rtId) +{ + if (m_wrappedClipboardHandler) + { + return; + } + + m_rtid = rtId; + auto& runTimeMgr = Ufe::RunTimeMgr::instance(); + m_wrappedClipboardHandler = runTimeMgr.clipboardHandler(m_rtid); + // Only register our override if there is a USD one registered. + // Thus the m_wrappedClipboardHandler is guaranteed to be non-null here. + if (m_wrappedClipboardHandler) + { + runTimeMgr.setClipboardHandler(m_rtid, std::make_shared()); + } +} + +void UsdClipboardHandler::unregisterHandler() +{ + if (m_wrappedClipboardHandler) + { + auto& runTimeMgr = Ufe::RunTimeMgr::instance(); + if (runTimeMgr.hasId(m_rtid)) + { + runTimeMgr.setClipboardHandler(m_rtid, m_wrappedClipboardHandler); + } + m_wrappedClipboardHandler.reset(); + } +} + +Ufe::UndoableCommand::Ptr UsdClipboardHandler::cutCmd_(const Ufe::Selection& selection) +{ + return m_wrappedClipboardHandler->cutCmd_(selection); +} + +Ufe::UndoableCommand::Ptr UsdClipboardHandler::copyCmd_(const Ufe::Selection& selection) +{ + return m_wrappedClipboardHandler->copyCmd_(selection); +} + +Ufe::PasteClipboardCommand::Ptr UsdClipboardHandler::pasteCmd_(const Ufe::SceneItem::Ptr& parentItem) +{ + return m_wrappedClipboardHandler->pasteCmd_(parentItem); +} + +Ufe::UndoableCommand::Ptr UsdClipboardHandler::pasteCmd_(const Ufe::Selection& parentItems) +{ + return m_wrappedClipboardHandler->pasteCmd_(parentItems); +} + +bool UsdClipboardHandler::hasMaterialToPasteImpl() +{ + return MayaUsdAPI::hasItemToPaste(m_wrappedClipboardHandler, &isMaterial); +} + +bool UsdClipboardHandler::hasItemsToPaste_() +{ + return m_wrappedClipboardHandler->hasItemsToPaste_(); +} + +bool UsdClipboardHandler::hasNodeGraphsToPasteImpl() +{ + return MayaUsdAPI::hasItemToPaste(m_wrappedClipboardHandler, &isNodeGraph); +} + +bool UsdClipboardHandler::hasNonMaterialToPasteImpl() +{ + return MayaUsdAPI::hasItemToPaste(m_wrappedClipboardHandler, &isNonMaterial); +} + +void UsdClipboardHandler::setClipboardPath(const std::string& clipboardPath) +{ + auto tmpPath = std::filesystem::path(clipboardPath); + tmpPath.append(clipboardFileName); + tmpPath.replace_extension("usda"); + MayaUsdAPI::setClipboardFilePath(m_wrappedClipboardHandler, tmpPath.string()); + MayaUsdAPI::setClipboardFileFormat(m_wrappedClipboardHandler, "usda"); +} + +bool UsdClipboardHandler::canBeCut_(const Ufe::SceneItem::Ptr& item) +{ + return m_wrappedClipboardHandler->canBeCut_(item); +} + +void UsdClipboardHandler::preCopy_() +{ + m_wrappedClipboardHandler->preCopy_(); +} + +void UsdClipboardHandler::preCut_() +{ + m_wrappedClipboardHandler->preCut_(); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdClipboardHandler.h b/lib/lookdevXUsd/UsdClipboardHandler.h new file mode 100644 index 0000000000..9c86d926aa --- /dev/null +++ b/lib/lookdevXUsd/UsdClipboardHandler.h @@ -0,0 +1,47 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include + +namespace LookdevXUsd +{ +/** + * @brief Clipboard handler. It handles the Clipboard. + */ +class UsdClipboardHandler : public LookdevXUfe::ClipboardHandler +{ +public: + UsdClipboardHandler() = default; + + static void registerHandler(const Ufe::Rtid& rtId); + static void unregisterHandler(); + + // UsdClipboardHandler overrides + [[nodiscard]] Ufe::UndoableCommand::Ptr cutCmd_(const Ufe::Selection& selection) override; + [[nodiscard]] Ufe::UndoableCommand::Ptr copyCmd_(const Ufe::Selection& selection) override; + [[nodiscard]] Ufe::PasteClipboardCommand::Ptr pasteCmd_(const Ufe::SceneItem::Ptr& parentItem) override; + [[nodiscard]] Ufe::UndoableCommand::Ptr pasteCmd_(const Ufe::Selection& parentItems) override; + [[nodiscard]] bool hasMaterialToPasteImpl() override; + [[nodiscard]] bool hasNonMaterialToPasteImpl() override; + [[nodiscard]] bool hasItemsToPaste_() override; + [[nodiscard]] bool hasNodeGraphsToPasteImpl() override; + void setClipboardPath(const std::string& clipboardPath) override; + [[nodiscard]] bool canBeCut_(const Ufe::SceneItem::Ptr& item) override; + void preCopy_() override; + void preCut_() override; + +private: + static Ufe::ClipboardHandler::Ptr m_wrappedClipboardHandler; + static Ufe::Rtid m_rtid; +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdComponentConnections.cpp b/lib/lookdevXUsd/UsdComponentConnections.cpp new file mode 100644 index 0000000000..2d261335e5 --- /dev/null +++ b/lib/lookdevXUsd/UsdComponentConnections.cpp @@ -0,0 +1,44 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdComponentConnections.h" + +#include +#include + +namespace LookdevXUsd +{ + +UsdComponentConnections::UsdComponentConnections(const Ufe::SceneItem::Ptr& item) : ComponentConnections(item) +{ +} + +UsdComponentConnections::Ptr UsdComponentConnections::create(const Ufe::SceneItem::Ptr& item) +{ + return std::make_shared(item); +} + +std::vector UsdComponentConnections::componentNames(const Ufe::Attribute::Ptr& attr) const +{ + return LookdevXUfe::UfeUtils::attributeComponentsAsStrings(attr); +} + +Ufe::Connections::Ptr UsdComponentConnections::connections(const Ufe::SceneItem::Ptr& sceneItem) const +{ + auto connHandler = Ufe::RunTimeMgr::instance().connectionHandler(sceneItem->runTimeId()); + if (!connHandler) + { + return nullptr; + } + return connHandler->sourceConnections(sceneItem); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdComponentConnections.h b/lib/lookdevXUsd/UsdComponentConnections.h new file mode 100644 index 0000000000..ded5e99424 --- /dev/null +++ b/lib/lookdevXUsd/UsdComponentConnections.h @@ -0,0 +1,49 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#ifndef USD_COMPONENT_CONNECTIONS_H +#define USD_COMPONENT_CONNECTIONS_H + +#include "Export.h" + +#include + +#include + +namespace LookdevXUsd +{ + +class LOOKDEVX_USD_EXPORT UsdComponentConnections : public LookdevXUfe::ComponentConnections +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdComponentConnections(const Ufe::SceneItem::Ptr& item); + ~UsdComponentConnections() override = default; + + //@{ + // Delete the copy/move constructors assignment operators. + UsdComponentConnections(const UsdComponentConnections&) = delete; + UsdComponentConnections& operator=(const UsdComponentConnections&) = delete; + UsdComponentConnections(UsdComponentConnections&&) = delete; + UsdComponentConnections& operator=(UsdComponentConnections&&) = delete; + //@} + + static UsdComponentConnections::Ptr create(const Ufe::SceneItem::Ptr& item); + + [[nodiscard]] std::vector componentNames(const Ufe::Attribute::Ptr& attr) const override; + +protected: + [[nodiscard]] Ufe::Connections::Ptr connections(const Ufe::SceneItem::Ptr& sceneItem) const override; +}; // UsdComponentConnections + +} // namespace LookdevXUsd + +#endif diff --git a/lib/lookdevXUsd/UsdConnectionCommands.cpp b/lib/lookdevXUsd/UsdConnectionCommands.cpp new file mode 100644 index 0000000000..ab0d3255ee --- /dev/null +++ b/lib/lookdevXUsd/UsdConnectionCommands.cpp @@ -0,0 +1,127 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdConnectionCommands.h" + +#include + +namespace LookdevXUsd +{ + +UsdCreateConnectionCommand::~UsdCreateConnectionCommand() = default; + +// Public for std::make_shared() access, use create() instead. +UsdCreateConnectionCommand::UsdCreateConnectionCommand(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) + : m_srcInfo(std::make_unique(srcAttr, srcComponent)), + m_dstInfo(std::make_unique(dstAttr, dstComponent)) +{ +} + +UsdCreateConnectionCommand::Ptr UsdCreateConnectionCommand::create(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) +{ + if (!srcComponent.empty() || !dstComponent.empty()) + { + throwIfSceneItemsNotComponentConnectable(srcAttr->sceneItem(), dstAttr->sceneItem()); + } + + const auto srcComponentNames = LookdevXUfe::UfeUtils::attributeComponentsAsStrings(srcAttr); + if (!srcComponent.empty() && + std::find(srcComponentNames.begin(), srcComponentNames.end(), srcComponent) == srcComponentNames.end()) + { + throw std::runtime_error("Connecting source attribute: '" + srcAttr->name() + "' component: '" + srcComponent + + "' is currently unsupported."); + } + const auto dstComponentNames = LookdevXUfe::UfeUtils::attributeComponentsAsStrings(dstAttr); + if (!dstComponent.empty() && + std::find(dstComponentNames.begin(), dstComponentNames.end(), dstComponent) == dstComponentNames.end()) + { + throw std::runtime_error("Connecting destination attribute: '" + dstAttr->name() + "' component: '" + + dstComponent + "' is currently unsupported."); + } + + return std::make_shared(srcAttr, srcComponent, dstAttr, dstComponent); +} + +void UsdCreateConnectionCommand::execute() +{ + const MayaUsdAPI::UsdUndoBlock undoBlock(&m_undoableItem); + + createConnection(*m_srcInfo, *m_dstInfo); +} + +void UsdCreateConnectionCommand::undo() +{ + m_undoableItem.undo(); +} + +void UsdCreateConnectionCommand::redo() +{ + m_undoableItem.redo(); +} + +std::shared_ptr UsdCreateConnectionCommand::extendedConnection() const +{ + return std::make_shared(*m_srcInfo, *m_dstInfo); +} + +std::vector UsdCreateConnectionCommand::componentNames(const Ufe::Attribute::Ptr& attr) const +{ + return LookdevXUfe::UfeUtils::attributeComponentsAsStrings(attr); +} + +UsdDeleteConnectionCommand::~UsdDeleteConnectionCommand() = default; + +UsdDeleteConnectionCommand::UsdDeleteConnectionCommand(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) + : m_srcInfo(std::make_unique(srcAttr, srcComponent)), + m_dstInfo(std::make_unique(dstAttr, dstComponent)) +{ +} + +UsdDeleteConnectionCommand::Ptr UsdDeleteConnectionCommand::create(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) +{ + return std::make_shared(srcAttr, srcComponent, dstAttr, dstComponent); +} + +void UsdDeleteConnectionCommand::execute() +{ + const MayaUsdAPI::UsdUndoBlock undoBlock(&m_undoableItem); + + deleteConnection(*m_srcInfo, *m_dstInfo); +} + +void UsdDeleteConnectionCommand::undo() +{ + m_undoableItem.undo(); +} + +void UsdDeleteConnectionCommand::redo() +{ + m_undoableItem.redo(); +} + +std::vector UsdDeleteConnectionCommand::componentNames(const Ufe::Attribute::Ptr& attr) const +{ + return LookdevXUfe::UfeUtils::attributeComponentsAsStrings(attr); +} + +} // namespace LookdevXUsd \ No newline at end of file diff --git a/lib/lookdevXUsd/UsdConnectionCommands.h b/lib/lookdevXUsd/UsdConnectionCommands.h new file mode 100644 index 0000000000..160ac2ccb9 --- /dev/null +++ b/lib/lookdevXUsd/UsdConnectionCommands.h @@ -0,0 +1,121 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#ifndef USD_UNDO_CONNECTION_COMMAND_H +#define USD_UNDO_CONNECTION_COMMAND_H + +#include "Export.h" + +#include +#include +#include + +#include + +namespace LookdevXUsd +{ + +//! \brief UsdUndoConnectionCommand +class UsdCreateConnectionCommand : public LookdevXUfe::CreateConnectionResultCommand +{ +public: + using Ptr = std::shared_ptr; + + // Public for std::make_shared() access, use create() instead. + LOOKDEVX_USD_EXPORT UsdCreateConnectionCommand(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent); + LOOKDEVX_USD_EXPORT ~UsdCreateConnectionCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdCreateConnectionCommand(const UsdCreateConnectionCommand&) = delete; + UsdCreateConnectionCommand& operator=(const UsdCreateConnectionCommand&) = delete; + UsdCreateConnectionCommand(UsdCreateConnectionCommand&&) = delete; + UsdCreateConnectionCommand& operator=(UsdCreateConnectionCommand&&) = delete; + + //! Create a UsdCreateConnectionCommand object + LOOKDEVX_USD_EXPORT static UsdCreateConnectionCommand::Ptr create(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent); + + LOOKDEVX_USD_EXPORT void execute() override; + LOOKDEVX_USD_EXPORT void undo() override; + LOOKDEVX_USD_EXPORT void redo() override; + + [[nodiscard]] LOOKDEVX_USD_EXPORT std::shared_ptr extendedConnection() + const override; + + [[nodiscard]] LOOKDEVX_USD_EXPORT const std::unique_ptr& srcInfo() const + { + return m_srcInfo; + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT const std::unique_ptr& dstInfo() const + { + return m_dstInfo; + } + +protected: + [[nodiscard]] LOOKDEVX_USD_EXPORT std::vector componentNames( + const Ufe::Attribute::Ptr& attr) const override; + +private: + MayaUsdAPI::UsdUndoableItem m_undoableItem; + + std::unique_ptr m_srcInfo; + std::unique_ptr m_dstInfo; +}; + +//! \brief UsdUndoComponentConnectionCommand +class UsdDeleteConnectionCommand : public LookdevXUfe::DeleteConnectionCommand +{ +public: + using Ptr = std::shared_ptr; + + // Public for std::make_shared() access, use create() instead. + LOOKDEVX_USD_EXPORT UsdDeleteConnectionCommand(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent); + LOOKDEVX_USD_EXPORT ~UsdDeleteConnectionCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdDeleteConnectionCommand(const UsdDeleteConnectionCommand&) = delete; + UsdDeleteConnectionCommand& operator=(const UsdDeleteConnectionCommand&) = delete; + UsdDeleteConnectionCommand(UsdDeleteConnectionCommand&&) = delete; + UsdDeleteConnectionCommand& operator=(UsdDeleteConnectionCommand&&) = delete; + + //! Create a UsdHiddenCommand object + LOOKDEVX_USD_EXPORT static UsdDeleteConnectionCommand::Ptr create(const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent); + + LOOKDEVX_USD_EXPORT void execute() override; + LOOKDEVX_USD_EXPORT void undo() override; + LOOKDEVX_USD_EXPORT void redo() override; + +protected: + [[nodiscard]] LOOKDEVX_USD_EXPORT std::vector componentNames( + const Ufe::Attribute::Ptr& attr) const override; + +private: + MayaUsdAPI::UsdUndoableItem m_undoableItem; + + std::unique_ptr m_srcInfo; + std::unique_ptr m_dstInfo; +}; + +} // namespace LookdevXUsd + +#endif diff --git a/lib/lookdevXUsd/UsdDebugHandler.cpp b/lib/lookdevXUsd/UsdDebugHandler.cpp new file mode 100644 index 0000000000..9a4eb78e27 --- /dev/null +++ b/lib/lookdevXUsd/UsdDebugHandler.cpp @@ -0,0 +1,64 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdDebugHandler.h" + +#include +#include +#include + +#include + +#include + +namespace LookdevXUsd +{ + +std::string UsdDebugHandler::dumpPrim(const PXR_NS::UsdPrim& prim) +{ + // There's no export to string for primitives, so instead we grab the stage for + // the prim and then copy the isolated prim to an empty layer and export that + // layer to a string. + PXR_NS::UsdStagePtr shapeStage = prim.GetStage(); + PXR_NS::SdfLayerRefPtr sourceLayer = shapeStage->Flatten(); + PXR_NS::SdfLayerRefPtr primLayer = PXR_NS::SdfLayer::CreateAnonymous(); + + std::string isolatedPrimPath = prim.GetName(); + isolatedPrimPath = "/" + isolatedPrimPath; + PXR_NS::SdfCopySpec(sourceLayer, prim.GetPath(), primLayer, PXR_NS::SdfPath(isolatedPrimPath)); + + std::string primLayerString; + primLayer->ExportToString(&primLayerString); + return primLayerString; +} + +std::string UsdDebugHandler::exportToString(Ufe::SceneItem::Ptr sceneItem) +{ + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(sceneItem); + if (prim.IsValid()) + { + return dumpPrim(prim); + } + return ""; +} + +void UsdDebugHandler::runCommand(const std::string& /*command*/, + const std::unordered_map& /*args*/) +{ +} + +bool UsdDebugHandler::hasViewportSupport(const Ufe::NodeDef::Ptr& /*nodeDef*/) const +{ + // TODO(LOOKDEVX-2713): Add viewport support indication for USD. + // Currently MayaUSD doesn't render Arnold nodes in the viewport, please refer to the ticket for more details. + return true; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdDebugHandler.h b/lib/lookdevXUsd/UsdDebugHandler.h new file mode 100644 index 0000000000..4a12b9de20 --- /dev/null +++ b/lib/lookdevXUsd/UsdDebugHandler.h @@ -0,0 +1,60 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include + +#include + +#include + +#include + +namespace LookdevXUsd +{ + +class UsdDebugHandler : public LookdevXUfe::DebugHandler +{ +public: + /** + * @brief Export the data for a given scene item to a string. + * + * @param sceneItem scene item we want the data model information for + * @return string representation for the given scene item + */ + std::string exportToString(Ufe::SceneItem::Ptr sceneItem) override; + + /** + * @brief Run arbitrary commands in the runtime for debug/prototype purposes. + * + * @param command command identifier. + * @param args key/value pairs for optional arguments of the command. + */ + void runCommand(const std::string& command, const std::unordered_map& args = {}) override; + + static LookdevXUfe::DebugHandler::Ptr create() + { + return std::make_shared(); + } + + [[nodiscard]] bool hasViewportSupport(const Ufe::NodeDef::Ptr& nodeDef) const override; + +private: + /** + * @brief Dump the data to a string for a given USD primitive. + * + * @param prim primitive we want to get a string representation for + * @return string representation for the given primitive + */ + static std::string dumpPrim(const PXR_NS::UsdPrim& prim); +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdDeleteCommand.cpp b/lib/lookdevXUsd/UsdDeleteCommand.cpp new file mode 100644 index 0000000000..e05d8b88aa --- /dev/null +++ b/lib/lookdevXUsd/UsdDeleteCommand.cpp @@ -0,0 +1,97 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdDeleteCommand.h" + +#include + +#include + +using namespace PXR_NS; + +namespace LookdevXUsd +{ +UsdDeleteCommand::UsdDeleteCommand(Ufe::UndoableCommand::Ptr mayaUsdDeleteCmd, Ufe::SceneItem::Ptr item) + : m_mayaUsdDeleteCommand(std::move(mayaUsdDeleteCmd)), m_item(std::move(item)) +{ +} + +UsdDeleteCommand::~UsdDeleteCommand() = default; + +UsdDeleteCommand::Ptr UsdDeleteCommand::create(const Ufe::UndoableCommand::Ptr& mayaUsdDeleteCmd, + const Ufe::SceneItem::Ptr& item) +{ + return std::make_shared(mayaUsdDeleteCmd, item); +} + +void UsdDeleteCommand::execute() +{ + if (!TF_VERIFY(m_item, "Invalid item\n") || !TF_VERIFY(m_mayaUsdDeleteCommand, "Invalid MayaUsd Delete Cmd\n")) + { + return; + } + + // 1. Before deleting the prim, when possible, delete all component connections connected to and from the item to be + // deleted. + { + m_deleteComponentConnectionsCmd = LookdevXUfe::UfeUtils::deleteComponentConnections(m_item); + if (m_deleteComponentConnectionsCmd) + { + m_deleteComponentConnectionsCmd->execute(); + } + } + + // 2. Delete also, when possible, the converter nodes connected to the node to be deleted. + { + m_deleteAdskConverterConnectionsCmd + = LookdevXUfe::UfeUtils::deleteAdskConverterConnections(m_item); + if (m_deleteAdskConverterConnectionsCmd) + { + m_deleteAdskConverterConnectionsCmd->execute(); + } + } + + // 3. Delete the item with its regular connections. + m_mayaUsdDeleteCommand->execute(); +} + +void UsdDeleteCommand::undo() +{ + m_mayaUsdDeleteCommand->undo(); + + if (m_deleteAdskConverterConnectionsCmd) + { + m_deleteAdskConverterConnectionsCmd->undo(); + } + + if (m_deleteComponentConnectionsCmd) + { + m_deleteComponentConnectionsCmd->undo(); + } +} + +void UsdDeleteCommand::redo() +{ + // Redo the component connections first, then redo the delete command. We need to redo the component connections + // first, otherwise some of the properties from the combined and separated items needed by the deleted item + // are not found. + if (m_deleteComponentConnectionsCmd) + { + m_deleteComponentConnectionsCmd->redo(); + } + + if (m_deleteAdskConverterConnectionsCmd) + { + m_deleteAdskConverterConnectionsCmd->redo(); + } + + m_mayaUsdDeleteCommand->redo(); +} +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdDeleteCommand.h b/lib/lookdevXUsd/UsdDeleteCommand.h new file mode 100644 index 0000000000..f6cbfe7c8e --- /dev/null +++ b/lib/lookdevXUsd/UsdDeleteCommand.h @@ -0,0 +1,52 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#ifndef USD_DELETE_COMMAND_H +#define USD_DELETE_COMMAND_H + +#include + +#include +#include + +namespace LookdevXUsd +{ +//! \brief This command wraps the MayaUsd::ufe::UsdUndoDeleteCommand to incorporate additional behavior into it. +class UsdDeleteCommand : public Ufe::UndoableCommand +{ +public: + using Ptr = std::shared_ptr; + + UsdDeleteCommand(Ufe::UndoableCommand::Ptr mayaUsdDeleteCmd, Ufe::SceneItem::Ptr item); + ~UsdDeleteCommand() override; + + // Delete the copy/move constructors assignment operators. + UsdDeleteCommand(const UsdDeleteCommand&) = delete; + UsdDeleteCommand& operator=(const UsdDeleteCommand&) = delete; + UsdDeleteCommand(UsdDeleteCommand&&) = delete; + UsdDeleteCommand& operator=(UsdDeleteCommand&&) = delete; + + //! Create a UsdDeleteCommand. + static UsdDeleteCommand::Ptr create(const Ufe::UndoableCommand::Ptr& mayaUsdDeleteCmd, + const Ufe::SceneItem::Ptr& item); + + void execute() override; + void undo() override; + void redo() override; + +private: + Ufe::UndoableCommand::Ptr m_mayaUsdDeleteCommand; + Ufe::SceneItem::Ptr m_item; + std::shared_ptr m_deleteComponentConnectionsCmd; + std::shared_ptr m_deleteAdskConverterConnectionsCmd; +}; // UsdDeleteCommand + +} // namespace LookdevXUsd +#endif \ No newline at end of file diff --git a/lib/lookdevXUsd/UsdExtendedAttributeHandler.cpp b/lib/lookdevXUsd/UsdExtendedAttributeHandler.cpp new file mode 100644 index 0000000000..cc2c1f1969 --- /dev/null +++ b/lib/lookdevXUsd/UsdExtendedAttributeHandler.cpp @@ -0,0 +1,36 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdExtendedAttributeHandler.h" + +#include + +#include + +namespace LookdevXUsd +{ + +UsdExtendedAttributeHandler::Ptr UsdExtendedAttributeHandler::create() +{ + return std::make_shared(); +} + +bool UsdExtendedAttributeHandler::isAuthoredAttribute(const Ufe::Attribute::Ptr& attribute) const +{ + if (attribute && attribute->sceneItem() && MayaUsdAPI::isUsdSceneItem(attribute->sceneItem())) + { + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(attribute->sceneItem()); + return prim.GetAttribute(PXR_NS::TfToken(attribute->name())).IsAuthored(); + } + return false; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdExtendedAttributeHandler.h b/lib/lookdevXUsd/UsdExtendedAttributeHandler.h new file mode 100644 index 0000000000..01d69e08ab --- /dev/null +++ b/lib/lookdevXUsd/UsdExtendedAttributeHandler.h @@ -0,0 +1,47 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#ifndef USD_EXTENDED_ATTRIBUTE_HANDLER_H +#define USD_EXTENDED_ATTRIBUTE_HANDLER_H + +#include "Export.h" + +#include + +namespace LookdevXUsd +{ + +class LOOKDEVX_USD_EXPORT UsdExtendedAttributeHandler : public LookdevXUfe::ExtendedAttributeHandler +{ +public: + using Ptr = std::shared_ptr; + + //! Constructor. + UsdExtendedAttributeHandler() = default; + //! Destructor. + ~UsdExtendedAttributeHandler() override = default; + + //@{ + // Delete the copy/move constructors assignment operators. + UsdExtendedAttributeHandler(const UsdExtendedAttributeHandler&) = delete; + UsdExtendedAttributeHandler& operator=(const UsdExtendedAttributeHandler&) = delete; + UsdExtendedAttributeHandler(UsdExtendedAttributeHandler&&) = delete; + UsdExtendedAttributeHandler& operator=(UsdExtendedAttributeHandler&&) = delete; + //@} + + static UsdExtendedAttributeHandler::Ptr create(); + + [[nodiscard]] bool isAuthoredAttribute(const Ufe::Attribute::Ptr& attribute) const override; + +}; // UsdExtendedAttributeHandler + +} // namespace LookdevXUsd + +#endif diff --git a/lib/lookdevXUsd/UsdExtendedConnectionHandler.cpp b/lib/lookdevXUsd/UsdExtendedConnectionHandler.cpp new file mode 100644 index 0000000000..7e411b5e2a --- /dev/null +++ b/lib/lookdevXUsd/UsdExtendedConnectionHandler.cpp @@ -0,0 +1,50 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdExtendedConnectionHandler.h" +#include "UsdComponentConnections.h" +#include "UsdConnectionCommands.h" + +#include + +namespace LookdevXUsd +{ + +UsdExtendedConnectionHandler::Ptr UsdExtendedConnectionHandler::create() +{ + return std::make_shared(); +} + +LookdevXUfe::ComponentConnections::Ptr UsdExtendedConnectionHandler::sourceComponentConnections( + const Ufe::SceneItem::Ptr& item) const +{ + return UsdComponentConnections::create(item); +} + +std::shared_ptr UsdExtendedConnectionHandler::createConnectionCmd( + const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) const +{ + return UsdCreateConnectionCommand::create(srcAttr, srcComponent, dstAttr, dstComponent); +} + +std::shared_ptr UsdExtendedConnectionHandler::deleteConnectionCmd( + const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) const +{ + return UsdDeleteConnectionCommand::create(srcAttr, srcComponent, dstAttr, dstComponent); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdExtendedConnectionHandler.h b/lib/lookdevXUsd/UsdExtendedConnectionHandler.h new file mode 100644 index 0000000000..b1f593910c --- /dev/null +++ b/lib/lookdevXUsd/UsdExtendedConnectionHandler.h @@ -0,0 +1,59 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#ifndef USD_EXTENDED_CONNECTION_HANDLER_H +#define USD_EXTENDED_CONNECTION_HANDLER_H + +#include "Export.h" + +#include + +namespace LookdevXUsd +{ + +class LOOKDEVX_USD_EXPORT UsdExtendedConnectionHandler : public LookdevXUfe::ExtendedConnectionHandler +{ +public: + using Ptr = std::shared_ptr; + + //! Constructor. + UsdExtendedConnectionHandler() = default; + //! Destructor. + ~UsdExtendedConnectionHandler() override = default; + + //@{ + // Delete the copy/move constructors assignment operators. + UsdExtendedConnectionHandler(const UsdExtendedConnectionHandler&) = delete; + UsdExtendedConnectionHandler& operator=(const UsdExtendedConnectionHandler&) = delete; + UsdExtendedConnectionHandler(UsdExtendedConnectionHandler&&) = delete; + UsdExtendedConnectionHandler& operator=(UsdExtendedConnectionHandler&&) = delete; + //@} + + static UsdExtendedConnectionHandler::Ptr create(); + + [[nodiscard]] LookdevXUfe::ComponentConnections::Ptr sourceComponentConnections( + const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] std::shared_ptr createConnectionCmd( + const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) const override; + + [[nodiscard]] std::shared_ptr deleteConnectionCmd( + const Ufe::Attribute::Ptr& srcAttr, + const std::string& srcComponent, + const Ufe::Attribute::Ptr& dstAttr, + const std::string& dstComponent) const override; +}; // UsdExtendedConnectionHandler + +} // namespace LookdevXUsd + +#endif diff --git a/lib/lookdevXUsd/UsdFileHandler.cpp b/lib/lookdevXUsd/UsdFileHandler.cpp new file mode 100644 index 0000000000..2e58c41655 --- /dev/null +++ b/lib/lookdevXUsd/UsdFileHandler.cpp @@ -0,0 +1,215 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdFileHandler.h" + +#include + +#include + +#include + +#include +#include + +namespace LookdevXUsd +{ + +namespace +{ +std::string getRelativePath(const Ufe::AttributeFilename::Ptr& fnAttr, const std::string& path) +{ + if (fnAttr && fnAttr->sceneItem()->runTimeId() == MayaUsdAPI::getUsdRunTimeId()) + { + auto stage = MayaUsdAPI::usdStage(fnAttr); + if (stage && stage->GetEditTarget().GetLayer()) + { + const std::string layerDirPath = MayaUsdAPI::getDir(stage->GetEditTarget().GetLayer()->GetRealPath()); + + auto relativePathAndSuccess = MayaUsdAPI::makePathRelativeTo(path, layerDirPath); + + if (relativePathAndSuccess.second && relativePathAndSuccess.first != path) + { + return relativePathAndSuccess.first; + } + } + } + return path; +} + +// This function is basically: +// return prefix + middle + suffix; +// But in a way that allocates memory only once and keeps clang-tidy happy. +// See: performance-inefficient-string-concatenation in clang-tidy docs. +std::string buildFullPath(const std::string_view& prefix, + const std::string_view& middle, + const std::string_view& suffix) +{ + std::string path; + path.reserve(prefix.size() + middle.size() + suffix.size() + 1); + path.append(prefix); + path.append(middle); + path.append(suffix); + return path; +} + +std::string resolveUdimPath(const std::string& path, const Ufe::Attribute::Ptr& attribute) +{ + constexpr auto kUdimMin = 1001; + constexpr auto kUdimMax = 1100; + constexpr auto kUdimNumSize = 4; + constexpr std::string_view kUdimTag = ""; + + const auto udimPos = path.rfind(kUdimTag); + if (udimPos == std::string::npos) + { + return {}; + } + + const auto stage = MayaUsdAPI::usdStage(attribute); + if (!stage || !stage->GetEditTarget().GetLayer()) + { + return {}; + } + + // Going for minimal alloc code using string_view to the fullest :) + const auto udimPrefix = std::string_view{path.data(), udimPos}; + const auto udimEndPos = udimPos + kUdimTag.size(); + + const auto udimPostfix = + std::string_view{path.data() + static_cast(udimEndPos), path.size() - udimEndPos}; + + // Build numericPath once and re-use it in the loop: + auto numericPath = buildFullPath(udimPrefix, std::to_string(kUdimMin), udimPostfix); + const auto numericStart = numericPath.begin() + static_cast(udimPrefix.size()); + const auto numericEnd = numericStart + static_cast(kUdimNumSize); + + for (auto i = kUdimMin; i < kUdimMax; ++i) + { + numericPath.replace(numericStart, numericEnd, std::to_string(i)); + + const auto resolved = + PXR_NS::SdfComputeAssetPathRelativeToLayer(stage->GetEditTarget().GetLayer(), numericPath); + if (resolved.size() != numericPath.size()) + { + // Restore the tag in the resolved path: + const auto prefixLength = resolved.size() - udimPostfix.size() - kUdimNumSize; + return buildFullPath({resolved.data(), prefixLength}, kUdimTag, udimPostfix); + } + } + + return {}; +} + +} // namespace + +UsdFileHandler::UsdFileHandler() : LookdevXUfe::FileHandler() +{ +} + +UsdFileHandler::~UsdFileHandler() = default; + +std::string UsdFileHandler::getResolvedPath(const Ufe::AttributeFilename::Ptr& fnAttr) const +{ + if (fnAttr && fnAttr->sceneItem()->runTimeId() == MayaUsdAPI::getUsdRunTimeId()) + { + auto attributeType = MayaUsdAPI::usdAttributeType(fnAttr); + if (attributeType == PXR_NS::SdfValueTypeNames->Asset) + { + PXR_NS::VtValue vt; + if (MayaUsdAPI::getUsdValue(fnAttr, vt, MayaUsdAPI::getTime(fnAttr->sceneItem()->path())) && + vt.IsHolding()) + { + const auto& assetPath = vt.UncheckedGet(); + auto path = assetPath.GetResolvedPath(); + if (path.empty()) + { + path = resolveUdimPath(assetPath.GetAssetPath(), fnAttr); + } +#ifdef _WIN32 + std::replace(path.begin(), path.end(), '\\', '/'); +#endif + return path; + } + } + } + + return {}; +} + +Ufe::UndoableCommand::Ptr UsdFileHandler::convertPathToAbsoluteCmd(const Ufe::AttributeFilename::Ptr& fnAttr) const +{ + std::string storedPath = fnAttr->get(); + std::string absolutePath = getResolvedPath(fnAttr); + if (!absolutePath.empty() && absolutePath != storedPath) + { + return fnAttr->setCmd(absolutePath); + } + return {}; +} + +Ufe::UndoableCommand::Ptr UsdFileHandler::convertPathToRelativeCmd(const Ufe::AttributeFilename::Ptr& fnAttr) const +{ + auto relativePath = getRelativePath(fnAttr, fnAttr->get()); + if (!relativePath.empty() && relativePath != fnAttr->get()) + { + return fnAttr->setCmd(relativePath); + } + return {}; +} + +Ufe::UndoableCommand::Ptr UsdFileHandler::setPreferredPathCmd(const Ufe::AttributeFilename::Ptr& fnAttr, + const std::string& path) const +{ + if (MayaUsdAPI::requireUsdPathsRelativeToEditTargetLayer()) + { + auto relativePath = getRelativePath(fnAttr, path); + + if (!relativePath.empty() && relativePath != path) + { + return fnAttr->setCmd(LookdevXUfe::UfeUtils::insertUdimTagInFilename(relativePath)); + } + } + return fnAttr->setCmd(LookdevXUfe::UfeUtils::insertUdimTagInFilename(path)); +} + +std::string UsdFileHandler::openFileDialog(const Ufe::AttributeFilename::Ptr& fnAttr) const +{ + if (fnAttr && fnAttr->sceneItem()->runTimeId() == MayaUsdAPI::getUsdRunTimeId()) + { + auto fileHandler = LookdevXUfe::FileHandler::get(fnAttr->sceneItem()->path().popSegment().runTimeId()); + if (!fileHandler) + { + return {}; + } + + std::string relativeRoot; + auto stage = MayaUsdAPI::usdStage(fnAttr); + if (stage && stage->GetEditTarget().GetLayer()) + { + relativeRoot = MayaUsdAPI::getDir(stage->GetEditTarget().GetLayer()->GetRealPath()); + } + + // Delegate to the DCC FileHandler: + std::string pickedPath = fileHandler->openImageFileDialog(getResolvedPath(fnAttr), true, relativeRoot); + + // Mark the path as potentially relative if it was added in an anonymous layer: + if (stage && stage->GetEditTarget().GetLayer()) + { + pickedPath = MayaUsdAPI::handleAssetPathThatMaybeRelativeToLayer( + pickedPath, fnAttr->name(), stage->GetEditTarget().GetLayer(), + "mayaUsd_MakePathRelativeToImageEditTargetLayer"); + } + + return pickedPath; + } + return {}; +} +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdFileHandler.h b/lib/lookdevXUsd/UsdFileHandler.h new file mode 100644 index 0000000000..ed7111c805 --- /dev/null +++ b/lib/lookdevXUsd/UsdFileHandler.h @@ -0,0 +1,47 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include + +namespace LookdevXUsd +{ +/** + * @brief File handler. It handles the file specific operations. + */ +class UsdFileHandler : public LookdevXUfe::FileHandler +{ +public: + static LookdevXUfe::FileHandler::Ptr create() + { + return std::make_shared(); + } + + UsdFileHandler(); + ~UsdFileHandler() override; + + UsdFileHandler(const UsdFileHandler&) = delete; + UsdFileHandler& operator=(const UsdFileHandler&) = delete; + UsdFileHandler(UsdFileHandler&&) = delete; + UsdFileHandler& operator=(UsdFileHandler&&) = delete; + + // UsdFileHandler overrides + [[nodiscard]] std::string getResolvedPath(const Ufe::AttributeFilename::Ptr& fnAttr) const override; + [[nodiscard]] Ufe::UndoableCommand::Ptr convertPathToAbsoluteCmd( + const Ufe::AttributeFilename::Ptr& fnAttr) const override; + [[nodiscard]] Ufe::UndoableCommand::Ptr convertPathToRelativeCmd( + const Ufe::AttributeFilename::Ptr& fnAttr) const override; + [[nodiscard]] Ufe::UndoableCommand::Ptr setPreferredPathCmd(const Ufe::AttributeFilename::Ptr& fnAttr, + const std::string& path) const override; + [[nodiscard]] std::string openFileDialog(const Ufe::AttributeFilename::Ptr& fnAttr) const override; +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdHierarchy.cpp b/lib/lookdevXUsd/UsdHierarchy.cpp new file mode 100644 index 0000000000..5c8f5ab2bf --- /dev/null +++ b/lib/lookdevXUsd/UsdHierarchy.cpp @@ -0,0 +1,38 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdHierarchy.h" + +#include "LookdevXUfe/SceneItemUI.h" + +#include +#include + +namespace LookdevXUsd +{ + +UsdHierarchy::~UsdHierarchy() = default; + +Ufe::SceneItemList UsdHierarchy::filteredChildren(const ChildFilter& filter) const +{ + // Pass the filter unchanged to maya-usd, as it will not return anything on an unsupported filter. + auto wrappedFiltered = m_wrappedUsdHierarchy->filteredChildren(filter); + + // Do extra post filtering here. + Ufe::SceneItemList filtered; + std::copy_if(wrappedFiltered.begin(), wrappedFiltered.end(), std::back_inserter(filtered), [](const auto& item) { + const auto sceneItemUI = LookdevXUfe::SceneItemUI::sceneItemUI(item); + return !sceneItemUI || !sceneItemUI->hidden(); + }); + return filtered; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdHierarchy.h b/lib/lookdevXUsd/UsdHierarchy.h new file mode 100644 index 0000000000..25804bb525 --- /dev/null +++ b/lib/lookdevXUsd/UsdHierarchy.h @@ -0,0 +1,126 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#pragma once + +#include "Export.h" + +#include "LookdevXUfe/SoloingHandler.h" + +#include + +namespace LookdevXUsd +{ + +class UsdHierarchy : public Ufe::Hierarchy +{ +public: + using Ptr = std::shared_ptr; + + LOOKDEVX_USD_EXPORT explicit UsdHierarchy(Ufe::Hierarchy::Ptr wrappedUsdHierarchy) + : m_wrappedUsdHierarchy(std::move(wrappedUsdHierarchy)) + { + } + + LOOKDEVX_USD_EXPORT ~UsdHierarchy() override; + + UsdHierarchy(const UsdHierarchy&) = delete; + UsdHierarchy& operator=(const UsdHierarchy&) = delete; + UsdHierarchy(UsdHierarchy&&) = delete; + UsdHierarchy& operator=(UsdHierarchy&&) = delete; + + LOOKDEVX_USD_EXPORT static UsdHierarchy::Ptr create(const Ufe::Hierarchy::Ptr& wrappedMayaUsdHierarchy) + { + return std::make_shared(wrappedMayaUsdHierarchy); + } + + // Override to perform custom filtering. + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::SceneItemList filteredChildren(const ChildFilter& filter) const override; + + // Forward all the rest. + //----------------------------------------------------------------------------------- + + // LCOV_EXCL_START + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::SceneItem::Ptr sceneItem() const override + { + return m_wrappedUsdHierarchy->sceneItem(); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT bool hasChildren() const override + { + return m_wrappedUsdHierarchy->hasChildren(); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::SceneItemList children() const override + { + return m_wrappedUsdHierarchy->children(); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT bool hasFilteredChildren(const ChildFilter& filter) const override + { + return m_wrappedUsdHierarchy->hasFilteredChildren(filter); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::SceneItem::Ptr parent() const override + { + return m_wrappedUsdHierarchy->parent(); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::SceneItem::Ptr defaultParent() const override + { + return m_wrappedUsdHierarchy->defaultParent(); + } + + LOOKDEVX_USD_EXPORT Ufe::SceneItem::Ptr insertChild(const Ufe::SceneItem::Ptr& child, + const Ufe::SceneItem::Ptr& pos) override + { + return m_wrappedUsdHierarchy->insertChild(child, pos); + } + + LOOKDEVX_USD_EXPORT Ufe::InsertChildCommand::Ptr insertChildCmd(const Ufe::SceneItem::Ptr& child, + const Ufe::SceneItem::Ptr& pos) override + { + return m_wrappedUsdHierarchy->insertChildCmd(child, pos); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::SceneItem::Ptr createGroup(const Ufe::PathComponent& name) const override + { + return m_wrappedUsdHierarchy->createGroup(name); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::InsertChildCommand::Ptr createGroupCmd( + const Ufe::PathComponent& name) const override + { + return m_wrappedUsdHierarchy->createGroupCmd(name); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::UndoableCommand::Ptr reorderCmd( + const Ufe::SceneItemList& orderedList) const override + { + return m_wrappedUsdHierarchy->reorderCmd(orderedList); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::UndoableCommand::Ptr ungroupCmd() const override + { + return m_wrappedUsdHierarchy->ungroupCmd(); + } + + // LCOV_EXCL_STOP + + //----------------------------------------------------------------------------------- + +private: + const Ufe::Hierarchy::Ptr m_wrappedUsdHierarchy; + +}; // UsdHierarchy + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdHierarchyHandler.cpp b/lib/lookdevXUsd/UsdHierarchyHandler.cpp new file mode 100644 index 0000000000..d8d798e46b --- /dev/null +++ b/lib/lookdevXUsd/UsdHierarchyHandler.cpp @@ -0,0 +1,56 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdHierarchyHandler.h" +#include "UsdHierarchy.h" + +#include + +namespace LookdevXUsd +{ + +Ufe::HierarchyHandler::Ptr UsdHierarchyHandler::m_wrappedHierarchyHandler; +Ufe::Rtid UsdHierarchyHandler::m_rtid; + +UsdHierarchyHandler::~UsdHierarchyHandler() = default; + +void UsdHierarchyHandler::registerHandler(const Ufe::Rtid& rtId) +{ + if (m_wrappedHierarchyHandler) + { + return; + } + + m_rtid = rtId; + auto& runTimeMgr = Ufe::RunTimeMgr::instance(); + m_wrappedHierarchyHandler = runTimeMgr.hierarchyHandler(m_rtid); + runTimeMgr.setHierarchyHandler(m_rtid, std::make_shared()); +} + +void UsdHierarchyHandler::unregisterHandler() +{ + if (m_wrappedHierarchyHandler) + { + auto& runTimeMgr = Ufe::RunTimeMgr::instance(); + if (runTimeMgr.hasId(m_rtid)) + { + runTimeMgr.setHierarchyHandler(m_rtid, m_wrappedHierarchyHandler); + } + m_wrappedHierarchyHandler.reset(); + } +} + +Ufe::Hierarchy::Ptr UsdHierarchyHandler::hierarchy(const Ufe::SceneItem::Ptr& item) const +{ + return UsdHierarchy::create(m_wrappedHierarchyHandler->hierarchy(item)); +} + +} // namespace LookdevXUsd \ No newline at end of file diff --git a/lib/lookdevXUsd/UsdHierarchyHandler.h b/lib/lookdevXUsd/UsdHierarchyHandler.h new file mode 100644 index 0000000000..5665bd2469 --- /dev/null +++ b/lib/lookdevXUsd/UsdHierarchyHandler.h @@ -0,0 +1,65 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#pragma once + +#include +#include + +#include "UsdHierarchy.h" + +namespace LookdevXUsd +{ +class UsdHierarchyHandler : public Ufe::HierarchyHandler +{ +public: + using Ptr = std::shared_ptr; + + LOOKDEVX_USD_EXPORT UsdHierarchyHandler() = default; + LOOKDEVX_USD_EXPORT ~UsdHierarchyHandler() override; + + UsdHierarchyHandler(const UsdHierarchyHandler&) = delete; + UsdHierarchyHandler& operator=(const UsdHierarchyHandler&) = delete; + UsdHierarchyHandler(UsdHierarchyHandler&&) = delete; + UsdHierarchyHandler& operator=(UsdHierarchyHandler&&) = delete; + + LOOKDEVX_USD_EXPORT static void registerHandler(const Ufe::Rtid& rtId); + LOOKDEVX_USD_EXPORT static void unregisterHandler(); + + // Override to return custom hierarchy. + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::Hierarchy::Ptr hierarchy(const Ufe::SceneItem::Ptr& item) const override; + + // Forward all the rest. + //----------------------------------------------------------------------------------- + + // LCOV_EXCL_START + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::SceneItem::Ptr createItem(const Ufe::Path& path) const override + { + return m_wrappedHierarchyHandler->createItem(path); + } + + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::Hierarchy::ChildFilter childFilter() const override + { + return m_wrappedHierarchyHandler->childFilter(); + } + + // LCOV_EXCL_STOP + + //----------------------------------------------------------------------------------- + +private: + static Ufe::HierarchyHandler::Ptr m_wrappedHierarchyHandler; + static Ufe::Rtid m_rtid; + +}; // UsdHierarchyHandler + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdLookdevHandler.cpp b/lib/lookdevXUsd/UsdLookdevHandler.cpp new file mode 100644 index 0000000000..88cb71cec8 --- /dev/null +++ b/lib/lookdevXUsd/UsdLookdevHandler.cpp @@ -0,0 +1,137 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdLookdevHandler.h" + +#include "UsdMaterialCommands.h" + +#include + +namespace +{ + +// Some MayaUSD commands still extend the deprecated Ufe::InsertChildCommand instead of the new +// Ufe::SceneItemResultUndoableCommand. They are basically equivalent. Wrap a InsertChildCommand, so that it can be +// returned as a SceneItemResultUndoableCommand. +class WrapInsertChildCommand : public Ufe::SceneItemResultUndoableCommand +{ +public: + using Ptr = std::shared_ptr; + + explicit WrapInsertChildCommand(Ufe::InsertChildCommand::Ptr cmd) : m_wrappedCmd(std::move(cmd)) + { + } + + ~WrapInsertChildCommand() override = default; + + // Delete the copy/move constructors assignment operators. + WrapInsertChildCommand(const WrapInsertChildCommand&) = delete; + WrapInsertChildCommand& operator=(const WrapInsertChildCommand&) = delete; + WrapInsertChildCommand(WrapInsertChildCommand&&) = delete; + WrapInsertChildCommand& operator=(WrapInsertChildCommand&&) = delete; + + //! Create a WrapUsdUndoAddNewMaterialCommand. + static WrapInsertChildCommand::Ptr create(const Ufe::InsertChildCommand::Ptr& cmd) + { + return std::make_shared(cmd); + } + + [[nodiscard]] Ufe::SceneItem::Ptr sceneItem() const override + { + return m_wrappedCmd ? m_wrappedCmd->insertedChild() : nullptr; + } + + void execute() override + { + if (m_wrappedCmd) + { + m_wrappedCmd->execute(); + } + } + + void undo() override + { + if (m_wrappedCmd) + { + m_wrappedCmd->undo(); + } + } + + void redo() override + { + if (m_wrappedCmd) + { + m_wrappedCmd->redo(); + } + } + +private: + Ufe::InsertChildCommand::Ptr m_wrappedCmd; +}; // WrapInsertChildCommand + +} // namespace + +namespace LookdevXUsd +{ + +//------------------------------------------------------------------------------ +// UsdLookdevHandler overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItemResultUndoableCommand::Ptr UsdLookdevHandler::createLookdevContainerCmdImpl( + const Ufe::SceneItem::Ptr& parent, const Ufe::PathComponent& name) const +{ + if (!MayaUsdAPI::isUsdSceneItem(parent) || !MayaUsdAPI::getPrimForUsdSceneItem(parent).IsValid()) + { + return nullptr; + } + + return MayaUsdAPI::createAddNewPrimCommand(parent, name.string(), "Material"); +} + +Ufe::SceneItemResultUndoableCommand::Ptr UsdLookdevHandler::createLookdevContainerCmdImpl( + const Ufe::SceneItem::Ptr& parent, const Ufe::NodeDef::Ptr& nodeDef) const +{ + if (!MayaUsdAPI::isUsdSceneItem(parent) || !MayaUsdAPI::getPrimForUsdSceneItem(parent).IsValid()) + { + return nullptr; + } + + if (!nodeDef) + { + return nullptr; + } + + auto cmd = + std::dynamic_pointer_cast(MayaUsdAPI::addNewMaterialCommand(parent, nodeDef->type())); + if (!cmd) + { + return nullptr; + } + return WrapInsertChildCommand::create(cmd); +} + +Ufe::SceneItemResultUndoableCommand::Ptr UsdLookdevHandler::createLookdevEnvironmentCmdImpl( + const Ufe::SceneItem::Ptr& ancestor, Ufe::Rtid targetRunTimeId) const +{ + if (!MayaUsdAPI::isUsdSceneItem(ancestor) || targetRunTimeId != MayaUsdAPI::getUsdRunTimeId()) + { + return nullptr; + } + + return UsdCreateMaterialParentCommand::create(ancestor); +} + +bool UsdLookdevHandler::isLookdevContainerImpl(const Ufe::SceneItem::Ptr& item) const +{ + return item->nodeType() == "Material"; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdLookdevHandler.h b/lib/lookdevXUsd/UsdLookdevHandler.h new file mode 100644 index 0000000000..7bfa9f650c --- /dev/null +++ b/lib/lookdevXUsd/UsdLookdevHandler.h @@ -0,0 +1,54 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include "Export.h" + +#include + +namespace LookdevXUsd +{ +//! \brief USD run-time Lookdev handler. +/*! + Factory object for Lookdev interfaces. + */ +class LOOKDEVX_USD_EXPORT UsdLookdevHandler : public LookdevXUfe::LookdevHandler +{ +public: + using Ptr = std::shared_ptr; + + static Ptr create() + { + return std::make_shared(); + } + + UsdLookdevHandler() = default; + ~UsdLookdevHandler() override = default; + + //@{ + //! Delete the copy/move constructors assignment operators. + UsdLookdevHandler(const UsdLookdevHandler&) = delete; + UsdLookdevHandler& operator=(const UsdLookdevHandler&) = delete; + UsdLookdevHandler(UsdLookdevHandler&&) = delete; + UsdLookdevHandler& operator=(UsdLookdevHandler&&) = delete; + //@} + + // UsdLookdevHandler overrides + [[nodiscard]] Ufe::SceneItemResultUndoableCommand::Ptr createLookdevContainerCmdImpl( + const Ufe::SceneItem::Ptr& parent, const Ufe::PathComponent& name) const override; + [[nodiscard]] Ufe::SceneItemResultUndoableCommand::Ptr createLookdevContainerCmdImpl( + const Ufe::SceneItem::Ptr& parent, const Ufe::NodeDef::Ptr& nodeDef) const override; + [[nodiscard]] Ufe::SceneItemResultUndoableCommand::Ptr createLookdevEnvironmentCmdImpl( + const Ufe::SceneItem::Ptr& ancestor, Ufe::Rtid targetRunTimeId) const override; + [[nodiscard]] bool isLookdevContainerImpl(const Ufe::SceneItem::Ptr& item) const override; +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterial.cpp b/lib/lookdevXUsd/UsdMaterial.cpp new file mode 100644 index 0000000000..e8fb4088d1 --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterial.cpp @@ -0,0 +1,131 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdMaterial.h" + +#include + +#include + +#include + +using namespace PXR_NS; + +namespace LookdevXUsd +{ + +UsdMaterial::UsdMaterial(Ufe::SceneItem::Ptr item) : m_item(std::move(item)) +{ +} + +UsdMaterial::~UsdMaterial() += default; + +/*static*/ +UsdMaterial::Ptr UsdMaterial::create(const Ufe::SceneItem::Ptr& item) +{ + return std::make_shared(item); +} + +std::vector UsdMaterial::getMaterials() const +{ + // Find the material(s) attached to our SceneItem. + + std::vector materials; + + if (!TF_VERIFY(m_item, "Invalid item\n")) + { + return materials; + } + + std::vector materialPrims; + + const PXR_NS::UsdPrim& prim = MayaUsdAPI::getPrimForUsdSceneItem(m_item); + PXR_NS::UsdShadeMaterialBindingAPI bindingApi(prim); + + // 1. Simple case: A material is directly attached to our object. + PXR_NS::UsdShadeMaterialBindingAPI::DirectBinding directBinding = bindingApi.GetDirectBinding(); + const PXR_NS::UsdShadeMaterial material = directBinding.GetMaterial(); + if (material) + { + materialPrims.push_back(material.GetPrim()); + } + + // 2. Check whether multiple materials are attached to this object via geometry subsets. + for (const auto& geometrySubset : bindingApi.GetMaterialBindSubsets()) + { + const PXR_NS::UsdShadeMaterialBindingAPI subsetBindingAPI(geometrySubset.GetPrim()); + const PXR_NS::UsdShadeMaterial materialOnSubset = + subsetBindingAPI.ComputeBoundMaterial(UsdShadeTokens->surface); + if (materialOnSubset) + { + materialPrims.push_back(materialOnSubset.GetPrim()); + } + } + + // 3. Find the associated Ufe::SceneItem for each material attached to our object. + for (auto& materialPrim : materialPrims) + { + const PXR_NS::SdfPath& materialSdfPath = materialPrim.GetPath(); + const Ufe::Path materialUfePath = MayaUsdAPI::usdPathToUfePathSegment(materialSdfPath); + + // Construct a UFE path consisting of two segments: + // 1. The path to the USD stage + // 2. The path to our material + const auto stagePathSegments = m_item->path().getSegments(); + const auto& materialPathSegments = materialUfePath.getSegments(); + if (stagePathSegments.empty() || materialPathSegments.empty()) + { + continue; + } + + const auto ufePath = Ufe::Path({stagePathSegments[0], materialPathSegments[0]}); + + // Now we have the full path to the material's SceneItem. + materials.push_back(MayaUsdAPI::createUsdSceneItem(ufePath, materialPrim)); + } + + return materials; +} + +bool UsdMaterial::hasMaterial() const +{ + if (!TF_VERIFY(m_item, "Invalid item\n")) + { + return false; + } + + const PXR_NS::UsdPrim& prim = MayaUsdAPI::getPrimForUsdSceneItem(m_item); + PXR_NS::UsdShadeMaterialBindingAPI bindingApi(prim); + + // 1. Simple case: A material is directly attached to our object. + PXR_NS::UsdShadeMaterialBindingAPI::DirectBinding directBinding = bindingApi.GetDirectBinding(); + const PXR_NS::UsdShadeMaterial material = directBinding.GetMaterial(); + if (material) + { + return true; + } + + // 2. Check whether any material is attached to this object via geometry subsets. + for (const auto& geometrySubset : bindingApi.GetMaterialBindSubsets()) + { + const PXR_NS::UsdShadeMaterialBindingAPI subsetBindingAPI(geometrySubset.GetPrim()); + const PXR_NS::UsdShadeMaterial materialOnSubset = + subsetBindingAPI.ComputeBoundMaterial(UsdShadeTokens->surface); + if (materialOnSubset) + { + return true; + } + } + + return false; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterial.h b/lib/lookdevXUsd/UsdMaterial.h new file mode 100644 index 0000000000..664a09abdf --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterial.h @@ -0,0 +1,53 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include "Export.h" + +#include + +#include + +namespace LookdevXUsd +{ + +//! \brief USD run-time material interface +/*! + This class implements the Material interface for USD prims. +*/ +class LOOKDEVX_USD_EXPORT UsdMaterial : public LookdevXUfe::Material +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdMaterial(Ufe::SceneItem::Ptr item); + ~UsdMaterial() override; + + //@{ + //! Delete the copy/move constructors assignment operators. + UsdMaterial(const UsdMaterial&) = delete; + UsdMaterial& operator=(const UsdMaterial&) = delete; + UsdMaterial(UsdMaterial&&) = delete; + UsdMaterial& operator=(UsdMaterial&&) = delete; + //@} + + //! Create a UsdMaterial. + static UsdMaterial::Ptr create(const Ufe::SceneItem::Ptr& item); + + [[nodiscard]] std::vector getMaterials() const override; + + [[nodiscard]] bool hasMaterial() const override; + +private: + Ufe::SceneItem::Ptr m_item; +}; // UsdMaterial + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterialCommands.cpp b/lib/lookdevXUsd/UsdMaterialCommands.cpp new file mode 100644 index 0000000000..cb65b3f232 --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterialCommands.cpp @@ -0,0 +1,80 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdMaterialCommands.h" + +#include + +namespace LookdevXUsd +{ + +UsdCreateMaterialParentCommand::UsdCreateMaterialParentCommand(Ufe::SceneItem::Ptr ancestor) + : m_ancestor(std::move(ancestor)), m_materialParent(nullptr), m_cmd(nullptr) +{ +} + +UsdCreateMaterialParentCommand::Ptr UsdCreateMaterialParentCommand::create(const Ufe::SceneItem::Ptr& ancestor) +{ + if (!ancestor) + { + return nullptr; + } + + return std::make_shared(ancestor); +} + +Ufe::SceneItem::Ptr UsdCreateMaterialParentCommand::sceneItem() const +{ + return m_materialParent; +} + +void UsdCreateMaterialParentCommand::execute() +{ + if (!m_ancestor || !MayaUsdAPI::getPrimForUsdSceneItem(m_ancestor).IsValid()) + { + return; + } + + // If m_ancestor is a materials scope, it can be used as material parent. + if (MayaUsdAPI::isMaterialsScope(m_ancestor)) + { + m_materialParent = m_ancestor; + return; + } + + // Create a materials scope. + m_cmd = std::dynamic_pointer_cast( + MayaUsdAPI::createMaterialsScopeCommand(m_ancestor)); + if (!m_cmd) + { + return; + } + m_cmd->execute(); + m_materialParent = m_cmd->sceneItem(); +} + +void UsdCreateMaterialParentCommand::undo() +{ + if (m_cmd) + { + m_cmd->undo(); + m_materialParent.reset(); + } +} + +void UsdCreateMaterialParentCommand::redo() +{ + if (m_cmd) + { + m_cmd->redo(); + } +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterialCommands.h b/lib/lookdevXUsd/UsdMaterialCommands.h new file mode 100644 index 0000000000..2c0220f74b --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterialCommands.h @@ -0,0 +1,54 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include "Export.h" + +#include +#include + +namespace LookdevXUsd +{ + +class LOOKDEVX_USD_EXPORT UsdCreateMaterialParentCommand : public Ufe::SceneItemResultUndoableCommand +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdCreateMaterialParentCommand(Ufe::SceneItem::Ptr ancestor); + ~UsdCreateMaterialParentCommand() override = default; + + // Delete the copy/move constructors assignment operators. + UsdCreateMaterialParentCommand(const UsdCreateMaterialParentCommand&) = delete; + UsdCreateMaterialParentCommand& operator=(const UsdCreateMaterialParentCommand&) = delete; + UsdCreateMaterialParentCommand(UsdCreateMaterialParentCommand&&) = delete; + UsdCreateMaterialParentCommand& operator=(UsdCreateMaterialParentCommand&&) = delete; + + //! Create a UsdCreateMaterialParentCommand that finds or creates an item under \p ancestor that can serve as a + //! parent of a material. + //! - If \p ancestor is a materials scope, it will be returned. + //! - If \p ancestor is the parent of a materials scope, the materials scope will be returned. + //! - Otherwise, a materials scope will be created under \p ancestor. + static UsdCreateMaterialParentCommand::Ptr create(const Ufe::SceneItem::Ptr& ancestor); + + [[nodiscard]] Ufe::SceneItem::Ptr sceneItem() const override; + + void execute() override; + void undo() override; + void redo() override; + +private: + Ufe::SceneItem::Ptr m_ancestor; + Ufe::SceneItem::Ptr m_materialParent; + Ufe::SceneItemResultUndoableCommand::Ptr m_cmd; +}; // UsdCreateMaterialParentCommand + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterialHandler.cpp b/lib/lookdevXUsd/UsdMaterialHandler.cpp new file mode 100644 index 0000000000..737eccdac0 --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterialHandler.cpp @@ -0,0 +1,115 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdMaterialHandler.h" + +#include "UsdMaterial.h" +#include "UsdMaterialValidator.h" + +#include + +#include +#include +#include + +namespace LookdevXUsd +{ + +UsdMaterialHandler::UsdMaterialHandler() : LookdevXUfe::MaterialHandler() +{ +} + +UsdMaterialHandler::~UsdMaterialHandler() +{ +} + +//------------------------------------------------------------------------------ +// UsdMaterialHandler overrides +//------------------------------------------------------------------------------ + +LookdevXUfe::Material::Ptr UsdMaterialHandler::material(const Ufe::SceneItem::Ptr& item) const +{ + using namespace PXR_NS; + + if (!TF_VERIFY(MayaUsdAPI::isUsdSceneItem(item), "Invalid item\n")) + { + return nullptr; + } + + // Test if this item is imageable. If not, then we cannot create a material + // interface for it, which is a valid case (such as for a material node type). + PXR_NS::UsdGeomImageable primSchema(MayaUsdAPI::getPrimForUsdSceneItem(item)); + if (!primSchema) + { + return nullptr; + } + + return UsdMaterial::create(item); +} + +Ufe::SceneItemResultUndoableCommand::Ptr UsdMaterialHandler::createBackdropCmdImpl(const Ufe::SceneItem::Ptr& parent, + const Ufe::PathComponent& name) const +{ + if (!MayaUsdAPI::isUsdSceneItem(parent) || (parent->nodeType() != "NodeGraph" && parent->nodeType() != "Material")) + { + return nullptr; + } + + return MayaUsdAPI::createAddNewPrimCommand(parent, name.string(), "Backdrop"); +} + +Ufe::SceneItemResultUndoableCommand::Ptr UsdMaterialHandler::createNodeGraphCmdImpl( + const Ufe::SceneItem::Ptr& parent, const Ufe::PathComponent& name) const +{ + return MayaUsdAPI::createAddNewPrimCommand(parent, name.string(), "NodeGraph"); +} + +LookdevXUfe::ValidationLog::Ptr UsdMaterialHandler::validateMaterial(const Ufe::SceneItem::Ptr& material) const +{ + if (!MayaUsdAPI::isUsdSceneItem(material)) + { + return nullptr; + } + + auto materialPrim = PXR_NS::UsdShadeMaterial(MayaUsdAPI::getPrimForUsdSceneItem(material)); + if (!materialPrim) + { + return nullptr; + } + + return UsdMaterialValidator(materialPrim).validate(); +} + +bool UsdMaterialHandler::isBackdropImpl(const Ufe::SceneItem::Ptr& item) const +{ + return item->nodeType() == "Backdrop"; +} + +bool UsdMaterialHandler::isNodeGraphImpl(const Ufe::SceneItem::Ptr& item) const +{ + return item->nodeType() == "NodeGraph"; +} + +bool UsdMaterialHandler::isMaterialImpl(const Ufe::SceneItem::Ptr& item) const +{ + return item->nodeType() == "Material"; +} + +bool UsdMaterialHandler::isShaderImpl(const Ufe::SceneItem::Ptr& item) const +{ + return item->nodeType() == "Shader"; +} + +bool UsdMaterialHandler::allowedInNodeGraph(const std::string& /*nodeDefType*/) const +{ + return true; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterialHandler.h b/lib/lookdevXUsd/UsdMaterialHandler.h new file mode 100644 index 0000000000..25ebbe5712 --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterialHandler.h @@ -0,0 +1,63 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include "Export.h" + +#include +#include + +namespace LookdevXUsd +{ +//! \brief USD run-time Material handler. +/*! + Factory object for Material interfaces. + */ +class LOOKDEVX_USD_EXPORT UsdMaterialHandler : public LookdevXUfe::MaterialHandler +{ +public: + typedef std::shared_ptr Ptr; + + static LookdevXUfe::MaterialHandler::Ptr create() + { + return std::make_shared(); + } + + UsdMaterialHandler(); + ~UsdMaterialHandler() override; + + //@{ + //! Delete the copy/move constructors assignment operators. + UsdMaterialHandler(const UsdMaterialHandler&) = delete; + UsdMaterialHandler& operator=(const UsdMaterialHandler&) = delete; + UsdMaterialHandler(UsdMaterialHandler&&) = delete; + UsdMaterialHandler& operator=(UsdMaterialHandler&&) = delete; + //@} + + // UsdMaterialHandler overrides + LookdevXUfe::Material::Ptr material(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] LookdevXUfe::ValidationLog::Ptr validateMaterial(const Ufe::SceneItem::Ptr& material) const override; + + [[nodiscard]] bool allowedInNodeGraph(const std::string& nodeDefType) const override; + +protected: + [[nodiscard]] Ufe::SceneItemResultUndoableCommand::Ptr createBackdropCmdImpl( + const Ufe::SceneItem::Ptr& parent, const Ufe::PathComponent& name) const override; + [[nodiscard]] Ufe::SceneItemResultUndoableCommand::Ptr createNodeGraphCmdImpl( + const Ufe::SceneItem::Ptr& parent, const Ufe::PathComponent& name) const override; + [[nodiscard]] bool isBackdropImpl(const Ufe::SceneItem::Ptr& item) const override; + [[nodiscard]] bool isNodeGraphImpl(const Ufe::SceneItem::Ptr& item) const override; + [[nodiscard]] bool isMaterialImpl(const Ufe::SceneItem::Ptr& item) const override; + [[nodiscard]] bool isShaderImpl(const Ufe::SceneItem::Ptr& item) const override; +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterialValidator.cpp b/lib/lookdevXUsd/UsdMaterialValidator.cpp new file mode 100644 index 0000000000..59d9aef611 --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterialValidator.cpp @@ -0,0 +1,1269 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdMaterialValidator.h" +#include "Utils.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +PXR_NAMESPACE_USING_DIRECTIVE + +namespace +{ +#define TF_TOKEN(s) \ + const TfToken& s() \ + { \ + static const TfToken tok(#s); \ + return tok; \ + } + +namespace UsdTokens +{ +TF_TOKEN(USD) +TF_TOKEN(glslfx) +TF_TOKEN(UsdPrimvarReader) +TF_TOKEN(UsdUVTexture) +TF_TOKEN(st) +TF_TOKEN(varname) +TF_TOKEN(string) +TF_TOKEN(token) +// TODO(LOOKDEVX-2045): Remove when boundary ports get added for soloing connections +TF_TOKEN(Autodesk) +TF_TOKEN(ldx_isSoloingItem) +TF_TOKEN(hidden) +} // namespace UsdTokens + +namespace MtlxTokens +{ +TF_TOKEN(MaterialX) +TF_TOKEN(mtlx) +TF_TOKEN(ND_standard_surface_surfaceshader) +TF_TOKEN(ND_standard_surface_surfaceshader_100) +TF_TOKEN(ND_open_pbr_surface_surfaceshader) +TF_TOKEN(ND_gltf_pbr_surfaceshader) +TF_TOKEN(ND_surface) +TF_TOKEN(bsdf) +TF_TOKEN(edf) +TF_TOKEN(defaultgeomprop) +TF_TOKEN(geompropvalue) +TF_TOKEN(geomprop) +TF_TOKEN(geomcolor) +TF_TOKEN(texcoord) +TF_TOKEN(uvindex) +TF_TOKEN(bitangent) +TF_TOKEN(tangent) +TF_TOKEN(specular_anisotropy) +TF_TOKEN(specular_roughness_anisotropy) +TF_TOKEN(transmission_scatter_anisotropy) +TF_TOKEN(subsurface_anisotropy) +TF_TOKEN(subsurface_scatter_anisotropy) +TF_TOKEN(coat_anisotropy) +TF_TOKEN(coat_roughness_anisotropy) + +// NodeDefs associated with component connections: +TF_TOKEN(ND_combine2_vector2) +TF_TOKEN(ND_combine3_color3) +TF_TOKEN(ND_combine3_vector3) +TF_TOKEN(ND_combine4_color4) +TF_TOKEN(ND_combine4_vector4) +TF_TOKEN(ND_separate2_vector2) +TF_TOKEN(ND_separate3_color3) +TF_TOKEN(ND_separate3_vector3) +TF_TOKEN(ND_separate4_color4) +TF_TOKEN(ND_separate4_vector4) +TF_TOKEN(out) +TF_TOKEN(in) +TF_TOKEN(outr) +TF_TOKEN(outg) +TF_TOKEN(outb) +TF_TOKEN(outa) +TF_TOKEN(outx) +TF_TOKEN(outy) +TF_TOKEN(outz) +TF_TOKEN(outw) + +const std::vector& anisotropicNames() +{ + static const auto kAnisotropicNames = std::vector{MtlxTokens::specular_anisotropy(), + MtlxTokens::specular_roughness_anisotropy(), + MtlxTokens::transmission_scatter_anisotropy(), + MtlxTokens::subsurface_anisotropy(), + MtlxTokens::subsurface_scatter_anisotropy(), + MtlxTokens::coat_anisotropy(), + MtlxTokens::coat_roughness_anisotropy()}; + return kAnisotropicNames; +} +} // namespace MtlxTokens + +enum class ComponentNodeType +{ + eNone, + eCombine, + eSeparate +}; + +ComponentNodeType isComponentNode(const UsdPrim& prim) +{ + // Using a regex here would make no sense. USD provides a token that can be quickly hashed and compared. + static const auto kCombineNodeDefs = std::unordered_set{ + MtlxTokens::ND_combine2_vector2(), MtlxTokens::ND_combine3_color3(), MtlxTokens::ND_combine3_vector3(), + MtlxTokens::ND_combine4_color4(), MtlxTokens::ND_combine4_vector4()}; + static const auto kSeparateNodeDefs = std::unordered_set{ + MtlxTokens::ND_separate2_vector2(), MtlxTokens::ND_separate3_color3(), MtlxTokens::ND_separate3_vector3(), + MtlxTokens::ND_separate4_color4(), MtlxTokens::ND_separate4_vector4()}; + + bool isHidden = false; + auto adskData = prim.GetCustomDataByKey(UsdTokens::Autodesk()); + if (adskData.IsHolding()) + { + auto adskDict = adskData.UncheckedGet(); + isHidden = adskDict.find(UsdTokens::hidden().GetText()) != adskDict.end(); + } + // The prim hidden check is for backwards compatibility. Newer files will use metadata only to hide nodes. + if (isHidden || prim.IsHidden()) + { + const auto shader = UsdShadeShader(prim); + if (shader) + { + TfToken shaderId; + shader.GetShaderId(&shaderId); + if (kCombineNodeDefs.count(shaderId) > 0) + { + return ComponentNodeType::eCombine; + } + if (kSeparateNodeDefs.count(shaderId) > 0) + { + return ComponentNodeType::eSeparate; + } + } + } + return ComponentNodeType::eNone; +} +} // namespace + +#undef TF_TOKEN + +namespace LookdevXUsd +{ + +struct UsdConnectionInfo +{ + UsdAttribute m_src; + UsdAttribute m_dst; +}; + +namespace +{ + +enum class ErrId +{ + // Node level errors: + kNotInCompound, + kNoIdentifier, + kNotInRegistry, + kNotInAScope, + kNotInACompound, + kWrongChild, + kNotAShader, + kMxIndexBased, + kMxOldDef, + kBadMatParent, + + // Connection level errors: + kTypeMismatch, + kImplMismatch, + kCycle, + kParentMismatch, + kImplMismatch2, + + // Attribute level errors: + kMissingNode, + kMissingAttr, + kInvalidAttr, + kUSDNoUV, + kUSDNoVarname, + kMxMissingReq, + kMxNoVarname, + kNotInNodeDef, + kNDTypeMismatch, + kInvalidSeparate, + kInvalidCombine +}; +using ErrorTable = std::vector>; +const ErrorTable& errorTable() +{ + // Requires same ordering as the ErrId enum. Validated in errorStr function. + static const auto kErrorTable = ErrorTable{ + // Node level errors: + {ErrId::kNotInCompound, "N001: Shader node is not inside a Compound or Material."}, + {ErrId::kNoIdentifier, "N002: Shader node is missing an identifier."}, + {ErrId::kNotInRegistry, "N003: Shader node identifier '@' could not be found in registry."}, + {ErrId::kNotInAScope, "N004: USD assets working group recommends grouping material nodes inside a Scope."}, + {ErrId::kNotInACompound, "N005: Compound node is not inside a Compound or Material."}, + {ErrId::kWrongChild, "N006: Node is not a child of material '@'."}, + {ErrId::kNotAShader, "N007: Node is not a shading primitive."}, + {ErrId::kMxIndexBased, "N008: Index based node '@' is not supported in a name based renderer."}, + {ErrId::kMxOldDef, "N009: Consider using the more recent '@' node definition."}, + {ErrId::kBadMatParent, "N010: Material cannot be child of connectable node '@'."}, + + // Connection level errors: + {ErrId::kTypeMismatch, "C001: Type mismatch between a '@' source and a '@' destination."}, + {ErrId::kImplMismatch, "C002: Source node is of type '@' and cannot be used to assemble a shader of type '@'."}, + {ErrId::kCycle, "C003: These connections form a cycle."}, + {ErrId::kParentMismatch, "C004: Connection source and destination are not in the same compound."}, + {ErrId::kImplMismatch2, "C005: Source node of type '@' cannot work with destination node of type '@'."}, + + // Attribute level errors: + {ErrId::kMissingNode, "A001: Is connected to missing node '@'."}, + {ErrId::kMissingAttr, "A002: Is connected to missing attribute '@'."}, + {ErrId::kInvalidAttr, "A003: Is connected to invalid attribute '@'."}, + {ErrId::kUSDNoUV, "A004: Texture node requires a connection to 'UsdPrimvarReader_float2'."}, + {ErrId::kUSDNoVarname, "A005: Varname attribute is undefined."}, + {ErrId::kMxMissingReq, "A006: Node requires @ connection."}, + {ErrId::kMxNoVarname, "A007: Geomprop attribute is undefined."}, + {ErrId::kNotInNodeDef, "A008: Attribute does not exist in '@'."}, + {ErrId::kNDTypeMismatch, "A009: Attribute should be '@' as defined in '@'."}, + {ErrId::kInvalidSeparate, "A010: Invalid component separate setup."}, + {ErrId::kInvalidCombine, "A011: Invalid component combine setup."}, + + }; + return kErrorTable; +} + +// Dev-only strings when making sure the enum and the array are in sync. +// LCOV_EXCL_START +const std::string& messageArrayOutOfSync() +{ + static const std::string kMessageArrayOutOfSync = "Error message array is out of sync with enum."; + return kMessageArrayOutOfSync; +} +const std::string& invalidNumberOfArguments() +{ + static const std::string kInvalidNumberOfArguments = "Invalid number of arguments."; + return kInvalidNumberOfArguments; +} +// LCOV_EXCL_STOP + +const std::string& errorStr(ErrId messageId) +{ + const auto& [id, message] = errorTable().at(static_cast(messageId)); + // LCOV_EXCL_START + if (id != messageId) + { + return messageArrayOutOfSync(); + } + if (message.find('@') != std::string::npos) + { + return invalidNumberOfArguments(); + } + // LCOV_EXCL_STOP + return message; +} + +std::string errorStr(ErrId messageId, const std::string& p1) +{ + const auto& [id, message] = errorTable().at(static_cast(messageId)); + // LCOV_EXCL_START + if (id != messageId) + { + return messageArrayOutOfSync(); + } + // LCOV_EXCL_STOP + auto fragments = LookdevXUsdUtils::splitString(message, "@"); + // LCOV_EXCL_START + if (fragments.size() != 2) + { + return invalidNumberOfArguments(); + } + // LCOV_EXCL_STOP + return fragments[0] + p1 + fragments[1]; +} + +std::string errorStr(ErrId messageId, const std::string& p1, const std::string& p2) +{ + const auto& [id, message] = errorTable().at(static_cast(messageId)); + // LCOV_EXCL_START + if (id != messageId) + { + return messageArrayOutOfSync(); + } + // LCOV_EXCL_STOP + auto fragments = LookdevXUsdUtils::splitString(message, "@"); + // LCOV_EXCL_START + if (fragments.size() != 3) + { + return invalidNumberOfArguments(); + } + // LCOV_EXCL_STOP + return fragments[0] + p1 + fragments[1] + p2 + fragments[2]; +} + +const std::string& niceSourceName(const TfToken& source) +{ + if (source == MtlxTokens::mtlx()) + { + return MtlxTokens::MaterialX().GetString(); + } + if (source == UsdTokens::glslfx()) + { + return UsdTokens::USD().GetString(); + } + return source; +} + +TfToken inputFullName(const TfToken& baseName) +{ + return UsdShadeUtils::GetFullName(baseName, UsdShadeAttributeType::Input); +} + +} // namespace + +LookdevXUfe::AttributeComponentInfo UsdMaterialValidator::remapComponentConnectionAttribute( + const UsdPrim& prim, const TfToken& attrName) const +{ + // Hidden nodes can be from component connection. If that is the case, we need to remap to the associated LookdevX + // visible node. + auto componentSetup = isComponentNode(prim); + if (componentSetup != ComponentNodeType::eNone) + { + + auto baseNameAndType = UsdShadeUtils::GetBaseNameAndType(attrName); + TfToken shaderId; + const auto shader = UsdShadeShader(prim); + shader.GetShaderId(&shaderId); + auto& registry = SdrRegistry::GetInstance(); + const auto* nodeDef = registry.GetShaderNodeByIdentifier(shaderId); + + if (componentSetup == ComponentNodeType::eCombine) + { + // If the error is about one of the known inputs of a combine, we map to the corresponding component. + // Please note that the port index is between 1 and 4 (inputs:in1, in2, in3, in4) and can not go over the + // last digit in the combine category name. + size_t portIndex = 0; // Keep zero as invalid value. + + if (baseNameAndType.second == UsdShadeAttributeType::Input) + { + const auto& portName = baseNameAndType.first.GetString(); + if (portName.size() == 3 && portName[0] == 'i' && portName[1] == 'n') + { + // ND_combineX_vectypeX + static const size_t kCombineSizePos = 10; + const auto combineMaxIndex = shaderId.GetString()[kCombineSizePos]; + const auto portCharIndex = portName[2]; + if (portCharIndex >= '1' && portCharIndex <= combineMaxIndex) + { + // Valid index: + portIndex = static_cast(portCharIndex - '0'); + } + } + } + std::string componentName; + if (portIndex > 0) + { + if (nodeDef) + { + const auto* output = nodeDef->GetShaderOutput(MtlxTokens::out()); + if (output) + { +#if PXR_VERSION > 2408 + const auto& outputType = output->GetTypeAsSdfType().GetSdfType(); +#else + const auto& outputType = output->GetTypeAsSdfType().first; +#endif + + if (outputType == SdfValueTypeNames->Color3f || outputType == SdfValueTypeNames->Color4f) + { + constexpr auto kRGBA = std::string_view{" rgba"}; + componentName = kRGBA.substr(portIndex, 1); + } + else + { + constexpr auto kXYZW = std::string_view{" xyzw"}; + componentName = kXYZW.substr(portIndex, 1); + } + } + } + } + + const auto combineIt = m_seenCombineConnections.find(prim.GetPath()); + if (combineIt != m_seenCombineConnections.end()) + { + auto componentLocation = LookdevXUfe::AttributeComponentInfo{ + toUfe(combineIt->second.GetPrim()), combineIt->second.GetName(), componentName}; + validateComponentLocation(componentLocation, errorStr(ErrId::kInvalidCombine)); + return componentLocation; + } + } + if (componentSetup == ComponentNodeType::eSeparate) + { + static const std::unordered_map kSeparateComponentMap = { + {MtlxTokens::outr(), "r"}, {MtlxTokens::outg(), "g"}, {MtlxTokens::outb(), "b"}, + {MtlxTokens::outa(), "a"}, {MtlxTokens::outx(), "x"}, {MtlxTokens::outy(), "y"}, + {MtlxTokens::outz(), "z"}, {MtlxTokens::outw(), "w"}}; + + // If the error is about one of the known outputs of a separate, we map to the corresponding component. + auto componentIt = kSeparateComponentMap.find(baseNameAndType.first); + if (componentIt != kSeparateComponentMap.end()) + { + // Make sure the output exists in the node definition: + if (!nodeDef || !nodeDef->GetShaderOutput(baseNameAndType.first)) + { + // Can not confirm this output exists: + componentIt = kSeparateComponentMap.end(); + } + } + const auto componentName = + (componentIt != kSeparateComponentMap.end()) ? componentIt->second : std::string{}; + + const auto separateInput = shader.GetInput(MtlxTokens::in()); + if (separateInput) + { + UsdShadeConnectableAPI source; + TfToken sourceName; + auto sourceType = UsdShadeAttributeType::Invalid; + const auto isConnected = separateInput.GetConnectedSource(&source, &sourceName, &sourceType); + if (isConnected) + { + auto componentLocation = LookdevXUfe::AttributeComponentInfo{ + toUfe(source.GetPrim()), UsdShadeUtils::GetFullName(sourceName, sourceType), componentName}; + validateComponentLocation(componentLocation, errorStr(ErrId::kInvalidSeparate)); + return componentLocation; + } + } + } + } + return {Ufe::Path{}, std::string{}, std::string{}}; +} + +void UsdMaterialValidator::validateComponentLocation(const LookdevXUfe::AttributeComponentInfo& attrInfo, + const std::string& errorDesc) const +{ + // If we could not resolve a component then signal that the combine/separate are broken. + if (attrInfo.component().empty()) + { + const auto brokenComponent = attrInfo.path().string() + "." + attrInfo.name(); + const auto seenIt = m_brokenComponents.find(brokenComponent); + if (seenIt == m_brokenComponents.end()) + { + m_log->addEntry( + {LookdevXUfe::Log::Severity::kError, errorDesc, {1, LookdevXUfe::AttributeComponentInfo{attrInfo}}}); + m_brokenComponents.insert(brokenComponent); + } + } +} + +Ufe::Path UsdMaterialValidator::toUfe(const UsdStageWeakPtr& stage, const SdfPath& path) +{ + auto stagePath = MayaUsdAPI::stagePath(stage); + return Ufe::Path::Segments{stagePath.getSegments()[0], MayaUsdAPI::usdPathToUfePathSegment(path)}; +} + +Ufe::Path UsdMaterialValidator::toUfe(const UsdPrim& prim) +{ + return {toUfe(prim.GetStage(), prim.GetPath())}; +} + +LookdevXUfe::AttributeComponentInfo UsdMaterialValidator::toUfe(const UsdAttribute& attrib) const +{ + return toUfe(attrib.GetPrim(), attrib.GetName()); +} + +LookdevXUfe::AttributeComponentInfo UsdMaterialValidator::toUfe(const UsdPrim& prim, const TfToken& attrName) const +{ + auto componentAttrInfo = remapComponentConnectionAttribute(prim, attrName); + if (!componentAttrInfo.path().empty()) + { + return componentAttrInfo; + } + + return {toUfe(prim), attrName.GetString(), ""}; +} + +LookdevXUfe::Log::ConnectionInfo UsdMaterialValidator::toUfe(const UsdConnectionInfo& cnx) const +{ + return {toUfe(cnx.m_src), toUfe(cnx.m_dst)}; +} + +UsdMaterialValidator::UsdMaterialValidator(const UsdShadeMaterial& prim) : m_material(prim) +{ +} + +LookdevXUfe::ValidationLog::Ptr UsdMaterialValidator::validate() +{ + m_log = LookdevXUfe::ValidationLog::create(); + + auto allOutputs = m_material.GetSurfaceOutputs(); + auto displacementOuts = m_material.GetDisplacementOutputs(); + allOutputs.insert(allOutputs.end(), displacementOuts.begin(), displacementOuts.end()); + auto volumeOuts = m_material.GetVolumeOutputs(); + allOutputs.insert(allOutputs.end(), volumeOuts.begin(), volumeOuts.end()); + + for (auto&& terminal : allOutputs) + { + // Find render context: + auto tokens = TfStringSplit(terminal.GetFullName(), ":"); + if (tokens.size() == 3) + { + m_renderContext = TfToken(tokens[1]); + } + else + { + m_renderContext = UsdTokens::glslfx(); + } + + visitDestination(terminal.GetAttr()); + } + + // Continue with a traversal of all the nodes below the material, but at the warning level because the remaining + // unvalidated nodes do not yet belong to any exported shader. + m_currentSeverity = LookdevXUfe::Log::Severity::kWarning; + m_renderContext = TfToken(); + std::vector componentNodes; + for (auto&& prim : m_material.GetPrim().GetDescendants()) + { + if (!m_validatedPrims.count(prim.GetPath())) + { + if (isComponentNode(prim) != ComponentNodeType::eNone) + { + // Delay checking these until we have processed more of the stage in case they do not come with a + // companion node. + componentNodes.push_back(prim); + continue; + } + if (validatePrim(prim)) + { + if (auto shader = UsdShadeShader(prim)) + { + for (auto&& destInput : shader.GetInputs()) + { + visitDestination(destInput.GetAttr()); + } + } + } + } + } + + // Now we can process free-floating component nodes. + for (auto&& prim : componentNodes) + { + if (!m_validatedPrims.count(prim.GetPath())) + { + if (validatePrim(prim)) + { + if (auto shader = UsdShadeShader(prim)) + { + for (auto&& destInput : shader.GetInputs()) + { + visitDestination(destInput.GetAttr()); + } + } + } + } + } + + return std::move(m_log); +} + +bool UsdMaterialValidator::visitDestination(const UsdAttribute& dest) +{ + if (!validatePrim(dest.GetPrim())) + { + return false; + } + + if (m_visitedDestinations.count(dest.GetPath())) + { + return true; + } + if (dest.GetPrim().IsA()) + { + // Track visited nodes, but not nodegraphs since they + // can be re-entered wihtout a cycle if the internals + // are split into distinct subgraphs. + m_visitedDestinations.insert(dest.GetPath()); + } + + auto primCnx = UsdShadeConnectableAPI(dest.GetPrim()); + if (!primCnx) + { + return false; + } + + SdfPathVector invalidSourcePaths; + auto sourceInfoVec = UsdShadeConnectableAPI::GetConnectedSources(dest, &invalidSourcePaths); + reportInvalidSources(dest, invalidSourcePaths); + + UsdConnectionInfo cnxInfo; + m_connectionStack.push_back(&cnxInfo); + cnxInfo.m_dst = dest; + for (auto&& sourceInfo : sourceInfoVec) + { + UsdPrim sourcePrim = sourceInfo.source.GetPrim(); + std::string prefix = UsdShadeUtils::GetPrefixForAttributeType(sourceInfo.sourceType); + TfToken sourceAttrName(prefix + sourceInfo.sourceName.GetString()); + UsdAttribute sourceAttr = sourcePrim.GetAttribute(sourceAttrName); + + cnxInfo.m_src = sourceAttr; + + if (isComponentNode(sourceAttr.GetPrim()) == ComponentNodeType::eCombine) + { + m_seenCombineConnections.emplace(sourceAttr.GetPrim().GetPath(), dest); + } + + validateConnection(); + + if (validateAcyclic()) + { + traverseConnection(); + } + } + + m_connectionStack.pop_back(); + return true; +} + +bool UsdMaterialValidator::validateShader(const UsdShadeShader& shader) +{ + // Can only have a NodeGraph as parent: + auto parentNode = shader.GetPrim().GetParent(); + if (!parentNode || !parentNode.IsA()) + { + // Argh... Need to use LookdevX nomenclature instead of USD. + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kNotInCompound), {1, toUfe(shader.GetPrim())}}); + } + + // Ensure shader validity against Sdr registry: + TfToken shaderId; + shader.GetShaderId(&shaderId); + if (shaderId.IsEmpty()) + { + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kNoIdentifier), {1, toUfe(shader.GetPrim())}}); + return false; + } + SdrShaderNodeConstPtr shaderNode = SdrRegistry::GetInstance().GetShaderNodeByIdentifier(shaderId); + if (!shaderNode) + { + m_log->addEntry( + {m_currentSeverity, errorStr(ErrId::kNotInRegistry, shaderId.GetString()), {1, toUfe(shader.GetPrim())}}); + return false; + } + + auto inputNames = std::set{shaderNode->GetInputNames().cbegin(), shaderNode->GetInputNames().cend()}; + for (auto&& input : shader.GetInputs()) + { + if (inputNames.count(input.GetBaseName()) == 0) + { + m_log->addEntry( + {m_currentSeverity, errorStr(ErrId::kNotInNodeDef, shaderId.GetString()), {1, toUfe(input.GetAttr())}}); + continue; + } + auto currentTypeName = input.GetTypeName().GetAsToken(); +#if PXR_VERSION > 2408 + auto expectedTypeName = shaderNode->GetInput(input.GetBaseName())->GetTypeAsSdfType().GetSdfType().GetAsToken(); +#else + auto expectedTypeName = shaderNode->GetInput(input.GetBaseName())->GetTypeAsSdfType().first.GetAsToken(); +#endif + if (currentTypeName != expectedTypeName && currentTypeName != UsdTokens::string() && + currentTypeName != UsdTokens::token()) + { + m_log->addEntry({m_currentSeverity, + errorStr(ErrId::kNDTypeMismatch, expectedTypeName.GetString(), shaderId.GetString()), + {1, toUfe(input.GetAttr())}}); + continue; + } + } + auto outputNames = std::set{shaderNode->GetOutputNames().cbegin(), shaderNode->GetOutputNames().cend()}; + for (auto&& output : shader.GetOutputs()) + { + if (outputNames.count(output.GetBaseName()) == 0) + { + m_log->addEntry({m_currentSeverity, + errorStr(ErrId::kNotInNodeDef, shaderId.GetString()), + {1, toUfe(output.GetAttr())}}); + continue; + } + auto currentTypeName = output.GetTypeName().GetAsToken(); +#if PXR_VERSION > 2408 + auto expectedTypeName = + shaderNode->GetOutput(output.GetBaseName())->GetTypeAsSdfType().GetSdfType().GetAsToken(); +#else + auto expectedTypeName = shaderNode->GetOutput(output.GetBaseName())->GetTypeAsSdfType().first.GetAsToken(); +#endif + if (currentTypeName != expectedTypeName && currentTypeName != UsdTokens::string() && + currentTypeName != UsdTokens::token()) + { + m_log->addEntry({m_currentSeverity, + errorStr(ErrId::kNDTypeMismatch, expectedTypeName.GetString(), shaderId.GetString()), + {1, toUfe(output.GetAttr())}}); + continue; + } + } + + auto connectableAPI = UsdShadeConnectableAPI(shader.GetPrim()); + if (shaderNode->GetSourceType() == UsdTokens::glslfx()) + { + validateGlslfxShader(connectableAPI, shaderNode); + } + else if (shaderNode->GetSourceType() == MtlxTokens::mtlx()) + { + validateMaterialXShader(connectableAPI, shaderNode); + } + + return true; +} + +void UsdMaterialValidator::validateGlslfxShader(const UsdShadeConnectableAPI& connectableAPI, + SdrShaderNodeConstPtr shaderNode) +{ + if (shaderNode->GetIdentifier() == UsdTokens::UsdUVTexture()) + { + // Image nodes require a texcoord primvar reader: + const auto stInput = connectableAPI.GetInput(UsdTokens::st()); + if (!stInput || !UsdShadeConnectableAPI::HasConnectedSource(stInput)) + { + m_log->addEntry({LookdevXUfe::Log::Severity::kWarning, + errorStr(ErrId::kUSDNoUV), + {1, toUfe(connectableAPI.GetPrim(), inputFullName(UsdTokens::st()))}}); + } + } + + if (shaderNode->GetFamily() == UsdTokens::UsdPrimvarReader()) + { + // Need to specify the primvar name that a primvar reader uses: + const auto varnameInput = connectableAPI.GetInput(UsdTokens::varname()); + if (!varnameInput || + (!UsdShadeConnectableAPI::HasConnectedSource(varnameInput) && !varnameInput.GetAttr().HasValue())) + { + m_log->addEntry({LookdevXUfe::Log::Severity::kWarning, + errorStr(ErrId::kUSDNoVarname), + {1, toUfe(connectableAPI.GetPrim(), inputFullName(UsdTokens::varname()))}}); + } + } +} + +void UsdMaterialValidator::validateMaterialXShader(const UsdShadeConnectableAPI& connectableAPI, + SdrShaderNodeConstPtr shaderNode) +{ + // This is problematic because some renderers (looking at you MayaUSD and usdView) will auto-fix these issues, + // thus teaching bad habits to users. + if (shaderNode->GetIdentifier() == MtlxTokens::ND_standard_surface_surfaceshader() || + shaderNode->GetIdentifier() == MtlxTokens::ND_standard_surface_surfaceshader_100() || + shaderNode->GetIdentifier() == MtlxTokens::ND_open_pbr_surface_surfaceshader()) + { + // Standard surface needs a tangent input if any anisotropic parameter is non-zero. + bool isAnisotropic = false; + for (auto&& anisoName : MtlxTokens::anisotropicNames()) + { + const auto anisoInput = connectableAPI.GetInput(anisoName); + if (anisoInput && + (UsdShadeConnectableAPI::HasConnectedSource(anisoInput) || anisoInput.GetAttr().HasValue())) + { + isAnisotropic = true; + break; + } + } + if (isAnisotropic) + { + const auto tangentInput = connectableAPI.GetInput(MtlxTokens::tangent()); + if (!tangentInput || !UsdShadeConnectableAPI::HasConnectedSource(tangentInput)) + { + std::string detail = "a "; + detail.append(MtlxTokens::tangent()); + detail.append(" reader"); + m_log->addEntry({LookdevXUfe::Log::Severity::kWarning, + errorStr(ErrId::kMxMissingReq, detail), + {1, toUfe(connectableAPI.GetPrim(), inputFullName(MtlxTokens::tangent()))}}); + } + } + } + else if (shaderNode->GetIdentifier() == MtlxTokens::ND_gltf_pbr_surfaceshader()) + { + // The glTf PBR shader has a tangent input, but all the MaterialX computations are isotropic, so connecting it + // is not required as of MaterialX 1.38.7. + // + // The glTf specification requires producing tangents in order to compute displacement. MaterialX does not yet + // do displacement. See https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html + // + // There is an anisotropic extension suggested for glTf, but it is not yet integrated in MaterialX. See + // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md + // + // Might need to be revised in a future versions of MaterialX. + } + else if (shaderNode->GetIdentifier() == MtlxTokens::ND_surface()) + { + // Surface node need both an EDF and BSDF connection to produce correct closures for GLSL: + static const auto mandatoryInputs = std::vector{MtlxTokens::bsdf(), MtlxTokens::edf()}; + for (auto const& mandatoryName : mandatoryInputs) + { + const auto mandatoryInput = connectableAPI.GetInput(mandatoryName); + if (!mandatoryInput || (!UsdShadeConnectableAPI::HasConnectedSource(mandatoryInput))) + { + std::string detail; + if (mandatoryName == MtlxTokens::edf()) + { + detail.append("an "); + } + else + { + detail.append("a "); + } + detail.append(mandatoryName); + detail.append(" node"); + m_log->addEntry({LookdevXUfe::Log::Severity::kError, + errorStr(ErrId::kMxMissingReq, detail), + {1, toUfe(connectableAPI.GetPrim(), inputFullName(mandatoryName))}}); + } + } + } + else + { + // For all the other MaterialX nodes, look for a defaultgeomprop entry, and flag it if it requires a manual + // reader node. + for (auto&& inputName : shaderNode->GetInputNames()) + { + const SdrShaderPropertyConstPtr input = shaderNode->GetShaderInput(inputName); + const auto hints = input->GetHints(); + const auto defaultgeompropIt = hints.find(MtlxTokens::defaultgeomprop()); + // Position and normal are handled natively by most renderers, and streams are available in USD. This leaves + // issues with non-default USD primvars, like UVs, tangents, and bitangents. + if (defaultgeompropIt != hints.cend() && defaultgeompropIt->second.front() != 'N' && + defaultgeompropIt->second.front() != 'P') + { + const auto geomInput = connectableAPI.GetInput(inputName); + if (!geomInput || !UsdShadeConnectableAPI::HasConnectedSource(geomInput)) + { + std::string streamName; + switch (defaultgeompropIt->second.front()) + { + case 'U': + streamName = MtlxTokens::texcoord().GetString(); + break; + case 'T': + streamName = MtlxTokens::tangent().GetString(); + break; + // No MaterialX node to test these cases: + // LCOV_EXCL_START + case 'B': + streamName = MtlxTokens::bitangent().GetString(); + break; + default: + streamName = "UNKNOWN"; + break; + // LCOV_EXCL_STOP + } + std::string detail = "a "; + detail.append(streamName); + detail.append(" reader"); + m_log->addEntry({LookdevXUfe::Log::Severity::kWarning, + errorStr(ErrId::kMxMissingReq, detail), + {1, toUfe(connectableAPI.GetPrim(), inputFullName(inputName))}}); + } + } + } + } + if (shaderNode->GetFamily() == MtlxTokens::geompropvalue()) + { + // Need to specify the primvar name that a geomprop reader uses: + const auto varnameInput = connectableAPI.GetInput(MtlxTokens::geomprop()); + if (!varnameInput || + (!UsdShadeConnectableAPI::HasConnectedSource(varnameInput) && !varnameInput.GetAttr().HasValue())) + { + m_log->addEntry({LookdevXUfe::Log::Severity::kWarning, + errorStr(ErrId::kMxNoVarname), + {1, toUfe(connectableAPI.GetPrim(), inputFullName(MtlxTokens::geomprop()))}}); + } + } + if (shaderNode->GetFamily() == MtlxTokens::geomcolor() || shaderNode->GetFamily() == MtlxTokens::texcoord() || + shaderNode->GetFamily() == MtlxTokens::bitangent() || shaderNode->GetFamily() == MtlxTokens::tangent() || + (shaderNode->GetInput(MtlxTokens::uvindex()) && shaderNode->GetFamily().GetString().rfind("gltf_", 0) == 0)) + { + // These MaterialX nodes use index-based streams. Some renderers will convert them to named primvar readers if + // there is an established naming convention, but support will be limited. + m_log->addEntry({LookdevXUfe::Log::Severity::kWarning, + errorStr(ErrId::kMxIndexBased, shaderNode->GetIdentifier().GetString()), + {1, toUfe(connectableAPI.GetPrim())}}); + } + if (shaderNode->GetIdentifier() == MtlxTokens::ND_standard_surface_surfaceshader_100()) + { + m_log->addEntry({LookdevXUfe::Log::Severity::kInfo, + errorStr(ErrId::kMxOldDef, MtlxTokens::ND_standard_surface_surfaceshader().GetString()), + {1, toUfe(connectableAPI.GetPrim())}}); + } +} + +bool UsdMaterialValidator::validateMaterial(const UsdShadeMaterial& material) +{ + // We recommend having a Scope as parent, but it is not a USD hard rule: + auto parentNode = material.GetPrim().GetParent(); + if (!parentNode || !parentNode.IsA()) + { + m_log->addEntry( + {LookdevXUfe::Log::Severity::kInfo, errorStr(ErrId::kNotInAScope), {1, toUfe(material.GetPrim())}}); + } + + // But not having a connectable parent is a USD hard rule: + while (parentNode) + { + if (auto connectableParent = UsdShadeConnectableAPI(parentNode)) + { + auto stage = parentNode.GetStage(); + auto parentPath = Ufe::PathString::string(toUfe(stage, parentNode.GetPath())); + m_log->addEntry({LookdevXUfe::Log::Severity::kError, + errorStr(ErrId::kBadMatParent, parentPath), + {1, toUfe(material.GetPrim())}}); + break; + } + parentNode = parentNode.GetParent(); + } + + return true; +} + +bool UsdMaterialValidator::validateNodeGraph(const UsdShadeNodeGraph& nodegraph) +{ + // Can only have a NodeGraph as parent: + auto parentNode = nodegraph.GetPrim().GetParent(); + if (!parentNode || !parentNode.IsA()) + { + // Argh... Need to use LookdevX nomenclature instead of USD. + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kNotInACompound), {1, toUfe(nodegraph.GetPrim())}}); + } + + return true; +} + +bool UsdMaterialValidator::validatePrim(const UsdPrim& prim) +{ + if (auto foundIt = m_validatedPrims.find(prim.GetPath()); foundIt != m_validatedPrims.end()) + { + return foundIt->second; + } + bool retVal = true; + + if (!prim.GetPath().HasPrefix(m_material.GetPath())) + { + auto materialPath = Ufe::PathString::string(toUfe(m_material.GetPrim())); + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kWrongChild, materialPath), {1, toUfe(prim)}}); + } + + if (auto shader = UsdShadeShader(prim)) + { + retVal = validateShader(shader); + } + else if (auto material = UsdShadeMaterial(prim)) + { + retVal = validateMaterial(material); + } + else if (auto nodegraph = UsdShadeNodeGraph(prim)) + { + retVal = validateNodeGraph(nodegraph); + } + else + { + if (!prim.IsA()) + { + m_log->addEntry({LookdevXUfe::Log::Severity::kError, errorStr(ErrId::kNotAShader), {1, toUfe(prim)}}); + retVal = false; + } + } + + m_validatedPrims.insert({prim.GetPath(), retVal}); + return retVal; +} + +void UsdMaterialValidator::validateConnection() +{ + if (m_connectionStack.empty()) + { + return; + } + + auto stackIter = m_connectionStack.rbegin(); + auto const& cnx = *stackIter; + + // If we do not have a global render context we can still validate connections using the destination as render + // context reference. + TfToken renderContext = m_renderContext; + if (renderContext.IsEmpty() && !cnx->m_dst.GetPrim().IsA()) + { + TfToken id; + UsdShadeShader(cnx->m_dst.GetPrim()).GetShaderId(&id); + SdrShaderNodeConstPtr dstShaderNode = SdrRegistry::GetInstance().GetShaderNodeByIdentifier(id); + if (dstShaderNode) + { + renderContext = dstShaderNode->GetSourceType(); + } + } + + // Validate the the source type matches the destination type: + if (renderContext == MtlxTokens::mtlx() && cnx->m_src.GetTypeName() != cnx->m_dst.GetTypeName()) + { + // If the source is a component combine output, then it is quite broken. Just mark it as such + auto emitError = true; + if (isComponentNode(cnx->m_src.GetPrim()) == ComponentNodeType::eCombine && + UsdShadeUtils::GetBaseNameAndType(cnx->m_src.GetName()).second == UsdShadeAttributeType::Output) + { + toUfe(cnx->m_src); + emitError = false; + } + // If the destination is a component separate input, then it is quite broken. Just mark it as such + if (isComponentNode(cnx->m_dst.GetPrim()) == ComponentNodeType::eSeparate && + UsdShadeUtils::GetBaseNameAndType(cnx->m_dst.GetName()).second == UsdShadeAttributeType::Input) + { + toUfe(cnx->m_src); + emitError = false; + } + // MaterialX is extremely strict: + if (emitError) + { + m_log->addEntry({m_currentSeverity, + errorStr(ErrId::kTypeMismatch, cnx->m_src.GetTypeName().GetAsToken(), + cnx->m_dst.GetTypeName().GetAsToken()), + {1, toUfe(*cnx)}}); + } + } + if (renderContext == UsdTokens::glslfx() && + cnx->m_src.GetTypeName().GetCPPTypeName() != cnx->m_dst.GetTypeName().GetCPPTypeName()) + { + // USD allows connecting if the C++ type matches, allowing the float3 output of UsdUVTexture to connect to the + // color3f input of UsdPreviewSurface. + m_log->addEntry({m_currentSeverity, + errorStr(ErrId::kTypeMismatch, cnx->m_src.GetTypeName().GetAsToken(), + cnx->m_dst.GetTypeName().GetAsToken()), + {1, toUfe(*cnx)}}); + } + + if (cnx->m_src.GetPrimPath().GetParentPath() != cnx->m_dst.GetPrimPath().GetParentPath()) + { + // The source and destination are not exactly under the same parent. Do a finer check: + const bool srcIsShader = cnx->m_src.GetPrim().IsA(); + const bool dstIsShader = cnx->m_dst.GetPrim().IsA(); + bool isProblematic = false; + if (srcIsShader && dstIsShader) + { + // src and dst are both shaders, they should be in the same compound: + isProblematic = true; + } + else + { + if (srcIsShader) + { + // src is a shader inside compound dst + if (cnx->m_src.GetPrimPath().GetParentPath() != cnx->m_dst.GetPrimPath()) + { + isProblematic = true; + } + } + else if (dstIsShader) + { + // dst is a shader inside compound src + if (cnx->m_dst.GetPrimPath().GetParentPath() != cnx->m_src.GetPrimPath()) + { + isProblematic = true; + } + } + else + { + // Two compounds: one must be child of the other: + if (cnx->m_src.GetPrimPath().GetParentPath() != cnx->m_dst.GetPrimPath() && + cnx->m_dst.GetPrimPath().GetParentPath() != cnx->m_src.GetPrimPath()) + { + isProblematic = true; + } + } + } + if (isProblematic) + { + // Soloing currently breaks the rules and requires silencing this error: + // TODO(LOOKDEVX-2045): Remove when boundary ports get added for soloing connections + auto adskData = cnx->m_dst.GetPrim().GetCustomDataByKey(UsdTokens::Autodesk()); + if (adskData.IsHolding()) + { + auto adskDict = adskData.UncheckedGet(); + auto itSolo = adskDict.find(UsdTokens::ldx_isSoloingItem().GetText()); + if (itSolo != adskDict.end()) + { + isProblematic = false; + } + } + } + if (isProblematic) + { + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kParentMismatch), {1, toUfe(*cnx)}}); + } + } + + // Validate that shader connections are between the same type of nodes + const auto& srcNode = cnx->m_src.GetPrim(); + if (!validatePrim(srcNode) || !srcNode.IsA()) + { + // Only checking shader to shader connections when looking for family mismatch. + return; + } + + if (renderContext == MtlxTokens::mtlx() || renderContext == UsdTokens::glslfx()) + { + // Make sure the node implementations all match: + TfToken id; + UsdShadeShader(srcNode).GetShaderId(&id); + SdrShaderNodeConstPtr srcShaderNode = SdrRegistry::GetInstance().GetShaderNodeByIdentifier(id); + + if (srcShaderNode->GetSourceType() != renderContext) + { + if (renderContext == m_renderContext) + { + m_log->addEntry({m_currentSeverity, + errorStr(ErrId::kImplMismatch, niceSourceName(srcShaderNode->GetSourceType()), + niceSourceName(renderContext)), + {1, toUfe(*cnx)}}); + } + else + { + // Unconnected island, different wording: + m_log->addEntry({m_currentSeverity, + errorStr(ErrId::kImplMismatch2, niceSourceName(srcShaderNode->GetSourceType()), + niceSourceName(renderContext)), + {1, toUfe(*cnx)}}); + } + } + } +} + +bool UsdMaterialValidator::validateAcyclic() +{ + // Take last source on the connection stack: + if (m_connectionStack.empty()) + { + return true; + } + + auto stackIter = m_connectionStack.rbegin(); + const auto& lastSrcNode = (*stackIter)->m_src.GetPrim(); + if (lastSrcNode.IsA()) + { + // Not checking NodeGraph boundaries as flattening the NodeGraph might resolve the cycle. See the NotACycle + // test scene for an example. + return true; + } + + // Component separate and combine nodes are not part of the cycle + auto isComponentCnx = [](const auto& cnx) { + return (isComponentNode(cnx.m_src.GetPrim()) == ComponentNodeType::eCombine || + isComponentNode(cnx.m_dst.GetPrim()) == ComponentNodeType::eSeparate); + }; + + for (; stackIter != m_connectionStack.rend(); ++stackIter) + { + if (lastSrcNode == (*stackIter)->m_dst.GetPrim()) + { + // Found a cycle + LookdevXUfe::Log::Locations locations; + locations.reserve(std::distance(m_connectionStack.rbegin(), stackIter) + 1); + // Top of CnxStack is the first back-edge found, so make it the first item in the list: + for (auto locIt = m_connectionStack.rbegin(); locIt != stackIter; ++locIt) + { + if (!isComponentCnx(**locIt)) + { + locations.emplace_back(toUfe(**locIt)); + } + } + if (!isComponentCnx(**stackIter)) + { + locations.emplace_back(toUfe(**stackIter)); + } + + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kCycle), std::move(locations)}); + return false; + } + } + + return true; +} + +void UsdMaterialValidator::reportInvalidSources(const UsdAttribute& dest, const SdfPathVector& invalidSourcePaths) +{ + if (invalidSourcePaths.empty()) + { + return; + } + + auto stage = dest.GetPrim().GetStage(); + for (SdfPath const& sourcePath : invalidSourcePaths) + { + + // Make sure the source node exists + auto srcPath = Ufe::PathString::string(toUfe(stage, sourcePath.GetPrimPath())); + UsdPrim sourcePrim = stage->GetPrimAtPath(sourcePath.GetPrimPath()); + if (!sourcePrim) + { + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kMissingNode, srcPath), {1, toUfe(dest)}}); + continue; + } + + // Make sure the source attribute exists + srcPath = Ufe::PathString::string(toUfe(stage, sourcePath)); + UsdAttribute sourceAttr = stage->GetAttributeAtPath(sourcePath); + if (!sourceAttr || !sourceAttr.IsAuthored()) + { + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kMissingAttr, srcPath), {1, toUfe(dest)}}); + continue; + } + + // Check that the attribute has a legal prefix + auto [sourceName, sourceType] = UsdShadeUtils::GetBaseNameAndType(sourcePath.GetNameToken()); + if (sourceType == UsdShadeAttributeType::Invalid) + { + m_log->addEntry({m_currentSeverity, errorStr(ErrId::kInvalidAttr, srcPath), {1, toUfe(dest)}}); + continue; + } + } +} + +void UsdMaterialValidator::traverseConnection() +{ + // Look at the source prim: + const UsdPrim& srcPrim = m_connectionStack.back()->m_src.GetPrim(); + + // Find all destinations of this node: + std::vector destinations; + if (auto srcNG = UsdShadeNodeGraph(srcPrim)) + { + // Traverse the NodeGraph connection: + destinations.push_back(m_connectionStack.back()->m_src); + } + else if (auto srcShade = UsdShadeShader(srcPrim)) + { + // Traverse all inputs: + for (auto&& input : srcShade.GetInputs()) + { + destinations.push_back(input.GetAttr()); + } + } + + for (auto&& dest : destinations) + { + visitDestination(dest); + } +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdMaterialValidator.h b/lib/lookdevXUsd/UsdMaterialValidator.h new file mode 100644 index 0000000000..7245e020f9 --- /dev/null +++ b/lib/lookdevXUsd/UsdMaterialValidator.h @@ -0,0 +1,106 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include "Export.h" + +#include "pxr/pxr.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace LookdevXUsd +{ + +struct UsdConnectionInfo; + +//! \brief USD run-time Material handler. +/*! + Factory object for Material interfaces. + */ +class UsdMaterialValidator +{ +public: + LOOKDEVX_USD_EXPORT explicit UsdMaterialValidator(const PXR_NS::UsdShadeMaterial& prim); + + LOOKDEVX_USD_EXPORT LookdevXUfe::ValidationLog::Ptr validate(); + +private: + LOOKDEVX_USD_EXPORT bool visitDestination(const PXR_NS::UsdAttribute& dest); + LOOKDEVX_USD_EXPORT bool validateShader(const PXR_NS::UsdShadeShader& shader); + LOOKDEVX_USD_EXPORT void validateGlslfxShader(const PXR_NS::UsdShadeConnectableAPI& connectableAPI, + PXR_NS::SdrShaderNodeConstPtr shaderNode); + LOOKDEVX_USD_EXPORT void validateMaterialXShader(const PXR_NS::UsdShadeConnectableAPI& connectableAPI, + PXR_NS::SdrShaderNodeConstPtr shaderNode); + LOOKDEVX_USD_EXPORT bool validateMaterial(const PXR_NS::UsdShadeMaterial& material); + LOOKDEVX_USD_EXPORT bool validateNodeGraph(const PXR_NS::UsdShadeNodeGraph& nodegraph); + LOOKDEVX_USD_EXPORT bool validatePrim(const PXR_NS::UsdPrim& prim); + LOOKDEVX_USD_EXPORT void validateConnection(); + LOOKDEVX_USD_EXPORT bool validateAcyclic(); + LOOKDEVX_USD_EXPORT void reportInvalidSources(const PXR_NS::UsdAttribute& dest, + const PXR_NS::SdfPathVector& invalidSourcePaths); + LOOKDEVX_USD_EXPORT void traverseConnection(); + + LOOKDEVX_USD_EXPORT LookdevXUfe::AttributeComponentInfo remapComponentConnectionAttribute( + const PXR_NS::UsdPrim& prim, const PXR_NS::TfToken& attrName) const; + LOOKDEVX_USD_EXPORT void validateComponentLocation(const LookdevXUfe::AttributeComponentInfo& attrInfo, + const std::string& errorDesc) const; + LOOKDEVX_USD_EXPORT static Ufe::Path toUfe(const PXR_NS::UsdStageWeakPtr& stage, const PXR_NS::SdfPath& path); + LOOKDEVX_USD_EXPORT static Ufe::Path toUfe(const PXR_NS::UsdPrim& prim); + LOOKDEVX_USD_EXPORT LookdevXUfe::AttributeComponentInfo toUfe(const PXR_NS::UsdAttribute& attrib) const; + LOOKDEVX_USD_EXPORT LookdevXUfe::AttributeComponentInfo toUfe(const PXR_NS::UsdPrim& prim, + const PXR_NS::TfToken& attrName) const; + LOOKDEVX_USD_EXPORT LookdevXUfe::Log::ConnectionInfo toUfe(const UsdConnectionInfo& cnx) const; + + const PXR_NS::UsdShadeMaterial& m_material; + LookdevXUfe::ValidationLog::Ptr m_log; + + // Keep a stack of the current connection chain we are following. We can detect a cycle by taking the source + // UsdShadeShader prim of the connection we are currently evaluating and traverse up the stack looking at the + // UsdShadeShader prim of the destinations. If we have a match, then we have a cycle from the current connection up + // to that one. Please note that we explicitly *ignore* the NodeGraph boundaries as it is possible to create a + // scenario where there appears to be a loop at the NodeGraph level that would be absent in a flattened of the same + // graph. + std::vector m_connectionStack; + + // This is the set of visited destinations. Once we are done the traversal from the material outputs, we wil + // traverse a second time all the children of the material in order to detect issues with isolated islands that are + // not yet connected to the material outputs. + std::unordered_set m_visitedDestinations; + + // Do not validate a node more than once: + std::unordered_map m_validatedPrims; + + // Current severity level. When evaluating the graph connected to a material output problems are given the Error + // level, but when we start looking at isolated subgraphs, we report issues as warnings. + LookdevXUfe::Log::Severity m_currentSeverity = LookdevXUfe::Log::Severity::kError; + + // The current render context we are traversing. Will affect some validation rules. + PXR_NS::TfToken m_renderContext; + + // we *might* need this map if we resolve a hidden combine node. In this case we + // need to find out all the potential inputs connected to a combine node. We suspect + // this will never be more that one + std::unordered_map m_seenCombineConnections; + + // Keep a map of broken combine/separate found so we error only once: + mutable std::set m_brokenComponents; +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdSceneItemOps.cpp b/lib/lookdevXUsd/UsdSceneItemOps.cpp new file mode 100644 index 0000000000..ac32369af7 --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemOps.cpp @@ -0,0 +1,123 @@ +//**************************************************************************/ +// Copyright 2024 Autodesk, Inc. All rights reserved. +// +// Use of this software is subject to the terms of the Autodesk +// license agreement provided at the time of installation or download, +// or which otherwise accompanies this software in either electronic +// or hard copy form. +//**************************************************************************/ +#include "UsdSceneItemOps.h" +#include "UsdDeleteCommand.h" + +#include + +#include + +using namespace PXR_NS; + +// Note: Some of the functions are not covered by unit tests because they are simple wrappers around the +// MayaUsd::ufe::UsdSceneItemOps functions. If we add our own logic to these functions, we should add unit tests for +// them. +namespace LookdevXUsd +{ +/*static*/ +UsdSceneItemOps::Ptr UsdSceneItemOps::create(Ufe::SceneItemOps::Ptr wrappedMayaUsdSceneItemOps) +{ + return std::make_shared(wrappedMayaUsdSceneItemOps); +} + +Ufe::SceneItem::Ptr UsdSceneItemOps::sceneItem() const +{ + // LCOV_EXCL_START + assert(m_wrappedMayaUsdSceneItemOps); + return m_wrappedMayaUsdSceneItemOps->sceneItem(); + // LCOV_EXCL_STOP +} + +Ufe::UndoableCommand::Ptr UsdSceneItemOps::deleteItemCmdNoExecute() +{ + assert(m_wrappedMayaUsdSceneItemOps); + + // Wrap the MayaUsd::ufe::UsdUndoDeleteCommand + return UsdDeleteCommand::create(m_wrappedMayaUsdSceneItemOps->deleteItemCmdNoExecute(), + m_wrappedMayaUsdSceneItemOps->sceneItem()); +} + +Ufe::UndoableCommand::Ptr UsdSceneItemOps::deleteItemCmd() +{ + auto deleteCmd = deleteItemCmdNoExecute(); + deleteCmd->execute(); + return deleteCmd; +} + +bool UsdSceneItemOps::deleteItem() +{ + auto prim = [this]() { + if (!TF_VERIFY(MayaUsdAPI::isUsdSceneItem(m_wrappedMayaUsdSceneItemOps->sceneItem()), "Invalid item\n")) + { + return PXR_NS::UsdPrim(); + } + return MayaUsdAPI::getPrimForUsdSceneItem(m_wrappedMayaUsdSceneItemOps->sceneItem()); + }; + + // Same check as in MayaUsd::ufe::UsdSceneItemOps::deleteItem() + if (prim()) + { + auto deleteCmd = deleteItemCmd(); + if (deleteCmd) + { + return true; + } + } + + return false; +} + +Ufe::SceneItemResultUndoableCommand::Ptr UsdSceneItemOps::duplicateItemCmdNoExecute() +{ + // LCOV_EXCL_START + assert(m_wrappedMayaUsdSceneItemOps); + return m_wrappedMayaUsdSceneItemOps->duplicateItemCmdNoExecute(); + // LCOV_EXCL_STOP +} + +Ufe::Duplicate UsdSceneItemOps::duplicateItemCmd() +{ + // LCOV_EXCL_START + assert(m_wrappedMayaUsdSceneItemOps); + return m_wrappedMayaUsdSceneItemOps->duplicateItemCmd(); + // LCOV_EXCL_STOP +} + +Ufe::SceneItem::Ptr UsdSceneItemOps::duplicateItem() +{ + // LCOV_EXCL_START + assert(m_wrappedMayaUsdSceneItemOps); + return m_wrappedMayaUsdSceneItemOps->duplicateItem(); + // LCOV_EXCL_STOP +} + +Ufe::SceneItemResultUndoableCommand::Ptr UsdSceneItemOps::renameItemCmdNoExecute(const Ufe::PathComponent& newName) +{ + // LCOV_EXCL_START + assert(m_wrappedMayaUsdSceneItemOps); + return m_wrappedMayaUsdSceneItemOps->renameItemCmdNoExecute(newName); + // LCOV_EXCL_STOP +} + +Ufe::Rename UsdSceneItemOps::renameItemCmd(const Ufe::PathComponent& newName) +{ + // LCOV_EXCL_START + assert(m_wrappedMayaUsdSceneItemOps); + return m_wrappedMayaUsdSceneItemOps->renameItemCmd(newName); + // LCOV_EXCL_STOP +} + +Ufe::SceneItem::Ptr UsdSceneItemOps::renameItem(const Ufe::PathComponent& newName) +{ + // LCOV_EXCL_START + assert(m_wrappedMayaUsdSceneItemOps); + return m_wrappedMayaUsdSceneItemOps->renameItem(newName); + // LCOV_EXCL_STOP +} +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdSceneItemOps.h b/lib/lookdevXUsd/UsdSceneItemOps.h new file mode 100644 index 0000000000..eacfa88f24 --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemOps.h @@ -0,0 +1,62 @@ +//**************************************************************************/ +// Copyright 2024 Autodesk, Inc. All rights reserved. +// +// Use of this software is subject to the terms of the Autodesk +// license agreement provided at the time of installation or download, +// or which otherwise accompanies this software in either electronic +// or hard copy form. +//**************************************************************************/ +#ifndef USD_SCENE_ITEM_OPS_H +#define USD_SCENE_ITEM_OPS_H + +#include +#include +#include +#include + +namespace LookdevXUsd +{ + +class UsdSceneItemOps : public Ufe::SceneItemOps +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdSceneItemOps(Ufe::SceneItemOps::Ptr wrappedMayaUsdSceneItemOps) + : m_wrappedMayaUsdSceneItemOps(std::move(wrappedMayaUsdSceneItemOps)) + { + } + + ~UsdSceneItemOps() override = default; + + //@{ + //! 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(Ufe::SceneItemOps::Ptr wrappedMayaUsdSceneItemOps); + + //! Ufe::SceneItemOps overrides. + [[nodiscard]] Ufe::SceneItem::Ptr sceneItem() const override; + [[nodiscard]] Ufe::UndoableCommand::Ptr deleteItemCmdNoExecute() override; + [[nodiscard]] Ufe::UndoableCommand::Ptr deleteItemCmd() override; + [[nodiscard]] bool deleteItem() override; + [[nodiscard]] Ufe::SceneItemResultUndoableCommand::Ptr duplicateItemCmdNoExecute() override; + [[nodiscard]] Ufe::Duplicate duplicateItemCmd() override; + [[nodiscard]] Ufe::SceneItem::Ptr duplicateItem() override; + [[nodiscard]] Ufe::SceneItemResultUndoableCommand::Ptr renameItemCmdNoExecute( + const Ufe::PathComponent& newName) override; + [[nodiscard]] Ufe::Rename renameItemCmd(const Ufe::PathComponent& newName) override; + [[nodiscard]] Ufe::SceneItem::Ptr renameItem(const Ufe::PathComponent& newName) override; + +private: + Ufe::SceneItemOps::Ptr m_wrappedMayaUsdSceneItemOps; + +}; // UsdSceneItemOps + +} // namespace LookdevXUsd +#endif \ No newline at end of file diff --git a/lib/lookdevXUsd/UsdSceneItemOpsHandler.cpp b/lib/lookdevXUsd/UsdSceneItemOpsHandler.cpp new file mode 100644 index 0000000000..b0bd299dbc --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemOpsHandler.cpp @@ -0,0 +1,41 @@ +//**************************************************************************/ +// Copyright 2024 Autodesk, Inc. All rights reserved. +// +// Use of this software is subject to the terms of the Autodesk +// license agreement provided at the time of installation or download, +// or which otherwise accompanies this software in either electronic +// or hard copy form. +//**************************************************************************/ +#include "UsdSceneItemOpsHandler.h" +#include "UsdSceneItemOps.h" + +#include + +namespace LookdevXUsd +{ +UsdSceneItemOpsHandler::UsdSceneItemOpsHandler(Ufe::SceneItemOpsHandler::Ptr mayaUsdSceneItemOpsHandler) + : m_mayaUsdSceneItemOpsHandler(std::move(mayaUsdSceneItemOpsHandler)) +{ +} + +/*static*/ +UsdSceneItemOpsHandler::Ptr UsdSceneItemOpsHandler::create( + const Ufe::SceneItemOpsHandler::Ptr& mayaUsdSceneItemOpsHandler) +{ + return std::make_shared(mayaUsdSceneItemOpsHandler); +} + +//------------------------------------------------------------------------------ +// SceneItemOpsHandler overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItemOps::Ptr UsdSceneItemOpsHandler::sceneItemOps(const Ufe::SceneItem::Ptr& item) const +{ + // Get the sceneItemOps interface from the next handler. + const auto nextSceneItemOps = m_mayaUsdSceneItemOpsHandler->sceneItemOps(item); + + // Wrap it up inside our decorator sceneItemOps. + return UsdSceneItemOps::create(nextSceneItemOps); +} + +} // namespace LookdevXUsd \ No newline at end of file diff --git a/lib/lookdevXUsd/UsdSceneItemOpsHandler.h b/lib/lookdevXUsd/UsdSceneItemOpsHandler.h new file mode 100644 index 0000000000..15c0a1923f --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemOpsHandler.h @@ -0,0 +1,46 @@ +//**************************************************************************/ +// Copyright 2024 Autodesk, Inc. All rights reserved. +// +// Use of this software is subject to the terms of the Autodesk +// license agreement provided at the time of installation or download, +// or which otherwise accompanies this software in either electronic +// or hard copy form. +//**************************************************************************/ +#ifndef USD_SCENE_ITEM_OPS_HANDLER_H +#define USD_SCENE_ITEM_OPS_HANDLER_H + +#include "Export.h" + +#include + +namespace LookdevXUsd +{ +class LOOKDEVX_USD_EXPORT UsdSceneItemOpsHandler : public Ufe::SceneItemOpsHandler +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdSceneItemOpsHandler(Ufe::SceneItemOpsHandler::Ptr mayaUsdSceneItemOpsHandler); + ~UsdSceneItemOpsHandler() override = default; + + //@{ + //! Delete the copy/move constructors assignment operators. + UsdSceneItemOpsHandler(const UsdSceneItemOpsHandler&) = delete; + UsdSceneItemOpsHandler& operator=(const UsdSceneItemOpsHandler&) = delete; + UsdSceneItemOpsHandler(UsdSceneItemOpsHandler&&) = delete; + UsdSceneItemOpsHandler& operator=(UsdSceneItemOpsHandler&&) = delete; + //@} + + // Ufe::SceneItemOpsHandler overrides + [[nodiscard]] Ufe::SceneItemOps::Ptr sceneItemOps(const Ufe::SceneItem::Ptr& item) const override; + + //! Create a UsdSceneItemOpsHandler. + static UsdSceneItemOpsHandler::Ptr create(const Ufe::SceneItemOpsHandler::Ptr& mayaUsdSceneItemOpsHandler); + +private: + Ufe::SceneItemOpsHandler::Ptr m_mayaUsdSceneItemOpsHandler; + +}; // UsdSceneItemOpsHandler + +} // namespace LookdevXUsd +#endif \ No newline at end of file diff --git a/lib/lookdevXUsd/UsdSceneItemUI.cpp b/lib/lookdevXUsd/UsdSceneItemUI.cpp new file mode 100644 index 0000000000..a6265f6ee9 --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemUI.cpp @@ -0,0 +1,78 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdSceneItemUI.h" +#include +#include +#include + +#include + +namespace +{ + +// For backwards compatibility for when we used usd prim hidden flag. Needs to be removed at some point. +bool isLegacyHiddenItem(const Ufe::SceneItem::Ptr& item) +{ + if (!item) + { + return false; + } + const auto soloingHandler = LookdevXUfe::SoloingHandler::get(item->runTimeId()); + const auto nodeDef = LookdevXUfe::UfeUtils::getNodeDef(item); + const auto isSeparateOrCombine = + nodeDef ? LookdevXUfe::identifyComponentNode(nodeDef->type()) != LookdevXUfe::ComponentNodeType::eNone : false; + + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(item); + return (prim.IsValid() && prim.IsHidden()) && + (isSeparateOrCombine || (soloingHandler && soloingHandler->isSoloingItem(item))); +} + +} // namespace + +namespace LookdevXUsd +{ + +UsdSceneItemUI::UsdSceneItemUI(Ufe::SceneItem::Ptr item, const PXR_NS::UsdPrim& prim) + : m_item(std::move(item)), m_stage(prim.IsValid() ? prim.GetStage() : nullptr), + m_path(prim.IsValid() ? prim.GetPath() : PXR_NS::SdfPath()) +{ +} + +/*static*/ +UsdSceneItemUI::Ptr UsdSceneItemUI::create(const Ufe::SceneItem::Ptr& item) +{ + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(item); + return std::make_shared(item, prim); +} + +//------------------------------------------------------------------------------ +// Ufe::SceneItemUI overrides +//------------------------------------------------------------------------------ + +Ufe::SceneItem::Ptr UsdSceneItemUI::sceneItem() const +{ + return m_item; +} + +bool UsdSceneItemUI::hidden() const +{ + return isLegacyHiddenItem(m_item) || + LookdevXUfe::getAutodeskMetadata(m_item, getHiddenKeyMetadata(m_item)).get() == "true"; +} + +Ufe::UndoableCommand::Ptr UsdSceneItemUI::setHiddenCmd(bool hidden) +{ + return LookdevXUfe::setAutodeskMetadataCmd(m_item, getHiddenKeyMetadata(m_item), + hidden ? std::string("true") : std::string("false")); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdSceneItemUI.h b/lib/lookdevXUsd/UsdSceneItemUI.h new file mode 100644 index 0000000000..a849b5aad7 --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemUI.h @@ -0,0 +1,59 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#ifndef USD_SCENE_ITEM_UI_H +#define USD_SCENE_ITEM_UI_H + +#include + +#include +#include + +#include + +namespace LookdevXUsd +{ + +//! \brief USD run-time Scene Item UI interface +/*! + This class implements the Scene Item UI interface for USD prims. +*/ +class UsdSceneItemUI : public LookdevXUfe::SceneItemUI +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdSceneItemUI(Ufe::SceneItem::Ptr item, const PXR_NS::UsdPrim& prim); + ~UsdSceneItemUI() override = default; + + // Delete the copy/move constructors assignment operators. + UsdSceneItemUI(const UsdSceneItemUI&) = delete; + UsdSceneItemUI& operator=(const UsdSceneItemUI&) = delete; + UsdSceneItemUI(UsdSceneItemUI&&) = delete; + UsdSceneItemUI& operator=(UsdSceneItemUI&&) = delete; + + //! Create a UsdSceneItemUI. + static UsdSceneItemUI::Ptr create(const Ufe::SceneItem::Ptr& item); + + // Ufe::SceneItemUI overrides + Ufe::SceneItem::Ptr sceneItem() const override; + bool hidden() const override; + Ufe::UndoableCommand::Ptr setHiddenCmd(bool hidden) override; + +private: + Ufe::SceneItem::Ptr m_item; + const PXR_NS::UsdStageWeakPtr m_stage; + const PXR_NS::SdfPath m_path; +}; + +} // namespace LookdevXUsd + +#endif diff --git a/lib/lookdevXUsd/UsdSceneItemUIHandler.cpp b/lib/lookdevXUsd/UsdSceneItemUIHandler.cpp new file mode 100644 index 0000000000..943dd371c8 --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemUIHandler.cpp @@ -0,0 +1,39 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdSceneItemUIHandler.h" +#include "UsdSceneItemUI.h" + +#include + +namespace LookdevXUsd +{ + +/*static*/ +UsdSceneItemUIHandler::Ptr UsdSceneItemUIHandler::create() +{ + return std::make_shared(); +} + +//------------------------------------------------------------------------------ +// UsdSceneItemUIHandler overrides +//------------------------------------------------------------------------------ + +LookdevXUfe::SceneItemUI::Ptr UsdSceneItemUIHandler::sceneItemUI(const Ufe::SceneItem::Ptr& item) const +{ +#if !defined(NDEBUG) + assert(MayaUsdAPI::isUsdSceneItem(item)); +#endif + + return LookdevXUsd::UsdSceneItemUI::create(item); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdSceneItemUIHandler.h b/lib/lookdevXUsd/UsdSceneItemUIHandler.h new file mode 100644 index 0000000000..7bac5a5cff --- /dev/null +++ b/lib/lookdevXUsd/UsdSceneItemUIHandler.h @@ -0,0 +1,48 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#ifndef USD_SCENE_ITEM_UI_HANDLER_H +#define USD_SCENE_ITEM_UI_HANDLER_H + +#include + +namespace LookdevXUsd +{ + +//! \brief USD run-time Scene Item UI handler. +/*! + Factory object for Scene Item UI interfaces. + */ +class UsdSceneItemUIHandler : public LookdevXUfe::SceneItemUIHandler +{ +public: + using Ptr = std::shared_ptr; + + UsdSceneItemUIHandler() = default; + ~UsdSceneItemUIHandler() override = default; + + // Delete the copy/move constructors assignment operators. + UsdSceneItemUIHandler(const UsdSceneItemUIHandler&) = delete; + UsdSceneItemUIHandler& operator=(const UsdSceneItemUIHandler&) = delete; + UsdSceneItemUIHandler(UsdSceneItemUIHandler&&) = delete; + UsdSceneItemUIHandler& operator=(UsdSceneItemUIHandler&&) = delete; + + //! Create a UsdSceneItemUIHandler. + static UsdSceneItemUIHandler::Ptr create(); + + // UsdSceneItemUIHandler overrides + [[nodiscard]] LookdevXUfe::SceneItemUI::Ptr sceneItemUI(const Ufe::SceneItem::Ptr& item) const override; + +}; // UsdSceneItemUIHandler + +} // namespace LookdevXUsd + +#endif diff --git a/lib/lookdevXUsd/UsdSoloingHandler.cpp b/lib/lookdevXUsd/UsdSoloingHandler.cpp new file mode 100644 index 0000000000..e2a6221cb0 --- /dev/null +++ b/lib/lookdevXUsd/UsdSoloingHandler.cpp @@ -0,0 +1,1025 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "UsdSoloingHandler.h" +#include "LookdevXUfe/SceneItemUI.h" +#include "Utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace LookdevXUfe; +using namespace PXR_NS; + +namespace LookdevXUsd +{ + +namespace +{ + +// Used for naming nodes and/or layers. +constexpr auto kSoloingTag = "LookdevXSoloing"; +// Custom item metadata to mark nodes that are created by soloing. +constexpr auto kSoloingItem = "ldx_isSoloingItem"; +// Custom item metadata to store on a soloing node that acts as state info holder. +constexpr auto kHasSoloingInfo = "ldx_hasSoloingInfo"; +// -- Info about replaced material output connections. +constexpr auto kReplacedMaterialAttribute = "ldx_replacedMaterialAttribute"; +constexpr auto kReplacedShaderAttribute = "ldx_replacedShaderAttribute"; +constexpr auto kReplacedShaderName = "ldx_replacedShaderName"; +// -- Info about currently soloed item. +constexpr auto kSoloedItemPath = "ldx_soloedItemPath"; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Metadata shortcuts + +std::string getMetadata(const Ufe::SceneItem::Ptr& item, const std::string& key) +{ + if (!item) + { + return ""; + } + return LookdevXUfe::getAutodeskMetadata(item, key).get(); +} + +void setMetadata(const Ufe::SceneItem::Ptr& item, const std::string& key, const std::string& value) +{ + if (!item) + { + return; + } + return LookdevXUfe::setAutodeskMetadataCmd(item, key, Ufe::Value(value))->execute(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Utility functions + +Ufe::ConnectionHandler::Ptr getConnHandler() +{ + static auto connHandler = Ufe::RunTimeMgr::instance().connectionHandler(MayaUsdAPI::getUsdRunTimeId()); + return connHandler; +} + +// Visitor for soloing items. The return value of the supplied function controls early stop of interation (on false). +void processSoloingPrimChildren(const Ufe::SceneItem::Ptr& parent, + const std::function& fn) +{ + if (!parent) + { + return; + } + + for (const auto& child : Ufe::Hierarchy::hierarchy(parent)->children()) + { + if (getMetadata(child, kSoloingItem) == "true") + { + if (!fn(child)) + { + break; + } + } + } +} + +Ufe::SceneItem::Ptr getSoloingInfoItem(const Ufe::SceneItem::Ptr& parent) +{ + Ufe::SceneItem::Ptr retval = nullptr; + processSoloingPrimChildren(parent, [&](const auto& child) { + if (getMetadata(child, kHasSoloingInfo) == "true") + { + retval = child; + return false; + } + return true; + }); + return retval; +} + +Ufe::SceneItem::Ptr getSoloedUsdItem(const Ufe::SceneItem::Ptr& material) +{ + const auto path = Ufe::PathString::path(getMetadata(getSoloingInfoItem(material), kSoloedItemPath)); + return Ufe::Hierarchy::createItem(path); +} + +Ufe::SceneItem::Ptr getParentUsdMaterial(const Ufe::SceneItem::Ptr& item) +{ + return UfeUtils::getLookdevContainer(item); +} + +class EditTargetGuard +{ +public: + explicit EditTargetGuard(UsdStageWeakPtr stage, const SdfLayerRefPtr& layer) + : m_stage(std::move(stage)), m_originalEditTarget(m_stage->GetEditTarget()) + { + m_stage->SetEditTarget(layer); + } + + EditTargetGuard(const EditTargetGuard&) = delete; + EditTargetGuard(EditTargetGuard&&) = delete; + EditTargetGuard& operator=(const EditTargetGuard&) = delete; + EditTargetGuard&& operator=(EditTargetGuard&&) = delete; + + ~EditTargetGuard() + { + m_stage->SetEditTarget(m_originalEditTarget); + } + +private: + UsdStageWeakPtr m_stage; + UsdEditTarget m_originalEditTarget; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// NodeGraphRegistry + +// Soloing is implemented by created hidden node graphs that route the desired result to the material output. +// This class maps node source types and attribute types to node graph creation functions. +class NodeGraphRegistry +{ +public: + using NodeGraph_F = std::function; + + NodeGraphRegistry() + { + // It is assumed that as long as a standard surface node exits, the rest of the nodes as well as the + // expected attributes also exist, and no further fine-grained error checking will happen during node creation. + + const auto* mtlxShaderNodeDef = + SdrRegistry::GetInstance().GetShaderNodeByIdentifier(TfToken(kMtlxStandardSurface)); + if (mtlxShaderNodeDef) + { + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kColorFloat4, mtlxColorFloat4); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kFloat4, mtlxFloat4); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kColorFloat3, mtlxColorFloat3); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kFloat3, mtlxFloat3); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kFloat2, mtlxFloat2); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kFloat, mtlxFloat); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kInt, mtlxInt); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kBool, mtlxBool); + registerNodeGraph(TfToken(kMtlx), Ufe::Attribute::kGeneric, surfaceShaderDirect); + } + const auto* arnoldShaderNodeDef = + SdrRegistry::GetInstance().GetShaderNodeByIdentifier(TfToken(kArnoldStandardSurface)); + if (arnoldShaderNodeDef) + { + registerNodeGraph(TfToken(kArnold), Ufe::Attribute::kColorFloat4, arnoldTypeless); + registerNodeGraph(TfToken(kArnold), Ufe::Attribute::kColorFloat3, arnoldTypeless); + registerNodeGraph(TfToken(kArnold), Ufe::Attribute::kFloat3, arnoldTypeless); + registerNodeGraph(TfToken(kArnold), Ufe::Attribute::kFloat, arnoldTypeless); + registerNodeGraph(TfToken(kArnold), Ufe::Attribute::kInt, arnoldTypeless); + registerNodeGraph(TfToken(kArnold), Ufe::Attribute::kBool, arnoldTypeless); + registerNodeGraph(TfToken(kArnold), Ufe::Attribute::kGeneric, surfaceShaderDirect); + } + } + + static NodeGraphRegistry& instance() + { + static NodeGraphRegistry instance; + return instance; + } + + [[nodiscard]] bool supports(const TfToken& shaderSourceType, const Ufe::Attribute::Ptr& attr) const + { + if (attr->type() == Ufe::Attribute::kGeneric) + { + const auto deepAttr = UfeUtils::getConnectedSource(attr); + if (!deepAttr) + { + return false; + } + if (shaderSourceType == TfToken(kMtlx)) + { + const auto genericAttr = std::dynamic_pointer_cast(deepAttr); + // A MaterialX "surfaceshader" output is a USD "terminal" one: + if (!genericAttr || genericAttr->nativeType() != SdrPropertyTypes->Terminal) + { + return false; + } + } + const auto nodeDef = UfeUtils::getNodeDef(deepAttr->sceneItem()); + if (shaderSourceType == TfToken(kArnold)) + { + // Since multiple things map to generic, have a hardcoded list of node categories. + static const std::set allowed = {"Surface", "Pbr"}; + if (!nodeDef || nodeDef->nbClassifications() < 2 || !allowed.count(nodeDef->classification(1))) + { + return false; + } + } + } + return attr && m_nodeGraphs.count(shaderSourceType) && m_nodeGraphs.at(shaderSourceType).count(attr->type()); + } + + void createNodeGraph(const Ufe::SceneItem::Ptr& parent, + const TfToken& shaderSourceType, + const Ufe::Attribute::Ptr& attr) const + { + if (parent && supports(shaderSourceType, attr)) + { + m_nodeGraphs.at(shaderSourceType).at(attr->type())(parent, attr); + } + } + +private: + void registerNodeGraph(const TfToken& shaderSourceType, + const Ufe::Attribute::Type& attrType, + const NodeGraph_F& nodeGraphF) + { + m_nodeGraphs[shaderSourceType][attrType] = nodeGraphF; + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + // MaterialX + + static void mtlxColorFloat4(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxSurfaceShader(parent, attr); + } + + static void mtlxFloat4(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxColorFloat4(parent, converterNode(parent, TfToken("ND_convert_vector4_color4"), attr)); + } + + static void mtlxColorFloat3(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxSurfaceShader(parent, attr); + } + + static void mtlxFloat3(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxColorFloat3(parent, converterNode(parent, TfToken("ND_convert_vector3_color3"), attr)); + } + + static void mtlxFloat2(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxFloat3(parent, converterNode(parent, TfToken("ND_convert_vector2_vector3"), attr)); + } + + static void mtlxFloat(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxColorFloat3(parent, converterNode(parent, TfToken("ND_convert_float_color3"), attr)); + } + + static void mtlxInt(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxFloat(parent, converterNode(parent, TfToken("ND_convert_integer_float"), attr)); + } + + static void mtlxBool(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + mtlxFloat(parent, converterNode(parent, TfToken("ND_convert_boolean_float"), attr)); + } + + static void mtlxSurfaceShader(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& color) + { + assert((color->type() == Ufe::Attribute::kColorFloat3 || color->type() == Ufe::Attribute::kColorFloat4)); + + auto shaderUsdItem = createNode(parent, TfToken(kMtlxStandardSurface)); + auto shaderAttrs = Ufe::Attributes::attributes(shaderUsdItem); + + auto base = shaderAttrs->attribute("inputs:base"); + std::dynamic_pointer_cast(base)->set(0.F); + auto specular = shaderAttrs->attribute("inputs:specular"); + std::dynamic_pointer_cast(specular)->set(0.F); + auto emission = shaderAttrs->attribute("inputs:emission"); + std::dynamic_pointer_cast(emission)->set(1.F); + + auto emissionIn = shaderAttrs->attribute("inputs:emission_color"); + auto opacityIn = shaderAttrs->attribute("inputs:opacity"); + + if (std::dynamic_pointer_cast(color)) + { + getConnHandler()->connect(color, emissionIn); + } + else if (std::dynamic_pointer_cast(color)) + { + getConnHandler()->connect(converterNode(parent, TfToken("ND_convert_color4_color3"), color), emissionIn); + getConnHandler()->connect( + converterNode(parent, TfToken("ND_convert_float_color3"), + converterNode(parent, TfToken("ND_separate4_color4"), color, "outputs:outa")), + opacityIn); + } + + setupMaterialConnection(parent, shaderUsdItem); + } + + static Ufe::Attribute::Ptr converterNode(const Ufe::SceneItem::Ptr& parent, + const TfToken& nodeId, + const Ufe::Attribute::Ptr& attr, + const std::string& outputName = "outputs:out") + { + auto convert = createNode(parent, nodeId); + auto convertAttrs = Ufe::Attributes::attributes(convert); + auto convertIn = convertAttrs->attribute("inputs:in"); + auto convertOut = convertAttrs->attribute(outputName); + getConnHandler()->connect(attr, convertIn); + return convertOut; + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + // Arnold + + static void arnoldTypeless(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + arnoldSurfaceShader(parent, attr); + } + + static void arnoldSurfaceShader(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& color) + { + auto shaderUsdItem = createNode(parent, TfToken(kArnoldStandardSurface)); + auto shaderAttrs = Ufe::Attributes::attributes(shaderUsdItem); + + auto base = shaderAttrs->attribute("inputs:base"); + std::dynamic_pointer_cast(base)->set(0.F); + auto specular = shaderAttrs->attribute("inputs:specular"); + std::dynamic_pointer_cast(specular)->set(0.F); + auto emission = shaderAttrs->attribute("inputs:emission"); + + if (color->type() != Ufe::Attribute::kBool) + { + std::dynamic_pointer_cast(emission)->set(1.F); + auto emissionIn = shaderAttrs->attribute("inputs:emission_color"); + getConnHandler()->connect(color, emissionIn); + + if (std::dynamic_pointer_cast(color)) + { + auto opacityIn = shaderAttrs->attribute("inputs:opacity"); + + auto convert = createNode(parent, TfToken("arnold:rgba_to_float")); + auto convertAttrs = Ufe::Attributes::attributes(convert); + auto convertIn = convertAttrs->attribute("inputs:input"); + auto convertOut = convertAttrs->attribute("outputs:out"); + auto mode = convertAttrs->attribute("inputs:mode"); + std::dynamic_pointer_cast(mode)->set("a"); + + getConnHandler()->connect(color, convertIn); + getConnHandler()->connect(convertOut, opacityIn); + } + } + // Boolean cannot connect directly to color and be converted, so it is connected to the emission weight itself. + else + { + getConnHandler()->connect(color, emission); + } + + setupMaterialConnection(parent, shaderUsdItem); + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + // Helper functions + + static Ufe::SceneItem::Ptr createNode(const Ufe::SceneItem::Ptr& parent, const TfToken& nodeId) + { + Ufe::NotificationGuard guard(Ufe::Scene::instance()); + + Ufe::NodeDef::Ptr nodeDef = Ufe::NodeDef::definition(parent->runTimeId(), nodeId); + auto cmd = nodeDef->createNodeCmd(parent, std::string(kSoloingTag)); + cmd->execute(); + + auto usdSceneItem = cmd->insertedChild(); + setMetadata(usdSceneItem, kSoloingItem, "true"); + const auto sceneItemUI = LookdevXUfe::SceneItemUI::sceneItemUI(usdSceneItem); + sceneItemUI->setHiddenCmd(true)->execute(); + + return usdSceneItem; + } + + static void setupMaterialConnection(const Ufe::SceneItem::Ptr& material, + const Ufe::SceneItem::Ptr& shader) + { + // Create a map of from material outputs to source attributes to find out which one is getting replaced. + // This is because depending on the type of shader connected, the output can have a different name than + // "outputs::surface", which is not known before the connection is created. + std::unordered_map attrToSrc; + auto connHandler = Ufe::RunTimeMgr::instance().connectionHandler(material->runTimeId()); + auto materialConns = connHandler->sourceConnections(material)->allConnections(); + for (const auto& conn : materialConns) + { + attrToSrc[conn->dst().name()] = conn->src().attribute(); + } + + // Connect shader output to material output + auto materialOut = Ufe::Attributes::attributes(material)->attribute("outputs:surface"); + auto shaderOut = Ufe::Attributes::attributes(shader)->attribute("outputs:out"); + auto cmd = getConnHandler()->createConnectionCmd(shaderOut, materialOut); + cmd->execute(); + + auto outputName = cmd->connection()->dst().name(); + // Always keep track of the material output that soloing connects to. + setMetadata(shader, kHasSoloingInfo, "true"); + setMetadata(shader, kReplacedMaterialAttribute, outputName); + + // Fetch the new output name and check if a connection existed before. If so, store the info to recreate it. + if (attrToSrc.count(outputName)) + { + auto src = attrToSrc[outputName]; + + setMetadata(shader, kReplacedShaderName, src->sceneItem()->nodeName()); + setMetadata(shader, kReplacedShaderAttribute, src->name()); + } + } + + static void surfaceShaderDirect(const Ufe::SceneItem::Ptr& parent, const Ufe::Attribute::Ptr& attr) + { + Ufe::NotificationGuard guard(Ufe::Scene::instance()); + // Use a noop compound as an intermediary node for holding soloing information. + auto cmd = MayaUsdAPI::createAddNewPrimCommand(parent, std::string(kSoloingTag), "NodeGraph"); + cmd->execute(); + + auto compound = cmd->sceneItem(); + setMetadata(compound, kSoloingItem, "true"); + const auto sceneItemUI = LookdevXUfe::SceneItemUI::sceneItemUI(compound); + sceneItemUI->setHiddenCmd(true)->execute(); + + auto compoundIn = Ufe::Attributes::attributes(compound)->addAttribute("inputs:in", attr->type()); + auto compoundOut = Ufe::Attributes::attributes(compound)->addAttribute("outputs:out", attr->type()); + std::string outputName = UfeUtils::getOutputPrefix() + "out"; + if (auto nodeDefHandler = Ufe::RunTimeMgr::instance().nodeDefHandler(attr->sceneItem()->runTimeId())) + { + if (auto nodeDef = nodeDefHandler->definition(attr->sceneItem())) + { + outputName = UfeUtils::getOutputPrefix() + nodeDef->outputNames().front(); + } + } + auto shaderOut = Ufe::Attributes::attributes(attr->sceneItem())->attribute(outputName); + getConnHandler()->connect(shaderOut, compoundIn); + getConnHandler()->connect(compoundIn, compoundOut); + + setupMaterialConnection(parent, compound); + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + + std::map> m_nodeGraphs; + + static constexpr auto kMtlxStandardSurface = "ND_standard_surface_surfaceshader"; + static constexpr auto kArnoldStandardSurface = "arnold:standard_surface"; + static constexpr auto kMtlx = "mtlx"; + static constexpr auto kArnold = "arnold"; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Undoable Commands (Solo/Unsolo) + +class UsdUnsoloCommand : public Ufe::UndoableCommand +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdUnsoloCommand(Ufe::SceneItem::Ptr item) : m_item(std::move(item)) + { + } + + ~UsdUnsoloCommand() override = default; + + UsdUnsoloCommand(const UsdUnsoloCommand&) = delete; + UsdUnsoloCommand& operator=(const UsdUnsoloCommand&) = delete; + UsdUnsoloCommand(UsdUnsoloCommand&&) = delete; + UsdUnsoloCommand& operator=(UsdUnsoloCommand&&) = delete; + + static Ptr create(const Ufe::SceneItem::Ptr& item) + { + return std::make_shared(item); + } + + [[nodiscard]] std::string commandString() const override + { + return "Unsolo"; + } + + void execute() override + { + const MayaUsdAPI::UsdUndoBlock undoBlock(&m_undoableItem); + + auto sessionLayer = m_item ? LookdevXUsdUtils::getSessionLayer(m_item) : nullptr; + if (!sessionLayer) + { + return; + } + + auto material = getParentUsdMaterial(m_item); + if (!material) + { + return; + } + + m_soloedItem = getSoloedUsdItem(material); + + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(material); + auto stage = prim.GetStage(); + + { + EditTargetGuard editTargetGuard(stage, sessionLayer); + + const auto& opsHandler = Ufe::RunTimeMgr::instance().sceneItemOpsHandler(material->runTimeId()); + processSoloingPrimChildren(material, [&opsHandler](const auto& child) { + opsHandler->sceneItemOps(child)->deleteItem(); + return true; + }); + } + + notify(false); + } + + void undo() override + { + m_undoableItem.undo(); + notify(true); + } + + void redo() override + { + m_undoableItem.redo(); + notify(false); + } + +private: + void notify(bool soloEnabled) + { + // If the soloed item no longer exists, it means the unsolo command happened + // in response to deleting it. In this case, the passed item was the parent. + // If the parent was a compound, it will be useful to process notifications. + if (m_soloedItem || (m_item && *getParentUsdMaterial(m_item) != *m_item)) + { + LookdevXUfe::SoloingStateChanged notif(m_soloedItem ? m_soloedItem : m_item, soloEnabled); + LookdevXUfe::Notifier::instance().notify(notif); + } + } + + MayaUsdAPI::UsdUndoableItem m_undoableItem; + + // Input item. + Ufe::SceneItem::Ptr m_item; + // Input could be either soloed item or parent material, so keep track of soloed item explicitly. + Ufe::SceneItem::Ptr m_soloedItem; +}; + +class UsdSoloCommand : public Ufe::UndoableCommand +{ +public: + using Ptr = std::shared_ptr; + + explicit UsdSoloCommand(Ufe::Attribute::Ptr attr) : m_attr(std::move(attr)) + { + assert(m_attr); + } + + ~UsdSoloCommand() override = default; + + UsdSoloCommand(const UsdSoloCommand&) = delete; + UsdSoloCommand& operator=(const UsdSoloCommand&) = delete; + UsdSoloCommand(UsdSoloCommand&&) = delete; + UsdSoloCommand& operator=(UsdSoloCommand&&) = delete; + + static Ptr create(const Ufe::Attribute::Ptr& attr) + { + return std::make_shared(attr); + } + + [[nodiscard]] std::string commandString() const override + { + return "Solo"; + } + + void execute() override + { + const MayaUsdAPI::UsdUndoBlock undoBlock(&m_undoableItem); + + auto item = m_attr->sceneItem(); + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(item); + auto stage = prim.GetStage(); + auto material = getParentUsdMaterial(item); + auto sessionLayer = LookdevXUsdUtils::getSessionLayer(item); + + if (!sessionLayer || !material) + { + return; + } + + m_previousSoloedItem = getSoloedUsdItem(material); + // Always unsolo at material level to ensure no stale nodes are left behind. + UsdUnsoloCommand::create(material)->execute(); + + { + EditTargetGuard guard(stage, sessionLayer); + NodeGraphRegistry::instance().createNodeGraph(material, LookdevXUsdUtils::getShaderSourceType(m_attr), + m_attr); + + auto soloingInfoItem = getSoloingInfoItem(material); + if (soloingInfoItem) + { + setMetadata(soloingInfoItem, kSoloedItemPath, Ufe::PathString::string(item->path())); + } + } + + if (m_attr->sceneItem()) + { + LookdevXUfe::SoloingStateChanged notif(m_attr->sceneItem(), true); + LookdevXUfe::Notifier::instance().notify(notif); + } + } + + void undo() override + { + m_undoableItem.undo(); + if (m_attr->sceneItem()) + { + LookdevXUfe::SoloingStateChanged notif(m_attr->sceneItem(), false); + LookdevXUfe::Notifier::instance().notify(notif); + } + if (m_previousSoloedItem) + { + LookdevXUfe::SoloingStateChanged notif(m_previousSoloedItem, true); + LookdevXUfe::Notifier::instance().notify(notif); + } + } + + void redo() override + { + m_undoableItem.redo(); + if (m_previousSoloedItem) + { + LookdevXUfe::SoloingStateChanged notif(m_previousSoloedItem, false); + LookdevXUfe::Notifier::instance().notify(notif); + } + if (m_attr->sceneItem()) + { + LookdevXUfe::SoloingStateChanged notif(m_attr->sceneItem(), true); + LookdevXUfe::Notifier::instance().notify(notif); + } + } + +private: + MayaUsdAPI::UsdUndoableItem m_undoableItem; + + // Input attribute to solo. + Ufe::Attribute::Ptr m_attr; + // Caching potential pre-soloed to notify on undo. + Ufe::SceneItem::Ptr m_previousSoloedItem; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SoloingObserver + +class SoloingObserver : public Ufe::Observer +{ +public: + void operator()(const Ufe::Notification& notification) override + { + auto getSoloedPath = [](const auto& item) { + if (!item) + { + return Ufe::Path(); + } + auto material = getParentUsdMaterial(item); + auto soloingInfoItem = getSoloingInfoItem(material); + return Ufe::PathString::path(getMetadata(soloingInfoItem, kSoloedItemPath)); + }; + + auto findAffectedItem = [&](const auto& usdItem, const auto& path) { + auto soloedPath = getSoloedPath(usdItem); + + Ufe::SceneItem::Ptr result = nullptr; + + if (soloedPath == path) + { + result = usdItem; + } + // The affected item may not necessarily be the same that triggered the notification. + // A changed path from an ancestor can invalidate connections. In this case, the new path is + // reconstructed by replacing the old prefix of the path with the new one. + else if (soloedPath.startsWith(path)) + { + result = Ufe::Hierarchy::createItem(soloedPath.reparent(path, usdItem->path())); + } + + return result; + }; + + if (const auto* pathNotif = dynamic_cast(¬ification)) + { + auto resoloOnPathChange = [&](const auto& item) { + auto affected = findAffectedItem(item, pathNotif->changedPath()); + if (affected && UfeUtils::getFirstOutput(affected)) + { + UsdSoloCommand::create(UfeUtils::getFirstOutput(affected))->execute(); + } + }; + + if (const auto* renameNotif = dynamic_cast(¬ification)) + { + resoloOnPathChange(renameNotif->item()); + } + else if (const auto* reparentNotif = dynamic_cast(¬ification)) + { + resoloOnPathChange(reparentNotif->item()); + } + } + else if (const auto* destroyNotif = dynamic_cast(¬ification)) + { + // Actual item no longer exists, but its parent potentially does. + auto parentPath = destroyNotif->path().pop(); + auto parentItem = Ufe::Hierarchy::createItem(parentPath); + auto soloedPath = getSoloedPath(parentItem); + auto deletedPath = destroyNotif->path(); + + // Case when deleting ancestor since there are no nested notifications. + if (parentItem && (soloedPath.startsWith(deletedPath))) + { + // If deleting the shader that was connected directly to the material before soloing, then when + // unsoloing the old connection will be looking for the deleted node and error. + // To avoid this, delete the attribute since there is nothing to connect to. + auto soloingInfoItem = getSoloingInfoItem(parentItem); + auto material = getParentUsdMaterial(parentItem); + auto shaderName = getMetadata(soloingInfoItem, kReplacedShaderName); + Ufe::UndoableCommand::Ptr removeAttrCmd = nullptr; + if (!UfeUtils::findChild(material, shaderName)) + { + auto materialOut = getMetadata(soloingInfoItem, kReplacedMaterialAttribute); + removeAttrCmd = Ufe::Attributes::attributes(material)->removeAttributeCmd(materialOut); + } + // Unsolo can work with any input that can be resolved to parent material. + UsdUnsoloCommand::create(parentItem)->execute(); + if (removeAttrCmd) + { + removeAttrCmd->execute(); + } + } + // Case when deleting descendant. Only unsolo if compound is no longer soloable after internal deletion. + else if (parentItem && deletedPath.startsWith(soloedPath)) + { + auto parentMaterial = getParentUsdMaterial(parentItem); + auto soloedItem = getSoloedUsdItem(parentMaterial); + if (soloedItem && !LookdevXUfe::SoloingHandler::get(soloedItem->runTimeId())->isSoloable(soloedItem)) + { + UsdUnsoloCommand::create(soloedItem)->execute(); + } + } + } + else if (const auto* connNotif = dynamic_cast(¬ification)) + { + // The user can attempt to connect other surface shaders to the material output while soloing is active. + // In that case, the fake connection information needs to be updated. + + const auto item = Ufe::Hierarchy::createItem(connNotif->path()); + const auto prim = MayaUsdAPI::getPrimForUsdSceneItem(item); + // Only applicable to material nodes. + if (!item || !prim || prim.GetTypeName() != TfToken("Material")) + { + return; + } + + auto soloingInfoItem = getSoloingInfoItem(item); + // Only applicable during soloing. + if (!soloingInfoItem) + { + return; + } + + auto attr = Ufe::Attributes::attributes(item)->attribute(connNotif->name()); + auto materialOut = getMetadata(soloingInfoItem, kReplacedMaterialAttribute); + // Only applicable if the changed output matches the one soloing is using. + if (!attr || materialOut != attr->name()) + { + return; + } + + auto stack = prim.GetPrimStack(); + // With active soloing, the prim stack of the material output will always contain the session layer edits. + assert(stack.size() > 1); + // Find the second strongest layer that has a connection for the material output (beyond the session one). + // The prim stack is sorted from strong to weak. + for (size_t i = 1; i < stack.size(); ++i) + { + const auto& primSpec = stack[i]; + for (const auto& attribute : primSpec->GetAttributes()) + { + if (attribute->GetName() == materialOut && attribute->HasConnectionPaths()) + { + const EditTargetGuard editTargetGuard(prim.GetStage(), prim.GetStage()->GetSessionLayer()); + + const auto& paths = attribute->GetConnectionPathList().GetAddedOrExplicitItems(); + assert(!paths.empty()); + // Only one connection should be on a specific material output. + const auto& path = paths.front(); + + setMetadata(soloingInfoItem, kReplacedShaderName, path.GetPrimPath().GetName()); + setMetadata(soloingInfoItem, kReplacedShaderAttribute, path.GetName()); + break; + } + } + } + } + } +}; + +Ufe::Observer::Ptr g_soloingObserver; + +} // namespace + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SoloingHandler + +UsdSoloingHandler::UsdSoloingHandler() +{ + g_soloingObserver = std::make_shared(); + Ufe::Attributes::addObserver(g_soloingObserver); + Ufe::Scene::instance().addObserver(g_soloingObserver); +} + +UsdSoloingHandler::~UsdSoloingHandler() +{ + Ufe::Attributes::removeObserver(g_soloingObserver); + Ufe::Scene::instance().removeObserver(g_soloingObserver); + g_soloingObserver.reset(); +} + +LookdevXUfe::SoloingHandler::Ptr UsdSoloingHandler::create() +{ + return std::make_shared(); +} + +bool UsdSoloingHandler::isSoloable(const Ufe::SceneItem::Ptr& item) const +{ + return item && isSoloable(UfeUtils::getFirstOutput(item)); +} + +bool UsdSoloingHandler::isSoloable(const Ufe::Attribute::Ptr& attr) const +{ + if (!attr) + { + return false; + } + + auto shaderSource = LookdevXUsdUtils::getShaderSourceType(attr); + + return NodeGraphRegistry::instance().supports(shaderSource, attr); +} + +Ufe::UndoableCommand::Ptr UsdSoloingHandler::soloCmd(const Ufe::SceneItem::Ptr& item) const +{ + return soloCmd(item ? UfeUtils::getFirstOutput(item) : nullptr); +} + +Ufe::UndoableCommand::Ptr UsdSoloingHandler::soloCmd(const Ufe::Attribute::Ptr& attr) const +{ + if (!isSoloable(attr)) + { + return nullptr; + } + + return UsdSoloCommand::create(attr); +} + +Ufe::UndoableCommand::Ptr UsdSoloingHandler::unsoloCmd(const Ufe::SceneItem::Ptr& item) const +{ + if (!item) + { + return nullptr; + } + + return UsdUnsoloCommand::create(item); +} + +bool UsdSoloingHandler::isSoloed(const Ufe::SceneItem::Ptr& item) const +{ + return getSoloedAttribute(item) != nullptr; +} + +bool UsdSoloingHandler::isSoloed(const Ufe::Attribute::Ptr& attr) const +{ + if (!attr) + { + return false; + } + + auto soloedAttr = getSoloedAttribute(attr->sceneItem()); + return soloedAttr && soloedAttr->name() == attr->name(); +} + +Ufe::SceneItem::Ptr UsdSoloingHandler::getSoloedItem(const Ufe::SceneItem::Ptr& item) const +{ + if (!item) + { + return nullptr; + } + + // Fetch the actual material if the user has supplied a descendant scene item for convenience. + auto material = getParentUsdMaterial(item); + return getSoloedUsdItem(material); +} + +bool UsdSoloingHandler::hasSoloedDescendant(const Ufe::SceneItem::Ptr& item) const +{ + if (isSoloed(item)) + { + return false; + } + + const auto soloedItem = getSoloedItem(item); + Ufe::Path soloedPath = soloedItem ? soloedItem->path() : Ufe::Path{}; + + return soloedPath.startsWith(item->path()); +} + +Ufe::Attribute::Ptr UsdSoloingHandler::getSoloedAttribute(const Ufe::SceneItem::Ptr& item) const +{ + if (!MayaUsdAPI::isUsdSceneItem(item)) + { + return nullptr; + } + auto material = getParentUsdMaterial(item); + + Ufe::Attribute::Ptr retval = nullptr; + + // USD does not seem to track outgoing connections. Instead, a search is performed on the + // incoming connections of soloing nodes until one is found that matches the input item. + processSoloingPrimChildren(material, [&](const auto& child) { + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(child); + const UsdShadeConnectableAPI connectableAttrs(prim); + for (const auto& input : connectableAttrs.GetInputs(false)) + { + for (const auto& sourceInfo : input.GetConnectedSources()) + { + const auto connectedPrim = sourceInfo.source.GetPrim(); + auto usdPrim = MayaUsdAPI::getPrimForUsdSceneItem(item); + if (connectedPrim == usdPrim) + { + auto attrOut = sourceInfo.source.GetOutput(sourceInfo.sourceName); + auto attrs = Ufe::Attributes::attributes(item); + retval = attrs->attribute(attrOut.GetFullName()); + return false; + } + } + } + return true; + }); + + return retval; +} + +bool UsdSoloingHandler::isSoloingItem(const Ufe::SceneItem::Ptr& item) const +{ + return getMetadata(item, kSoloingItem) == "true"; +} + +Ufe::Connection::Ptr UsdSoloingHandler::replacedConnection(const Ufe::SceneItem::Ptr& item) const +{ + if (!MayaUsdAPI::isUsdSceneItem(item)) + { + return nullptr; + } + auto material = getParentUsdMaterial(item); + + auto soloingInfoItem = getSoloingInfoItem(material); + if (!soloingInfoItem) + { + return nullptr; + } + + auto materialOut = getMetadata(soloingInfoItem, kReplacedMaterialAttribute); + auto shaderOut = getMetadata(soloingInfoItem, kReplacedShaderAttribute); + auto shaderName = getMetadata(soloingInfoItem, kReplacedShaderName); + + auto shaderItem = UfeUtils::findChild(material, shaderName); + if (shaderItem) + { + Ufe::AttributeInfo src(Ufe::Attributes::attributes(shaderItem)->attribute(shaderOut)); + Ufe::AttributeInfo dst(Ufe::Attributes::attributes(material)->attribute(materialOut)); + + return std::make_shared(src, dst); + } + + return nullptr; +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdSoloingHandler.h b/lib/lookdevXUsd/UsdSoloingHandler.h new file mode 100644 index 0000000000..e226a94f86 --- /dev/null +++ b/lib/lookdevXUsd/UsdSoloingHandler.h @@ -0,0 +1,58 @@ +//***************************************************************************** +// Copyright (c) 2023 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include + +#include + +namespace LookdevXUsd +{ + +class UsdSoloingHandler : public LookdevXUfe::SoloingHandler +{ +public: + UsdSoloingHandler(); + ~UsdSoloingHandler() override; + + UsdSoloingHandler(const UsdSoloingHandler&) = delete; + UsdSoloingHandler& operator=(const UsdSoloingHandler&) = delete; + UsdSoloingHandler(UsdSoloingHandler&&) = delete; + UsdSoloingHandler& operator=(UsdSoloingHandler&&) = delete; + + static LookdevXUfe::SoloingHandler::Ptr create(); + + [[nodiscard]] bool isSoloable(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] bool isSoloable(const Ufe::Attribute::Ptr& attr) const override; + + [[nodiscard]] Ufe::UndoableCommand::Ptr soloCmd(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] Ufe::UndoableCommand::Ptr soloCmd(const Ufe::Attribute::Ptr& attr) const override; + + [[nodiscard]] Ufe::UndoableCommand::Ptr unsoloCmd(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] bool isSoloed(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] bool isSoloed(const Ufe::Attribute::Ptr& attr) const override; + + [[nodiscard]] Ufe::SceneItem::Ptr getSoloedItem(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] bool hasSoloedDescendant(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] Ufe::Attribute::Ptr getSoloedAttribute(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] bool isSoloingItem(const Ufe::SceneItem::Ptr& item) const override; + + [[nodiscard]] Ufe::Connection::Ptr replacedConnection(const Ufe::SceneItem::Ptr& item) const override; +}; + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdUINodeGraphNode.cpp b/lib/lookdevXUsd/UsdUINodeGraphNode.cpp new file mode 100644 index 0000000000..81d72d5bb3 --- /dev/null +++ b/lib/lookdevXUsd/UsdUINodeGraphNode.cpp @@ -0,0 +1,29 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdUINodeGraphNode.h" + +#include + +namespace LookdevXUsd +{ + +UsdUINodeGraphNode::UsdUINodeGraphNode(const Ufe::UINodeGraphNode::Ptr& wrappedUsdUINodeGraphNode) + : m_wrappedUsdUINodeGraphNode(wrappedUsdUINodeGraphNode) +{ +} + +LookdevXUfe::UINodeGraphNode::Ptr UsdUINodeGraphNode::create(const Ufe::UINodeGraphNode::Ptr& wrappedUsdUINodeGraphNode) +{ + return std::make_shared(wrappedUsdUINodeGraphNode); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdUINodeGraphNode.h b/lib/lookdevXUsd/UsdUINodeGraphNode.h new file mode 100644 index 0000000000..f39a549aa0 --- /dev/null +++ b/lib/lookdevXUsd/UsdUINodeGraphNode.h @@ -0,0 +1,110 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#ifndef USD_UI_NODE_GRAPH_NODE_H +#define USD_UI_NODE_GRAPH_NODE_H + +#include + +#include + +namespace LookdevXUsd +{ +/** + * @brief Class to handle UINodeGraphNode data that doesn't exist + * in Ufe::UINodeGraphNode. + */ +class UsdUINodeGraphNode : public LookdevXUfe::UINodeGraphNode +{ +public: + using Ptr = std::shared_ptr; + + //! Constructor. + explicit UsdUINodeGraphNode(const Ufe::UINodeGraphNode::Ptr& wrappedUsdUINodeGraphNode); + //! Destructor. + ~UsdUINodeGraphNode() override = default; + + //@{ + //! Delete the copy/move constructors assignment operators. + UsdUINodeGraphNode(const UsdUINodeGraphNode&) = delete; + UsdUINodeGraphNode& operator=(const UsdUINodeGraphNode&) = delete; + UsdUINodeGraphNode(UsdUINodeGraphNode&&) = delete; + UsdUINodeGraphNode& operator=(UsdUINodeGraphNode&&) = delete; + //@} + + //! Create a UsdUINodeGraphNode. + static LookdevXUfe::UINodeGraphNode::Ptr create(const Ufe::UINodeGraphNode::Ptr& wrappedUsdUINodeGraphNode); + + // Forward all wrapped API calls. + //----------------------------------------------------------------------------------- + + // LCOV_EXCL_START + + [[nodiscard]] Ufe::SceneItem::Ptr sceneItem() const override + { + return m_wrappedUsdUINodeGraphNode->sceneItem(); + } + + [[nodiscard]] bool hasPosition() const override + { + return m_wrappedUsdUINodeGraphNode->hasPosition(); + } + + [[nodiscard]] Ufe::Vector2f getPosition() const override + { + return m_wrappedUsdUINodeGraphNode->getPosition(); + } + + [[nodiscard]] Ufe::UndoableCommand::Ptr setPositionCmd(const Ufe::Vector2f& pos) override + { + return m_wrappedUsdUINodeGraphNode->setPositionCmd(pos); + } + + [[nodiscard]] bool hasSize() const override + { + return m_wrappedUsdUINodeGraphNode->hasSize(); + } + + [[nodiscard]] Ufe::Vector2f getSize() const override + { + return m_wrappedUsdUINodeGraphNode->getSize(); + } + + [[nodiscard]] Ufe::UndoableCommand::Ptr setSizeCmd(const Ufe::Vector2f& size) override + { + return m_wrappedUsdUINodeGraphNode->setSizeCmd(size); + } + + [[nodiscard]] bool hasDisplayColor() const override + { + return m_wrappedUsdUINodeGraphNode->hasDisplayColor(); + } + + [[nodiscard]] Ufe::Color3f getDisplayColor() const override + { + return m_wrappedUsdUINodeGraphNode->getDisplayColor(); + } + + [[nodiscard]] Ufe::UndoableCommand::Ptr setDisplayColorCmd(const Ufe::Color3f& color) override + { + return m_wrappedUsdUINodeGraphNode->setDisplayColorCmd(color); + } + + // LCOV_EXCL_STOP + + //----------------------------------------------------------------------------------- + +private: + Ufe::UINodeGraphNode::Ptr m_wrappedUsdUINodeGraphNode; +}; + +} // namespace LookdevXUsd + +#endif diff --git a/lib/lookdevXUsd/UsdUINodeGraphNodeHandler.cpp b/lib/lookdevXUsd/UsdUINodeGraphNodeHandler.cpp new file mode 100644 index 0000000000..ecb12ad513 --- /dev/null +++ b/lib/lookdevXUsd/UsdUINodeGraphNodeHandler.cpp @@ -0,0 +1,61 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#include "UsdUINodeGraphNodeHandler.h" +#include "UsdUINodeGraphNode.h" + +#include + +namespace LookdevXUsd +{ + +Ufe::UINodeGraphNodeHandler::Ptr UsdUINodeGraphNodeHandler::m_wrappedUINodeGraphNodeHandler; +Ufe::Rtid UsdUINodeGraphNodeHandler::m_rtid; + +UsdUINodeGraphNodeHandler::~UsdUINodeGraphNodeHandler() = default; + +void UsdUINodeGraphNodeHandler::registerHandler(const Ufe::Rtid& rtId) +{ + if (m_wrappedUINodeGraphNodeHandler) + { + return; + } + + m_rtid = rtId; + auto& runTimeMgr = Ufe::RunTimeMgr::instance(); + m_wrappedUINodeGraphNodeHandler = runTimeMgr.uiNodeGraphNodeHandler(m_rtid); + runTimeMgr.setUINodeGraphNodeHandler(m_rtid, std::make_shared()); +} + +void UsdUINodeGraphNodeHandler::unregisterHandler() +{ + if (m_wrappedUINodeGraphNodeHandler) + { + auto& runTimeMgr = Ufe::RunTimeMgr::instance(); + if (runTimeMgr.hasId(m_rtid)) + { + runTimeMgr.setUINodeGraphNodeHandler(m_rtid, m_wrappedUINodeGraphNodeHandler); + } + m_wrappedUINodeGraphNodeHandler.reset(); + } +} + +Ufe::UINodeGraphNode::Ptr UsdUINodeGraphNodeHandler::uiNodeGraphNode(const Ufe::SceneItem::Ptr& item) const +{ + return lxUINodeGraphNode(item); +} + +LookdevXUfe::UINodeGraphNode::Ptr UsdUINodeGraphNodeHandler::lxUINodeGraphNode(const Ufe::SceneItem::Ptr& item) const +{ + return LookdevXUsd::UsdUINodeGraphNode::create(m_wrappedUINodeGraphNodeHandler->uiNodeGraphNode(item)); +} + +} // namespace LookdevXUsd diff --git a/lib/lookdevXUsd/UsdUINodeGraphNodeHandler.h b/lib/lookdevXUsd/UsdUINodeGraphNodeHandler.h new file mode 100644 index 0000000000..fbcb9dd61b --- /dev/null +++ b/lib/lookdevXUsd/UsdUINodeGraphNodeHandler.h @@ -0,0 +1,55 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** + +#ifndef USD_UI_NODE_GRAPH_NODE_HANDLER_H +#define USD_UI_NODE_GRAPH_NODE_HANDLER_H + +#include "Export.h" + +#include + +namespace LookdevXUsd +{ + +//! \brief Implementation of Ufe::UINodeGraphNodeHandler interface for USD objects. +class UsdUINodeGraphNodeHandler : public LookdevXUfe::UINodeGraphNodeHandler +{ +public: + using Ptr = std::shared_ptr; + + LOOKDEVX_USD_EXPORT UsdUINodeGraphNodeHandler() = default; + LOOKDEVX_USD_EXPORT ~UsdUINodeGraphNodeHandler() override; + + // Delete the copy/move constructors assignment operators. + UsdUINodeGraphNodeHandler(const UsdUINodeGraphNodeHandler&) = delete; + UsdUINodeGraphNodeHandler& operator=(const UsdUINodeGraphNodeHandler&) = delete; + UsdUINodeGraphNodeHandler(UsdUINodeGraphNodeHandler&&) = delete; + UsdUINodeGraphNodeHandler& operator=(UsdUINodeGraphNodeHandler&&) = delete; + + LOOKDEVX_USD_EXPORT static void registerHandler(const Ufe::Rtid& rtId); + LOOKDEVX_USD_EXPORT static void unregisterHandler(); + + // Ufe::UINodeGraphNodeHandler overrides + [[nodiscard]] LOOKDEVX_USD_EXPORT Ufe::UINodeGraphNode::Ptr uiNodeGraphNode( + const Ufe::SceneItem::Ptr& item) const override; + + // LookdevXUfe::UINodeGraphNodeHandler overrides + [[nodiscard]] LOOKDEVX_USD_EXPORT LookdevXUfe::UINodeGraphNode::Ptr lxUINodeGraphNode( + const Ufe::SceneItem::Ptr& item) const override; + +private: + static Ufe::UINodeGraphNodeHandler::Ptr m_wrappedUINodeGraphNodeHandler; + static Ufe::Rtid m_rtid; +}; + +} // namespace LookdevXUsd + +#endif /* USDUINODEGRAPHNODEHANDLER_H */ diff --git a/lib/lookdevXUsd/Utils.cpp b/lib/lookdevXUsd/Utils.cpp new file mode 100644 index 0000000000..c853ad6e9b --- /dev/null +++ b/lib/lookdevXUsd/Utils.cpp @@ -0,0 +1,85 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#include "Utils.h" + +#include + +#include + +#include +#include + +using namespace LookdevXUfe; +using namespace PXR_NS; + +namespace LookdevXUsdUtils +{ +SdfLayerRefPtr getSessionLayer(const Ufe::SceneItem::Ptr& item) +{ + if (!item) + { + return {}; + } + + auto prim = MayaUsdAPI::getPrimForUsdSceneItem(item); + auto sessionLayer = prim.GetStage()->GetSessionLayer(); + return sessionLayer; +} + +TfToken getShaderSourceType(const Ufe::Attribute::Ptr& attr) +{ + std::string shaderSource; + + auto deepAttr = UfeUtils::getConnectedSource(attr); + if (deepAttr) + { + auto nodeDef = UfeUtils::getNodeDef(deepAttr->sceneItem()); + if (nodeDef && nodeDef->nbClassifications() != 0) + { + shaderSource = TfToken(nodeDef->classification(nodeDef->nbClassifications() - 1)); + } + } + + return TfToken(shaderSource); +} + +bool isConnected(const PXR_NS::UsdAttribute& srcUsdAttr, const PXR_NS::UsdAttribute& dstUsdAttr) +{ + PXR_NS::SdfPathVector connectedAttrs; + dstUsdAttr.GetConnections(&connectedAttrs); + + return std::any_of(connectedAttrs.begin(), connectedAttrs.end(), + [&srcUsdAttr](const auto& path) { return path == srcUsdAttr.GetPath(); }); +} + +Strings splitString(const std::string& str, const std::string& separators) +{ + Strings split; + + if (str.empty()) + { + return split; + } + + std::string::size_type lastPos = str.find_first_not_of(separators, 0); + std::string::size_type pos = str.find_first_of(separators, lastPos); + + while (pos != std::string::npos || lastPos != std::string::npos) + { + split.push_back(str.substr(lastPos, pos - lastPos)); + lastPos = str.find_first_not_of(separators, pos); + pos = str.find_first_of(separators, lastPos); + } + + return split; +} + +} // namespace LookdevXUsdUtils diff --git a/lib/lookdevXUsd/Utils.h b/lib/lookdevXUsd/Utils.h new file mode 100644 index 0000000000..271a572cb4 --- /dev/null +++ b/lib/lookdevXUsd/Utils.h @@ -0,0 +1,42 @@ +//***************************************************************************** +// Copyright (c) 2024 Autodesk, Inc. +// All rights reserved. +// +// These coded instructions, statements, and computer programs contain +// unpublished proprietary information written by Autodesk, Inc. and are +// protected by Federal copyright law. They may not be disclosed to third +// parties or copied or duplicated in any form, in whole or in part, without +// the prior written consent of Autodesk, Inc. +//***************************************************************************** +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include + +/** + * Namespace for LookdevXUsd utility functions. + */ +namespace LookdevXUsdUtils +{ + using Strings = std::vector; + +PXR_NS::SdfLayerRefPtr getSessionLayer(const Ufe::SceneItem::Ptr& item); + +// Tries to find the shader source of the item the attribute belongs to (e.g. arnold, mtlx). +// The attribute connection is recursively traced through compounds until it can reach a valid node definition. +PXR_NS::TfToken getShaderSourceType(const Ufe::Attribute::Ptr& attr); + +// Check if the src and dst attributes are connected. +bool isConnected(const PXR_NS::UsdAttribute& srcUsdAttr, const PXR_NS::UsdAttribute& dstUsdAttr); + +/// Splits a string based on a list of separators. Splits on the first separator found. +Strings splitString(const std::string& str, const std::string& separators); + +} // namespace LookdevXUsdUtils diff --git a/plugin/adsk/plugin/CMakeLists.txt b/plugin/adsk/plugin/CMakeLists.txt index 872d07845c..d18a0db2d7 100644 --- a/plugin/adsk/plugin/CMakeLists.txt +++ b/plugin/adsk/plugin/CMakeLists.txt @@ -100,6 +100,12 @@ if(IS_MACOSX OR IS_LINUX) ) endif() +# For initializing the handlers of lookdevXUsd +target_link_libraries(${TARGET_NAME} + PRIVATE + lookdevXUsd +) + # ----------------------------------------------------------------------------- # properties # ----------------------------------------------------------------------------- diff --git a/plugin/adsk/plugin/plugin.cpp b/plugin/adsk/plugin/plugin.cpp index c2c946bb50..0abb3d54a7 100644 --- a/plugin/adsk/plugin/plugin.cpp +++ b/plugin/adsk/plugin/plugin.cpp @@ -55,6 +55,8 @@ #include #include +#include + #include #include @@ -194,6 +196,8 @@ MStatus initializePlugin(MObject obj) status.perror("mayaUsdPlugin: unable to initialize ufe."); } + LookdevXUsd::initialize(); + status = plugin.registerShape( MayaUsd::ProxyShape::typeName, MayaUsd::ProxyShape::typeId, @@ -381,6 +385,8 @@ MStatus uninitializePlugin(MObject obj) MGlobal::executeCommand("mayaUSDUnregisterStrings()"); + LookdevXUsd::uninitialize(); + status = MayaUsd::ufe::finalize(); CHECK_MSTATUS(status); diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index 623f9f5bad..96288defb2 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -173,6 +173,7 @@ foreach(script ${TEST_SCRIPT_FILES}) "UFE_SCENE_SEGMENT_HANDLER_ROOT_PATH=${UFE_SCENE_SEGMENT_HANDLER_ROOT_PATH}" "UFE_VOLUME_LIGHTS_SUPPORT=${UFE_VOLUME_LIGHTS_SUPPORT}" "UFE_SCENEITEM_HAS_METADATA=${UFE_SCENEITEM_HAS_METADATA}" + "BUILD_LOOKDEVXUSD_LIBRARY=${BUILD_LOOKDEVXUSD_LIBRARY}" ) # Add a ctest label to these tests for easy filtering. diff --git a/test/lib/ufe/testAttribute.py b/test/lib/ufe/testAttribute.py index a7d18b5057..f357f315ed 100644 --- a/test/lib/ufe/testAttribute.py +++ b/test/lib/ufe/testAttribute.py @@ -1224,6 +1224,9 @@ def testObservationWithFineGrainedNotifications(self): # Maya registers a single global observer on startup. # Maya-Usd lib registers a single global observer when it is initialized. kNbGlobalObs = 2 + # If LookdevXUsd is built, there is a third global observer. + if(os.getenv('BUILD_LOOKDEVXUSD_LIBRARY', 'NOT-FOUND') == 'ON'): + kNbGlobalObs = 3 self.assertEqual(ufe.Attributes.nbObservers(), kNbGlobalObs) # No item-specific observers.