{"id":853,"date":"2018-01-07T20:11:14","date_gmt":"2018-01-07T19:11:14","guid":{"rendered":"http:\/\/stefan-loser.de\/wordpress\/?page_id=853"},"modified":"2021-01-28T16:41:47","modified_gmt":"2021-01-28T15:41:47","slug":"maya-pyton-simple-exporter","status":"publish","type":"page","link":"https:\/\/stefan-loser.de\/?page_id=853","title":{"rendered":"Maya Python FBX Exporter"},"content":{"rendered":"\n<p>Hey guys, so this is a very simple but useful example of how to easily build an fbx exporter for maya with python. By no means, this one covers all export capabilities which maya offers but it has some nice ideas and concepts which are worth showing to you.<\/p>\n\n\n\n<p>For this one I&#8217;ve used the built-in maya GUI. It is overall straight forward, only a few things I want to point out. First, for easier readability and accessibility, I always declare class member on the top and not just somewhere in the code by typing self.myVariable.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"godzilla\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import maya.cmds as cmds\nimport maya.mel as mel\nimport os\n\n##############\n###\n### FBX exporter class which can export multiple poly objects \n### it puts each object on the origin point, freezes the transform, \n###\tdeletes the construction history and exports them into a specified \n### directory with specified name\n###\n#############\nclass FBXExporterWindow:\n\n    windowName = 'FBXExporterWindow'\n    textfieldGroupName = 'directoryPathTextfield'\n    columnLayoutName = 'columnLayout'\n    exportButtonName = 'exportButton'\n    textfieldName = 'fileNameTextfield'\n    textFieldUserMessage = 'textFieldUserMessage'\n\n    #opens the window \n    def open(self):\n        # safety check for window\n        self.__deleteAndResetWindow__()\n        #creates the window\n        exporterWindow = cmds.window(self.windowName, title=\"FBX exporter\", iconName=self.windowName, widthHeight=(280, 120))\n        #adds all ui elements\n        cmds.columnLayout(self.columnLayoutName, w=375, adjustableColumn=False, parent=self.windowName )\n        cmds.textField(self.textfieldName,placeholderText='Enter Optional File Name', text='',parent=self.columnLayoutName, width=150)\n        cmds.textFieldButtonGrp(self.textfieldGroupName,placeholderText='Directory Path',label='Save Location', cw3=[80,190,50], text='', buttonLabel='Browse', buttonCommand=self.__openDirectoryBrowser__, parent=self.columnLayoutName)\n        cmds.button(self.exportButtonName, label=\"Export\", parent=self.columnLayoutName, width = 322, command = self.startExport)\n        cmds.separator(parent=self.columnLayoutName, height=10)\n        cmds.text(self.textFieldUserMessage,visible=False, parent=self.columnLayoutName) \n        #shows the window \n        cmds.showWindow(exporterWindow)\n\n\u00a0\u00a0\u00a0 #removes and resets the window\n\u00a0\u00a0\u00a0 def __deleteAndResetWindow__(self):\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 # deletes old window and preference, if it still exists\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(cmds.window(self.windowName, query=True, exists=True)):\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cmds.deleteUI(self.windowName)\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if(cmds.windowPref(self.windowName, query=1, exists=1)):\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cmds.windowPref(self.windowName, r=1)\n<\/pre>\n\n\n\n<p>This part of the code sets up the general UI elements inside of a layout. It provides basic functionality for recreating the window and voids duplicates.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"godzilla\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">    ####################\n    #buttonevents\n    ####################\n\n    #sets the path of the textfield\n    def __setPath__(self,directoryPath):\n        cmds.textFieldButtonGrp(self.textfieldGroupName, edit=True, text=str(directoryPath))\n\n    # exports the objects\n    def __startExport__(self,path):\n\n        selectedObjects = cmds.ls(selection=True)\n\n        # error handling in case nothing got selected\n        if len(selectedObjects) == 0:\n            self.__showUserMessage__(\"No Objects are selected. Abbort!\", False)\n            return\n\n\n        filePathStr = cmds.textFieldButtonGrp(self.textfieldGroupName, query=True, text=True).strip()\n\n        # error handling in case no path has been provided\n        if len(filePathStr) == 0:\n            self.__showUserMessage__(\"No path has been chosen. Abbort!\", False)\n            return            \n\n        counter = 0\n\n        for obj in selectedObjects:\n\n            #creating a duplicate so we do not change our object\n            duplicate = cmds.duplicate(obj)\n\n            # reseting position to center with the help of a point constraint\n            self.__setPositionToOrigin__(duplicate)\n\n            #setting up the export path\n            textFieldContent = cmds.textField(self.textfieldName,query=True,text=True).strip()\n\n            if len(textFieldContent) == 0:\n                textFieldContent = obj\n                \n            extension = '{0}.fbx'.format(counter)\n            obj_export = filePathStr + \"\/\" + textFieldContent + extension\n\n            counter+=1\n\n            # freezes transform            \n            self.__freezeTransform__(duplicate)\n\n            try:\n                self.__deleteConstructionHistory__(duplicate)\n                # calling mel fbx export command as there is no python equivalent\n                mel.eval('FBXExport -f \"{}\" -s'.format(obj_export))\n            except:\n                self.__showUserMessage__(\"Ignoring object named: '%s'. Export failed, probably not a polygonal object. \"%(obj), False)\n\n            #after exporting, lets delete the duplicate\n            cmds.delete(duplicate)\n\n        self.__showUserMessage__(\"Exporting Complete!\", True)\n        \n\n    #opens the browser to pick the directory\n    def __openDirectoryBrowser__(self):\n        path = cmds.fileDialog2(fileMode=3, dialogStyle=1, caption='Choose Directory')\n\n        if path != None:\n            self.setPath(path[0])<\/pre>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<p>Following you find three helper methods<\/p>\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"godzilla\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">    ####################\n    ### helper methods\n    ####################\n    \n    #sets position to origin\n    def __setPositionToOrigin__(self,obj):\n        temporaryObject = 'gridCenter'        \n        cmds.spaceLocator(name=temporaryObject)\n        cmds.select(obj)\n        cmds.move(temporaryObject)\n        cmds.pointConstraint(temporaryObject, obj)\n        cmds.pointConstraint(remove=True)\n        cmds.delete(temporaryObject)\n    \n    #freezes the transform setting all rotation,scales and position values to default\n    def __freezeTransform__(self, obj):\n        cmds.select(obj)\n        #normals = 2, the normals on polygonal objects will be frozen\n        cmds.makeIdentity( apply=True, translate=1, rotate=1, scale=1, normal=2 )\n        \n    #deletes the construction history of an object\n    def __deleteConstructionHistory__(self,obj):\n        #lets make sure there's only the target object selected\n        cmds.select(obj, replace=True)\n        #removes full construction history\n        cmds.delete(constructionHistory=True)<\/pre>\n<\/div><\/div>\n\n\n\n<p>This is a nice one. Only adding error or warning messages to the log is a bit unhandy, so I&#8217;ve prepared a field which shows errors in the window to make sure that the user always has nice feedback on his actions.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"c\" data-enlighter-theme=\"godzilla\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\u00a0#\u00a0show\u00a0a\u00a0message\u00a0to\u00a0the\u00a0user\u00a0\ndef\u00a0__showUserMessage__(self,\u00a0message,\u00a0isPositiveMessage):\u00a0\n    colorHighlight\u00a0=\u00a0[1,0,0]\u00a0\n    if\u00a0isPositiveMessage:\u00a0\n        colorHighlight\u00a0=\u00a0[0,1,0]\u00a0\n        cmds.text(self.textFieldUserMessage,edit=True,\u00a0label=message,visible=True,\u00a0backgroundColor\u00a0=\u00a0colorHighlight)<\/pre>\n\n\n\n<p>That&#8217;s it. Thanks for reading and have a nice day!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey guys, so this is a very simple but useful example of how to easily build an fbx exporter for maya with python. By no [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1215,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"pgc_sgb_lightbox_settings":"","footnotes":""},"class_list":["post-853","page","type-page","status-publish","has-post-thumbnail","hentry"],"_links":{"self":[{"href":"https:\/\/stefan-loser.de\/index.php?rest_route=\/wp\/v2\/pages\/853","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/stefan-loser.de\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/stefan-loser.de\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/stefan-loser.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/stefan-loser.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=853"}],"version-history":[{"count":44,"href":"https:\/\/stefan-loser.de\/index.php?rest_route=\/wp\/v2\/pages\/853\/revisions"}],"predecessor-version":[{"id":1226,"href":"https:\/\/stefan-loser.de\/index.php?rest_route=\/wp\/v2\/pages\/853\/revisions\/1226"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/stefan-loser.de\/index.php?rest_route=\/wp\/v2\/media\/1215"}],"wp:attachment":[{"href":"https:\/\/stefan-loser.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=853"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}