├── .gitattributes ├── XMesh.exp ├── NOTICE.txt ├── icons ├── XMeshSaver_icon.png └── XMeshLoader_icon.png ├── XMesh.map ├── src ├── XMeshLogoMesh.hpp ├── XMeshSaverUISettingsNode.hpp ├── vertices_to_edge_map.hpp ├── material_utils.hpp ├── maya_xmesh_timing.hpp ├── XMeshSaverUISettingsNode.cpp ├── progress_bar_progress_logger.hpp ├── SaveXMeshCommand.hpp ├── XMeshLoggingCommand.hpp ├── material_id_map.hpp ├── SequenceSaverHelper.hpp ├── maya_xmesh_timing.cpp ├── material_id_map.cpp ├── SequenceSaverHelper.cpp ├── progress_bar_progress_logger.cpp ├── XMeshLoggingCommand.cpp ├── vertices_to_edge_map.cpp ├── SequenceXMeshGeometryOverride.hpp ├── dllmain.cpp ├── SequenceXMeshNode.hpp ├── material_utils.cpp └── SequenceXMeshGeometryOverride.cpp ├── scripts ├── showXMeshHelp.mel ├── XMeshUtils.py ├── showXMeshAbout.mel ├── XMeshNoticesDialog.py ├── XMeshLoadDialog.mel ├── XMeshDeleteUI.mel ├── XMeshMaterialUtils.py ├── XMeshCreateUI.mel ├── XMeshDeadlineUtils.py ├── createXMeshLoader.py ├── AEsequenceXMeshTemplate.mel └── saveXMeshSequence.py ├── CODE_OF_CONDUCT.md ├── .clang-format ├── resource.h ├── version_gen.py ├── README.md ├── XMesh.rc ├── CMakeLists.txt ├── conanfile.py ├── CONTRIBUTING.md └── LICENSE.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /XMesh.exp: -------------------------------------------------------------------------------- 1 | *initializePlugin* 2 | *uninitializePlugin* 3 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | XMeshMY 2 | Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /icons/XMeshSaver_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/thinkbox-xmesh-my/HEAD/icons/XMeshSaver_icon.png -------------------------------------------------------------------------------- /icons/XMeshLoader_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/thinkbox-xmesh-my/HEAD/icons/XMeshLoader_icon.png -------------------------------------------------------------------------------- /XMesh.map: -------------------------------------------------------------------------------- 1 | { 2 | global: 3 | extern "C++" { 4 | initializePlugin*; 5 | uninitializePlugin* 6 | }; 7 | local: 8 | *; 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /src/XMeshLogoMesh.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include 4 | 5 | void BuildMesh_XMeshLogoMesh( frantic::geometry::trimesh3& outMesh ); 6 | -------------------------------------------------------------------------------- /scripts/showXMeshHelp.mel: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | global proc showXMeshHelp() 5 | { 6 | launch -web "https://docs.thinkboxsoftware.com/products/xmesh/1.10/1_Documentation/manual/xmesh_my/xmeshmy_index.html"; 7 | } 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | Standard: c++17 3 | UseTab: Never 4 | ColumnLimit: 120 5 | IndentWidth: 4 6 | BreakBeforeBraces: Attach 7 | NamespaceIndentation: None 8 | AlwaysBreakTemplateDeclarations: true 9 | SpacesInParentheses: true 10 | SpaceBeforeParens: Never 11 | BreakConstructorInitializersBeforeComma: true 12 | PointerAlignment: Left 13 | -------------------------------------------------------------------------------- /scripts/XMeshUtils.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Scripted utilities for Maya XMesh 5 | 6 | from contextlib import contextmanager 7 | 8 | import maya.cmds 9 | 10 | @contextmanager 11 | def waitCursor(): 12 | maya.cmds.waitCursor(state=True) 13 | try: 14 | yield 15 | finally: 16 | maya.cmds.waitCursor(state=False) 17 | -------------------------------------------------------------------------------- /src/XMeshSaverUISettingsNode.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | 7 | class XMeshSaverUISettingsNode : public MPxNode { 8 | public: 9 | XMeshSaverUISettingsNode( void ); 10 | ~XMeshSaverUISettingsNode( void ); 11 | 12 | static void* creator(); 13 | static MStatus initialize(); 14 | static MTypeId typeId; 15 | }; 16 | -------------------------------------------------------------------------------- /src/vertices_to_edge_map.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class vertices_to_edge_map { 9 | public: 10 | vertices_to_edge_map( MObject& polyObject ); 11 | bool get_edge( std::pair vertices, int& outEdgeIndex ) const; 12 | 13 | private: 14 | std::vector m_lesserVertexOffset; 15 | std::vector> m_greaterVertexToEdge; 16 | }; 17 | -------------------------------------------------------------------------------- /src/material_utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class material_id_map; 9 | 10 | void create_material_id_channel( const MDagPath& dagPath, MFnMesh& fnMesh, frantic::geometry::polymesh3_ptr& mesh, 11 | material_id_map& materialIDMap ); 12 | 13 | void parse_material_id_map( material_id_map& materialIDMap, const frantic::tstring& s ); 14 | -------------------------------------------------------------------------------- /src/maya_xmesh_timing.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include 4 | 5 | #include 6 | 7 | class maya_xmesh_timing : public xmesh::xmesh_timing { 8 | public: 9 | maya_xmesh_timing(); 10 | 11 | void set_playback_graph( const MObject& node, const MObject& attribute ); 12 | 13 | protected: 14 | virtual double evaluate_playback_graph( double frame ) const; 15 | 16 | private: 17 | bool m_enablePlaybackGraph; 18 | MObject m_node; 19 | MObject m_attribute; 20 | }; 21 | -------------------------------------------------------------------------------- /src/XMeshSaverUISettingsNode.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #include 8 | 9 | #include "XMeshSaverUISettingsNode.hpp" 10 | 11 | MTypeId XMeshSaverUISettingsNode::typeId( 0x00117487 ); 12 | 13 | XMeshSaverUISettingsNode::XMeshSaverUISettingsNode( void ) {} 14 | 15 | XMeshSaverUISettingsNode::~XMeshSaverUISettingsNode( void ) {} 16 | 17 | void* XMeshSaverUISettingsNode::creator() { return new XMeshSaverUISettingsNode; } 18 | 19 | MStatus XMeshSaverUISettingsNode::initialize() { return MStatus::kSuccess; } 20 | -------------------------------------------------------------------------------- /src/progress_bar_progress_logger.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | 7 | class progress_bar_progress_logger : public frantic::logging::progress_logger { 8 | public: 9 | progress_bar_progress_logger(); 10 | virtual ~progress_bar_progress_logger(); 11 | 12 | virtual void set_title( const frantic::tstring& title ); 13 | virtual void update_progress( long long completed, long long maximum ); 14 | virtual void update_progress( float percent ); 15 | 16 | virtual void check_for_abort(); 17 | }; 18 | -------------------------------------------------------------------------------- /src/SaveXMeshCommand.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | class SaveXMeshCommand : public MPxCommand { 10 | 11 | public: 12 | static void* creator(); 13 | static MSyntax newSyntax(); 14 | 15 | virtual MStatus doIt( const MArgList& args ); 16 | }; 17 | 18 | class SaveXMeshSequenceCommand : public MPxCommand { 19 | 20 | public: 21 | static void* creator(); 22 | static MSyntax newSyntax(); 23 | 24 | virtual MStatus doIt( const MArgList& args ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/XMeshLoggingCommand.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | class XMeshLoggingCommand : public MPxCommand { 11 | public: 12 | static void* creator(); 13 | static MSyntax newSyntax(); 14 | static void initialize_logging(); 15 | 16 | virtual MStatus doIt( const MArgList& args ); 17 | 18 | private: 19 | static void to_error_log( const frantic::tchar* szMsg ); 20 | static void to_warning_log( const frantic::tchar* szMsg ); 21 | static void to_debug_log( const frantic::tchar* szMsg ); 22 | static void to_stats_log( const frantic::tchar* szMsg ); 23 | static void to_progress_log( const frantic::tchar* szMsg ); 24 | }; 25 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | //{{NO_DEPENDENCIES}} 4 | // Microsoft Visual C++ generated include file. 5 | // Used by XMesh.rc 6 | 7 | #define IDD_LICENSEFINDER_FRAME 178 8 | #define IDD_LICENSEFINDER 178 9 | #define IDC_CHECK_USESERVERPORT 3103 10 | #define IDC_LICENSEFINDER_CONTENTFRAME 3219 11 | #define IDC_RADIO_SERVER 3222 12 | #define IDC_RADIO_FILE 3223 13 | #define IDC_EDIT_SERVERPORT 3228 14 | #define IDC_SPIN_SERVERPORT 3229 15 | #define IDC_EDIT_SERVERNAME 3230 16 | #define IDC_EDIT_FILEPATH 3232 17 | #define IDC_BUTTONFILEBROWSE 3233 18 | #define IDC_BUTTON_FILEBROWSE 3233 19 | #define IDC_STATIC_REASON 3234 20 | 21 | // Next default values for new objects 22 | // 23 | #ifdef APSTUDIO_INVOKED 24 | #ifndef APSTUDIO_READONLY_SYMBOLS 25 | #define _APS_NEXT_RESOURCE_VALUE 101 26 | #define _APS_NEXT_COMMAND_VALUE 40001 27 | #define _APS_NEXT_CONTROL_VALUE 1001 28 | #define _APS_NEXT_SYMED_VALUE 101 29 | #endif 30 | #endif 31 | -------------------------------------------------------------------------------- /src/material_id_map.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | class material_id_map { 13 | public: 14 | material_id_map(); 15 | 16 | void swap( material_id_map& other ); 17 | 18 | void clear(); 19 | void lock(); 20 | 21 | bool has_material( const frantic::tstring& name ) const; 22 | bool has_undefined_material() const; 23 | boost::uint16_t get_material_id( const frantic::tstring& name ); 24 | boost::uint16_t get_undefined_material_id(); 25 | 26 | void insert_material( boost::uint16_t id, const frantic::tstring& name ); 27 | void insert_undefined_material( boost::uint16_t id ); 28 | 29 | private: 30 | boost::uint16_t get_next_unused_id(); 31 | 32 | typedef std::map map_t; 33 | map_t m_map; 34 | std::set m_usedIDs; 35 | boost::uint16_t m_nextIDHint; 36 | bool m_locked; 37 | bool m_hasUndefinedID; 38 | boost::uint16_t m_undefinedID; 39 | }; 40 | -------------------------------------------------------------------------------- /version_gen.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import argparse 4 | 5 | def write_version_file(version: str, filename: str='XMeshVersion.h') -> None: 6 | major, minor, patch = version.split('.') 7 | with open(filename, 'w') as version_header: 8 | version_header.write('#pragma once\n') 9 | version_header.write('/////////////////////////////////////////////////////\n') 10 | version_header.write('// AWS Thinkbox auto generated version include file.\n') 11 | version_header.write(f'#define FRANTIC_VERSION\t\t\t\t"{version}"\n') 12 | version_header.write(f'#define FRANTIC_MAJOR_VERSION\t\t"{major}"') 13 | version_header.write(f'#define FRANTIC_MINOR_VERSION\t\t"{minor}"') 14 | version_header.write(f'#define FRANTIC_PATCH_NUMBER\t\t"{patch}"') 15 | version_header.write(f'#define FRANTIC_DESCRIPTION\t\t\t"Thinkbox XMesh for Maya"') 16 | 17 | 18 | if __name__ == '__main__': 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument(dest='version', required=True, help='The version number to use.') 21 | args = parser.parse_args() 22 | write_version_file(args.version) 23 | -------------------------------------------------------------------------------- /scripts/showXMeshAbout.mel: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | global proc xmeshAboutUI() 5 | { 6 | int $edge = 12; 7 | int $pad = 8; 8 | int $rowPad = 5; 9 | 10 | string $version = `pluginInfo -q -version "XMesh"`; 11 | string $attributions = `getXMeshAttributions`; 12 | 13 | string $form = `setParent -q`; 14 | 15 | formLayout -e -width 640 -height 450 $form; 16 | 17 | string $titleText = `text -l "XMesh MY"`; 18 | string $mainTextBox = `scrollField -ww true -ed false -tx $attributions`; 19 | string $okButton = `button -l "OK" -width 70 -c "layoutDialog -dismiss \"OK\""`; 20 | 21 | string $versionRow = `rowLayout -nc 2`; 22 | text -l "Version:"; 23 | textField -ed false -tx $version; 24 | 25 | formLayout -edit 26 | -af $titleText "top" $edge 27 | -af $titleText "left" $edge 28 | 29 | -ac $versionRow "top" $rowPad $titleText 30 | -af $versionRow "left" $edge 31 | 32 | -af $okButton "bottom" $edge 33 | -af $okButton "right" $edge 34 | 35 | -ac $mainTextBox "top" $pad $versionRow 36 | -ac $mainTextBox "bottom" $edge $okButton 37 | -af $mainTextBox "left" $edge 38 | -af $mainTextBox "right" $edge 39 | $form; 40 | } 41 | 42 | global proc showXMeshAbout() 43 | { 44 | layoutDialog -title "About XMesh" -ui "xmeshAboutUI"; 45 | } 46 | -------------------------------------------------------------------------------- /scripts/XMeshNoticesDialog.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | import maya.cmds as cmds 5 | import os 6 | 7 | xmeshmyNoticesWindowId = 'xmeshmyNoticesDlg' 8 | xmeshmyNoticesText = None 9 | xmeshmyNoticesError = "Could not read third_party_licenses.txt" 10 | 11 | def open_xmeshmy_notices_dialog(): 12 | global xmeshmyNoticesWindowId 13 | global xmeshmyNoticesText 14 | global xmeshmyNoticesError 15 | 16 | if cmds.window( xmeshmyNoticesWindowId, exists=True ): 17 | cmds.deleteUI( xmeshmyNoticesWindowId ) 18 | 19 | if xmeshmyNoticesText == None or xmeshmyNoticesText == xmeshmyNoticesError : 20 | noticesPath = os.path.join( os.path.dirname( __file__ ), '..', '..', 'Legal', 'third_party_licenses.txt' ) 21 | try: 22 | with open( noticesPath, 'r' ) as theFile: 23 | xmeshmyNoticesText = theFile.read() 24 | except IOError: 25 | xmeshmyNoticesText = xmeshmyNoticesError 26 | 27 | cmds.window( xmeshmyNoticesWindowId, title="XMesh MY Notices", width=516 ) 28 | 29 | cmds.formLayout( "resizeForm" ) 30 | 31 | sf = cmds.scrollField( wordWrap=True, text=xmeshmyNoticesText, editable=False ) 32 | 33 | cmds.formLayout( "resizeForm", edit=True, attachForm=[( sf, 'top', 0 ), ( sf, 'right', 0 ), ( sf, 'left', 0 ), ( sf, 'bottom', 0 )] ) 34 | 35 | cmds.setParent( '..' ) 36 | 37 | cmds.showWindow() -------------------------------------------------------------------------------- /src/SequenceSaverHelper.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "material_id_map.hpp" 12 | 13 | class SequenceSaverHelper { 14 | public: 15 | frantic::files::filename_pattern m_filenamePattern; 16 | frantic::geometry::xmesh_sequence_saver m_xss; 17 | std::map> 18 | m_smoothingGroupAssignments; // utf8 dag path to previous smoothing group assignment 19 | material_id_map m_materialIDMap; 20 | std::vector m_dagPaths; 21 | std::vector m_meshes; 22 | 23 | public: 24 | void Clear(); 25 | 26 | // outmesh metadata, sequencename[frame] 27 | }; 28 | 29 | // extern //? 30 | // SequenceSaverHelper& GetSequenceSaverHelper(); 31 | 32 | // class ClearXMeshCommand : public MPxCommand 33 | //{ 34 | // public: 35 | // static void* creator(); 36 | // static MSyntax newSyntax(); 37 | // 38 | // virtual MStatus doIt( const MArgList& args ); 39 | // }; 40 | 41 | // class SetXMeshChannelCommand : public MPxCommand 42 | //{ 43 | // public: 44 | // static void* creator(); 45 | // static MSyntax newSyntax(); 46 | // 47 | // virtual MStatus doIt( const MArgList& args ); 48 | // }; 49 | -------------------------------------------------------------------------------- /scripts/XMeshLoadDialog.mel: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //////////////////////////////////// 5 | // File Dialog UI Controls for setting XMesh parameters on load 6 | /////////////////////////////////// 7 | 8 | global proc xmeshLoadDialog_setupParams(string $parent) 9 | { 10 | setParent $parent; 11 | columnLayout -adj true; 12 | frameLayout -l "Viewport" -cll true -mh 5; 13 | columnLayout; 14 | 15 | string $displayOptionList[] = {"Mesh", "Bounding Box", "Vertices", "Faces"}; 16 | optionMenuGrp -l "Display: " -cc xmeshLoadDialog_displayStyleCB ddl_displayStyle; 17 | string $option; 18 | for ($option in $displayOptionList) 19 | { 20 | menuItem -l $option; 21 | } 22 | 23 | floatSliderGrp -l "Percent: " -field true -minValue 0 -maxValue 100 -value 5 -enable false flt_displayPercent; 24 | } 25 | 26 | global proc xmeshLoadDialog_commitParams(string $parent) 27 | { 28 | global int $xmesh_displayStyle; 29 | $xmesh_displayStyle = `optionMenuGrp -q -sl ddl_displayStyle`; 30 | global float $xmesh_displayPercent; 31 | $xmesh_displayPercent = `floatSliderGrp -q -v flt_displayPercent`; 32 | } 33 | 34 | global proc xmeshLoadDialog_displayStyleCB() 35 | { 36 | $displayStyle = `optionMenuGrp -q -v ddl_displayStyle`; 37 | if ($displayStyle == "Mesh" || $displayStyle == "Bounding Box") 38 | { 39 | floatSliderGrp -e -enable false flt_displayPercent; 40 | } 41 | else 42 | { 43 | floatSliderGrp -e -enable true flt_displayPercent; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/maya_xmesh_timing.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #include "maya_xmesh_timing.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #if MAYA_API_VERSION >= 201800 14 | #include 15 | #endif 16 | 17 | maya_xmesh_timing::maya_xmesh_timing() 18 | : m_enablePlaybackGraph( false ) {} 19 | 20 | void maya_xmesh_timing::set_playback_graph( const MObject& node, const MObject& attribute ) { 21 | m_node = node; 22 | m_attribute = attribute; 23 | m_enablePlaybackGraph = true; 24 | } 25 | 26 | double maya_xmesh_timing::evaluate_playback_graph( double frame ) const { 27 | if( m_enablePlaybackGraph ) { 28 | MStatus stat; 29 | if( m_node.isNull() ) { 30 | throw std::runtime_error( "maya_xmesh_timing::evaluate_playback_graph Error: node is NULL" ); 31 | } 32 | if( m_attribute.isNull() ) { 33 | throw std::runtime_error( "maya_xmesh_timing::evaluate_playback_graph Error: attribute is NULL" ); 34 | } 35 | 36 | MDGContext context( MTime( frame, MTime::uiUnit() ) ); 37 | MPlug playbackGraphPlug( m_node, m_attribute ); 38 | 39 | #if MAYA_API_VERSION >= 201800 40 | MDGContextGuard contextGuard( context ); 41 | MTime result = playbackGraphPlug.asMTime( &stat ); 42 | #else 43 | MTime result = playbackGraphPlug.asMTime( context, &stat ); 44 | #endif 45 | if( !stat ) { 46 | throw std::runtime_error( 47 | "maya_xmesh_timing::evaluate_playback_graph Error: unable to evaluate playback graph" ); 48 | } 49 | 50 | return result.as( MTime::uiUnit() ); 51 | } else { 52 | return frame; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /scripts/XMeshDeleteUI.mel: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //////////////////////////////////// 5 | // XMesh (Maya) de-initialization commands 6 | /////////////////////////////////// 7 | 8 | 9 | // Removes menu items in the given menu identified by the given tag. 10 | // Example: This will remove the XMesh Loader item in the Create menu, if passed mainCreateMenu as $parent. 11 | // Usage: removeFromMenuByTag( "mainCreateMenu", "XMesh" ); 12 | // 13 | // @param $parent The Maya label for the parent menu that contains the menu items you wish to remove. 14 | // @param $tag The unique tag assigned to the menu item when it was created. 15 | proc removeFromMenuByTag( string $parent, string $tag ){ 16 | string $menuItems[] = `menu -query -itemArray $parent`; // Get all menu items under the given parent menu. 17 | int $numItems = size( $menuItems ); 18 | 19 | for( $i = 0; $i < $numItems; ++$i ){ 20 | string $doctag = `menuItem -query -docTag $menuItems[$i]`; // Get the docTag of the current menu item. 21 | if( $doctag == $tag ){ 22 | deleteUI -menuItem $menuItems[$i]; // Remove the menuItem. 23 | } 24 | } 25 | } 26 | 27 | 28 | 29 | // Given a shelf label, returns a 1 if the shelf exists, or a 0 otherwise. 30 | // Usage: int $exists = shelf_exists("XMesh"); 31 | // 32 | // @param $label The name of the shelf to test for. 33 | // @return 1 if the shelf exists, 0 otherwise. 34 | proc int shelf_exists( string $label ){ 35 | int $result = `shelfLayout -exists $label`; 36 | return $result; 37 | } 38 | 39 | 40 | proc delete_shelf( string $label ){ 41 | string $shelfChildren[] = `shelfLayout -q -childArray $label`; 42 | for($child in $shelfChildren){ 43 | deleteUI $child; 44 | } 45 | deleteUI $label; 46 | } 47 | 48 | 49 | proc removeFromShelfByLabel( string $shelfLabel ){ 50 | if( shelf_exists( $shelfLabel ) ){ 51 | delete_shelf( $shelfLabel ); 52 | } 53 | } 54 | 55 | 56 | // Cleans up the UI when our plug-in is unloaded. 57 | global proc XMeshDeleteUI(){ 58 | removeFromMenuByTag( "mainCreateMenu", "XMesh" ); // Removes any menu items from the Create menu tagged with "XMesh" (such as XMesh Loader). 59 | removeFromShelfByLabel( "XMesh" ); // Removes any buttons within this shelf and deletes the shelf. 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XMeshMY 2 | 3 | ## Overview 4 | 5 | XMeshMY is a Maya plugin that enables loading and saving geometry in the XMesh file format in Autodesk Maya. 6 | 7 | The official documentation for the plugin can be found [here](https://docs.thinkboxsoftware.com/products/xmesh/1.10/1_Documentation/manual/xmesh_my/xmeshmy_index.html). 8 | 9 | ## Table of Contents 10 | 11 | - [Reporting Bugs/Feature Requests](#reporting-bugs/feature-requests) 12 | - [Security issue notifications](#security-issue-notifications) 13 | - [Contributing](#contributing) 14 | - [Code of Conduct](#code-of-conduct) 15 | - [Licensing](#licensing) 16 | 17 | ## Reporting Bugs/Feature Requests 18 | 19 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 20 | 21 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 22 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 23 | 24 | - A reproducible test case or series of steps 25 | - The version of our code being used 26 | - Any modifications you've made relevant to the bug 27 | - Anything unusual about your environment or deployment 28 | 29 | ## Security issue notifications 30 | 31 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 32 | 33 | ## Contributing 34 | 35 | Contributions to XMeshMY are encouraged. If you want to fix a problem, or want to enhance the library in any way, then 36 | we are happy to accept your contribution. Information on contributing to XMeshMY can be found 37 | [in CONTRIBUTING.md](CONTRIBUTING.md). 38 | 39 | ## Code of Conduct 40 | 41 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 42 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 43 | opensource-codeofconduct@amazon.com with any additional questions or comments. 44 | 45 | ## Licensing 46 | 47 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 48 | 49 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 50 | -------------------------------------------------------------------------------- /src/material_id_map.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include "material_id_map.hpp" 4 | 5 | #include 6 | #include 7 | 8 | material_id_map::material_id_map() { clear(); } 9 | 10 | void material_id_map::swap( material_id_map& other ) { 11 | m_map.swap( other.m_map ); 12 | m_usedIDs.swap( other.m_usedIDs ); 13 | std::swap( m_nextIDHint, other.m_nextIDHint ); 14 | std::swap( m_locked, other.m_locked ); 15 | std::swap( m_hasUndefinedID, other.m_hasUndefinedID ); 16 | std::swap( m_undefinedID, other.m_undefinedID ); 17 | } 18 | 19 | void material_id_map::clear() { 20 | m_map.clear(); 21 | m_usedIDs.clear(); 22 | m_nextIDHint = 0; 23 | m_locked = false; 24 | m_hasUndefinedID = false; 25 | m_undefinedID = 0; 26 | } 27 | 28 | void material_id_map::lock() { m_locked = true; } 29 | 30 | bool material_id_map::has_material( const frantic::tstring& name ) const { return m_map.count( name ) != 0; } 31 | 32 | bool material_id_map::has_undefined_material() const { return m_hasUndefinedID; } 33 | 34 | boost::uint16_t material_id_map::get_material_id( const frantic::tstring& name ) { 35 | map_t::iterator i = m_map.find( name ); 36 | if( i == m_map.end() ) { 37 | if( m_locked ) { 38 | return get_undefined_material_id(); 39 | } else { 40 | const boost::uint16_t id = get_next_unused_id(); 41 | insert_material( id, name ); 42 | return id; 43 | } 44 | } else { 45 | return i->second; 46 | } 47 | } 48 | 49 | boost::uint16_t material_id_map::get_undefined_material_id() { 50 | if( m_hasUndefinedID ) { 51 | return m_undefinedID; 52 | } else { 53 | const boost::uint16_t id = get_next_unused_id(); 54 | m_undefinedID = id; 55 | m_hasUndefinedID = true; 56 | return m_undefinedID; 57 | } 58 | } 59 | 60 | void material_id_map::insert_material( boost::uint16_t id, const frantic::tstring& name ) { 61 | map_t::iterator i = m_map.find( name ); 62 | if( i == m_map.end() ) { 63 | m_map[name] = id; 64 | } 65 | m_usedIDs.insert( id ); 66 | } 67 | 68 | void material_id_map::insert_undefined_material( boost::uint16_t id ) { 69 | if( !m_hasUndefinedID ) { 70 | m_undefinedID = id; 71 | m_hasUndefinedID = true; 72 | } 73 | m_usedIDs.insert( id ); 74 | } 75 | 76 | boost::uint16_t material_id_map::get_next_unused_id() { 77 | while( m_usedIDs.count( m_nextIDHint ) ) { 78 | if( m_nextIDHint == std::numeric_limits::max() ) { 79 | throw std::runtime_error( "material_id_map::get_next_unused_id Error: exhausted available Material IDs. " 80 | "Please contact Thinkbox Support." ); 81 | } 82 | ++m_nextIDHint; 83 | } 84 | return m_nextIDHint; 85 | } 86 | -------------------------------------------------------------------------------- /src/SequenceSaverHelper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #ifdef _WIN32 8 | #define WIN32_LEAN_AND_MEAN 9 | #define NOMINMAX 10 | #include 11 | #endif 12 | 13 | #include "SequenceSaverHelper.hpp" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // SequenceSaverHelper& GetSequenceSaverHelper() { 21 | // static SequenceSaverHelper theSequenceSaverHelper; 22 | // return theSequenceSaverHelper; 23 | // } 24 | 25 | void SequenceSaverHelper::Clear() { 26 | // MGlobal::displayInfo("\tSequenceSaverHelper::Clear()"); 27 | m_filenamePattern = frantic::files::filename_pattern(); 28 | m_xss.clear(); 29 | m_smoothingGroupAssignments.clear(); 30 | m_materialIDMap.clear(); 31 | m_dagPaths.clear(); 32 | m_meshes.clear(); 33 | } 34 | 35 | //------------------------------------------------------------ 36 | // void* ClearXMeshCommand::creator(){ 37 | // //MGlobal::displayInfo("SequenceSaverHelper::creator()"); 38 | // return new ClearXMeshCommand; 39 | //} 40 | // 41 | // 42 | // MSyntax ClearXMeshCommand::newSyntax(){ 43 | // MSyntax syntax; 44 | // syntax.enableEdit(); //todo 45 | // syntax.enableQuery(); //todo 46 | // return syntax; 47 | //} 48 | // 49 | // MStatus ClearXMeshCommand::doIt( const MArgList& /*args*/ ){ 50 | // 51 | // MStatus stat; 52 | // //MGlobal::displayInfo("ClearXMeshCommand"); 53 | // SequenceSaverHelper& ssh = GetSequenceSaverHelper(); 54 | // ssh.Clear(); 55 | // return stat; 56 | //} 57 | 58 | //------------------------------------------------------------ 59 | // void* SetXMeshChannelCommand::creator(){ 60 | // return new SetXMeshChannelCommand; 61 | //} 62 | // 63 | // MSyntax SetXMeshChannelCommand::newSyntax(){ 64 | // MSyntax syntax; 65 | // syntax.enableEdit(); 66 | // syntax.enableQuery(); 67 | // syntax.addArg(MSyntax::kString); 68 | // syntax.addFlag( "-g", "-get", MSyntax::kString); 69 | // syntax.setObjectType(MSyntax::kStringObjects); 70 | // return syntax; 71 | //} 72 | // 73 | ////setXMeshActiveChannels 74 | // MStatus SetXMeshChannelCommand::doIt( const MArgList& args ){ 75 | // 76 | // MStatus stat; 77 | // char buf [4000]; 78 | // MArgDatabase argData(syntax(),args,&stat); 79 | // MArgList argList; 80 | // unsigned int i, idx; 81 | // argData.getFlagArgumentList("-g", i, argList); 82 | // for (int j = 0; j < argData.numberOfFlagUses("-g"); j++) 83 | // { 84 | // MString m; 85 | // argData.getFlagArgument("-g", j, m); 86 | // MGlobal::displayInfo(m); 87 | // } 88 | // 89 | // idx = 0; 90 | // 91 | // MStringArray str_ary = args.asStringArray( idx, &stat); 92 | // //MGlobal::displayInfo("ClearXMeshCommand"); 93 | // SequenceSaverHelper& ssh = GetSequenceSaverHelper(); 94 | // ssh.cpp.set_to_exclude_policy(); 95 | // 96 | // //if( !argData.isFlagSet( "-p" ) ){ 97 | // MGlobal::displayInfo(str_ary[0]); 98 | // return MStatus::kFailure; 99 | // //} 100 | // return stat; 101 | // } 102 | -------------------------------------------------------------------------------- /src/progress_bar_progress_logger.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #include "progress_bar_progress_logger.hpp" 4 | 5 | #ifndef _BOOL 6 | #define _BOOL 7 | #endif 8 | 9 | #include 10 | 11 | #include 12 | 13 | namespace { 14 | 15 | std::string escape_mel_string( const std::string& inString ) { 16 | std::string resultString( inString ); 17 | size_t currentPos = 0; 18 | 19 | while( currentPos < resultString.length() ) { 20 | currentPos = resultString.find_first_of( '\"', currentPos ); 21 | 22 | if( currentPos == std::string::npos ) { 23 | break; 24 | } else { 25 | resultString.replace( currentPos, 1, "\\\"" ); 26 | currentPos += 2; 27 | } 28 | } 29 | 30 | return "\"" + resultString + "\""; 31 | } 32 | 33 | void set_progress_min_max( int minValue, int maxValue ) { 34 | std::ostringstream os; 35 | os << "progressBar -edit -minValue " << minValue << " -maxValue " << maxValue << " $gMainProgressBar;"; 36 | MGlobal::executeCommand( os.str().c_str() ); 37 | } 38 | 39 | void begin_display() { 40 | std::ostringstream os; 41 | os << "progressBar -edit -isInterruptable true -beginProgress $gMainProgressBar;"; 42 | MGlobal::executeCommand( os.str().c_str() ); 43 | } 44 | 45 | void end_display() { 46 | std::ostringstream os; 47 | os << "progressBar -edit -endProgress $gMainProgressBar;"; 48 | MGlobal::executeCommand( os.str().c_str() ); 49 | } 50 | 51 | void set_progress( float progress ) { 52 | std::ostringstream os; 53 | os << "progressBar -edit -progress " << int( (progress)*100.0f ) << " $gMainProgressBar;"; 54 | MGlobal::executeCommand( os.str().c_str() ); 55 | } 56 | 57 | bool is_cancelled() { 58 | std::ostringstream os; 59 | os << "progressBar -query -isCancelled $gMainProgressBar;"; 60 | int result; 61 | MGlobal::executeCommand( os.str().c_str(), result ); 62 | return result ? true : false; 63 | } 64 | 65 | } // namespace 66 | 67 | progress_bar_progress_logger::progress_bar_progress_logger() { 68 | set_progress_min_max( 0, 10000 ); 69 | 70 | begin_display(); 71 | 72 | // seems to get stuck sometimes? TODO: investigate this 73 | if( is_cancelled() ) { 74 | end_display(); 75 | begin_display(); 76 | } 77 | 78 | set_progress( 0.f ); 79 | } 80 | 81 | progress_bar_progress_logger::~progress_bar_progress_logger() { end_display(); } 82 | 83 | void progress_bar_progress_logger::set_title( const frantic::tstring& title ) { 84 | std::ostringstream os; 85 | os << "progressBar -edit -status " << escape_mel_string( frantic::strings::to_string( title ) ) 86 | << " $gMainProgressBar;"; 87 | MGlobal::executeCommand( os.str().c_str() ); 88 | } 89 | 90 | void progress_bar_progress_logger::update_progress( long long completed, long long maximum ) { 91 | update_progress( 100.f * static_cast( completed ) / maximum ); 92 | } 93 | 94 | void progress_bar_progress_logger::update_progress( float percent ) { 95 | check_for_abort(); 96 | set_progress( percent ); 97 | } 98 | 99 | void progress_bar_progress_logger::check_for_abort() { 100 | if( is_cancelled() ) { 101 | throw frantic::logging::progress_cancel_exception( "Operation cancelled" ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/XMeshLoggingCommand.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #ifdef _WIN32 8 | #define WIN32_LEAN_AND_MEAN 9 | #define NOMINMAX 10 | #include 11 | #endif 12 | 13 | #include 14 | using std::cerr; 15 | using std::endl; 16 | 17 | #include "XMeshLoggingCommand.hpp" 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | void* XMeshLoggingCommand::creator() { return new XMeshLoggingCommand; } 25 | 26 | MSyntax XMeshLoggingCommand::newSyntax() { 27 | MSyntax syntax; 28 | syntax.enableQuery(); 29 | 30 | syntax.addFlag( "-lvl", "-loggingLevel", MSyntax::kUnsigned ); 31 | 32 | return syntax; 33 | } 34 | 35 | MStatus XMeshLoggingCommand::doIt( const MArgList& args ) { 36 | try { 37 | MStatus stat; 38 | 39 | MArgDatabase argData( syntax(), args, &stat ); 40 | CHECK_MSTATUS_AND_RETURN_IT( stat ); 41 | 42 | int loggingLevel; 43 | 44 | if( argData.isQuery() ) { 45 | if( argData.isFlagSet( "-lvl" ) ) { 46 | loggingLevel = frantic::logging::get_logging_level(); 47 | setResult( loggingLevel ); 48 | } 49 | } else { 50 | if( argData.isFlagSet( "-lvl" ) ) { 51 | stat = argData.getFlagArgument( "-lvl", 0, loggingLevel ); 52 | CHECK_MSTATUS_AND_RETURN_IT( stat ); 53 | 54 | frantic::logging::set_logging_level( loggingLevel ); 55 | } 56 | } 57 | 58 | } catch( const std::exception& e ) { 59 | FF_LOG( error ) << e.what() << endl; 60 | return MStatus::kFailure; 61 | } 62 | 63 | return MStatus::kSuccess; 64 | } 65 | 66 | void XMeshLoggingCommand::to_progress_log( const frantic::tchar* szMsg ) { 67 | if( frantic::logging::is_logging_progress() ) { 68 | std::cout << "PRG: " << szMsg << std::endl; 69 | } 70 | } 71 | 72 | void XMeshLoggingCommand::to_debug_log( const frantic::tchar* szMsg ) { 73 | if( frantic::logging::is_logging_debug() ) { 74 | std::cout << "DBG: " << szMsg << std::endl; 75 | } 76 | } 77 | 78 | void XMeshLoggingCommand::to_stats_log( const frantic::tchar* szMsg ) { 79 | if( frantic::logging::is_logging_stats() ) { 80 | std::cout << "STS: " << szMsg << std::endl; 81 | } 82 | } 83 | 84 | void XMeshLoggingCommand::to_warning_log( const frantic::tchar* szMsg ) { 85 | if( frantic::logging::is_logging_warnings() ) { 86 | MGlobal::displayWarning( szMsg ); 87 | } 88 | } 89 | 90 | void XMeshLoggingCommand::to_error_log( const frantic::tchar* szMsg ) { 91 | if( frantic::logging::is_logging_errors() ) { 92 | MGlobal::displayError( szMsg ); 93 | } 94 | } 95 | 96 | void XMeshLoggingCommand::initialize_logging() { 97 | frantic::logging::set_logging_level( frantic::logging::LOG_WARNINGS ); 98 | 99 | frantic::logging::error.rdbuf( frantic::logging::new_ffstreambuf( &to_error_log ) ); 100 | frantic::logging::warning.rdbuf( frantic::logging::new_ffstreambuf( &to_warning_log ) ); 101 | frantic::logging::stats.rdbuf( frantic::logging::new_ffstreambuf( &to_stats_log ) ); 102 | frantic::logging::debug.rdbuf( frantic::logging::new_ffstreambuf( &to_debug_log ) ); 103 | frantic::logging::progress.rdbuf( frantic::logging::new_ffstreambuf( &to_progress_log ) ); 104 | } 105 | -------------------------------------------------------------------------------- /XMesh.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "afxres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (Canada) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENC) 19 | #ifdef _WIN32 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_CAN 21 | #pragma code_page(1252) 22 | #endif //_WIN32 23 | 24 | ///////////////////////////////////////////////////////////////////////////// 25 | // 26 | // Dialog 27 | // 28 | 29 | IDD_LICENSEFINDER DIALOGEX 0, 0, 200, 234 30 | STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU 31 | CAPTION "Maya XMesh Saver Licensing Error" 32 | FONT 8, "MS Shell Dlg", 400, 0, 0x1 33 | BEGIN 34 | CONTROL "",IDC_LICENSEFINDER_CONTENTFRAME,"Static",SS_BLACKFRAME,7,7,186,203 35 | LTEXT "Maya XMesh Saver was unable to obtain a license.",IDC_STATIC,10,15,162,8 36 | CONTROL "License Server",IDC_RADIO_SERVER,"Button",BS_AUTORADIOBUTTON | WS_GROUP,10,107,63,10 37 | CONTROL "Node-Locked License File",IDC_RADIO_FILE,"Button",BS_AUTORADIOBUTTON,10,168,95,10 38 | LTEXT "Hostname or IP:",IDC_STATIC,20,120,53,8 39 | EDITTEXT IDC_EDIT_SERVERNAME,76,117,114,14,ES_AUTOHSCROLL 40 | CONTROL "Server is using a specific port (for firewalls)",IDC_CHECK_USESERVERPORT, 41 | "Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,133,154,10 42 | LTEXT "Port:",IDC_STATIC,56,149,17,8 43 | EDITTEXT IDC_EDIT_SERVERPORT,76,146,101,14,ES_AUTOHSCROLL | ES_NUMBER 44 | CONTROL "",IDC_SPIN_SERVERPORT,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS | UDS_NOTHOUSANDS,178,146,11,14 45 | LTEXT "Path:",IDC_STATIC,20,181,18,8 46 | EDITTEXT IDC_EDIT_FILEPATH,39,178,151,14,ES_AUTOHSCROLL 47 | PUSHBUTTON "Browse",IDC_BUTTON_FILEBROWSE,155,193,35,14 48 | DEFPUSHBUTTON "Accept",IDOK,91,213,50,14 49 | PUSHBUTTON "Cancel",IDCANCEL,143,213,50,14 50 | LTEXT "",IDC_STATIC_REASON,10,29,180,73 51 | END 52 | 53 | 54 | ///////////////////////////////////////////////////////////////////////////// 55 | // 56 | // DESIGNINFO 57 | // 58 | 59 | #ifdef APSTUDIO_INVOKED 60 | GUIDELINES DESIGNINFO 61 | BEGIN 62 | IDD_LICENSEFINDER, DIALOG 63 | BEGIN 64 | LEFTMARGIN, 7 65 | RIGHTMARGIN, 193 66 | VERTGUIDE, 10 67 | VERTGUIDE, 20 68 | VERTGUIDE, 30 69 | VERTGUIDE, 190 70 | TOPMARGIN, 7 71 | BOTTOMMARGIN, 227 72 | END 73 | END 74 | #endif // APSTUDIO_INVOKED 75 | 76 | 77 | #ifdef APSTUDIO_INVOKED 78 | ///////////////////////////////////////////////////////////////////////////// 79 | // 80 | // TEXTINCLUDE 81 | // 82 | 83 | 1 TEXTINCLUDE 84 | BEGIN 85 | "resource.h\0" 86 | END 87 | 88 | 2 TEXTINCLUDE 89 | BEGIN 90 | "#include ""afxres.h""\r\n" 91 | "\0" 92 | END 93 | 94 | 3 TEXTINCLUDE 95 | BEGIN 96 | "\r\n" 97 | "\0" 98 | END 99 | 100 | #endif // APSTUDIO_INVOKED 101 | 102 | #endif // English (Canada) resources 103 | ///////////////////////////////////////////////////////////////////////////// 104 | 105 | 106 | 107 | #ifndef APSTUDIO_INVOKED 108 | ///////////////////////////////////////////////////////////////////////////// 109 | // 110 | // Generated from the TEXTINCLUDE 3 resource. 111 | // 112 | 113 | 114 | ///////////////////////////////////////////////////////////////////////////// 115 | #endif // not APSTUDIO_INVOKED 116 | 117 | -------------------------------------------------------------------------------- /src/vertices_to_edge_map.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #include "vertices_to_edge_map.hpp" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace { 16 | 17 | struct comp_first { 18 | bool operator()( const std::pair& a, const std::pair& b ) { return a.first < b.first; } 19 | }; 20 | 21 | } // anonymous namespace 22 | 23 | vertices_to_edge_map::vertices_to_edge_map( MObject& polyObject ) { 24 | MStatus stat; 25 | 26 | MFnMesh fnMesh( polyObject, &stat ); 27 | if( !stat ) { 28 | throw std::runtime_error( "vertices_to_edge_map Error: unable to attach MFnMesh to object" ); 29 | } 30 | 31 | const int vertexCount = fnMesh.numVertices(); 32 | const int edgeCount = fnMesh.numEdges(); 33 | 34 | // Each edge has two vertices. 35 | // Let lesserVertex = min(vertices). 36 | // m_lesserVertexOffset[lesserVertex] is the first index in m_greaterVertexToEdge 37 | // used by edges that have this lesserVertex. 38 | m_lesserVertexOffset.reserve( vertexCount + 1 ); 39 | m_lesserVertexOffset.resize( vertexCount ); 40 | 41 | for( int edgeIndex = 0; edgeIndex < edgeCount; ++edgeIndex ) { 42 | int2 vertices; 43 | stat = fnMesh.getEdgeVertices( edgeIndex, vertices ); 44 | if( !stat ) { 45 | throw std::runtime_error( "vertices_to_edge_map Error: unable to get vertices" ); 46 | } 47 | if( vertices[0] > vertices[1] ) { 48 | std::swap( vertices[0], vertices[1] ); 49 | } 50 | ++m_lesserVertexOffset[vertices[0]]; 51 | } 52 | 53 | // extra entry for one past the end 54 | m_lesserVertexOffset.push_back( 0 ); 55 | 56 | int sum = 0; 57 | for( std::size_t i = 0; i < m_lesserVertexOffset.size(); ++i ) { 58 | const int val = m_lesserVertexOffset[i]; 59 | m_lesserVertexOffset[i] = sum; 60 | sum += val; 61 | } 62 | 63 | std::vector nextFreeIndex( m_lesserVertexOffset ); 64 | 65 | // Each edge has two vertices. 66 | // Let greaterVertex = max(vertices). 67 | // m_greaterVertexToEdge is a list of (greaterVertex,edgeIndex) pairs. 68 | m_greaterVertexToEdge.clear(); 69 | m_greaterVertexToEdge.resize( sum, std::pair( -1, -1 ) ); 70 | 71 | for( int edgeIndex = 0; edgeIndex < edgeCount; ++edgeIndex ) { 72 | int2 vertices; 73 | stat = fnMesh.getEdgeVertices( edgeIndex, vertices ); 74 | if( !stat ) { 75 | throw std::runtime_error( "vertices_to_edge_map Error: unable to get vertices" ); 76 | } 77 | if( vertices[0] > vertices[1] ) { 78 | std::swap( vertices[0], vertices[1] ); 79 | } 80 | const int i = nextFreeIndex[vertices[0]]; 81 | ++nextFreeIndex[vertices[0]]; 82 | m_greaterVertexToEdge[i] = std::pair( vertices[1], edgeIndex ); 83 | } 84 | 85 | for( int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex ) { 86 | std::sort( m_greaterVertexToEdge.begin() + m_lesserVertexOffset[vertexIndex], 87 | m_greaterVertexToEdge.begin() + m_lesserVertexOffset[vertexIndex + 1], comp_first() ); 88 | } 89 | } 90 | 91 | bool vertices_to_edge_map::get_edge( std::pair vertices, int& outEdgeIndex ) const { 92 | typedef std::vector>::const_iterator iter_t; 93 | 94 | if( vertices.first > vertices.second ) { 95 | std::swap( vertices.first, vertices.second ); 96 | } 97 | 98 | iter_t begin = m_greaterVertexToEdge.begin() + m_lesserVertexOffset[vertices.first]; 99 | iter_t end = m_greaterVertexToEdge.begin() + m_lesserVertexOffset[vertices.first + 1]; 100 | 101 | iter_t i = std::lower_bound( begin, end, std::pair( vertices.second, 0 ), comp_first() ); 102 | if( i != end && i->first == vertices.second ) { 103 | outEdgeIndex = i->second; 104 | return true; 105 | } 106 | 107 | return false; 108 | } 109 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | cmake_minimum_required( VERSION 3.20 FATAL_ERROR ) 4 | 5 | project( XMeshMY ) 6 | 7 | find_package( thinkboxcmlibrary REQUIRED ) 8 | include( PrecompiledHeader) 9 | include( ThinkboxCMLibrary) 10 | 11 | set( SUPPORTED_MAYA_VERSIONS 2022 2023 ) 12 | 13 | option( MAYA_VERSION "The version of Maya SDK to build the library against." 2022 ) 14 | 15 | if( NOT MAYA_VERSION IN_LIST SUPPORTED_MAYA_VERSIONS ) 16 | message( FATAL_ERROR "ERROR: Cannot build for unsupported Maya version ${MAYA_VERSION}" ) 17 | endif() 18 | 19 | add_library( xmeshmy SHARED ) 20 | 21 | set_target_properties( xmeshmy PROPERTIES OUTPUT_NAME "XMesh" ) 22 | set_target_properties( xmeshmy PROPERTIES PREFIX "" ) 23 | 24 | target_include_directories( xmeshmy PUBLIC 25 | $ 26 | $ ) 27 | 28 | if( WIN32 ) 29 | set_target_properties( xmeshmy PROPERTIES SUFFIX ".mll" ) 30 | elseif( APPLE ) 31 | set_target_properties( xmeshmy PROPERTIES SUFFIX ".bundle" ) 32 | elseif( UNIX ) 33 | set_target_properties( xmeshmy PROPERTIES SUFFIX ".so" ) 34 | endif() 35 | 36 | file( GLOB H_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp" ) 37 | file( GLOB CXX_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp" ) 38 | 39 | target_sources( xmeshmy PRIVATE 40 | "resource.h" 41 | "XMeshVersion.h" 42 | ${H_FILES} 43 | ${CXX_FILES} 44 | ) 45 | 46 | # The Conan version of Boost was built with this, and it changes the library names. 47 | # As a result, we need to set this to tell Boost to look for the right libraries to 48 | # link against. 49 | target_compile_definitions( xmeshmy PUBLIC BOOST_AUTO_LINK_SYSTEM ) 50 | 51 | find_package( thinkboxlibrary REQUIRED ) 52 | find_package( mayasdk REQUIRED ) 53 | find_package( thinkboxmylibrary REQUIRED ) 54 | find_package( xmeshcore REQUIRED ) 55 | find_package( Boost REQUIRED ) 56 | find_package( OpenEXR REQUIRED ) 57 | find_package( ZLIB REQUIRED ) 58 | find_package( TBB REQUIRED ) 59 | find_package( xxHash REQUIRED ) 60 | 61 | target_include_directories( xmeshmy PUBLIC ${thinkboxlibrary_INCLUDE_DIRS} ) 62 | target_include_directories( xmeshmy PUBLIC ${mayasdk_INCLUDE_DIRS} ) 63 | target_include_directories( xmeshmy PUBLIC ${thinkboxmylibrary_INCLUDE_DIRS} ) 64 | target_include_directories( xmeshmy PUBLIC ${xmeshcore_INCLUDE_DIRS} ) 65 | target_include_directories( xmeshmy PUBLIC ${Boost_INCLUDE_DIRS} ) 66 | target_include_directories( xmeshmy PUBLIC ${OpenEXR_INCLUDE_DIRS} ) 67 | target_include_directories( xmeshmy PUBLIC ${ZLIB_INCLUDE_DIRS} ) 68 | target_include_directories( xmeshmy PUBLIC ${TBB_INCLUDE_DIRS} ) 69 | target_include_directories( xmeshmy PUBLIC ${xxHash_INCLUDE_DIRS} ) 70 | 71 | target_link_libraries( xmeshmy PUBLIC thinkboxlibrary::thinkboxlibrary ) 72 | target_link_libraries( xmeshmy PUBLIC mayasdk::mayasdk ) 73 | target_link_libraries( xmeshmy PUBLIC thinkboxmylibrary::thinkboxmylibrary ) 74 | target_link_libraries( xmeshmy PUBLIC xmeshcore::xmeshcore ) 75 | target_link_libraries( xmeshmy PUBLIC Boost::Boost ) 76 | target_link_libraries( xmeshmy PUBLIC OpenEXR::OpenEXR ) 77 | target_link_libraries( xmeshmy PUBLIC ZLIB::ZLIB ) 78 | target_link_libraries( xmeshmy PUBLIC TBB::tbb ) 79 | target_link_libraries( xmeshmy PUBLIC xxHash::xxHash ) 80 | 81 | find_package( OpenGL REQUIRED ) 82 | include_directories( ${OPENGL_INCLUDE_DIRS} ) 83 | target_link_libraries( xmeshmy PUBLIC ${OPENGL_LIBRARIES} ) 84 | 85 | if( UNIX ) 86 | set_target_properties( xmeshmy PROPERTIES COMPILE_FLAGS "-fPIC -pthread -O3" ) 87 | if( APPLE ) 88 | set_property( TARGET xmeshmy APPEND_STRING PROPERTY LINK_FLAGS " -Wl,-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/XMesh.exp" ) 89 | else() 90 | set_property( TARGET xmeshmy APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/XMesh.map -s" ) 91 | endif() 92 | endif() 93 | 94 | frantic_common_platform_setup( xmeshmy ) 95 | frantic_default_source_groups( xmeshmy HEADERDIR include SOURCEDIR src ) 96 | 97 | # Disable optimization for the RelWithDebInfo configuration on Windows. 98 | # This allows breakpoints to be hit reliably when debugging in Visual Studio. 99 | if( WIN32 ) 100 | target_compile_options( xmeshmy PRIVATE "$<$:/O2>$<$:/Od>" ) 101 | endif() 102 | 103 | install( TARGETS xmeshmy 104 | RUNTIME DESTINATION bin 105 | LIBRARY DESTINATION lib 106 | ARCHIVE DESTINATION lib 107 | ) 108 | -------------------------------------------------------------------------------- /src/SequenceXMeshGeometryOverride.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | 7 | #if MAYA_API_VERSION >= 201300 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "SequenceXMeshNode.hpp" 16 | #include 17 | 18 | class SequenceXMeshGeometryOverride : public MHWRender::MPxGeometryOverride { 19 | public: 20 | static MHWRender::MPxGeometryOverride* create( const MObject& obj ); 21 | 22 | SequenceXMeshGeometryOverride( const MObject& obj ); 23 | 24 | void updateDG(); 25 | 26 | void updateRenderItems( const MDagPath& path, MHWRender::MRenderItemList& list ); 27 | 28 | #if MAYA_API_VERSION >= 201400 29 | void populateGeometry( const MHWRender::MGeometryRequirements& requirements, 30 | const MHWRender::MRenderItemList& renderItems, MHWRender::MGeometry& data ); 31 | #else 32 | void populateGeometry( const MHWRender::MGeometryRequirements& requirements, 33 | MHWRender::MRenderItemList& renderItems, MHWRender::MGeometry& data ); 34 | #endif 35 | 36 | #if MAYA_API_VERSION >= 201600 37 | virtual MHWRender::DrawAPI supportedDrawAPIs() const; 38 | #endif 39 | 40 | void cleanUp(); 41 | 42 | private: 43 | MHWRender::MVertexBuffer* m_vertexBuffer; 44 | MObject m_obj; 45 | MColor m_cachedColor; 46 | frantic::graphics::boundbox3f m_cachedBoundBox; 47 | frantic::geometry::const_polymesh3_ptr m_cachedMesh; 48 | display_mode m_cachedDisplayMode; 49 | SequenceXMeshNode* m_seqXMeshNode; 50 | double m_cachedVertexFraction; 51 | 52 | /** 53 | * Caches whether or not the the object is selected 54 | */ 55 | void cache_wireframe_color( const MFnDagNode& dagNodeFunctionSet ); 56 | 57 | /** 58 | * Caches the poly mesh object and its bounding box 59 | */ 60 | void cache_mesh_geometry(); 61 | 62 | /** 63 | * Sets up a render item of a specified name 64 | */ 65 | void setup_render_item( const MString& renderItemName, const MHWRender::MGeometry::Primitive geometryType, 66 | MHWRender::MRenderItemList& renderItemList, 67 | const MHWRender::MShaderManager& shaderManager ); 68 | 69 | /** 70 | * Sets up the shaders for coloring the render items 71 | */ 72 | void set_shader_color( MHWRender::MShaderInstance& shader ); 73 | 74 | /** 75 | * Enables/disables the appropriate render items according to the display mode 76 | */ 77 | void enable_render_items( MHWRender::MRenderItem& renderItem ); 78 | 79 | /** 80 | * Creates the vertex buffer using the vertexRequirements 81 | */ 82 | void create_vertex_buffer( const MHWRender::MVertexBufferDescriptorList& vertexRequirements, 83 | MHWRender::MGeometry& data ); 84 | 85 | /** 86 | * Acquires and populates the vertexBuffer 87 | */ 88 | void populate_vertex_buffer( const size_t numIconVertices, const size_t numMeshVertices, 89 | const size_t numBoundingBoxVertices, 90 | const boost::shared_ptr iconMesh ); 91 | 92 | /** 93 | * Populates the beginning of the vertexBuffer with the vertices of the bounding box 94 | */ 95 | void populate_bounding_box_vertices( float* bufferPositions ); 96 | 97 | /** 98 | * Populates the middle of the vertexBuffer with the vertices of the icon mesh 99 | */ 100 | void populate_icon_mesh_vertices( float* bufferPositions, const size_t vertexIndexOffset, 101 | const boost::shared_ptr iconMesh ); 102 | 103 | /** 104 | * Populates the end of the vertexBuffer with vertices of the loaded/created mesh object 105 | */ 106 | void populate_mesh_object_vertices( float* bufferPositions, const size_t vertexIndexOffset ); 107 | 108 | /** 109 | * Creates and populates an index buffer for the bounding box 110 | */ 111 | void populate_bounding_box_indices( MHWRender::MGeometry& geometryData, const MHWRender::MRenderItem& renderItem ); 112 | 113 | /** 114 | * Creates and populates an index buffer for the icon mesh 115 | */ 116 | void populate_icon_mesh_indices( MHWRender::MGeometry& geometryData, const MHWRender::MRenderItem& renderItem, 117 | const size_t vertexIndexOffset, 118 | const boost::shared_ptr iconMesh ); 119 | 120 | /** 121 | * Creates and populates an index buffer for the vertices of the mesh object 122 | */ 123 | void populate_mesh_object_indices( MHWRender::MGeometry& geometryData, const MHWRender::MRenderItem& renderItem, 124 | const size_t vertexIndexOffset, const size_t vertexCount ); 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from typing import Any 4 | from conans import ConanFile, CMake 5 | 6 | import os 7 | import posixpath 8 | import shutil 9 | 10 | import version_gen 11 | 12 | 13 | VALID_MAYA_CONFIGS: dict[tuple[str, str], set[str]] = { 14 | ('Visual Studio', '16'): { '2022', '2023' }, 15 | ('gcc', '7'): { '2022', '2023' }, 16 | ('gcc', '9'): { '2022', '2023' }, 17 | ('apple-clang', '10.0'): { '2022', '2023' } 18 | } 19 | 20 | SETTINGS: dict[str, Any] = { 21 | 'os': ['Windows', 'Linux', 'Macos'], 22 | 'compiler': { 23 | 'Visual Studio': {'version': ['16']}, 24 | 'gcc': {'version': ['7', '9']}, 25 | 'apple-clang': {'version': ['10.0']} 26 | }, 27 | 'build_type': None, 28 | 'arch': 'x86_64' 29 | } 30 | 31 | TOOL_REQUIRES: list [str] = [ 32 | 'cmake/3.24.1', 33 | 'thinkboxcmlibrary/1.0.0' 34 | ] 35 | 36 | REQUIRES: list[str] = [ 37 | 'thinkboxlibrary/1.0.0', 38 | 'mayasdk/1.0.0', 39 | 'thinkboxmylibrary/1.0.0', 40 | 'xmeshcore/1.0.0' 41 | ] 42 | 43 | NO_LICENSE_ALLOWLIST: set[str] = { 44 | # ThinkboxCMLibrary is not distributed with this package 45 | 'thinkboxcmlibrary', 46 | # The Maya SDK is not open source and we do not distribute it 47 | 'mayasdk', 48 | # We do not distribute OpenGL 49 | 'opengl', 50 | # We do not distribute CMake 51 | 'cmake' 52 | } 53 | 54 | UNUSED_LICENSE_DENYLIST: set[str] = { 55 | # Parts of Eigen are licensed under GPL or LGPL 56 | # Eigen provides an option to disable these parts such 57 | # that a compiler error will be generated if they are used. 58 | # We do not use these parts, and enable this option. 59 | 'licenses/eigen/licenses/COPYING.GPL', 60 | 'licenses/eigen/licenses/COPYING.LGPL', 61 | # Freetype is dual licensed under it's own BSD style license, FTL 62 | # as well as GPLv2. We are licensing it under FTL so we will not 63 | # include GPLv2 in our attributions document. 64 | 'licenses/freetype/licenses/GPLv2.txt' 65 | } 66 | 67 | 68 | class ThinkboxMYLibraryConan(ConanFile): 69 | name: str = 'xmeshmy' 70 | version: str = '1.9.1' 71 | license: str = 'Apache-2.0' 72 | description: str = 'The XMesh Plugin for Maya' 73 | settings: dict[str, Any] = SETTINGS 74 | requires: list[str] = REQUIRES 75 | tool_requires: list[str] = TOOL_REQUIRES 76 | generators: str | list[str] = 'cmake_find_package' 77 | options: dict[str, Any] = { 78 | 'maya_version': ['2022', '2023'] 79 | } 80 | 81 | def configure(self) -> None: 82 | if self.options.maya_version == None: 83 | self.options.maya_version = '2022' 84 | self.options['mayasdk'].maya_version = self.options.maya_version 85 | self.options['thinkboxmylibrary'].maya_version = self.options.maya_version 86 | 87 | def validate(self) -> None: 88 | if self.options.maya_version != self.options['mayasdk'].maya_version: 89 | raise Exception('Option \'maya_version\' must be the same as mayasdk') 90 | if self.options.maya_version != self.options['thinkboxmylibrary'].maya_version: 91 | raise Exception('Option \'maya_version\' must be the same as mayasdk') 92 | compiler = str(self.settings.compiler) 93 | compiler_version = str(self.settings.compiler.version) 94 | compiler_tuple = (compiler, compiler_version) 95 | maya_version = str(self.options.maya_version) 96 | if maya_version not in VALID_MAYA_CONFIGS[compiler_tuple]: 97 | raise Exception(f'{str(compiler_tuple)} is not a valid configuration for Maya {maya_version}') 98 | 99 | def imports(self) -> None: 100 | self.copy("license*", dst="licenses", folder=True, ignore_case=True) 101 | self.generate_attributions_doc() 102 | 103 | def build(self) -> None: 104 | version_gen.write_version_file(self.version, os.path.join(self.source_folder, 'XMeshVersion.h')) 105 | shutil.copyfile('attributions.txt', os.path.join(self.source_folder, 'third_party_licenses.txt')) 106 | 107 | cmake = CMake(self) 108 | cmake.configure(defs={ 109 | 'MAYA_VERSION': self.options.maya_version 110 | }) 111 | cmake.build() 112 | 113 | def export_sources(self) -> None: 114 | self.copy('**.h', src='', dst='') 115 | self.copy('**.hpp', src='', dst='') 116 | self.copy('**.cpp', src='', dst='') 117 | self.copy('CMakeLists.txt', src='', dst='') 118 | self.copy('version_gen.py', src='', dst='') 119 | self.copy('XMesh.exp', src='', dst='') 120 | self.copy('XMesh.map', src='', dst='') 121 | self.copy('*', dst='icons', src='icons') 122 | self.copy('*', dst='scripts', src='scripts') 123 | self.copy('*', dst='BitRockInstaller', src='BitRockInstaller') 124 | 125 | def package(self) -> None: 126 | cmake = CMake(self) 127 | cmake.install() 128 | self.copy('*', dst='icons', src='icons') 129 | self.copy('*', dst='scripts', src='scripts') 130 | self.copy('third_party_licenses.txt', dst='Legal', src='') 131 | 132 | def deploy(self) -> None: 133 | self.copy('*', dst='bin', src='bin') 134 | self.copy('*', dst='lib', src='lib') 135 | self.copy('*', dst='include', src='include') 136 | self.copy('*', dst='icons', src='icons') 137 | self.copy('*', dst='scripts', src='scripts') 138 | self.copy('*', dst='Legal', src='Legal') 139 | 140 | def generate_attributions_doc(self) -> None: 141 | dependencies = [str(dependency[0].ref).split('/') for dependency in self.dependencies.items()] 142 | with open('attributions.txt', 'w') as attributions_doc: 143 | for name, version in dependencies: 144 | if name not in NO_LICENSE_ALLOWLIST: 145 | attributions_doc.write(f'######## {name} {version} ########\n\n') 146 | licensedir = posixpath.join('licenses', name, 'licenses') 147 | if not os.path.exists(licensedir): 148 | raise Exception(f'Could not find license files for package {name} {version}.') 149 | for licensefile in os.listdir(licensedir): 150 | licensefilepath = posixpath.join(licensedir, licensefile) 151 | if licensefilepath not in UNUSED_LICENSE_DENYLIST: 152 | with open(licensefilepath, 'r') as license: 153 | attributions_doc.writelines(license.readlines()) 154 | attributions_doc.write('\n') 155 | -------------------------------------------------------------------------------- /src/dllmain.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #ifdef _WIN32 8 | #define WIN32_LEAN_AND_MEAN 9 | #define NOMINMAX 10 | #include 11 | #endif 12 | 13 | #include 14 | 15 | using std::cerr; 16 | using std::endl; 17 | 18 | #include "SaveXMeshCommand.hpp" 19 | #include "SequenceSaverHelper.hpp" 20 | #include "SequenceXMeshGeometryOverride.hpp" 21 | #include "SequenceXMeshNode.hpp" 22 | #include "XMeshLoggingCommand.hpp" 23 | #include "XMeshSaverUISettingsNode.hpp" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "XMeshVersion.h" 33 | 34 | #ifdef _WIN32 35 | #define EXPORT __declspec( dllexport ) 36 | #elif __GNUC__ >= 4 37 | #define EXPORT __attribute__( ( visibility( "default" ) ) ) 38 | #else 39 | #define EXPORT 40 | #endif 41 | 42 | #ifdef _WIN32 43 | HINSTANCE hInstance; 44 | 45 | BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID /*lpvReserved*/ ) { 46 | if( fdwReason == DLL_PROCESS_ATTACH ) 47 | hInstance = hinstDLL; // Hang on to this DLL's instance handle. 48 | return TRUE; 49 | } 50 | #endif 51 | 52 | namespace { 53 | 54 | std::string create_set_xmesh_render_command( const std::string& commandName, bool render ) { 55 | std::ostringstream ss; 56 | 57 | ss << "global proc " << commandName << "() {"; 58 | ss << " string $xmeshNodes[] = `ls -type \"sequenceXMesh\"`;"; 59 | ss << " for ($xmeshNode in $xmeshNodes) {"; 60 | ss << " if (`objExists ($xmeshNode + \".inRender\")`) {"; 61 | ss << " setAttr ($xmeshNode + \".inRender\") " << ( render ? "1" : "0" ) << ";"; 62 | ss << " }"; 63 | ss << " }"; 64 | ss << "}"; 65 | 66 | return ss.str().c_str(); 67 | } 68 | 69 | bool command_exists( const std::string& commandName ) { 70 | MStatus stat; 71 | MString result; 72 | stat = MGlobal::executeCommand( ( "whatIs " + commandName ).c_str(), result ); 73 | if( !stat ) { 74 | throw std::runtime_error( "unable to execute whatIs command" ); 75 | } 76 | return result == "Command"; 77 | } 78 | 79 | // This manages all data related to the plugin, so that it can easily be unloaded if anything goes wrong 80 | frantic::maya::plugin_manager s_pluginManager; 81 | 82 | } // anonymous namespace 83 | 84 | using namespace frantic::maya; 85 | 86 | // static MCallbackId beginRenderId = 0; 87 | // static MCallbackId endRenderId = 0; 88 | 89 | MStatus EXPORT initializePlugin( MObject obj ) { 90 | 91 | MStatus status; 92 | XMeshLoggingCommand::initialize_logging(); 93 | s_pluginManager.initialize( obj, _T("Thinkbox Software"), frantic::strings::to_tstring( FRANTIC_VERSION ), 94 | _T("Any") ); 95 | 96 | try { 97 | // beginRenderId = MSceneMessage::addCallback( MSceneMessage::kBeforeSoftwareRender, beginRender); 98 | // endRenderId = MSceneMessage::addCallback( MSceneMessage::kAfterSoftwareRender, endRender); 99 | 100 | // Check if saveXMesh command already exists. Without this check, that would 101 | // produce an "Unexpected Internal Failure" error. 102 | // I think this was happening to some beta testers who had the old XMeshLoader.mll 103 | // plugin installed. 104 | if( command_exists( "saveXMesh" ) ) { 105 | throw std::runtime_error( "saveXMesh command already exists. Are you loading the XMesh plugin twice?" ); 106 | } 107 | 108 | status = 109 | s_pluginManager.register_command( "saveXMesh", &SaveXMeshCommand::creator, &SaveXMeshCommand::newSyntax ); 110 | CHECK_MSTATUS_AND_RETURN_IT( status ); 111 | status = s_pluginManager.register_command( "saveXMeshSequence", &SaveXMeshSequenceCommand::creator, 112 | &SaveXMeshSequenceCommand::newSyntax ); 113 | CHECK_MSTATUS_AND_RETURN_IT( status ); 114 | status = s_pluginManager.register_command( "xmeshLogging", &XMeshLoggingCommand::creator, 115 | &XMeshLoggingCommand::newSyntax ); 116 | CHECK_MSTATUS_AND_RETURN_IT( status ); 117 | // status = s_pluginManager.register_command( "clearXMeshCache", &ClearXMeshCommand::creator, 118 | //&ClearXMeshCommand::newSyntax ); CHECK_MSTATUS_AND_RETURN_IT(status); status = 119 | //s_pluginManager.register_command( "setXMeshActiveChannels", &SetXMeshChannelCommand::creator, 120 | //&SetXMeshChannelCommand::newSyntax ); CHECK_MSTATUS_AND_RETURN_IT(status); 121 | 122 | status = s_pluginManager.register_ui( "XMeshCreateUI", "XMeshDeleteUI" ); 123 | CHECK_MSTATUS_AND_RETURN_IT( status ); 124 | 125 | status = s_pluginManager.register_node( "sequenceXMesh", SequenceXMeshNode::typeID, &SequenceXMeshNode::creator, 126 | &SequenceXMeshNode::initialize, MPxNode::kLocatorNode, 127 | &SequenceXMeshNode::drawClassification ); 128 | CHECK_MSTATUS_AND_RETURN_IT( status ); 129 | 130 | status = s_pluginManager.register_node( "xmeshSaverUISettings", XMeshSaverUISettingsNode::typeId, 131 | &XMeshSaverUISettingsNode::creator, 132 | &XMeshSaverUISettingsNode::initialize, MPxNode::kDependNode ); 133 | CHECK_MSTATUS_AND_RETURN_IT( status ); 134 | 135 | #if MAYA_API_VERSION >= 201300 136 | status = s_pluginManager.register_geometry_override_creator( SequenceXMeshNode::drawClassification, 137 | SequenceXMeshNode::drawRegistrantId, 138 | SequenceXMeshGeometryOverride::create ); 139 | CHECK_MSTATUS_AND_RETURN_IT( status ); 140 | #endif 141 | 142 | // Define the xmeshPreRender and xmeshPostRender procs. These procs 143 | // are used to switch between the viewport 144 | // We also do this using the kBeforeSoftwareRender and 145 | // kAfterSoftwareRender callbacks, but those only seem to work for the 146 | // Maya Software renderer. 147 | MGlobal::executeCommand( create_set_xmesh_render_command( "xmeshPreRender", true ).c_str() ); 148 | MGlobal::executeCommand( create_set_xmesh_render_command( "xmeshPostRender", false ).c_str() ); 149 | } catch( std::exception& e ) { 150 | // if we run into absolutely anything wrong, we should just bail and put up an error 151 | s_pluginManager.unregister_all(); 152 | status.perror( e.what() ); 153 | status = MStatus::kFailure; 154 | } catch( ... ) { 155 | // if we run into absolutely anything wrong, we should just bail and put up an error 156 | s_pluginManager.unregister_all(); 157 | status.perror( "XMesh: Unknown exception thrown during initialization." ); 158 | status = MStatus::kFailure; 159 | } 160 | 161 | return status; 162 | } 163 | 164 | MStatus EXPORT uninitializePlugin( MObject obj ) { 165 | s_pluginManager.unregister_all(); 166 | // if (beginRenderId) 167 | //{ 168 | // MMessage::removeCallback(beginRenderId); 169 | // beginRenderId = 0; 170 | // } 171 | // 172 | // if (endRenderId) 173 | //{ 174 | // MMessage::removeCallback(endRenderId); 175 | // endRenderId = 0; 176 | // } 177 | 178 | return MS::kSuccess; 179 | } 180 | 181 | // Workaround for "unresolved external symbol __iob_func" linker error when 182 | // using FlexLM with MSVC2015. 183 | // This can be removed when we get a version of Flex which supports Visual Studio 2015 184 | #if defined( WIN32 ) && MAYA_API_VERSION >= 201800 185 | extern "C" { 186 | FILE __iob_func[3] = { *stdin, *stdout, *stderr }; 187 | } 188 | #endif 189 | -------------------------------------------------------------------------------- /src/SequenceXMeshNode.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace frantic { 16 | namespace geometry { 17 | 18 | class trimesh3; 19 | 20 | } 21 | } // namespace frantic 22 | 23 | enum clamp_mode { 24 | CLAMP_MODE_HOLD = 1, 25 | CLAMP_MODE_BLANK, 26 | }; 27 | 28 | enum clamp_region { CLAMP_REGION_INSIDE, CLAMP_REGION_BEFORE, CLAMP_REGION_AFTER }; 29 | 30 | enum seq_ID { 31 | SEQ_RENDER = 1, 32 | SEQ_PROXY, 33 | }; 34 | 35 | enum display_mode { 36 | DISPLAY_MODE_MESH = 1, 37 | DISPLAY_MODE_BOX, 38 | DISPLAY_MODE_VERTEX, 39 | DISPLAY_MODE_FACE, 40 | }; 41 | 42 | enum scene_units { 43 | UNITS_GENERIC = 1, 44 | UNITS_CM, 45 | UNITS_MM, 46 | UNITS_M, 47 | UNITS_IN, 48 | UNITS_FT, 49 | UNITS_KM, 50 | UNITS_MILES, 51 | UNITS_CUSTOM, 52 | }; 53 | 54 | enum load_mode { 55 | LOADMODE_STATIC, 56 | LOADMODE_BLANK, 57 | LOADMODE_VELOCITY_OFFSET, 58 | LOADMODE_SUBFRAME_VELOCITY_OFFSET, 59 | LOADMODE_FRAME_INTERPOLATION, 60 | LOADMODE_SUBFRAME_INTERPOLATION, 61 | }; 62 | 63 | // void beginRender( void* cliendData); 64 | // void endRender( void* cliendData); 65 | 66 | class SequenceXMeshNode : public MPxLocatorNode { 67 | public: 68 | static MObject seqPath; 69 | static MObject seqProxyPath; 70 | 71 | static MObject outMesh; 72 | 73 | static MObject inTime; 74 | static MObject inGroupIds; 75 | static MObject inPlaybackGraph; 76 | static MObject inEnablePlaybackGraph; 77 | static MObject inFrameOffset; 78 | static MObject inUseCustomRange; 79 | static MObject inCustomRangeStart; 80 | static MObject inCustomRangeEnd; 81 | static MObject inCustomRangeStartClampMode; 82 | static MObject inCustomRangeEndClampMode; 83 | static MObject inSingleFileOnly; // loading mode? 84 | 85 | static MObject inCustomScale; 86 | static MObject inLengthUnit; 87 | static MObject inLoadingMode; 88 | static MObject inAutoProxyPath; 89 | 90 | static MObject inViewportSource; 91 | static MObject inDisplayMode; 92 | static MObject inDisplayPercent; 93 | static MObject inRenderSource; 94 | static MObject inRender; 95 | 96 | static MObject outMinimumAvailableFileIndex; 97 | static MObject outMaximumAvailableFileIndex; 98 | 99 | static MTypeId typeID; 100 | static MString drawClassification; 101 | static MString drawRegistrantId; 102 | 103 | private: 104 | MCallbackId m_computeViewportCallbackId; 105 | MCallbackId m_computeRenderCallbackId; 106 | 107 | frantic::files::filename_sequence m_cachedFilenameSequence; 108 | frantic::files::filename_sequence m_cachedProxyFilenameSequence; 109 | 110 | // which filename_sequence path was used to sync_frame_set() 111 | frantic::tstring m_cachedFilenameSequencePath; 112 | frantic::tstring m_cachedProxyFilenameSequencePath; 113 | 114 | frantic::graphics::boundbox3f m_meshBoundingBox; 115 | frantic::geometry::const_polymesh3_ptr m_cachedPolymesh3; 116 | 117 | std::pair m_cachedPolymesh3Interval; 118 | 119 | xmesh::cached_polymesh3_loader m_polymesh3Loader; 120 | 121 | double m_cachedFrame; 122 | std::pair m_cachedInterval; 123 | frantic::tstring m_cachedFilenamePattern; // what filename pattern does the cache represent? 124 | bool m_cachedUseFirst; // first or second in range; 125 | load_mode m_cachedLoadingMode; // which loading mode was the cache constructed in? 126 | int m_cachedLoadMask; // which load mask was used to load the mesh? 127 | 128 | // local data 129 | frantic::geometry::xmesh_metadata m_metadata; 130 | 131 | MBoundingBox m_boundingBox; 132 | 133 | static boost::shared_ptr g_iconMesh; 134 | 135 | // methods 136 | // void handle_custom_range( load_mode& loadMode, MTime &requestTime, MTime inTime, MTime inPivotTime); 137 | 138 | // void clear_cache();//? 139 | 140 | // load_mode get_load_mode_from_clamp_mode( clamp_mode clampMode ); 141 | // these allow you to grab the nearest subframe/wholeframe from a sequence 142 | // they return false when an appropriate frame can't be found 143 | // bool get_nearest_subframe( float time, double &frameNumber ); //? 144 | // bool get_nearest_wholeframe( float time, double &frameNumber );//? 145 | // int round_to_nearest_wholeframe( MTime t ) const; 146 | 147 | // cached mesh info utility functions 148 | // void invalidate_cache();//? 149 | // check for a frame in the cache at time t 150 | // bool frame_exists( float t );//?! 151 | 152 | // load a pair of meshes, suitable for interpolation, into the m_cachedPolymesh3Interval 153 | void load_mesh_interval( seq_ID seqId, std::pair interval, int loadMask ); 154 | 155 | // loads a mesh into the locally cached polymesh 156 | void load_mesh_at_frame( seq_ID seqId, double frame, int loadMask ); 157 | 158 | // void build_channel_assignment( mesh_channel_assignment& channels, bool useVelocity, float timeOffset, float 159 | // timeDerivative, const frantic::graphics::transform4f & xform ); 160 | 161 | // HELPERS 162 | frantic::tstring get_sequence_path( seq_ID seqId ); 163 | frantic::files::filename_sequence& get_sequence( seq_ID seqId, bool throwIfMissing = true ); //? 164 | // frantic::tstring get_current_sequence_path();//? 165 | // frantic::tstring get_render_sequence_path();//? 166 | // frantic::files::filename_sequence& get_render_sequence();//? 167 | // int get_current_display_mode();//? 168 | display_mode get_effective_display_mode(); 169 | frantic::tstring get_render_path(); 170 | frantic::tstring get_proxy_path(); 171 | 172 | // void set_to_valid_frame_range( bool notify = false, bool setLoadSingleFrame = false 173 | // ); 174 | bool is_autogen_proxy_path_enabled(); 175 | frantic::tstring get_auto_proxy_path(); 176 | frantic::tstring get_auto_proxy_directory(); 177 | void check_auto_proxy_path(); 178 | 179 | clamp_mode get_start_clamp_mode() const; 180 | clamp_mode get_end_clamp_mode() const; 181 | 182 | // void build_normals();//? 183 | 184 | void cache_bounding_box(); 185 | 186 | public: 187 | static void* creator(); 188 | static MStatus initialize(); 189 | 190 | virtual MBoundingBox boundingBox() const; 191 | virtual bool isBounded() const; 192 | virtual void draw( M3dView& view, const MDagPath& path, M3dView::DisplayStyle style, 193 | M3dView::DisplayStatus status ); 194 | 195 | #if MAYA_API_VERSION >= 201800 196 | bool getInternalValue( const MPlug&, MDataHandle& ) override; 197 | bool setInternalValue( const MPlug&, const MDataHandle& ) override; 198 | #else 199 | virtual bool getInternalValueInContext( const MPlug& plug, MDataHandle& dataHandle, MDGContext& currentContext ); 200 | virtual bool setInternalValueInContext( const MPlug& plug, const MDataHandle& dataHandle, 201 | MDGContext& currentContext ); 202 | #endif 203 | 204 | const frantic::graphics::boundbox3f& get_mesh_bounding_box(); 205 | display_mode get_display_mode(); 206 | float get_display_fraction(); 207 | 208 | virtual MStatus compute( const MPlug& plug, MDataBlock& data ); 209 | 210 | void postConstructor(); 211 | SequenceXMeshNode(); 212 | ~SequenceXMeshNode(); 213 | 214 | static boost::shared_ptr get_icon_mesh(); 215 | frantic::geometry::const_polymesh3_ptr get_cached_mesh(); 216 | }; 217 | -------------------------------------------------------------------------------- /scripts/XMeshMaterialUtils.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # XMesh Material Utils 5 | 6 | import maya 7 | import io 8 | import os 9 | import re 10 | import string 11 | 12 | try: 13 | # py3 14 | import configparser 15 | except: 16 | # py2 17 | import ConfigParser as configparser 18 | 19 | from io import StringIO 20 | 21 | # This takes the incoming material name and sanitizes it to be a valid maya identifier 22 | # This will potentially result in un-reversible translations when saving in maya. 23 | # Currently this is unavoidable, however if non-modifying transformation through 24 | # maya are required, perhaps a simple name-mangling system could be used 25 | def cleanedMaterialName(materialName): 26 | # Remove all invalid identifier characters 27 | result = re.sub("[^A-Za-z0-9_]", "_", materialName.strip()) 28 | # place an '_' in front of the first character if it is a digit 29 | if len(result) == 0 or result[0].isdigit(): 30 | result = "_" + result 31 | return result 32 | 33 | def readTextFileWithUnknownEncoding(filename): 34 | for encoding in ['utf-8', 'utf-16', 'latin-1']: 35 | try: 36 | with io.open(filename, "r", encoding=encoding) as f: 37 | lines = f.readlines() 38 | return lines 39 | except UnicodeError: 40 | pass 41 | except UnicodeDecodeError: 42 | pass 43 | return [] 44 | 45 | def readMaterialIDMapFile(filename): 46 | lines = readTextFileWithUnknownEncoding(filename) 47 | 48 | cfg = configparser.ConfigParser() 49 | cfg.readfp(StringIO(''.join(lines))) 50 | 51 | result = [] 52 | if cfg.has_section('MaterialNames'): 53 | for (id,name) in cfg.items('MaterialNames'): 54 | id = id.strip() 55 | name = name.strip() 56 | if id.isdigit(): 57 | result.append((int(id),name)) 58 | return result 59 | 60 | def replaceFrameNumber(path, newFrameNumber): 61 | (root,ext) = os.path.splitext(path) 62 | 63 | frameNumberCharacters = string.digits + '#' 64 | if root.endswith(','): 65 | root = root[:-1] 66 | else: 67 | if root.endswith(tuple(frameNumberCharacters)): 68 | root = root.rstrip(frameNumberCharacters) 69 | if len(root) > 2 and root.endswith(',') and root[-2] in frameNumberCharacters: 70 | root = root[:-1] 71 | root = root.rstrip(frameNumberCharacters) 72 | if root.endswith('-'): 73 | root = root[:-1] 74 | 75 | return root + newFrameNumber + ext 76 | 77 | def getMaterialIDMapFilename(xmeshFilename): 78 | result = replaceFrameNumber(xmeshFilename, '') 79 | return os.path.splitext(result)[0] + '.matIDmap' 80 | 81 | def isMesh(path): 82 | try: 83 | fnMesh = maya.OpenMaya.MFnMesh() 84 | fnMesh.setObject(path) 85 | return True 86 | except: 87 | return False 88 | 89 | def getMeshDagPathsFromSelectionList(selectionList): 90 | result = [] 91 | for selectionIndex in range(0,selectionList.length()): 92 | object = maya.OpenMaya.MObject() 93 | selectionList.getDependNode(selectionIndex, object) 94 | fnDagNode = maya.OpenMaya.MFnDagNode() 95 | fnDagNode.setObject(object) 96 | dagPath = maya.OpenMaya.MDagPath() 97 | fnDagNode.getPath(dagPath) 98 | numberOfShapesUtil = maya.OpenMaya.MScriptUtil() 99 | numberOfShapesUtil.createFromInt(0) 100 | numberOfShapesPtr = numberOfShapesUtil.asUintPtr() 101 | dagPath.numberOfShapesDirectlyBelow(numberOfShapesPtr) 102 | numberOfShapes = numberOfShapesUtil.getUint(numberOfShapesPtr) 103 | for i in range(0, numberOfShapes): 104 | childPath = maya.OpenMaya.MDagPath(dagPath) 105 | childPath.extendToShapeDirectlyBelow(i) 106 | if isMesh(childPath): 107 | result.append(childPath) 108 | return result 109 | 110 | def getSelectedMeshDagPaths(): 111 | selectionList = maya.OpenMaya.MSelectionList() 112 | maya.OpenMaya.MGlobal_getActiveSelectionList(selectionList) 113 | return getMeshDagPathsFromSelectionList(selectionList) 114 | 115 | def getSurfaceShaderName(shadingEngine): 116 | dependencyNode = maya.OpenMaya.MFnDependencyNode(shadingEngine) 117 | plug = dependencyNode.findPlug('surfaceShader') 118 | plugArray = maya.OpenMaya.MPlugArray() 119 | plug.connectedTo(plugArray, True, False) 120 | if plugArray.length() > 0: 121 | surfaceShader = maya.OpenMaya.MFnDependencyNode(plugArray[0].node()) 122 | return surfaceShader.name() 123 | return '' 124 | 125 | class MaterialIDMap: 126 | def __init__(self): 127 | self.list = [] 128 | self.materialToID = {} 129 | self.usedIDs = set() 130 | self.nextID = 0 131 | 132 | @classmethod 133 | def readFromFile(cls, path): 134 | result = cls() 135 | lines = readMaterialIDMapFile(path) 136 | for (id, name) in lines: 137 | result._addMaterialMapping(id, name, force=True) 138 | return result 139 | 140 | def writeToFile(self, path): 141 | cfg = configparser.ConfigParser() 142 | cfg.add_section('MaterialNames') 143 | for (id, name) in sorted(self.list): 144 | if len(name) == 0: 145 | name = 'undefined' 146 | cfg.set('MaterialNames', str(id), name) 147 | file = open(path, 'w') 148 | cfg.write(file) 149 | 150 | def _addMaterialMapping(self, id, name, force=False): 151 | internalName = name 152 | if internalName == 'undefined': 153 | internameName = '' 154 | 155 | if internalName not in self.materialToID: 156 | self.materialToID[internalName] = id 157 | self.usedIDs.add(id) 158 | if (id,name) not in self.list and (force or (id,cleanedMaterialName(name)) not in self.list): 159 | self.list.append((id,name)) 160 | 161 | def _getNextMaterialID(self): 162 | while self.nextID in self.usedIDs: 163 | self.nextID += 1 164 | return self.nextID 165 | 166 | def _addMayaMaterial(self, name): 167 | if name in self.materialToID: 168 | return 169 | 170 | for otherName in self.materialToID: 171 | if cleanedMaterialName(otherName) == name: 172 | self._addMaterialMapping(self.materialToID[otherName], name) 173 | return 174 | 175 | self._addMaterialMapping(self._getNextMaterialID(), name) 176 | 177 | def addMaterialsFromSelectionList(self, selectionList): 178 | dagPaths = getMeshDagPathsFromSelectionList(selectionList) 179 | for dagPath in dagPaths: 180 | instanceNumber = 0 181 | isInstanced = dagPath.isInstanced() 182 | if isInstanced: 183 | instanceNumber = dagPath.instanceNumber() 184 | fnMesh = maya.OpenMaya.MFnMesh() 185 | fnMesh.setObject(dagPath) 186 | shaderArray = maya.OpenMaya.MObjectArray() 187 | shaderIndexArray = maya.OpenMaya.MIntArray() 188 | fnMesh.getConnectedShaders(instanceNumber, shaderArray, shaderIndexArray) 189 | for i in range(0, shaderArray.length()): 190 | shadingEngineNode = maya.OpenMaya.MFnDependencyNode(shaderArray[i]) 191 | shadingEngineName = shadingEngineNode.name() 192 | if shadingEngineName not in self.materialToID: 193 | self._addMayaMaterial(getSurfaceShaderName(shaderArray[i])) 194 | 195 | def addMaterialsFromSelection(self): 196 | selectionList = maya.OpenMaya.MSelectionList() 197 | maya.OpenMaya.MGlobal_getActiveSelectionList(selectionList) 198 | self.addMaterialsFromSelectionList(selectionList) 199 | 200 | def getIDList(self): 201 | return sorted(self.usedIDs) 202 | 203 | def getMaterialName(self, id): 204 | for (listID, listName) in self.list: 205 | if listID == id: 206 | return listName 207 | return None 208 | 209 | def getMaterialIDMapString(self): 210 | return ','.join(str(id) + '=' + str(name) for (name,id) in self.materialToID.items()) 211 | -------------------------------------------------------------------------------- /scripts/XMeshCreateUI.mel: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //////////////////////////////////// 5 | // XMesh (Maya) initialization commands 6 | /////////////////////////////////// 7 | 8 | 9 | // Appends a new menu item to a given parent menu, which executes a given command. 10 | // Example: Adding the XMesh Loader menu item to the Create menu, tagging it with 'XMesh'. 11 | // Usage: append_to_menu( "XMesh Loader", "mainCreateMenu", "python(\"import createXMeshLoader; createXMeshLoader.createXMeshLoader();\");", "XMesh" ); 12 | // 13 | // @param $label The label of the new menu item to appear in the menu. 14 | // @param $parent The Maya label for the parent menu. 15 | // @param $command The command to be executed when the menu item is clicked. 16 | // @param $tag A unique identifier to attach to the menu item, which allows you to identify it in code later. 17 | proc append_to_menu( string $label, string $parent, string $command, string $tag ){ 18 | // VALIDATION: Check if the parent menu exists. 19 | int $parentExists = `menu -q -exists $parent`; 20 | if( $parentExists == 0 ){ return; } 21 | 22 | // VALIDATION: Ensure our new menu item isn't already in the menu. 23 | string $createMenuItems[] = `menu -query -itemArray mainCreateMenu`; // Get all menu items under the Create menu. 24 | int $numItems = size( $createMenuItems ); 25 | for( $i = 0; $i < $numItems; ++$i ){ 26 | string $existingLabel = `menuItem -query -label $createMenuItems[$i]`; // Get the docTag of the current menu item. 27 | if( $existingLabel == $label ){ 28 | return; 29 | } 30 | } 31 | 32 | // We only reach this point if our checks passed. 33 | menuItem -divider true -parent $parent -docTag $tag; 34 | menuItem -label $label -parent $parent -docTag $tag -command $command; 35 | } 36 | 37 | 38 | // Given a shelf label, returns a 1 if the shelf exists, or a 0 otherwise. 39 | // Usage: int $exists = shelf_exists("XMesh"); 40 | // 41 | // @param $label The name of the shelf to test for. 42 | // @return 1 if the shelf exists, 0 otherwise. 43 | proc int shelf_exists( string $label ){ 44 | int $result = `shelfLayout -exists $label`; 45 | return $result; 46 | } 47 | 48 | 49 | // Removes all buttons from the given shelf. 50 | // Usage: clear_shelf( "XMesh" ); 51 | // 52 | // @param $label The name of the shelf from which to remove buttons. 53 | proc clear_shelf( string $label ){ 54 | string $shelfChildren[] = `shelfLayout -q -childArray $label`; 55 | for($child in $shelfChildren){ 56 | deleteUI $child; 57 | } 58 | } 59 | 60 | 61 | // Creates a new shelf with the given label. Includes a check to ensure the shelf name doesn't already exist. 62 | // Usage: int $result = create_shelf("XMesh"); 63 | // 64 | // @param $label The displayed name of the new shelf. 65 | // @return true if the shelf is created, false if it already exists. 66 | proc int create_shelf( string $label ){ 67 | if( !shelf_exists( $label ) ){ 68 | addNewShelfTab $label; 69 | return true; 70 | } else { 71 | return false; 72 | } 73 | } 74 | 75 | 76 | // Adds a button to the given shelf, or overwrites the button if it already exists (match is based on equivalent docTag). 77 | // Usage: create_shelf_button("XMesh", "Create XMesh Loader", "Create a new XMesh Loader node.", "python(\\\"import createXMeshLoader;reload(createXMeshLoader);createXMeshLoader.createXMeshLoader();\\\");", "XMeshLoader_icon.png", "XMeshLoader_doctag"); 78 | // 79 | // @param $parent The name/label of the shelf to add the button to. 80 | // @param $label The label for the command, used in Maya for managing the shelf. 81 | // @param $annotation Tooltip / status line text on mouseover. 82 | // @param $command The command to run when the button is clicked. 83 | // @param $image1 An image/icon to use for the button. 84 | // @param $doctag A unique label to attach to each button so it can be found later. 85 | // @return The fully qualified name of the button within Maya. 86 | proc string create_shelf_button( string $parent, string $label, string $annotation, string $command, string $image1, string $doctag ){ 87 | string $commandSuffix = "-parent \"" + $parent + "\" -label \"" + $label + "\" -annotation \"" + $annotation + "\" -command \"" + $command + "\" -docTag \"" + $doctag + "\""; 88 | if( size( $image1 ) > 0 ){ 89 | $commandSuffix += " -image1 \"" + $image1 + "\""; 90 | } 91 | 92 | // Check to see if the button already exists. Return early if it does to avoid creating duplicates. 93 | string $shelfChildren[] = `shelfLayout -q -childArray $parent`; 94 | for($child in $shelfChildren){ 95 | string $childTag = `shelfButton -query -docTag $child`; 96 | if( $childTag == $doctag ){ 97 | string $melCommand = "shelfButton -edit " + $commandSuffix + " " + $child; 98 | eval( $melCommand ); 99 | return $child; 100 | } 101 | } 102 | 103 | // If we didn't drop out early, then add the button to the shelf 104 | string $melCommand = "shelfButton " + $commandSuffix; 105 | string $buttonName = eval( $melCommand ); 106 | return $buttonName; 107 | } 108 | 109 | 110 | // Reutrns the name/label of the currently'selected shelf tab. 111 | // @return A string containing the name of the current shelf. 112 | proc string get_current_shelf(){ 113 | global string $gShelfTopLevel; 114 | string $shelfName = `tabLayout -q -selectTab $gShelfTopLevel`; 115 | return $shelfName; 116 | } 117 | 118 | 119 | // Switches the selected shelf in the interface to the specified label. 120 | // Usage: switch_to_shelf("XMesh"); 121 | // 122 | // @param $label The label of the shelf to select. 123 | proc switch_to_shelf( string $label ){ 124 | global string $gShelfTopLevel; 125 | tabLayout -e -selectTab $label $gShelfTopLevel; 126 | } 127 | 128 | 129 | 130 | // Creates the XMesh Loader menu item in the Create menu. 131 | // Wrapped in its own proc to reduce size of XMeshInit() and reduce unnecessary state in that general method. 132 | proc add_xmesh_loader_menu_item(){ 133 | ModCreateMenu mainCreateMenu; // Forces Maya to build the Create menu, so we append to it instead of overriding it. 134 | 135 | string $label = "XMesh Loader..."; 136 | string $parent = "mainCreateMenu"; 137 | string $command = "python(\"import createXMeshLoader; createXMeshLoader.createXMeshLoader();\");"; 138 | string $xmeshTag = "XMesh"; // Always create items with a unique tag so they can be identified and cleaned up later. 139 | 140 | append_to_menu( $label, $parent, $command, $xmeshTag ); 141 | } 142 | 143 | 144 | 145 | // Creates the XMesh shelf and buttons. 146 | proc create_xmesh_shelf(){ 147 | string $XMESH_SHELF_LABEL = "XMesh"; 148 | 149 | string $SHELF_BUTTON_LABEL_XMESH_LOADER = "XMesh Loader"; 150 | string $SHELF_BUTTON_LABEL_XMESH_SAVER = "XMesh Saver"; 151 | 152 | string $SHELF_BUTTON_ANNOTATION_XMESH_LOADER = "XMesh Loader: Create an XMesh Loader"; 153 | string $SHELF_BUTTON_ANNOTATION_XMESH_SAVER = "XMesh Saver: Show XMesh Saver dialog"; 154 | 155 | string $SHELF_BUTTON_COMMAND_XMESH_LOADER = "python(\\\"import createXMeshLoader; createXMeshLoader.createXMeshLoader();\\\");"; 156 | string $SHELF_BUTTON_COMMAND_XMESH_SAVER = "python(\\\"import showXMeshSaver; showXMeshSaver.showXMeshSaver();\\\");"; 157 | 158 | string $SHELF_BUTTON_IMAGE_XMESH_LOADER = "XMeshLoader_icon.png"; 159 | string $SHELF_BUTTON_IMAGE_XMESH_SAVER = "XMeshSaver_icon.png"; 160 | 161 | string $SHELF_BUTTON_DOCTAG_XMESH_LOADER = "XMeshLoader_doctag"; 162 | string $SHELF_BUTTON_DOCTAG_XMESH_SAVER = "XMeshSaver_doctag"; 163 | 164 | 165 | string $currentShelf = get_current_shelf(); 166 | if( shelf_exists( $XMESH_SHELF_LABEL ) ){ 167 | clear_shelf( $XMESH_SHELF_LABEL ); 168 | } else { 169 | create_shelf( $XMESH_SHELF_LABEL ); 170 | } 171 | 172 | create_shelf_button( $XMESH_SHELF_LABEL, 173 | $SHELF_BUTTON_LABEL_XMESH_LOADER, 174 | $SHELF_BUTTON_ANNOTATION_XMESH_LOADER, 175 | $SHELF_BUTTON_COMMAND_XMESH_LOADER, 176 | $SHELF_BUTTON_IMAGE_XMESH_LOADER, 177 | $SHELF_BUTTON_DOCTAG_XMESH_LOADER ); 178 | create_shelf_button( $XMESH_SHELF_LABEL, 179 | $SHELF_BUTTON_LABEL_XMESH_SAVER, 180 | $SHELF_BUTTON_ANNOTATION_XMESH_SAVER, 181 | $SHELF_BUTTON_COMMAND_XMESH_SAVER, 182 | $SHELF_BUTTON_IMAGE_XMESH_SAVER, 183 | $SHELF_BUTTON_DOCTAG_XMESH_SAVER ); 184 | 185 | switch_to_shelf( $currentShelf ); // Switch back to original shelf. 186 | } 187 | 188 | 189 | // Initialization code for the XMesh plugin intended to run when the plugin is loaded. 190 | global proc XMeshCreateUI(){ 191 | add_xmesh_loader_menu_item(); // "XMesh Loader" in the Create menu. 192 | create_xmesh_shelf(); // "XMesh" shelf tab with buttons for the XMesh functions. 193 | } 194 | -------------------------------------------------------------------------------- /src/material_utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #include 8 | 9 | #include "material_utils.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include "material_id_map.hpp" 23 | 24 | namespace { 25 | 26 | unsigned int get_instance_number( const MDagPath& dagPath ) { 27 | MStatus stat; 28 | 29 | bool isInstanced = dagPath.isInstanced( &stat ); 30 | if( !stat ) { 31 | throw std::runtime_error( "get_instance_number Error: error calling isInstanced()" ); 32 | } 33 | 34 | if( isInstanced ) { 35 | unsigned int instanceNumber = dagPath.instanceNumber( &stat ); 36 | if( !stat ) { 37 | throw std::runtime_error( "get_instance_number Error: error calling instanceNumber()" ); 38 | } 39 | return instanceNumber; 40 | } else { 41 | return 0; 42 | } 43 | } 44 | 45 | bool try_get_shading_engine_name( const MFnDependencyNode& shadingEngineObject, frantic::tstring& outName ) { 46 | MStatus stat; 47 | const frantic::tstring shadingEngineName = frantic::maya::from_maya_t( shadingEngineObject.name( &stat ) ); 48 | if( stat ) { 49 | outName = shadingEngineName; 50 | return true; 51 | } else { 52 | return false; 53 | } 54 | } 55 | 56 | bool try_get_surface_shader_name( const MFnDependencyNode& shadingEngineObject, frantic::tstring& outName ) { 57 | MStatus stat; 58 | 59 | MPlug plug( shadingEngineObject.findPlug( "surfaceShader", &stat ) ); 60 | 61 | if( !stat ) 62 | return false; 63 | 64 | MPlugArray connectionPlugs; 65 | bool isConnected = plug.connectedTo( connectionPlugs, true, false, &stat ); 66 | if( !stat ) 67 | return false; 68 | 69 | if( isConnected && connectionPlugs.length() == 1 ) { 70 | MFnDependencyNode shaderNode( connectionPlugs[0].node(), &stat ); 71 | if( !stat ) 72 | return false; 73 | const frantic::tstring surfaceShaderName = frantic::maya::from_maya_t( shaderNode.name( &stat ) ); 74 | if( !stat ) 75 | return false; 76 | outName = surfaceShaderName; 77 | return true; 78 | } 79 | 80 | return false; 81 | } 82 | 83 | boost::uint16_t get_material_id( material_id_map& materialIDMap, const MFnDependencyNode& shadingEngineObject ) { 84 | frantic::tstring materialName; 85 | if( try_get_shading_engine_name( shadingEngineObject, materialName ) ) { 86 | if( materialIDMap.has_material( materialName ) ) { 87 | return materialIDMap.get_material_id( materialName ); 88 | } 89 | } 90 | if( try_get_surface_shader_name( shadingEngineObject, materialName ) ) { 91 | return materialIDMap.get_material_id( materialName ); 92 | } 93 | return materialIDMap.get_undefined_material_id(); 94 | } 95 | 96 | } // anonymous namespace 97 | 98 | void create_material_id_channel( const MDagPath& dagPath, MFnMesh& fnMesh, frantic::geometry::polymesh3_ptr& mesh, 99 | material_id_map& materialIDMap ) { 100 | const frantic::tstring materialIDChannelName( _T("MaterialID") ); 101 | 102 | if( !mesh ) { 103 | throw std::runtime_error( "create_material_id_channel Error: mesh is NULL" ); 104 | } 105 | 106 | const unsigned int instanceNumber = get_instance_number( dagPath ); 107 | 108 | MObjectArray shaders; 109 | MIntArray indices; 110 | MStatus getConnectedShadersStat = fnMesh.getConnectedShaders( instanceNumber, shaders, indices ); 111 | 112 | const std::size_t faceCount = mesh->face_count(); 113 | 114 | frantic::graphics::raw_byte_buffer materialIDBuffer; 115 | materialIDBuffer.resize( faceCount * sizeof( boost::uint16_t ) ); 116 | boost::uint16_t* materialIDArray = reinterpret_cast( materialIDBuffer.begin() ); 117 | 118 | if( getConnectedShadersStat ) { 119 | if( indices.length() != faceCount ) { 120 | throw std::runtime_error( "create_material_id_channel Error: number of shader indices (" + 121 | boost::lexical_cast( indices.length() ) + 122 | ") does not match number of faces in the mesh (" + 123 | boost::lexical_cast( faceCount ) + ")" ); 124 | } 125 | 126 | std::vector shaderIndexToMaterialID; 127 | shaderIndexToMaterialID.reserve( shaders.length() ); 128 | for( unsigned i = 0, ie = shaders.length(); i < ie; ++i ) { 129 | shaderIndexToMaterialID.push_back( get_material_id( materialIDMap, shaders[i] ) ); 130 | } 131 | 132 | for( std::size_t faceIndex = 0; faceIndex < faceCount; ++faceIndex ) { 133 | const int shaderIndex = indices[static_cast( faceIndex )]; 134 | if( shaderIndex >= 0 && shaderIndex < static_cast( shaderIndexToMaterialID.size() ) ) { 135 | materialIDArray[faceIndex] = shaderIndexToMaterialID[shaderIndex]; 136 | } else { 137 | materialIDArray[faceIndex] = materialIDMap.get_undefined_material_id(); 138 | } 139 | } 140 | } else { 141 | const boost::uint16_t undefinedMaterialID = materialIDMap.get_undefined_material_id(); 142 | for( std::size_t faceIndex = 0; faceIndex < faceCount; ++faceIndex ) { 143 | materialIDArray[faceIndex] = undefinedMaterialID; 144 | } 145 | } 146 | 147 | mesh->add_face_channel( materialIDChannelName, frantic::channels::data_type_uint16, 1, materialIDBuffer ); 148 | } 149 | 150 | void parse_material_id_map( material_id_map& outMaterialIDMap, const frantic::tstring& s ) { 151 | material_id_map result; 152 | 153 | std::vector lines; 154 | boost::algorithm::split( lines, s, boost::is_any_of( _T(",") ) ); 155 | 156 | BOOST_FOREACH( frantic::tstring line, lines ) { 157 | boost::algorithm::trim( line ); 158 | 159 | if( boost::algorithm::starts_with( line, _T("=") ) ) 160 | throw std::runtime_error( "parse_material_id_map: entry must not begin with '='" ); 161 | 162 | const std::size_t assignmentCharacterCount = std::count( line.begin(), line.end(), _T( '=' ) ); 163 | if( assignmentCharacterCount != 1 ) 164 | throw std::runtime_error( "parse_material_id_map: entry must contain exactly one '=', but found " + 165 | boost::lexical_cast( assignmentCharacterCount ) + " instead." ); 166 | 167 | std::vector tokens; 168 | boost::algorithm::split( tokens, line, boost::is_any_of( "=" ) ); 169 | if( tokens.size() == 0 ) 170 | throw std::runtime_error( "parse_material_id_map: no tokens found in entry" ); 171 | 172 | BOOST_FOREACH( frantic::tstring& token, tokens ) { 173 | boost::algorithm::trim( token ); 174 | } 175 | 176 | int intID = 0; 177 | try { 178 | intID = boost::lexical_cast( tokens[0] ); 179 | } catch( boost::bad_lexical_cast& ) { 180 | throw std::runtime_error( 181 | "parse_material_id_map: left side of '=' must be an integer, but instead it is '" + 182 | frantic::strings::to_string( tokens[0] ) + "'" ); 183 | } 184 | 185 | boost::uint16_t id = 0; 186 | try { 187 | id = boost::numeric_cast( intID ); 188 | } catch( boost::bad_numeric_cast& e ) { 189 | throw std::runtime_error( std::string() + "parse_material_id_map: unable to convert id '" + 190 | frantic::strings::to_string( tokens[0] ) + "' to uint16: " + e.what() ); 191 | } 192 | 193 | frantic::tstring name; 194 | if( tokens.size() > 1 ) { 195 | name = tokens[1]; 196 | } 197 | 198 | if( name.empty() ) { 199 | if( result.has_undefined_material() ) { 200 | throw std::runtime_error( 201 | "parse_material_id_map: found empty (undefined) material name more than once" ); 202 | } 203 | result.insert_undefined_material( id ); 204 | } else { 205 | if( result.has_material( name ) ) { 206 | throw std::runtime_error( "parse_material_id_map: found material name '" + 207 | frantic::strings::to_string( name ) + "' more than once" ); 208 | } 209 | result.insert_material( id, name ); 210 | } 211 | } 212 | 213 | outMaterialIDMap.swap( result ); 214 | } 215 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to XMeshMY 2 | 3 | Thanks for your interest in contributing to XMeshMY! ❤️ 4 | 5 | This document describes how to set up a development environment and submit your contributions. Please read it carefully 6 | and let us know if it's not up-to-date (even better, submit a PR with your corrections ;-)). 7 | 8 | - [Prerequisites](#prerequisites) 9 | - [Building](#building) 10 | - [Testing](#testing) 11 | - [Publishing to Local Conan Cache](#publishing-to-local-conan-cache) 12 | - [Building Multiple Configurations](#building-multiple-configurations) 13 | - [Pull Requests](#pull-requests) 14 | - [Pull Request Checklist](#pull-request-checklist) 15 | - [Step 1: Open Issue](#step-1-open-issue) 16 | - [Step 2: Design (optional)](#step-2-design-optional) 17 | - [Step 3: Work your Magic](#step-3-work-your-magic) 18 | - [Step 4: Commit](#step-4-commit) 19 | - [Step 5: Pull Request](#step-5-pull-request) 20 | - [Step 6: Merge](#step-6-merge) 21 | 22 | ## Prerequisites 23 | 24 | You will need Python 3.10 or later and the C++ compiler for your platform installed before you are able to build XMeshMY. The compilers used for each platform are as follows: 25 | 26 | * Windows: Visual C++ 14.1 (Visual Studio 2017) or later 27 | * macOS: Clang 10.0 or later 28 | * Linux: GCC 7 or later 29 | 30 | You will then need to install Conan. You can do this by running: 31 | 32 | ```bash 33 | pip install conan conan_package_tools 34 | ``` 35 | 36 | Conan is a C++ package manager that is used to install the 3rd party dependencies. 37 | 38 | XMeshMY uses the C++17 standard. 39 | 40 | ## Dependencies 41 | 42 | You will need to build the following dependencies to your local Conan cache: 43 | 44 | * https://github.com/aws/thinkbox-cm-library 45 | * https://github.com/aws/thinkbox-library 46 | * https://github.com/aws/thinkbox-my-library 47 | * https://github.com/aws/thinkbox-xmesh 48 | 49 | Please see the instructions in the linked repositories for detailed instructions for building these packages. 50 | 51 | ## Building 52 | 53 | From the project root directory run the following commands to install the dependencies and build XMeshMY: 54 | 55 | ```bash 56 | conan install . --install-folder build 57 | conan build . --build-folder build 58 | ``` 59 | 60 | If you wish to generate a development environment without building the package immediately, you can add `--configure` to the `conan build` command. 61 | 62 | If you are using Windows, once run, you can open `build/XMeshMY.sln` in Visual Studio to use Visual Studio for development and debugging. 63 | 64 | ### Publishing to Local Conan Cache 65 | 66 | If you need to publish build artifacts to your local Conan cache manually, after completing the [building](#building) steps, you can run the following commands to package XMeshMY and publish it to your local Conan cache: 67 | 68 | ```bash 69 | conan package . --install-folder build --build-folder build --package-folder build/package 70 | conan export-pkg . --package-folder build/package 71 | ``` 72 | 73 | ### Building Multiple Configurations 74 | 75 | To quickly build all supported configurations on the current platform you can run: 76 | 77 | ``` 78 | python build.py 79 | ``` 80 | 81 | This will build the configurations and publish them to your local conan cache. You can add the `--dry-run` flag to preview the configurations that will be built without building them. 82 | 83 | ## Installation 84 | 85 | In order to run the XMesh plugin that you built, you will need to run perform the following setup on you machine. We will assume that you already 86 | have the correct version of Maya installed for the plugin you have built. 87 | 88 | __Warning__: The code in these files and directories will be executed when the plugin is run. As such it is crucial that the permissions of these files are set such that they cannot be written to without elevated privlidges. 89 | 90 | Create the following directory structure somewhere on your system: 91 | 92 | ``` 93 | Installation Root/ 94 | ├─ Maya####_x64/ 95 | │ ├─ icons/ 96 | │ ├─ plug-ins/ 97 | │ │ ├─ XMesh.mll 98 | │ ├─ scripts/ 99 | ├─ Legal/ 100 | │ ├─ third_party_licenses.txt 101 | ``` 102 | 103 | * The `####` in `Maya####_x64/` should be substituted for the Maya version you built the plugin for 104 | * `XMesh.mll` can be found in `build/Release` after it has been built using the above instructions 105 | * On Linux, the `.mll` extension will be replaced with `.so` and on macOS it will be replaced with `.bundle` 106 | * `third_party_licenses.txt` can be found in the project root directory after the plugin as been built 107 | * Copy the contents of the `icons/` directory in the project root directory to the `icons/` directory in the installation directory 108 | * Copy the contents of the `scripts/` directory in the project root directory to the `scripts/` directory in the installation directory 109 | 110 | Finally, in the `modules/` directory within your Maya installation, create a file called `XMesh.module` with the following contents: 111 | 112 | ``` 113 | + XMesh 1.0 /path/to/Maya####_x64 114 | ``` 115 | 116 | Where the path is substituted for the absolute path to the `Maya####_x64/` directory you created earlier. 117 | 118 | Once this is complete, you should be able to load XMeshMY as a plugin in Maya. You will need to repeat this process for any additional versions of Maya that you wish to use the plugin for. 119 | 120 | ### Pull Requests 121 | 122 | #### Pull Request Checklist 123 | 124 | - Testing 125 | - Unit test added (prefer not to modify an existing test, otherwise, it's probably a breaking change) 126 | - Title and Description 127 | - __Change type__: title prefixed with **fix**, **feat** and module name in parens, which will appear in changelog 128 | - __Title__: use lower-case and doesn't end with a period 129 | - __Breaking?__: last paragraph: "BREAKING CHANGE: " 130 | - __Issues__: Indicate issues fixed via: "**Fixes #xxx**" or "**Closes #xxx**" 131 | 132 | #### Step 1: Open Issue 133 | 134 | If there isn't one already, open an issue describing what you intend to contribute. It's useful to communicate in 135 | advance, because sometimes, someone is already working in this space, so maybe it's worth collaborating with them 136 | instead of duplicating the efforts. 137 | 138 | #### Step 2: Design (optional) 139 | 140 | In some cases, it is useful to seek for feedback by iterating on a design document. This is useful 141 | when you plan a big change or feature, or you want advice on what would be the best path forward. 142 | 143 | Sometimes, the GitHub issue is sufficient for such discussions, and can be sufficient to get 144 | clarity on what you plan to do. Sometimes, a design document would work better, so people can provide 145 | iterative feedback. 146 | 147 | In such cases, use the GitHub issue description to collect **requirements** and 148 | **use cases** for your feature. 149 | 150 | #### Step 3: Work your Magic 151 | 152 | Work your magic. Here are some guidelines: 153 | 154 | - Coding style: 155 | - Code should conform to the style defined in .clang-format 156 | - Every change requires a unit test 157 | - Try to maintain a single feature/bugfix per pull request. It's okay to introduce a little bit of housekeeping 158 | changes along the way, but try to avoid conflating multiple features. Eventually all these are going to go into a 159 | single commit, so you can use that to frame your scope. 160 | 161 | #### Step 4: Commit 162 | 163 | Create a commit with the proposed changes: 164 | 165 | - Commit title and message (and PR title and description) must adhere to [conventionalcommits](https://www.conventionalcommits.org). 166 | - The title must begin with `feat: title`, `fix: title`, `refactor: title` or 167 | `chore: title`. 168 | - Title should be lowercase. 169 | - No period at the end of the title. 170 | 171 | - Commit message should describe _motivation_. Think about your code reviewers and what information they need in 172 | order to understand what you did. If it's a big commit (hopefully not), try to provide some good entry points so 173 | it will be easier to follow. 174 | 175 | - Commit message should indicate which issues are fixed: `fixes #` or `closes #`. 176 | 177 | - Shout out to collaborators. 178 | 179 | - If not obvious (i.e. from unit tests), describe how you verified that your change works. 180 | 181 | - If this commit includes breaking changes, they must be listed at the end in the following format (notice how multiple breaking changes should be formatted): 182 | 183 | ``` 184 | BREAKING CHANGE: Description of what broke and how to achieve this behavior now 185 | - **module-name:** Another breaking change 186 | - **module-name:** Yet another breaking change 187 | ``` 188 | 189 | #### Step 5: Pull Request 190 | 191 | - Push to a personal GitHub fork. 192 | - Submit a Pull Request on GitHub. A reviewer will later be assigned by the maintainers. 193 | - Please follow the PR checklist written above. We trust our contributors to self-check, and this helps that process! 194 | - Discuss review comments and iterate until you get at least one "Approve". When iterating, push new commits to the 195 | same branch. Usually all these are going to be squashed when you merge to master. The commit messages should be hints 196 | for you when you finalize your merge commit message. 197 | - Make sure to update the PR title/description if things change. The PR title/description are going to be used as the 198 | commit title/message and will appear in the CHANGELOG, so maintain them all the way throughout the process. 199 | 200 | #### Step 6: Merge 201 | 202 | - Make sure your PR builds successfully 203 | - Once approved and tested, a maintainer will squash-merge to master and will use your PR title/description as the 204 | commit message. 205 | -------------------------------------------------------------------------------- /scripts/XMeshDeadlineUtils.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Scripted utilities for Maya XMesh Saver Deadline Submission 5 | 6 | ############################################################################### 7 | # Deadline Utility Functions 8 | ############################################################################### 9 | 10 | from __future__ import print_function 11 | 12 | import errno 13 | import maya.cmds 14 | import maya.mel 15 | import os 16 | import sys 17 | import subprocess 18 | import traceback 19 | 20 | from XMeshUtils import * 21 | 22 | ########## 23 | #Strings 24 | ########## 25 | 26 | _s = { 27 | "noDeadlineMsg" : "DEADLINE WAS NOT DETECTED ON YOUR SYSTEM!\nNOTE: Deadline provides a FREE MODE supporting up to 2 NODES without limitations.\nYou could install Deadline in FREE MODE to take advantage of network saving.\nYou can download Deadline from the link below:\n", 28 | "downloadAddress" : "http://www.thinkboxsoftware.com/deadline-downloads/", 29 | "detectionErrorMsg" : "\nAn error occurred while attempting to detect Deadline. Please see the Script Editor for more information.\nPlease contact us at support@thinkboxsoftware.com, and send us a copy of the error message that\nappears in the Script Editor\n", 30 | } 31 | 32 | class DeadlineError(Exception): 33 | pass 34 | 35 | class DeadlineNotFoundError(Exception): 36 | pass 37 | 38 | class DeadlineInvokeError(Exception): 39 | pass 40 | 41 | class deadlineSettings(): 42 | def __init__(self): 43 | self.d = { 44 | 'Plugin' : 'MayaBatch', 45 | 'Name' : 'XMeshMayaScriptJob', 46 | 'Comment' : '', 47 | 'MachineLimit' : 0, 48 | 'Priority' : 50, 49 | 'Frames' : '1', 50 | 'OutputDirectory0' : '' 51 | } 52 | self.p = { 53 | 'ScriptJob' : True, 54 | 'ScriptFilename' : 'XMeshSaverParams.py', 55 | 'ScriptData' : 'XMeshSaverParams.py', 56 | 'ProjectPath' : getProjectPath(), 57 | 'IncludeSceneFile' : False, 58 | 'StrictErrorChecking' : True, 59 | 'Version' : getMayaVersion(), 60 | 'Build' : "none", 61 | 62 | } 63 | 64 | def getDeadlineSettings(self): 65 | return os.linesep.join('%s=%s' % (k, v) for (k, v) in self.d.items()) 66 | def getMayaPluginSettings(self): 67 | return os.linesep.join('%s=%s' % (k, v) for (k, v) in self.p.items()) 68 | 69 | 70 | def isInstalled(): 71 | try: 72 | safeDeadlineCommand("About") 73 | return True 74 | except DeadlineError as e: 75 | print("// XMesh: Deadline error:", str(e)) 76 | return False 77 | except DeadlineNotFoundError: 78 | return False 79 | except DeadlineInvokeError as e: 80 | raise 81 | 82 | 83 | # Verifies the slashes are consistent in a given filename. A useful utility function. 84 | def checkSlashes(fileName): 85 | newResult = fileName.replace('\\', os.sep) 86 | newResult = newResult.replace('/', os.sep) 87 | return newResult 88 | 89 | # Returns True if the path is on C:, D:, or E: 90 | def isLocalDrive(path): 91 | if len(path) > 0: 92 | if path.startswith(('C', 'c', 'D', 'd', 'E', 'e')): 93 | return True 94 | return False 95 | 96 | def safeDeadlineCommand(command): 97 | result = '' 98 | 99 | creationflags = 0 100 | 101 | #windows 102 | if os.name is 'nt': 103 | # still show top-level windows, but don't show a console window 104 | CREATE_NO_WINDOW = 0x08000000 #MSDN process creation flag 105 | creationflags = CREATE_NO_WINDOW 106 | 107 | dlc = 'deadlinecommand.exe' 108 | 109 | deadlineCommandPath = os.getenv('DEADLINE_PATH', os.getenv('programfiles', '') + '\\Thinkbox\\Deadline10\\bin') 110 | if not os.path.isdir(deadlineCommandPath): 111 | #deadline 5 112 | deadlineCommandPath = os.getenv('programfiles', '') + '\\Thinkbox\\Deadline\\bin' 113 | elif os.name is 'posix': 114 | dlc = 'deadlinecommand' 115 | if sys.platform == 'darwin': 116 | #mac 117 | deadlineCommandPath = '/Applications/Thinkbox/Deadline10/Resources' 118 | deadlineBinPath = '/Users/Shared/Thinkbox/DEADLINE_PATH' 119 | if os.path.isfile(deadlineBinPath): 120 | with open(deadlineBinPath, 'r') as f: 121 | deadlineBin = f.read() 122 | deadlineCommandPath = deadlineBin.strip() 123 | 124 | if not os.path.isdir(deadlineCommandPath): 125 | deadlineCommandPath = '/Applications/Deadline/Resources/bin' 126 | else: 127 | #linux 128 | deadlineCommandPath = os.getenv('DEADLINE_PATH', '/opt/Thinkbox/Deadline10/bin') 129 | if not os.path.isdir(deadlineCommandPath): 130 | deadlineCommandPath = '/usr/local/Thinkbox/Deadline/bin' 131 | else: 132 | raise RuntimeError('Unknown operating system. Unable to detect Deadline.') 133 | 134 | if not os.path.isdir(deadlineCommandPath): 135 | deadlineCommand = dlc 136 | else: 137 | deadlineCommand = deadlineCommandPath + os.sep + dlc 138 | 139 | if isinstance(command, str): 140 | command = [command] 141 | 142 | args = [deadlineCommand] + command 143 | 144 | print("// XMesh: Deadline command:", " ".join(args)) 145 | 146 | try: 147 | proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=creationflags) 148 | result, err = proc.communicate() 149 | proc.stdin.close() 150 | if proc.returncode != 0: 151 | raise DeadlineError(err) 152 | except OSError as e: 153 | if e.errno == errno.ENOENT: 154 | raise DeadlineNotFoundError() 155 | elif e.errno == errno.EINVAL: 156 | print("// XMesh: Error detecting Deadline:") 157 | traceback.print_exc() 158 | raise DeadlineInvokeError() 159 | else: 160 | raise 161 | 162 | return result 163 | 164 | # Take the parameters specified in the saver window, bundle them into a file 'XMeshSaverParams.py' 165 | # and initiate the Deadline script command, passing in the file as well. 166 | def saveOverDeadline(xmeshSettings, deadlineSettings): 167 | print('// XMesh: Submitting job to Deadline...') 168 | 169 | outputPath = os.path.dirname(xmeshSettings.fullPath) 170 | projectPath = getProjectPath() 171 | 172 | # Handle Maya Scene File 173 | sceneFilePath = getSceneName() 174 | if deadlineSettings.p['IncludeSceneFile'] == True: 175 | submitMayaSceneFile = True 176 | else: 177 | deadlineSettings.p['SceneFile'] = sceneFilePath 178 | submitMayaSceneFile = False 179 | 180 | if len(maya.cmds.file(q=True, sn=True)) == 0: 181 | maya.cmds.confirmDialog(title='Scene Not Saved', 182 | message='The current scene must be saved before being submitted to Deadline.', 183 | button='OK') 184 | return 185 | 186 | # Detect if output folder exists 187 | if not os.path.isdir(outputPath): 188 | answer = maya.cmds.confirmDialog( title='Create Directory?', 189 | message='The specified directory does not exist. Would you like to create it?', 190 | button=['Create', 'Cancel'], 191 | defaultButton='Create', 192 | cancelButton='Cancel', 193 | dismissString='Cancel', 194 | messageAlign='left') 195 | if answer == 'Create': 196 | os.makedirs(outputPath) 197 | else: 198 | return 199 | 200 | #error checking for Local Drive References and Missing Files 201 | message = '' 202 | if(not submitMayaSceneFile and isLocalDrive(sceneFilePath)): 203 | message = message + 'Maya scene file, \"' + sceneFilePath + '\" is on a local drive and is not being submitted.\nWorkers will not be able to access the scene file.\n\n' 204 | if not os.path.isdir(outputPath): 205 | message = message + 'Output Path, \"' + outputPath + '\" does not exist! The final files may be lost!\n\n' 206 | elif isLocalDrive(outputPath): 207 | message = message + 'Output Path, \"' + outputPath + '\" is on a local drive.\nWorkers will not be able to write files to this drive.\n\n' 208 | elif len(outputPath) == 0: 209 | message = message + 'The output path is blank! The final files will be lost!\n\n' 210 | 211 | # Display the error messages 212 | if len(message) > 0: 213 | message = message + '\nAre you sure you want to submit this job?' 214 | result = maya.cmds.confirmDialog(title='Confirm', message=(message), button=['Yes','No'], defaultButton='Yes', cancelButton='No', dismissString='No') 215 | if result == 'No': 216 | return 217 | 218 | # Save the scene if changes have been made. 219 | if maya.cmds.file(query=True, modified=True): 220 | print('// XMesh: Maya scene file has been modified, saving file') 221 | maya.cmds.file(save=True) 222 | 223 | #Temp Folder 224 | tempDir = maya.cmds.internalVar(userTmpDir=True) 225 | 226 | #Info Job 227 | submitInfoJob = checkSlashes(tempDir + 'maya_job_info.job') 228 | with open(submitInfoJob, 'w') as f: 229 | f.write(deadlineSettings.getDeadlineSettings()) 230 | 231 | #Maya Plugin Job 232 | submitPluginJob = checkSlashes(tempDir + 'maya_plugin_info.job') 233 | with open(submitPluginJob, 'w') as f: 234 | f.write(deadlineSettings.getMayaPluginSettings()) 235 | 236 | #Custom XMeshSaverParams.py 237 | XMeshSaverParamsFile = checkSlashes(tempDir + 'XMeshSaverParams.py') 238 | with open(XMeshSaverParamsFile, 'w') as f: 239 | f.write(xmeshSettings.pout('xmeshsettings')) 240 | 241 | # Job submission 242 | submissionCommand = [ checkSlashes(submitInfoJob), 243 | checkSlashes(submitPluginJob), 244 | checkSlashes(XMeshSaverParamsFile) 245 | ] 246 | 247 | if submitMayaSceneFile: 248 | submissionCommand.insert(2, sceneFilePath) 249 | 250 | with waitCursor(): 251 | try: 252 | submitResults = safeDeadlineCommand(submissionCommand) 253 | icon = '' 254 | except DeadlineError as e: 255 | submitResults = str(e) 256 | icon = 'critical' 257 | 258 | maya.cmds.confirmDialog(title='Submission Results', message=submitResults, button='OK', icon=icon) 259 | print("// XMesh: Deadline submission results:", submitResults) 260 | 261 | def getMayaVersion(): 262 | vers = maya.cmds.about(version=True) 263 | vers = vers.partition(' ')[0] 264 | return vers 265 | 266 | def getSceneName(): 267 | return maya.cmds.file(q=True, sn=True) 268 | 269 | def getProjectPath(): 270 | return maya.cmds.workspace(q=True, fn=True) 271 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /scripts/createXMeshLoader.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Scripted utilities for Maya XMesh Loader 5 | 6 | import maya.cmds 7 | import os.path 8 | import re 9 | import io 10 | import string 11 | import platform 12 | import XMeshMaterialUtils 13 | 14 | def createXMeshLoader(): 15 | if not maya.cmds.pluginInfo('XMesh', query=True, loaded=True): 16 | if platform.system() in ['Linux', 'Darwin']: 17 | # avoid calling loadPlugin for now due to crashes observed on these platforms 18 | maya.cmds.confirmDialog(title='XMesh', m='XMesh is not loaded.\n\nPlease ensure that XMesh is loaded by using the Plug-in Manager.\n\nYou can access the Plug-in Manager by using the Window > Settings/Preferences > Plug-in Manager menu item.') 19 | return 20 | else: 21 | results = maya.cmds.loadPlugin('XMesh') 22 | if results is None or len(results) == 0: 23 | raise Exception('Could not load XMesh plugin. Cannot proceed.') 24 | return 25 | 26 | xmeshFilter = 'XMesh Mel Scenes, XMesh Files (*.mel *.xmesh);;XMesh Mel Scenes (*.mel);;XMesh Files (*.xmesh)' 27 | 28 | maya.mel.eval('source XMeshLoadDialog.mel') 29 | path = maya.cmds.fileDialog2(optionsUICreate='xmeshLoadDialog_setupParams', optionsUICommit='xmeshLoadDialog_commitParams', fileFilter=xmeshFilter, dialogStyle=2, fileMode=1) 30 | 31 | displayPercentage = maya.mel.eval('$tempVal = $xmesh_displayPercent') 32 | displayStyle = maya.mel.eval('$tempVal = $xmesh_displayStyle') 33 | 34 | if path is not None and len(path) > 0: 35 | ext = os.path.splitext(path[0])[1].lower() 36 | if ext == '.mel': 37 | fn = path[0] 38 | with open(fn, 'r') as f: 39 | mel_data = f.read() 40 | maya.mel.eval(mel_data) 41 | else: 42 | createXMeshLoaderSub(path[0], displayStyle=displayStyle, displayPercentage=displayPercentage) 43 | # the file dialog returns an array of strings, 44 | # the first of which is the filepath of the selected file 45 | 46 | def createXMeshLoaderFromPath(path): 47 | if not maya.cmds.pluginInfo('XMesh', query=True, loaded=True): 48 | if platform.system() in ['Linux', 'Darwin']: 49 | # avoid calling loadPlugin for now due to crashes observed on these platforms 50 | maya.cmds.confirmDialog(title='XMesh', m='XMesh is not loaded.\n\nPlease ensure that XMesh is loaded by using the Plug-in Manager.\n\nYou can access the Plug-in Manager by using the Window > Settings/Preferences > Plug-in Manager menu item.') 51 | return 52 | else: 53 | results = maya.cmds.loadPlugin('XMesh') 54 | if results is None or len(results) == 0: 55 | raise Exception('Could not load XMesh plugin. Cannot proceed.') 56 | return 57 | 58 | createXMeshLoaderSub(path) 59 | 60 | # All of this is the original script required to create the graph in Maya 61 | def createXMeshLoaderSub(fileName, **kwargs): 62 | displayStyle = None 63 | displayPercentage = None 64 | for (key, value) in kwargs.items(): 65 | if key == 'displayStyle': 66 | displayStyle = value 67 | elif key == 'displayPercentage': 68 | displayPercentage = value 69 | else: 70 | raise TypeError("createXMeshLoaderSub() got an unexpected keyword argument '" + key + "'") 71 | 72 | baseName = 'xmesh_' + stripFrameNumber(os.path.splitext(os.path.split(fileName)[1])[0]) 73 | xmeshTransform = maya.cmds.createNode('transform', name=baseName+"Transform#") 74 | xmeshShape = maya.cmds.createNode('mesh', parent=xmeshTransform, name=baseName+"Shape#") 75 | maya.cmds.setAttr(xmeshShape+'.motionVectorColorSet', 'velocityPV', type='string') 76 | xmesh = maya.cmds.createNode('sequenceXMesh', parent=xmeshTransform, name=baseName+"#") 77 | maya.cmds.connectAttr('time1.outTime',xmesh+'.inTime') 78 | maya.cmds.connectAttr(xmesh+'.outMesh',xmeshShape+'.inMesh') 79 | if displayStyle is not None: 80 | maya.cmds.setAttr(xmesh + '.inDisplayStyle', displayStyle ) 81 | if displayPercentage is not None: 82 | maya.cmds.setAttr(xmesh + '.inDisplayPercent', displayPercentage ) 83 | loadXMeshSequence(fileName, xmesh) 84 | maya.cmds.select(xmeshShape) 85 | maya.cmds.polyColorSet(query=True, allColorSets=True) 86 | maya.cmds.select(xmesh) 87 | 88 | def sequenceXMeshGetMeshNode(xmeshNodeName): 89 | meshNodeAttr = maya.cmds.connectionInfo(xmeshNodeName + ".outMesh", destinationFromSource=True) 90 | if len(meshNodeAttr) > 0: 91 | return meshNodeAttr[0].split(".")[0] 92 | else: 93 | return None 94 | 95 | # This locates the name of an existing shading group based on the 96 | # name of an existing shader. Note that this will grab only the one 97 | # such shading group (multiple shading groups can be attached to a single shader) 98 | def shadingGroupFromShader(shaderName): 99 | shadingGroups = maya.cmds.listConnections(shaderName + ".outColor") 100 | if shadingGroups != None and len(shadingGroups) > 0: 101 | return shadingGroups[-1] 102 | else: 103 | return None 104 | 105 | # This method will remove _all_ shaders and clean up _all_ groupId nodes attached to any given mesh node 106 | # please call this before trying to assign materials to an xmesh node 107 | def disconnectAllShaders(xmeshShapeNodeName): 108 | destinationConnections = maya.cmds.listConnections(xmeshShapeNodeName, type="shadingEngine", destination=True, source=False, connections=True, plugs=True) 109 | if destinationConnections != None: 110 | for i in range(0, len(destinationConnections), 2): 111 | maya.cmds.disconnectAttr(destinationConnections[i], destinationConnections[i+1]) 112 | sourceConnections = maya.cmds.listConnections(xmeshShapeNodeName, type="shadingEngine", destination=False, source=True, connections=True, plugs=True) 113 | if sourceConnections != None: 114 | for i in range(0, len(sourceConnections), 2): 115 | maya.cmds.disconnectAttr(sourceConnections[i+1], sourceConnections[i]) 116 | groupNodes = maya.cmds.listConnections(xmeshShapeNodeName, type="groupId", destination=False, source=True, connections=False, plugs=False) 117 | if groupNodes != None: 118 | for node in groupNodes: 119 | maya.cmds.delete(node) 120 | 121 | def looksLikeSingleFile(filename): 122 | root = os.path.splitext(filename)[0] 123 | if len(root) > 0 and root[-1].isdigit(): 124 | return False 125 | return True 126 | 127 | def stripFrameNumber(s): 128 | gotDecimalPoint = False 129 | while len(s): 130 | if s[-1].isdigit(): 131 | s = s.rstrip(string.digits) 132 | elif s[-1] == '#': 133 | s = s.rstrip('#') 134 | elif s[-1] == '-': 135 | return s[:-1] 136 | elif s[-1] == ',': 137 | if gotDecimalPoint: 138 | return s 139 | else: 140 | gotDecimalPoint = True 141 | s = s[:-1] 142 | else: 143 | return s 144 | 145 | def addXMeshDefaultRenderGlobalsCommands(): 146 | def addCommand(attribute, command): 147 | s = maya.cmds.getAttr('defaultRenderGlobals.' + attribute) 148 | if s is None: 149 | s = '' 150 | if len(s) > 0 and re.match('.*[};][ \t\n\r]*$', s) is None: 151 | s += ';' 152 | if s.find(command) == -1: 153 | s += 'if(`exists ' + command + '`){' + command + ';}' 154 | maya.cmds.setAttr('defaultRenderGlobals.' + attribute, s, type='string') 155 | addCommand('preMel', 'xmeshPreRender') 156 | addCommand('postMel', 'xmeshPostRender') 157 | 158 | def loadXMeshSequence(fileName, xmeshNodeName): 159 | maya.cmds.setAttr(xmeshNodeName+'.seqPath', fileName, type='string') 160 | 161 | if looksLikeSingleFile(fileName): 162 | maya.cmds.setAttr(xmeshNodeName+'.inLoadingMode', 0) 163 | else: 164 | syncXMeshFrameRange(xmeshNodeName) 165 | 166 | addXMeshDefaultRenderGlobalsCommands() 167 | 168 | #Material Assignment 169 | connectedShader = False 170 | xmeshShapeNodeName = sequenceXMeshGetMeshNode(xmeshNodeName) 171 | 172 | materialIDFilename = XMeshMaterialUtils.getMaterialIDMapFilename(fileName) 173 | if os.path.exists(materialIDFilename): 174 | # we have to disconnect any existing shaders before assigning any new ones, otherwise things will not work out very nicely 175 | disconnectAllShaders(xmeshShapeNodeName) 176 | shadingEngineList = maya.cmds.ls(type="shadingEngine") 177 | materialsList = maya.cmds.ls(materials=True) 178 | 179 | groupNumber = 0 180 | materialIDMap = XMeshMaterialUtils.MaterialIDMap.readFromFile(materialIDFilename) 181 | for materialID in materialIDMap.getIDList(): 182 | materialName = materialIDMap.getMaterialName(materialID) 183 | 184 | if materialName is None or materialName == '' or materialName == 'undefined': 185 | continue 186 | 187 | if materialName not in shadingEngineList and materialName not in materialsList: 188 | materialName = XMeshMaterialUtils.cleanedMaterialName(materialName) 189 | 190 | materialNameSG = materialName + 'SG' 191 | 192 | shadingGroupName = None 193 | materialExists = materialName in materialsList 194 | if materialName in shadingEngineList: 195 | shadingGroupName = materialName 196 | elif materialNameSG in shadingEngineList: 197 | shadingGroupName = materialNameSG 198 | elif materialExists: 199 | shadingGroupName = shadingGroupFromShader(materialName) 200 | 201 | # if no shading group could be found, create a default one and attatch this shader to it 202 | # TODO: check if more than one default material needs to be made. 203 | if shadingGroupName is None: 204 | shadingGroupName = maya.cmds.sets(renderable=True, empty=True, noSurfaceShader=True, name=materialNameSG) 205 | if shadingGroupName != materialNameSG: 206 | shadingGroupName = maya.cmds.rename(shadingGroupName, xmeshNodeName + "MaterialSet_" + materialName) 207 | 208 | # update shadingEngineList to include the shader we just added 209 | shadingEngineList = maya.cmds.ls(type="shadingEngine") 210 | 211 | if materialExists: 212 | maya.cmds.connectAttr(materialName+".outColor", shadingGroupName+".surfaceShader") 213 | else: 214 | defaultSurfaceShader = maya.cmds.connectionInfo('initialShadingGroup.surfaceShader', sourceFromDestination=True) 215 | if defaultSurfaceShader: 216 | maya.cmds.connectAttr(defaultSurfaceShader, shadingGroupName+".surfaceShader") 217 | 218 | groupNode = maya.cmds.createNode("groupId") 219 | if xmeshShapeNodeName is not None: 220 | maya.cmds.connectAttr(groupNode+".groupId", xmeshNodeName+".groupIds["+str(materialID)+"]", force=True) 221 | maya.cmds.connectAttr(groupNode+".message", shadingGroupName+".groupNodes", nextAvailable=True, force=True) 222 | maya.cmds.connectAttr(xmeshShapeNodeName+".instObjGroups.objectGroups["+str(groupNumber)+"]", shadingGroupName+".dagSetMembers", nextAvailable=True, force=True) 223 | maya.cmds.connectAttr(groupNode+".groupId", xmeshShapeNodeName+".instObjGroups.objectGroups["+str(groupNumber)+"].objectGroupId", force=True) 224 | maya.cmds.connectAttr(shadingGroupName+".memberWireframeColor", xmeshShapeNodeName+".instObjGroups.objectGroups["+str(groupNumber)+"].objectGrpColor", force=True) 225 | connectedShader = True 226 | groupNumber += 1 227 | 228 | if not connectedShader: 229 | maya.cmds.sets(xmeshShapeNodeName, edit=True, forceElement="initialShadingGroup") 230 | 231 | def syncXMeshFrameRange(xmeshNodeName): 232 | minFile = maya.cmds.getAttr(xmeshNodeName + ".outMinimumAvailableFileIndex") 233 | maxFile = maya.cmds.getAttr(xmeshNodeName + ".outMaximumAvailableFileIndex") 234 | maya.cmds.setAttr(xmeshNodeName + ".inUseCustomRange", True) 235 | maya.cmds.setAttr(xmeshNodeName + ".inCustomRangeStart", minFile) 236 | maya.cmds.setAttr(xmeshNodeName + ".inCustomRangeEnd", maxFile) 237 | -------------------------------------------------------------------------------- /scripts/AEsequenceXMeshTemplate.mel: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //////////////////////////////////// 5 | // Attribute Editor Template for the sequenceXMesh Node 6 | /////////////////////////////////// 7 | 8 | source showXMeshAbout; 9 | source showXMeshHelp; 10 | 11 | global proc AEsequenceXMeshUpdateEnables( string $nodeName ) 12 | { 13 | if (`objExists $nodeName`) 14 | { 15 | int $autoProxyPath = getAttr($nodeName + ".inAutoProxyPath"); 16 | if (`control -exists SequenceXMeshFileProxySequenceText`) 17 | text -edit -enable (!$autoProxyPath) SequenceXMeshFileProxySequenceText; 18 | if (`control -exists SequenceXMeshFileProxySequenceTextField`) 19 | textField -edit -enable (!$autoProxyPath) SequenceXMeshFileProxySequenceTextField; 20 | if (`control -exists SequenceXMeshFileProxySequenceButton`) 21 | symbolButton -edit -enable (!$autoProxyPath) SequenceXMeshFileProxySequenceButton; 22 | 23 | int $loadingMode = getAttr($nodeName + ".inLoadingMode"); 24 | int $loadingSequence = ($loadingMode > 1); 25 | int $enablePlaybackGraph = getAttr($nodeName + ".inEnablePlaybackGraph"); 26 | int $useCustomRange = getAttr($nodeName + ".inUseCustomRange"); 27 | 28 | editorTemplate -dimControl $nodeName "inEnablePlaybackGraph" (!$loadingSequence); 29 | editorTemplate -dimControl $nodeName "inPlaybackGraph" (!($loadingSequence && $enablePlaybackGraph)); 30 | editorTemplate -dimControl $nodeName "inFrameOffset" (!$loadingSequence); 31 | if (`control -exists SequenceXMeshSyncFrameRangeButton`) 32 | button -e -enable $loadingSequence SequenceXMeshSyncFrameRangeButton; 33 | editorTemplate -dimControl $nodeName "inUseCustomRange" (!$loadingSequence); 34 | editorTemplate -dimControl $nodeName "inCustomRangeStart" (!($loadingSequence && $useCustomRange)); 35 | editorTemplate -dimControl $nodeName "inCustomRangeEnd" (!($loadingSequence && $useCustomRange)); 36 | editorTemplate -dimControl $nodeName "inCustomRangeStartClampMode" (!($loadingSequence && $useCustomRange)); 37 | editorTemplate -dimControl $nodeName "inCustomRangeEndClampMode" (!($loadingSequence && $useCustomRange)); 38 | 39 | int $displayStyle = getAttr($nodeName + ".inDisplayStyle"); 40 | editorTemplate -dimControl $nodeName "inDisplayPercent" (!($displayStyle == 3 || $displayStyle == 4)); 41 | } 42 | } 43 | 44 | global proc AEsequenceXMeshTemplate( string $nodeName ) 45 | { 46 | editorTemplate -beginScrollLayout; 47 | 48 | editorTemplate -beginLayout "Help" -collapse 1; 49 | editorTemplate -callCustom "sequenceXMeshCreateVersionLabel" "sequenceXMeshUpdateVersionLabel"; 50 | editorTemplate -callCustom "sequenceXMeshCreateHelpButton" "sequenceXMeshUpdateHelpButton"; 51 | editorTemplate -callCustom "sequenceXMeshCreateAboutButton" "sequenceXMeshUpdateAboutButton"; 52 | editorTemplate -endLayout; 53 | 54 | editorTemplate -beginLayout "Files" -collapse 0; 55 | editorTemplate -callCustom "AEsequenceXMesh_seqPath_new" "AEsequenceXMesh_seqPath_replace" "seqPath"; 56 | editorTemplate -label "Automatic Proxy Path" -addControl "inAutoProxyPath" "AEsequenceXMeshUpdateEnables"; 57 | editorTemplate -callCustom "AEsequenceXMesh_seqProxyPath_new" "AEsequenceXMesh_seqProxyPath_replace" "seqProxyPath"; 58 | editorTemplate -endLayout; 59 | 60 | editorTemplate -beginLayout "Timing" -collapse 0; 61 | editorTemplate -label "Loading Mode:" -addControl "inLoadingMode" "AEsequenceXMeshUpdateEnables"; 62 | editorTemplate -label "Enable Playback Graph" -addControl "inEnablePlaybackGraph" "AEsequenceXMeshUpdateEnables"; 63 | editorTemplate -label "Playback Graph:" -addControl "inPlaybackGraph"; 64 | editorTemplate -label "Frame Offset:" -addControl "inFrameOffset"; 65 | // 66 | editorTemplate -callCustom "sequenceXMeshCreateSyncFrameRangeButton" "sequenceXMeshUpdateSyncFrameRangeButton" "inUseCustomRange"; 67 | editorTemplate -label "Limit to Custom Range" -addControl "inUseCustomRange" "AEsequenceXMeshUpdateEnables"; 68 | editorTemplate -label "Range Start:" -addControl "inCustomRangeStart"; 69 | editorTemplate -label "Range End:" -addControl "inCustomRangeEnd"; 70 | editorTemplate -label "Range Start Mode:" -addControl "inCustomRangeStartClampMode"; 71 | editorTemplate -label "Range End Mode:" -addControl "inCustomRangeEndClampMode"; 72 | editorTemplate -endLayout; 73 | 74 | editorTemplate -beginLayout "Viewport" -collapse 0; 75 | editorTemplate -label "Viewport Source:" -addControl "inViewportSource"; 76 | editorTemplate -label "Display:" -addControl "inDisplayStyle" "AEsequenceXMeshUpdateEnables"; 77 | editorTemplate -label "Percent:" -addControl "inDisplayPercent"; 78 | editorTemplate -endLayout; 79 | 80 | editorTemplate -beginLayout "Render" -collapse 0; 81 | editorTemplate -label "Render Source:" -addControl "inRenderSource"; 82 | editorTemplate -endLayout; 83 | 84 | //editorTemplate -beginLayout "Unit Settings" -collapse 0; 85 | // editorTemplate -label "Length Unit" -addControl "inLengthUnit"; 86 | // editorTemplate -label "Custom Scale" -addControl "inCustomScale"; 87 | //editorTemplate -endLayout; 88 | 89 | // Unless an attribute is manually supressed, maya will automatically fill in all remaining attributes at the bottom of the attribute editor panel. 90 | // This was generated from a call to `listAttr $nodeName`; 91 | string $toSuppress[] = { 92 | "message", "caching", "frozen", "isHistoricallyInteresting", "nodeState", "binMembership", 93 | "hyperLayout", "isCollapsed", "blackBox", "borderConnections", "isHierarchicalConnection", 94 | "publishedNodeInfo", "publishedNodeInfo.publishedNode", 95 | "publishedNodeInfo.isHierarchicalNode", "publishedNodeInfo.publishedNodeType", 96 | "rmbCommand", "templateName", "templatePath", "viewName", "iconName", "viewMode", 97 | "templateVersion", "uiTreatment", "customTreatment", "creator", "creationDate", 98 | "containerType", "boundingBox", "boundingBoxMin", "boundingBoxMinX", "boundingBoxMinY", 99 | "boundingBoxMinZ", "boundingBoxMax", "boundingBoxMaxX", "boundingBoxMaxY", 100 | "boundingBoxMaxZ", "boundingBoxSize", "boundingBoxSizeX", "boundingBoxSizeY", 101 | "boundingBoxSizeZ", "center", "boundingBoxCenterX", "boundingBoxCenterY", 102 | "boundingBoxCenterZ", "matrix", "inverseMatrix", "worldMatrix", "worldInverseMatrix", 103 | "parentMatrix", "parentInverseMatrix", "visibility", "intermediateObject", "template", 104 | "ghosting", "instObjGroups", "instObjGroups.objectGroups", 105 | "instObjGroups.objectGroups.objectGrpCompList", "instObjGroups.objectGroups.objectGroupId", 106 | "instObjGroups.objectGroups.objectGrpColor", "objectColorRGB", "objectColorR", 107 | "objectColorG", "objectColorB", "wireColorRGB", "wireColorR", "wireColorG", "wireColorB", 108 | "useObjectColor", "objectColor", "drawOverride", "overrideDisplayType", 109 | "overrideLevelOfDetail", "overrideShading", "overrideTexturing", "overridePlayback", 110 | "overrideEnabled", "overrideVisibility", "hideOnPlayback", "overrideRGBColors", 111 | "overrideColor", "overrideColorRGB", "overrideColorR", "overrideColorG", "overrideColorB", 112 | "lodVisibility", "selectionChildHighlighting", "renderInfo", "identification", 113 | "layerRenderable", "layerOverrideColor", "renderLayerInfo", 114 | "renderLayerInfo.renderLayerId", "renderLayerInfo.renderLayerRenderable", 115 | "renderLayerInfo.renderLayerColor", "ghostingControl", "ghostCustomSteps", "ghostPreSteps", 116 | "ghostPostSteps", "ghostStepSize", "ghostFrames", "ghostColorPreA", "ghostColorPre", 117 | "ghostColorPreR", "ghostColorPreG", "ghostColorPreB", "ghostColorPostA", "ghostColorPost", 118 | "ghostColorPostR", "ghostColorPostG", "ghostColorPostB", "ghostRangeStart", 119 | "ghostRangeEnd", "ghostDriver", "hiddenInOutliner", "useOutlinerColor", "outlinerColor", 120 | "outlinerColorR", "outlinerColorG", "outlinerColorB", "renderType", "renderVolume", 121 | "visibleFraction", "hardwareFogMultiplier", "motionBlur", "visibleInReflections", 122 | "visibleInRefractions", "castsShadows", "receiveShadows", "asBackground", 123 | "maxVisibilitySamplesOverride", "maxVisibilitySamples", "geometryAntialiasingOverride", 124 | "antialiasingLevel", "shadingSamplesOverride", "shadingSamples", "maxShadingSamples", 125 | "volumeSamplesOverride", "volumeSamples", "depthJitter", "ignoreSelfShadowing", 126 | "primaryVisibility", "referenceObject", "compInstObjGroups", 127 | "compInstObjGroups.compObjectGroups", 128 | "compInstObjGroups.compObjectGroups.compObjectGrpCompList", 129 | "compInstObjGroups.compObjectGroups.compObjectGroupId", "pickTexture", "underWorldObject", 130 | "localPosition", "localPositionX", "localPositionY", "localPositionZ", "worldPosition", 131 | "worldPosition.worldPositionX", "worldPosition.worldPositionY", 132 | "worldPosition.worldPositionZ", "localScale", "localScaleX", "localScaleY", "localScaleZ", 133 | "inTime", "inGroupIds" }; 134 | 135 | // manually supress each such item 136 | for ($value in $toSuppress) 137 | editorTemplate -suppress $value; 138 | 139 | editorTemplate -addExtraControls; 140 | 141 | editorTemplate -endScrollLayout; 142 | } 143 | 144 | global proc sequenceXMeshCreateVersionLabel() 145 | { 146 | string $oldParent = `setParent -q`; 147 | setUITemplate -pushTemplate attributeEditorTemplate; 148 | 149 | string $xmeshVersion = `pluginInfo -q -version "XMesh"`; 150 | text ( "Version: " + $xmeshVersion ); 151 | 152 | setUITemplate -popTemplate; 153 | setParent $oldParent; 154 | } 155 | 156 | global proc sequenceXMeshUpdateVersionLabel() 157 | { 158 | } 159 | 160 | global proc sequenceXMeshCreateHelpButton() 161 | { 162 | string $oldParent = `setParent -q`; 163 | setUITemplate -pushTemplate attributeEditorTemplate; 164 | 165 | button -label "Open Online Help" -c "showXMeshHelp"; 166 | 167 | setUITemplate -popTemplate; 168 | setParent $oldParent; 169 | } 170 | 171 | global proc sequenceXMeshUpdateHelpButton() 172 | { 173 | } 174 | 175 | global proc sequenceXMeshCreateAboutButton() 176 | { 177 | string $oldParent = `setParent -q`; 178 | setUITemplate -pushTemplate attributeEditorTemplate; 179 | 180 | button -label "About XMesh" -c "showXMeshAbout"; 181 | 182 | setUITemplate -popTemplate; 183 | setParent $oldParent; 184 | } 185 | 186 | global proc sequenceXMeshUpdateAboutButton() 187 | { 188 | } 189 | 190 | global proc sequenceXMeshCreateSyncFrameRangeButton( string $attributeName ) 191 | { 192 | string $oldParent = `setParent -q`; 193 | setUITemplate -pushTemplate attributeEditorTemplate; 194 | 195 | rowLayout -numberOfColumns 2; 196 | text -l ""; 197 | button -label "Sync Frame Range" SequenceXMeshSyncFrameRangeButton; 198 | setParent ..; 199 | 200 | setUITemplate -popTemplate; 201 | setParent $oldParent; 202 | 203 | sequenceXMeshUpdateSyncFrameRangeButton( $attributeName ); 204 | } 205 | 206 | global proc sequenceXMeshUpdateSyncFrameRangeButton( string $attributeName ) 207 | { 208 | $nodeName = python("\"" + $attributeName + "\".split(\".\")[0]"); 209 | button -e -enable true SequenceXMeshSyncFrameRangeButton; 210 | button -e -c ("sequenceXMeshSyncFrameRange(\"" + $nodeName + "\");") SequenceXMeshSyncFrameRangeButton; 211 | } 212 | 213 | global proc sequenceXMeshSyncFrameRange( string $nodeName ) 214 | { 215 | python("import createXMeshLoader;\ncreateXMeshLoader.syncXMeshFrameRange(\"" + $nodeName + "\");"); 216 | } 217 | 218 | global proc AEsequenceXMesh_seqPath_new( string $attributeName ) 219 | { 220 | string $oldParent = `setParent -q`; 221 | 222 | setUITemplate -pushTemplate attributeEditorTemplate; 223 | 224 | rowLayout -numberOfColumns 3 -adjustableColumn 2 -columnWidth 3 26; 225 | text -label "Render Sequence:"; 226 | textField -editable false SequenceXMeshFileSequenceTextField; 227 | symbolButton -image "navButtonBrowse.png" SequenceXMeshFileSequenceButton; 228 | setParent ..; 229 | 230 | setUITemplate -popTemplate; 231 | 232 | setParent $oldParent; 233 | 234 | AEsequenceXMesh_seqPath_replace( $attributeName ); 235 | } 236 | 237 | global proc sequenceXMeshLoadFileDialog( string $attributeName ) 238 | { 239 | // filemode 1 is a single, existing file 240 | string $results[] = `fileDialog2 -caption "Open XMesh File sequence..." -okCaption "Open" -fileFilter ".xmesh Files (*.xmesh)" -fileMode 1`; 241 | 242 | for ($filename in $results) 243 | { 244 | string $toks[]; 245 | tokenize $attributeName "." $toks; 246 | setAttr ($toks[0] + ".seqPath") -type "string" $filename; 247 | } 248 | } 249 | 250 | global proc AEsequenceXMesh_seqPath_replace( string $attributeName ) 251 | { 252 | if (`objExists $attributeName`) 253 | { 254 | symbolButton -e -c ("sequenceXMeshLoadFileDialog(\"" + $attributeName + "\");") SequenceXMeshFileSequenceButton; 255 | scriptJob -parent SequenceXMeshFileSequenceTextField -replacePrevious -attributeChange $attributeName ("AEsequenceXMesh_seqPath_update " + $attributeName); 256 | AEsequenceXMesh_seqPath_update $attributeName; 257 | } 258 | } 259 | 260 | global proc AEsequenceXMesh_seqPath_update( string $attributeName ) 261 | { 262 | if (`objExists $attributeName`) 263 | { 264 | string $sequenceText = `getAttr $attributeName`; 265 | textField -e -text $sequenceText SequenceXMeshFileSequenceTextField; 266 | } 267 | } 268 | 269 | //proxy 270 | 271 | global proc AEsequenceXMesh_seqProxyPath_new( string $attributeName ) 272 | { 273 | string $oldParent = `setParent -q`; 274 | 275 | setUITemplate -pushTemplate attributeEditorTemplate; 276 | 277 | rowLayout -numberOfColumns 3 -adjustableColumn 2 -columnWidth 3 26; 278 | text -label "Proxy Sequence:" SequenceXMeshFileProxySequenceText; 279 | textField -editable false SequenceXMeshFileProxySequenceTextField; 280 | symbolButton -image "navButtonBrowse.png" SequenceXMeshFileProxySequenceButton; 281 | setParent ..; 282 | 283 | setUITemplate -popTemplate; 284 | 285 | setParent $oldParent; 286 | 287 | AEsequenceXMesh_seqProxyPath_replace( $attributeName ); 288 | } 289 | 290 | global proc sequenceXMeshLoadProxyFileDialog( string $attributeName ) 291 | { 292 | // filemode 1 is a single, existing file 293 | string $results[] = `fileDialog2 -caption "Open XMesh Proxy File sequence..." -okCaption "Open" -fileFilter ".xmesh Files (*.xmesh)" -fileMode 1`; 294 | 295 | for ($filename in $results) 296 | { 297 | string $toks[]; 298 | tokenize $attributeName "." $toks; 299 | setAttr ($toks[0] + ".seqProxyPath") -type "string" $filename; 300 | } 301 | } 302 | 303 | global proc AEsequenceXMesh_seqProxyPath_replace( string $attributeName ) 304 | { 305 | if (`objExists $attributeName`) 306 | { 307 | symbolButton -e -c ("sequenceXMeshLoadProxyFileDialog(\"" + $attributeName + "\");") SequenceXMeshFileProxySequenceButton; 308 | scriptJob -parent SequenceXMeshFileProxySequenceTextField -replacePrevious -attributeChange $attributeName ("AEsequenceXMesh_seqProxyPath_update " + $attributeName); 309 | AEsequenceXMesh_seqProxyPath_update $attributeName; 310 | } 311 | } 312 | 313 | global proc AEsequenceXMesh_seqProxyPath_update( string $attributeName ) 314 | { 315 | if (`objExists $attributeName`) 316 | { 317 | string $sequenceText = `getAttr $attributeName`; 318 | textField -e -text $sequenceText SequenceXMeshFileProxySequenceTextField; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /src/SequenceXMeshGeometryOverride.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | #ifndef _BOOL 4 | #define _BOOL 5 | #endif 6 | 7 | #ifdef _WIN32 8 | #define WIN32_LEAN_AND_MEAN 9 | #define NOMINMAX 10 | #include 11 | #endif 12 | 13 | #include 14 | 15 | using std::cerr; 16 | using std::endl; 17 | 18 | #include "SequenceXMeshGeometryOverride.hpp" 19 | 20 | #if MAYA_API_VERSION >= 201300 21 | 22 | #include "SequenceXMeshNode.hpp" 23 | 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | static const MString ICON_ITEM_NAME = "icon"; 33 | static const MString VERTICES_ITEM_NAME = "vertices"; 34 | static const MString BBOX_ITEM_NAME = "boundingBox"; 35 | 36 | using namespace MHWRender; 37 | 38 | namespace { 39 | 40 | MRenderItem* create_render_item( const MString& name, MGeometry::Primitive primitive, MGeometry::DrawMode mode, 41 | bool raiseAboveShaded ) { 42 | #if MAYA_API_VERSION >= 201400 43 | return MRenderItem::Create( name, primitive, mode, raiseAboveShaded ); 44 | #else 45 | return new MRenderItem( name, primitive, mode, raiseAboveShaded ); 46 | #endif 47 | } 48 | 49 | void release_shader( const MShaderManager& shaderManager, MShaderInstance* shader ) { 50 | if( shader ) { 51 | #if MAYA_API_VERSION >= 201400 52 | shaderManager.releaseShader( shader ); 53 | #else 54 | delete shader; 55 | #endif 56 | } 57 | } 58 | 59 | template 60 | T* acquire_buffer( MHWRender::MVertexBuffer* vertexBuffer, unsigned int size, bool writeOnly ) { 61 | if( vertexBuffer ) { 62 | #if MAYA_API_VERSION >= 201400 63 | return reinterpret_cast( vertexBuffer->acquire( size, writeOnly ) ); 64 | #else 65 | return reinterpret_cast( vertexBuffer->acquire( size ) ); 66 | #endif 67 | } 68 | return NULL; 69 | } 70 | 71 | template 72 | T* acquire_buffer( MHWRender::MIndexBuffer* indexBuffer, unsigned int size, bool writeOnly ) { 73 | if( indexBuffer ) { 74 | #if MAYA_API_VERSION >= 201400 75 | return reinterpret_cast( indexBuffer->acquire( size, writeOnly ) ); 76 | #else 77 | return reinterpret_cast( indexBuffer->acquire( size ) ); 78 | #endif 79 | } 80 | return NULL; 81 | } 82 | 83 | } // anonymous namespace 84 | 85 | MPxGeometryOverride* SequenceXMeshGeometryOverride::create( const MObject& obj ) { 86 | return new SequenceXMeshGeometryOverride( obj ); 87 | } 88 | 89 | SequenceXMeshGeometryOverride::SequenceXMeshGeometryOverride( const MObject& obj ) 90 | : MPxGeometryOverride( obj ) 91 | , m_obj( obj ) 92 | , m_vertexBuffer( NULL ) 93 | , m_seqXMeshNode( NULL ) { 94 | MStatus stat; 95 | MFnDependencyNode node( obj, &stat ); 96 | 97 | if( stat ) { 98 | m_seqXMeshNode = dynamic_cast( node.userNode() ); 99 | } 100 | } 101 | 102 | // updateDG and related helper functions 103 | // 104 | // caches information from associated helper functions 105 | void SequenceXMeshGeometryOverride::updateDG() { 106 | MStatus status; 107 | MFnDagNode objFunctionSet; 108 | 109 | status = objFunctionSet.setObject( m_obj ); 110 | 111 | if( status ) { 112 | cache_wireframe_color( objFunctionSet ); 113 | cache_mesh_geometry(); 114 | 115 | m_cachedDisplayMode = m_seqXMeshNode->get_display_mode(); 116 | m_cachedVertexFraction = m_seqXMeshNode->get_display_fraction(); 117 | } 118 | } 119 | 120 | void SequenceXMeshGeometryOverride::cache_wireframe_color( const MFnDagNode& dagNodeFunctionSet ) { 121 | MStatus status; 122 | MDagPath dagPath; 123 | 124 | status = dagNodeFunctionSet.getPath( dagPath ); 125 | 126 | if( status ) { 127 | m_cachedColor = MGeometryUtilities::wireframeColor( dagPath ); 128 | } 129 | } 130 | 131 | void SequenceXMeshGeometryOverride::cache_mesh_geometry() { 132 | m_cachedMesh = m_seqXMeshNode->get_cached_mesh(); 133 | m_cachedBoundBox = m_seqXMeshNode->get_mesh_bounding_box(); 134 | } 135 | 136 | void SequenceXMeshGeometryOverride::updateRenderItems( const MDagPath& /*path*/, MRenderItemList& list ) { 137 | MRenderer* renderer = MRenderer::theRenderer(); 138 | if( !renderer ) 139 | return; 140 | 141 | const MShaderManager* shaderManager = renderer->getShaderManager(); 142 | if( !shaderManager ) 143 | return; 144 | 145 | // mesh vertices render item setup 146 | setup_render_item( VERTICES_ITEM_NAME, MGeometry::kPoints, list, *shaderManager ); 147 | 148 | // icon render item setup 149 | setup_render_item( ICON_ITEM_NAME, MGeometry::kTriangles, list, *shaderManager ); 150 | 151 | // bounding box render item setup 152 | setup_render_item( BBOX_ITEM_NAME, MGeometry::kLines, list, *shaderManager ); 153 | } 154 | 155 | void SequenceXMeshGeometryOverride::setup_render_item( const MString& renderItemName, 156 | const MGeometry::Primitive geometryType, 157 | MRenderItemList& renderItemList, 158 | const MShaderManager& shaderManager ) { 159 | int index = renderItemList.indexOf( renderItemName, geometryType, MGeometry::kAll ); 160 | MRenderItem* renderItem = NULL; 161 | 162 | if( index < 0 ) { 163 | // Create the render item and append it to the list of render items 164 | renderItem = create_render_item( renderItemName, geometryType, MGeometry::kAll, false ); 165 | renderItemList.append( renderItem ); 166 | } else { 167 | renderItem = renderItemList.itemAt( index ); 168 | } 169 | 170 | // Get shader and enable render item 171 | if( renderItem ) { 172 | MShaderInstance* shader = shaderManager.getStockShader( MShaderManager::k3dSolidShader ); 173 | 174 | if( shader ) { 175 | set_shader_color( *shader ); 176 | renderItem->setShader( shader ); 177 | release_shader( shaderManager, shader ); 178 | 179 | enable_render_items( *renderItem ); 180 | } 181 | } 182 | } 183 | 184 | void SequenceXMeshGeometryOverride::set_shader_color( MShaderInstance& shader ) { 185 | float shaderColor[4]; 186 | 187 | shaderColor[0] = m_cachedColor.r; 188 | shaderColor[1] = m_cachedColor.g; 189 | shaderColor[2] = m_cachedColor.b; 190 | shaderColor[3] = m_cachedColor.a; 191 | 192 | shader.setParameter( "solidColor", shaderColor ); 193 | } 194 | 195 | void SequenceXMeshGeometryOverride::enable_render_items( MRenderItem& renderItem ) { 196 | const MString renderItemName = renderItem.name(); 197 | 198 | if( renderItemName == ICON_ITEM_NAME ) { 199 | renderItem.enable( true ); 200 | } else if( renderItemName == BBOX_ITEM_NAME && m_cachedDisplayMode == DISPLAY_MODE_BOX ) { 201 | renderItem.enable( true ); 202 | } else if( renderItemName == VERTICES_ITEM_NAME && m_cachedDisplayMode == DISPLAY_MODE_VERTEX ) { 203 | renderItem.enable( true ); 204 | } else { 205 | renderItem.enable( false ); 206 | } 207 | } 208 | 209 | #if MAYA_API_VERSION >= 201400 210 | void SequenceXMeshGeometryOverride::populateGeometry( const MGeometryRequirements& requirements, 211 | const MRenderItemList& renderItems, MGeometry& data ) 212 | #else 213 | void SequenceXMeshGeometryOverride::populateGeometry( const MGeometryRequirements& requirements, 214 | MRenderItemList& renderItems, MGeometry& data ) 215 | #endif 216 | { 217 | const std::size_t numBoundingBoxVertices = 8; // Number of vertices of the bounding box 218 | 219 | boost::shared_ptr iconMesh = SequenceXMeshNode::get_icon_mesh(); 220 | int numItems = renderItems.length(); 221 | 222 | // Create the vertex buffer if it has not already been created 223 | create_vertex_buffer( requirements.vertexRequirements(), data ); 224 | 225 | size_t vertexCount; 226 | if( m_cachedMesh && m_cachedDisplayMode == DISPLAY_MODE_VERTEX ) 227 | vertexCount = xmesh::fractional_index_iterator( m_cachedMesh->vertex_count(), (float)m_cachedVertexFraction ) 228 | .num_indices(); 229 | else 230 | vertexCount = 0; 231 | populate_vertex_buffer( numBoundingBoxVertices, iconMesh->vertex_count(), vertexCount, iconMesh ); 232 | 233 | // populate the index buffers of the render items 234 | for( int i = 0; i < numItems; ++i ) { 235 | const MRenderItem* item = renderItems.itemAt( i ); 236 | 237 | if( !item ) 238 | continue; 239 | 240 | if( item->name() == BBOX_ITEM_NAME ) { 241 | populate_bounding_box_indices( data, *item ); 242 | } else if( item->name() == ICON_ITEM_NAME ) { 243 | populate_icon_mesh_indices( data, *item, numBoundingBoxVertices, iconMesh ); 244 | } else if( item->name() == VERTICES_ITEM_NAME ) { 245 | populate_mesh_object_indices( data, *item, numBoundingBoxVertices + iconMesh->vertex_count(), vertexCount ); 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * Creates the vertex buffer using the requirements 252 | * 253 | * Buffer is organized as follows: 254 | * 8 vertices - bounding box 255 | * n vertices - icon mesh vertices 256 | * m vertices - mesh vertices 257 | */ 258 | void SequenceXMeshGeometryOverride::create_vertex_buffer( const MVertexBufferDescriptorList& vertexRequirements, 259 | MGeometry& data ) { 260 | // Handle the vertex requirements 261 | for( int j = 0; j < vertexRequirements.length(); ++j ) { 262 | MVertexBufferDescriptor desc; 263 | vertexRequirements.getDescriptor( j, desc ); 264 | 265 | switch( desc.semantic() ) { 266 | case MGeometry::kPosition: 267 | // Create the vertex buffer 268 | m_vertexBuffer = data.createVertexBuffer( desc ); 269 | break; 270 | } 271 | } 272 | } 273 | 274 | /** 275 | * Populates the vertex buffer with the bounding box, icon mesh, and mesh vertices 276 | * 277 | * See comment before createVertexBuffer() for a description of how the vertices are laid 278 | * out in the buffer 279 | */ 280 | void SequenceXMeshGeometryOverride::populate_vertex_buffer( 281 | const size_t numBoundingBoxVertices, const size_t numIconVertices, const size_t numMeshVertices, 282 | const boost::shared_ptr iconMesh ) { 283 | const size_t numTotalVertices = numBoundingBoxVertices + numIconVertices + numMeshVertices; 284 | float* bufferPositions = 285 | acquire_buffer( m_vertexBuffer, static_cast( numTotalVertices ), true ); 286 | 287 | if( bufferPositions ) { 288 | populate_bounding_box_vertices( bufferPositions ); 289 | populate_icon_mesh_vertices( bufferPositions, numBoundingBoxVertices, iconMesh ); 290 | populate_mesh_object_vertices( bufferPositions, numBoundingBoxVertices + numIconVertices ); 291 | 292 | m_vertexBuffer->commit( bufferPositions ); 293 | } 294 | } 295 | 296 | /** 297 | * Populates the beginning of the vertex buffer with the bounding boxes vertices 298 | */ 299 | void SequenceXMeshGeometryOverride::populate_bounding_box_vertices( float* bufferPositions ) { 300 | for( int corner = 0; corner < 8; ++corner ) { 301 | const frantic::graphics::vector3f currCorner = m_cachedBoundBox.get_corner( corner ); 302 | bufferPositions[3 * corner] = currCorner.x; 303 | bufferPositions[3 * corner + 1] = currCorner.y; 304 | bufferPositions[3 * corner + 2] = currCorner.z; 305 | } 306 | } 307 | 308 | /** 309 | * Populates the middle of the vertexBuffer with the verices of the icon mesh 310 | */ 311 | void SequenceXMeshGeometryOverride::populate_icon_mesh_vertices( 312 | float* bufferPositions, const std::size_t vertexIndexOffset, 313 | const boost::shared_ptr iconMesh ) { 314 | const std::vector& iconMeshVertices = iconMesh->vertices_ref(); 315 | 316 | for( int i = 0; i < iconMesh->vertex_count(); ++i ) { 317 | bufferPositions[3 * ( i + vertexIndexOffset )] = iconMeshVertices[i].x; 318 | bufferPositions[3 * ( i + vertexIndexOffset ) + 1] = iconMeshVertices[i].y; 319 | bufferPositions[3 * ( i + vertexIndexOffset ) + 2] = iconMeshVertices[i].z; 320 | } 321 | } 322 | 323 | /** 324 | * Populates the end of the vertexBuffer with vertices of the loaded/created mesh object 325 | */ 326 | void SequenceXMeshGeometryOverride::populate_mesh_object_vertices( float* bufferPositions, 327 | const size_t vertexIndexOffset ) { 328 | std::size_t vertexCount = m_cachedMesh ? m_cachedMesh->vertex_count() : 0; 329 | if( vertexCount > 0 && m_cachedDisplayMode == DISPLAY_MODE_VERTEX ) { 330 | std::size_t currBufferPos = vertexIndexOffset; 331 | for( xmesh::fractional_index_iterator i( vertexCount, (float)m_cachedVertexFraction ), ie; i != ie; 332 | ++i, ++currBufferPos ) { 333 | const frantic::graphics::vector3f currVertex = 334 | frantic::maya::graphics::to_maya_space( m_cachedMesh->get_vertex( *i ) ); 335 | bufferPositions[3 * currBufferPos] = currVertex.x; 336 | bufferPositions[3 * currBufferPos + 1] = currVertex.y; 337 | bufferPositions[3 * currBufferPos + 2] = currVertex.z; 338 | } 339 | } 340 | } 341 | 342 | /** 343 | * Creates and populates an index buffer for the bounding box 344 | */ 345 | void SequenceXMeshGeometryOverride::populate_bounding_box_indices( MGeometry& geometryData, 346 | const MRenderItem& renderItem ) { 347 | MIndexBuffer* indexBuffer = geometryData.createIndexBuffer( MGeometry::kUnsignedInt32 ); 348 | 349 | if( indexBuffer ) { 350 | // 2 * 12 since a cube is composed of 12 edges and each edge requires 2 vertices to specify it 351 | unsigned int* buffer = acquire_buffer( indexBuffer, 24, true ); 352 | 353 | if( buffer ) { 354 | buffer[0] = 0; 355 | buffer[1] = 1; 356 | buffer[2] = 1; 357 | buffer[3] = 3; 358 | buffer[4] = 3; 359 | buffer[5] = 2; 360 | buffer[6] = 2; 361 | buffer[7] = 0; 362 | 363 | buffer[8] = 4; 364 | buffer[9] = 5; 365 | buffer[10] = 5; 366 | buffer[11] = 7; 367 | buffer[12] = 7; 368 | buffer[13] = 6; 369 | buffer[14] = 6; 370 | buffer[15] = 4; 371 | 372 | buffer[16] = 0; 373 | buffer[17] = 4; 374 | buffer[18] = 1; 375 | buffer[19] = 5; 376 | buffer[20] = 3; 377 | buffer[21] = 7; 378 | buffer[22] = 2; 379 | buffer[23] = 6; 380 | } 381 | 382 | indexBuffer->commit( buffer ); 383 | renderItem.associateWithIndexBuffer( indexBuffer ); 384 | } 385 | } 386 | 387 | /** 388 | * Creates and populates an index buffer for the icon mesh 389 | */ 390 | void SequenceXMeshGeometryOverride::populate_icon_mesh_indices( 391 | MGeometry& geometryData, const MRenderItem& renderItem, const std::size_t vertexIndexOffset, 392 | const boost::shared_ptr iconMesh ) { 393 | MIndexBuffer* indexBuffer = geometryData.createIndexBuffer( MGeometry::kUnsignedInt32 ); 394 | const std::vector& meshFaces = iconMesh->faces_ref(); 395 | 396 | if( indexBuffer ) { 397 | unsigned int* buffer = 398 | acquire_buffer( indexBuffer, 3 * static_cast( iconMesh->face_count() ), true ); 399 | 400 | // Populate the index buffer 401 | if( buffer ) { 402 | for( int i = 0; i < iconMesh->face_count(); ++i ) { 403 | buffer[3 * i] = meshFaces[i].x + static_cast( vertexIndexOffset ); 404 | buffer[3 * i + 1] = meshFaces[i].y + static_cast( vertexIndexOffset ); 405 | buffer[3 * i + 2] = meshFaces[i].z + static_cast( vertexIndexOffset ); 406 | } 407 | } 408 | 409 | indexBuffer->commit( buffer ); 410 | renderItem.associateWithIndexBuffer( indexBuffer ); 411 | } 412 | } 413 | 414 | /** 415 | * Creates and populates an index buffer for the vertices of the mesh object 416 | */ 417 | void SequenceXMeshGeometryOverride::populate_mesh_object_indices( MGeometry& geometryData, 418 | const MRenderItem& renderItem, 419 | const size_t vertexIndexOffset, 420 | const size_t vertexCount ) { 421 | if( !m_cachedMesh ) { 422 | return; 423 | } 424 | 425 | MIndexBuffer* indexBuffer = geometryData.createIndexBuffer( MGeometry::kUnsignedInt32 ); 426 | 427 | if( indexBuffer ) { 428 | unsigned int* buffer = 429 | acquire_buffer( indexBuffer, static_cast( vertexCount ), true ); 430 | 431 | if( buffer ) { 432 | unsigned int offsetIndex = static_cast( vertexIndexOffset ); 433 | for( int i = 0; i < vertexCount; ++i, ++offsetIndex ) { 434 | // Must add 8 vertices for the bounding box and the number of vertices in the icon mesh since the mesh 435 | // vertices occur at the end of the vertex buffer 436 | buffer[i] = offsetIndex; 437 | } 438 | } 439 | 440 | indexBuffer->commit( buffer ); 441 | renderItem.associateWithIndexBuffer( indexBuffer ); 442 | } 443 | } 444 | 445 | void SequenceXMeshGeometryOverride::cleanUp() { 446 | m_vertexBuffer = NULL; 447 | m_cachedMesh = NULL; 448 | } 449 | 450 | #if MAYA_API_VERSION >= 201600 451 | 452 | MHWRender::DrawAPI SequenceXMeshGeometryOverride::supportedDrawAPIs() const { return kOpenGL | kOpenGLCoreProfile; } 453 | 454 | #endif 455 | 456 | #endif 457 | -------------------------------------------------------------------------------- /scripts/saveXMeshSequence.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Scripted XMesh Sequence Saver for Maya 5 | # 6 | 7 | # Allows one to save multiple frames of elements to .xmesh format 8 | # Since Maya requires .xmesh files to end in a frame number, the script 9 | # will append frame numbers onto the given filename. 10 | 11 | # This is the script run locally when the save operation is happening locally. 12 | 13 | # Usage: 14 | # import saveXMeshSequence 15 | # mySettings = saveXMeshSequence.xmSettings() 16 | # .... 17 | # #assign settings (eg. mySettings.fullPath = "/myproject/export/sequence1_.xmesh" 18 | # .... 19 | # saveXMeshSequence.XMeshSaverProc(mySettings) 20 | 21 | from __future__ import print_function 22 | 23 | import maya.cmds 24 | import maya.mel 25 | import math 26 | import os.path 27 | import string 28 | import XMeshMaterialUtils 29 | from contextlib import contextmanager 30 | from distutils.version import StrictVersion 31 | 32 | #Parameter Class 33 | class xmSettings: 34 | xmVersion = '1.2' 35 | 36 | def __init__(self): 37 | self.version = xmSettings.xmVersion 38 | self.fullPath = "" 39 | self.suffix = "xmesh" 40 | self.saveMain = True 41 | self.saveProxy = False 42 | self.proxyOptimization = 'polyReduce' 43 | self.visibleOnly = False 44 | self.worldSpace = True 45 | self.combineObjects = True 46 | self.startFrame = 0 47 | self.endFrame = 48 48 | self.frameStep = 1.0 49 | self.objectList = [] 50 | self.subFolders = False 51 | self.channelMask = [] 52 | self.channelProperties = {} 53 | 54 | self.proxyChannelMask = [] 55 | self.proxyFrameStep = 1.0 56 | self.proxyPercent = 10.0 57 | 58 | self.useDeadlineOutputDirectory = False 59 | self.deadlineTaskCount = None 60 | 61 | def __repr__(self): 62 | return os.linesep.join('%s=%s' % (k, v) for (k, v) in self.__dict__.items()) 63 | 64 | #output python compilable instance 65 | def pout(self, out): 66 | lines = '\n'.join('%s.%s = %s' % (out, k, repr(v)) for (k, v) in self.__dict__.items()) 67 | items = [ 'print("XMesh: start saving...")', 68 | '', 69 | 'import saveXMeshSequence', 70 | '', 71 | '%s = saveXMeshSequence.xmSettings()' % out, 72 | '', 73 | lines, 74 | '', 75 | 'proc = saveXMeshSequence.XMeshSaverProc(%s)' % out, 76 | '', 77 | 'print("XMesh: done saving")' 78 | ] 79 | 80 | return os.linesep.join(items) 81 | 82 | def upgrade_1_to_1_2(self): 83 | self.saveProxy = self.useProxy 84 | self.proxyOptimization = self.proxySampling 85 | self.frameStep = 1.0 / self.subFrames 86 | self.proxyFrameStep = 1.0 / self.proxySubFrames 87 | 88 | del self.useProxy 89 | del self.vertsOnly 90 | del self.worldspace 91 | del self.proxySampling 92 | del self.subFrames 93 | del self.proxySubFrames 94 | del self.channelMaskType 95 | del self.proxyMinVerts 96 | 97 | self.version = '1.2' 98 | 99 | def upgrade(self): 100 | if StrictVersion(self.version) == StrictVersion('1.0'): 101 | self.upgrade_1_to_1_2() 102 | 103 | #Control Prodedure 104 | class XMeshSaverProc: 105 | def __init__(self, settings=None): 106 | if settings is None: 107 | self.s = None 108 | print("// XMesh: No Settings Found") 109 | else: 110 | if settings.version == 1: 111 | settings.version = "1.0" 112 | 113 | if StrictVersion(settings.version) > StrictVersion(xmSettings.xmVersion): 114 | maya.cmds.error("XMesh version %s is outdated. Version required: %s" % (str(xmSettings.xmVersion), str(settings.version))) 115 | else: 116 | settings.upgrade() 117 | if settings.useDeadlineOutputDirectory: 118 | outputDirectory = maya.mel.eval('DeadlineValue "OutputDirectory0"') 119 | if len(outputDirectory) > 0: 120 | settings.fullPath = outputDirectory + '/' + os.path.basename(settings.fullPath) 121 | 122 | settings.frameStep = getNearestValidFrameStepTime(settings.frameStep).asUnits(maya.OpenMaya.MTime.uiUnit()) 123 | settings.proxyFrameStep = getNearestValidFrameStepTime(settings.proxyFrameStep).asUnits(maya.OpenMaya.MTime.uiUnit()) 124 | 125 | self.s = settings 126 | 127 | self.inProxyMode = False 128 | self.reduceNodes = [] 129 | 130 | if self.s.deadlineTaskCount is None: 131 | task = 0 132 | taskCount = 1 133 | else: 134 | task = int(maya.mel.eval('DeadlineValue "StartFrame"')) 135 | taskCount = self.s.deadlineTaskCount 136 | (self.startFrame, self.endFrame, self.saveMain, self.saveProxy) = getTaskParameters(self.s.startFrame, self.s.endFrame, self.s.frameStep, self.s.saveProxy, task, taskCount) 137 | 138 | self.frameStep = self.s.frameStep 139 | 140 | #save 141 | self.saveFrames() 142 | 143 | @contextmanager 144 | def renderMode(self): 145 | self.toggleRenderMode(True) 146 | try: 147 | yield 148 | finally: 149 | self.toggleRenderMode(False) 150 | 151 | def toggleRenderMode(self, mode): 152 | if mode: 153 | script = maya.cmds.getAttr("defaultRenderGlobals.preMel") 154 | else: 155 | script = maya.cmds.getAttr("defaultRenderGlobals.postMel") 156 | 157 | if script is not None: 158 | maya.mel.eval(script) 159 | 160 | @contextmanager 161 | def proxyMode(self): 162 | self.toggleProxyMode(True) 163 | try: 164 | yield 165 | finally: 166 | self.toggleProxyMode(False) 167 | 168 | def toggleProxyMode(self,mode): 169 | _stCH = maya.cmds.constructionHistory(q=True, tgl=True) 170 | #Turn On constructionHistory 171 | maya.cmds.constructionHistory(tgl=True) 172 | if mode: 173 | self.inProxyMode = True 174 | self.frameStep = float(self.s.proxyFrameStep) 175 | self.reduceNodes = [] 176 | if self.s.proxyOptimization == '': 177 | pass 178 | elif self.s.proxyOptimization == 'polyReduce': 179 | for i in self.s.objectList: 180 | tmp = maya.cmds.polyReduce(i, percentage=(100.0 - self.s.proxyPercent)) 181 | self.reduceNodes.append(tmp) 182 | else: 183 | maya.cmds.error('Unknown Proxy Optimization mode: ' + str(self.s.proxyOptimization)) 184 | else: 185 | self.inProxyMode = False 186 | self.frameStep = float(self.s.frameStep) 187 | for i in self.reduceNodes: 188 | maya.cmds.delete(i) 189 | self.reduceNodes = [] 190 | #Reset the constructionHistory 191 | maya.cmds.constructionHistory(tgl=_stCH) 192 | 193 | # manages saving of all necessary file sequences 194 | def saveFrames(self): 195 | self.s.objectList.sort() 196 | itemsToSave = self.s.objectList 197 | filePath = self.s.fullPath 198 | 199 | #remember current frame 200 | initialTime = maya.cmds.currentTime(query=True) 201 | 202 | if itemsToSave is None or len(itemsToSave) == 0: 203 | maya.cmds.warning("No objects selected.") 204 | elif len(filePath) == 0: 205 | maya.cmds.warning("No pathname specified.") 206 | elif not (self.saveMain or self.saveProxy): 207 | maya.cmds.warning("No sequence will be saved, because saving is disabled for both the main sequence and the proxy sequence.") 208 | else: 209 | # Prompt to create new directory if specified base path does not exist 210 | if not os.path.isdir(os.path.dirname(filePath)): 211 | raise RuntimeError('The specified save directory "' + os.path.dirname(filePath) + '" does not exist') 212 | 213 | # Use .xmesh suffix by default 214 | if len(os.path.splitext(filePath)[1]) == 0: 215 | filePath += self.s.suffix 216 | 217 | #remove missing items 218 | for i in itemsToSave: 219 | if not maya.cmds.objExists(i): 220 | maya.cmds.warning("No object in the scene exists with the name: " + i) 221 | self.s.objectList.remove(i) 222 | 223 | #progressBar 224 | try: 225 | #single object (Combined) 226 | if self.s.combineObjects: 227 | if self.saveMain: 228 | with self.renderMode(): 229 | self.procCombined() 230 | if self.saveProxy: 231 | with self.proxyMode(): 232 | self.procCombined() 233 | 234 | #individual objects 235 | else: 236 | if self.saveMain: 237 | with self.renderMode(): 238 | self.procIndividual() 239 | if self.saveProxy: 240 | with self.proxyMode(): 241 | self.procIndividual() 242 | finally: 243 | #restore current time 244 | maya.cmds.currentTime(initialTime) 245 | 246 | def saveXMeshLoaderCreators(self, pathList): 247 | for aPath in pathList: 248 | saveXMeshLoaderCreatorMEL(aPath, self.s.startFrame, self.s.endFrame) 249 | saveXMeshLoaderCreatorMS(aPath, self.s.startFrame, self.s.endFrame) 250 | 251 | # EXAMPLE 252 | # input: c:\path\f.xmesh obj1 253 | 254 | # full 255 | # txtF: 256 | # ind : 257 | # c:\path\f\obj1_0000.xmesh 258 | # indSub : 259 | # c:\path\f\obj1\obj1_0000.xmesh 260 | # combine : 261 | # c:\path\f_0000.xmesh 262 | # proxy 263 | # ind : 264 | # c:\path\f\obj1_0000.xmesh 265 | # c:\path\f\obj1_proxy\obj1_proxy_0000.xmesh 266 | # indSub : 267 | # c:\path\f\obj1\obj1_0000.xmesh 268 | # c:\path\f\obj1\obj1_proxy\obj1_proxy_0000.xmesh 269 | # combine : 270 | # c:\path\f_0000.xmesh 271 | # c:\path\f_proxy\f_proxy_0000.xmesh 272 | 273 | def procCombined(self): 274 | if self.inProxyMode: 275 | cm = self.s.proxyChannelMask 276 | arg = getProxyPath(self.s.fullPath) 277 | createMissingDirectory(os.path.dirname(arg)) 278 | else: 279 | cm = self.s.channelMask 280 | arg = self.s.fullPath 281 | 282 | savePerSequenceFiles = (self.s.startFrame == self.startFrame) 283 | 284 | #save 285 | saveFiles([arg], [self.s.objectList], self.s.worldSpace, cm, self.s.channelProperties, self.startFrame, self.endFrame, self.frameStep, self.s.visibleOnly, savePerSequenceFiles) 286 | 287 | if savePerSequenceFiles: 288 | self.saveXMeshLoaderCreators([arg]) 289 | 290 | def procIndividual(self): 291 | #split filename/extension 292 | sPath,sFile,sExt = splitFilename(self.s.fullPath) 293 | 294 | sRoot = sPath 295 | 296 | createMissingDirectory(sRoot) #common root or not? 297 | if not self.s.subFolders: 298 | createMissingDirectory(sRoot) 299 | 300 | pathList = [] 301 | objectListList = [] 302 | 303 | for objectName in self.s.objectList: 304 | i = cleanName(objectName) 305 | 306 | if self.s.subFolders: 307 | createMissingDirectory(sRoot + i) 308 | arg = sRoot+ i+'/'+ i+'_' +sExt 309 | else: 310 | arg = sRoot+ i+'_' +sExt 311 | 312 | if self.inProxyMode: 313 | arg = getProxyPath(arg) 314 | createMissingDirectory(os.path.dirname(arg)) 315 | cm = self.s.proxyChannelMask 316 | else: 317 | cm = self.s.channelMask 318 | 319 | pathList.append(arg) 320 | objectListList.append([objectName]) 321 | 322 | savePerSequenceFiles = (self.s.startFrame == self.startFrame) 323 | 324 | #save 325 | saveFiles(pathList, objectListList, self.s.worldSpace, cm, self.s.channelProperties, self.startFrame, self.endFrame, self.frameStep, self.s.visibleOnly, savePerSequenceFiles) 326 | 327 | if savePerSequenceFiles: 328 | self.saveXMeshLoaderCreators(pathList) 329 | 330 | 331 | def getTaskFrameRange(numFrames, numTasks, taskNum, frameStep, startFrame, endFrame): 332 | 333 | remainder = numFrames % numTasks 334 | taskFrames = numFrames // numTasks 335 | 336 | taskFramesToAdd = taskFrames - 1 337 | 338 | if taskNum == 0 or remainder == 0: 339 | partFramesBefore = 0 340 | else: 341 | partFramesBefore = min(remainder, taskNum) 342 | 343 | taskStartFrame = ((taskFramesToAdd * taskNum) + partFramesBefore + taskNum) * frameStep 344 | taskFramesToAdd += 1 if taskNum < remainder else 0 345 | taskEndFrame = taskStartFrame + (taskFramesToAdd * frameStep) 346 | 347 | taskStartFrame += startFrame 348 | taskEndFrame += startFrame 349 | 350 | taskStartFrame = min(endFrame, taskStartFrame) 351 | taskEndFrame = min(endFrame, taskEndFrame) 352 | 353 | taskSaveRender = True 354 | 355 | return (taskStartFrame, taskEndFrame) 356 | 357 | 358 | def getTaskParameters(startFrame, endFrame, frameStep, saveProxy, taskNum, taskCount): 359 | 360 | if startFrame > endFrame: 361 | raise Exception("The starting frame cannot be after the ending frame.") 362 | if taskCount <= taskNum or taskNum < 0: 363 | raise Exception("taskNum must be positive and less than taskCount.") 364 | if frameStep <= 0: 365 | raise Exception("frameStep cannot be less than or equal to 0.") 366 | if taskCount < 1: 367 | raise Exception("taskCount cannot be less than 1.") 368 | 369 | if saveProxy: 370 | proxyTasks = int(taskCount/2) 371 | taskSaveProxy = True if taskNum < proxyTasks and saveProxy else False 372 | else: 373 | proxyTasks = 0 374 | taskSaveProxy = False 375 | 376 | renderTasks = int(taskCount) - proxyTasks 377 | renderTaskNum = taskNum - proxyTasks 378 | 379 | numFrames = math.fabs(endFrame - startFrame) + 1 380 | numStepFrames = int(math.ceil((numFrames -1)/frameStep) + 1 ) 381 | 382 | if (taskSaveProxy and taskNum >= numStepFrames ) or (not taskSaveProxy and renderTaskNum >= numStepFrames ): 383 | taskStartFrame = 0 384 | taskEndFrame = 0 385 | taskSaveProxy = False 386 | taskSaveRender = False 387 | 388 | elif taskCount > 1: 389 | if taskSaveProxy: 390 | taskStartFrame, taskEndFrame = getTaskFrameRange(numStepFrames, proxyTasks, taskNum, frameStep, startFrame, endFrame) 391 | taskSaveRender = False 392 | 393 | else: 394 | taskStartFrame, taskEndFrame = getTaskFrameRange(numStepFrames, renderTasks, renderTaskNum, frameStep, startFrame, endFrame) 395 | taskSaveRender = True 396 | 397 | elif taskCount == 1: 398 | 399 | taskStartFrame = startFrame 400 | taskEndFrame = endFrame 401 | taskSaveProxy = saveProxy 402 | taskSaveRender = True 403 | 404 | 405 | return (taskStartFrame, taskEndFrame, taskSaveRender, taskSaveProxy) 406 | 407 | 408 | def getValidSamplingSteps(): 409 | result = [] 410 | tickUnit = maya.OpenMaya.MTime.k6000FPS 411 | ticksPerFrame = int(maya.OpenMaya.MTime(1.0, maya.OpenMaya.MTime.uiUnit()).asUnits(tickUnit)) 412 | for i in range(1, ticksPerFrame+1): 413 | if float(ticksPerFrame/i) == float(ticksPerFrame)/i: 414 | result.append(maya.OpenMaya.MTime(float(i), tickUnit)) 415 | for step in [2, 5, 10]: 416 | result.append(maya.OpenMaya.MTime(float(step), maya.OpenMaya.MTime.uiUnit())) 417 | return result 418 | 419 | def getNearestValidFrameStepTime(frameStep): 420 | validFrameStep = min(getValidSamplingSteps(), key=lambda x: abs(x.asUnits(maya.OpenMaya.MTime.uiUnit()) - frameStep)) 421 | return validFrameStep 422 | 423 | def getProxyPath(path): 424 | sPath,sFile,sExt = splitFilename(path) 425 | return sPath + sFile + "_proxy/" + sFile + "_proxy" + sExt 426 | 427 | 428 | def saveFiles(pathList, objectListList, worldSpace, channelMask, channelProperties, minFrame, maxFrame, frameStep, visibleOnly, saveMaterialIDMap): 429 | if len(pathList) != len(objectListList): 430 | raise Exception("Mismatch between length of pathList and length of objectListList") 431 | 432 | step = getNearestValidFrameStepTime(frameStep) 433 | 434 | materialIDMapList = [] 435 | for (path,objectList) in zip(pathList,objectListList): 436 | if 'MaterialID' in channelMask: 437 | matIDMapPath = XMeshMaterialUtils.getMaterialIDMapFilename(path) 438 | if os.path.exists(matIDMapPath): 439 | matIDMap = XMeshMaterialUtils.MaterialIDMap.readFromFile(matIDMapPath) 440 | else: 441 | matIDMap = XMeshMaterialUtils.MaterialIDMap() 442 | selectionList = maya.OpenMaya.MSelectionList() 443 | for i in objectList: 444 | selectionList.add(i) 445 | matIDMap.addMaterialsFromSelectionList(selectionList) 446 | if saveMaterialIDMap: 447 | matIDMap.writeToFile(matIDMapPath) 448 | else: 449 | matIDMap = XMeshMaterialUtils.MaterialIDMap() 450 | materialIDMapList.append(matIDMap.getMaterialIDMapString()) 451 | 452 | channelMap = [] 453 | for ch in channelMask: 454 | if ch in channelProperties: 455 | channelMap.append(ch+'='+channelProperties[ch]) 456 | else: 457 | channelMap.append(ch) 458 | 459 | maya.cmds.saveXMeshSequence(path=pathList, 460 | objects=[','.join(objectList) for objectList in objectListList], 461 | worldSpace=worldSpace, 462 | frameRange=(minFrame, maxFrame), 463 | step=step.asUnits(maya.OpenMaya.MTime.uiUnit()), 464 | materialIDMap=materialIDMapList, 465 | channelMap=','.join(channelMap), 466 | visibleOnly=visibleOnly) 467 | 468 | def splitFilename(sFile): 469 | 470 | #todo use os.path 471 | fullpath = sFile.replace('\\','/') 472 | parts = fullpath.split('/') 473 | filenameExt = parts.pop() 474 | parts2 = filenameExt.split('.') 475 | ext = '.' + parts2.pop() 476 | file = '.'.join(parts2) 477 | path = '/'.join(parts) + '/' 478 | 479 | return (path, file, ext) 480 | 481 | def createMissingDirectory(dirStructure): 482 | if not os.path.isdir(dirStructure): 483 | os.mkdir(dirStructure) 484 | 485 | def cleanName(name): 486 | name = name.replace("|","_") 487 | name = name.replace(":",".") 488 | return name 489 | 490 | def saveXMeshLoaderCreatorMEL(theXmeshFile, minFrame, maxFrame): #this creates a MEL script to create an XMesh Loader in Maya 491 | sPath,sFile,sExt = splitFilename(theXmeshFile) 492 | melPath = sPath+sFile+".MEL" 493 | xmeshPath = sPath+sFile+"0001"+sExt 494 | escapedXMeshPath = ''.join(('\\' + c) if c in '\\\'"' else c for c in xmeshPath) 495 | with open(melPath, 'w') as f: 496 | f.write("//Exported from Autodesk Maya\n") 497 | pythonCommand = "import createXMeshLoader; reload(createXMeshLoader); createXMeshLoader.createXMeshLoaderFromPath('"+escapedXMeshPath+"');" 498 | f.write('python("' + maya.cmds.encodeString(pythonCommand) + '");\n') 499 | #f.write("string $sel[] = `ls -sl`;\n") 500 | #f.write("string $xmloader = `substitute \"Transform\" $sel[0] \"\"`;\n") 501 | 502 | def saveXMeshLoaderCreatorMS(theXmeshFile, minFrame, maxFrame): #this creates a MAXScript file to create an XMesh Loader in 3ds Max 503 | sPath,sFile,sExt = splitFilename(theXmeshFile) 504 | msPath = sPath+sFile+".MS" 505 | xmeshPath = sPath+sFile+"0001"+sExt 506 | xmeshName = sFile+"0001"+sExt 507 | with open(msPath, 'w') as f: 508 | f.write("--Exported from Autodesk Maya--\n") 509 | f.write("(\nlocal theXMeshLoader = XMeshLoader()\n") 510 | f.write("local thePath = getFilenamePath (getThisScriptFilename())\n") 511 | f.write("local goOn = true\n") 512 | f.write("if not doesFileExist (thePath+\"\\\\\"+\""+xmeshName+"\" ) do (thePath = @\""+sPath+"\")\n") 513 | f.write("if not doesFileExist (thePath+\"\\\\\"+\""+xmeshName+"\" ) do ((messagebox \"Please ensure you are executing the script from a MAPPED PATH or local drive to automatically resolve the path.\n\n If you are executing from a Network location, make sure the hard-coded path in the script exists.\" title:\"XMesh Source Sequence Path Not Found\"); goOn = false) \n" ) 514 | f.write("if goOn == true do (\n") 515 | f.write("theXMeshLoader.renderSequence = thePath + \""+xmeshName+"\" \n") 516 | f.write("theXMeshLoader.rangeFirstFrame = "+str(minFrame)+"\n") 517 | f.write("theXMeshLoader.rangeLastFrame = "+str(maxFrame)+"\n") 518 | f.write("theXMeshLoader.limitToRange = true\n") 519 | 520 | f.write("local theXMeshLayer = LayerManager.getLayerFromName \"XMesh Loaders\" \n") 521 | f.write("if theXMeshLayer == undefined do theXMeshLayer = LayerManager.newLayerFromName \"XMesh Loaders\" \n") 522 | f.write("theXMeshLayer.addnode theXMeshLoader \n") 523 | f.write("theXMeshLoader.viewportSequenceID = 0 \n") 524 | f.write("theXMeshLoader.name = uniquename \""+sFile+"_\" \n") 525 | f.write("select theXMeshLoader \n") 526 | f.write(")\n)\n") --------------------------------------------------------------------------------