├── Qtpy
├── LICENSE
├── Qt.py
├── Qt.pyc
├── __init__.py
└── __init__.pyc
├── README.md
├── __init__.py
├── license.txt
├── uExport.py
└── uExport.ui
/Qtpy/LICENSE:
--------------------------------------------------------------------------------
1 | Qt.py | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Marcus Ottosson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Qtpy/Qt.py:
--------------------------------------------------------------------------------
1 | """Map all bindings to PySide2
2 |
3 | This module replaces itself with the most desirable binding.
4 |
5 | Project goals:
6 | Qt.py was born in the film and visual effects industry to address
7 | the growing need for the development of software capable of running
8 | with more than one flavour of the Qt bindings for Python - PySide,
9 | PySide2, PyQt4 and PyQt5.
10 |
11 | 1. Build for one, run with all
12 | 2. Explicit is better than implicit
13 | 3. Support co-existence
14 |
15 | Default resolution order:
16 | - PySide2
17 | - PyQt5
18 | - PySide
19 | - PyQt4
20 |
21 | Usage:
22 | >> import sys
23 | >> from Qt import QtWidgets
24 | >> app = QtWidgets.QApplication(sys.argv)
25 | >> button = QtWidgets.QPushButton("Hello World")
26 | >> button.show()
27 | >> app.exec_()
28 |
29 | """
30 |
31 | import os
32 | import sys
33 | import shutil
34 |
35 | self = sys.modules[__name__]
36 |
37 | self.__version__ = "0.6.1"
38 |
39 | self.__added__ = list() # All unique members of Qt.py
40 | self.__remapped__ = list() # Members copied from elsewhere
41 | self.__modified__ = list() # Existing members modified in some way
42 |
43 | # Below members are set dynamically on import relative the original binding.
44 | self.__qt_version__ = "0.0.0"
45 | self.__binding__ = "None"
46 | self.__binding_version__ = "0.0.0"
47 | self.load_ui = lambda fname: None
48 | self.translate = lambda context, sourceText, disambiguation, n: None
49 | self.setSectionResizeMode = lambda *args, **kwargs: None
50 |
51 |
52 | def convert(lines):
53 | """Convert compiled .ui file from PySide2 to Qt.py
54 |
55 | Arguments:
56 | lines (list): Each line of of .ui file
57 |
58 | Usage:
59 | >> with open("myui.py") as f:
60 | .. lines = convert(f.readlines())
61 |
62 | """
63 |
64 | def parse(line):
65 | line = line.replace("from PySide2 import", "from Qt import")
66 | line = line.replace("QtWidgets.QApplication.translate",
67 | "Qt.QtCompat.translate")
68 | return line
69 |
70 | parsed = list()
71 | for line in lines:
72 | line = parse(line)
73 | parsed.append(line)
74 |
75 | return parsed
76 |
77 |
78 | def _remap(object, name, value, safe=True):
79 | """Prevent accidental assignment of existing members
80 |
81 | Arguments:
82 | object (object): Parent of new attribute
83 | name (str): Name of new attribute
84 | value (object): Value of new attribute
85 | safe (bool): Whether or not to guarantee that
86 | the new attribute was not overwritten.
87 | Can be set to False under condition that
88 | it is superseded by extensive testing.
89 |
90 | """
91 |
92 | if os.getenv("QT_TESTING") is not None and safe:
93 | # Cannot alter original binding.
94 | if hasattr(object, name):
95 | raise AttributeError("Cannot override existing name: "
96 | "%s.%s" % (object.__name__, name))
97 |
98 | # Cannot alter classes of functions
99 | if type(object).__name__ != "module":
100 | raise AttributeError("%s != 'module': Cannot alter "
101 | "anything but modules" % object)
102 |
103 | elif hasattr(object, name):
104 | # Keep track of modifications
105 | self.__modified__.append(name)
106 |
107 | self.__remapped__.append(name)
108 |
109 | setattr(object, name, value)
110 |
111 |
112 | def _add(object, name, value):
113 | """Append to self, accessible via Qt.QtCompat"""
114 | self.__added__.append(name)
115 | setattr(self, name, value)
116 |
117 |
118 | def _pyqt5():
119 | import PyQt5.Qt
120 | from PyQt5 import QtCore, QtWidgets, uic
121 |
122 | _remap(QtCore, "Signal", QtCore.pyqtSignal)
123 | _remap(QtCore, "Slot", QtCore.pyqtSlot)
124 | _remap(QtCore, "Property", QtCore.pyqtProperty)
125 |
126 | _add(PyQt5, "__binding__", PyQt5.__name__)
127 | _add(PyQt5, "load_ui", lambda fname: uic.loadUi(fname))
128 | _add(PyQt5, "translate", lambda context, sourceText, disambiguation, n: (
129 | QtCore.QCoreApplication(context, sourceText,
130 | disambiguation, n)))
131 | _add(PyQt5,
132 | "setSectionResizeMode",
133 | QtWidgets.QHeaderView.setSectionResizeMode)
134 |
135 | _maintain_backwards_compatibility(PyQt5)
136 |
137 | return PyQt5
138 |
139 |
140 | def _pyqt4():
141 | # Attempt to set sip API v2 (must be done prior to importing PyQt4)
142 | import sip
143 | try:
144 | sip.setapi("QString", 2)
145 | sip.setapi("QVariant", 2)
146 | sip.setapi("QDate", 2)
147 | sip.setapi("QDateTime", 2)
148 | sip.setapi("QTextStream", 2)
149 | sip.setapi("QTime", 2)
150 | sip.setapi("QUrl", 2)
151 | except AttributeError:
152 | raise ImportError
153 | # PyQt4 < v4.6
154 | except ValueError:
155 | # API version already set to v1
156 | raise ImportError
157 |
158 | import PyQt4.Qt
159 | from PyQt4 import QtCore, QtGui, uic
160 |
161 | _remap(PyQt4, "QtWidgets", QtGui)
162 | _remap(QtCore, "Signal", QtCore.pyqtSignal)
163 | _remap(QtCore, "Slot", QtCore.pyqtSlot)
164 | _remap(QtCore, "Property", QtCore.pyqtProperty)
165 | _remap(QtCore, "QItemSelection", QtGui.QItemSelection)
166 | _remap(QtCore, "QStringListModel", QtGui.QStringListModel)
167 | _remap(QtCore, "QItemSelectionModel", QtGui.QItemSelectionModel)
168 | _remap(QtCore, "QSortFilterProxyModel", QtGui.QSortFilterProxyModel)
169 | _remap(QtCore, "QAbstractProxyModel", QtGui.QAbstractProxyModel)
170 |
171 | try:
172 | from PyQt4 import QtWebKit
173 | _remap(PyQt4, "QtWebKitWidgets", QtWebKit)
174 | except ImportError:
175 | # QtWebkit is optional in Qt , therefore might not be available
176 | pass
177 |
178 | _add(PyQt4, "QtCompat", self)
179 | _add(PyQt4, "__binding__", PyQt4.__name__)
180 | _add(PyQt4, "load_ui", lambda fname: uic.loadUi(fname))
181 | _add(PyQt4, "translate", lambda context, sourceText, disambiguation, n: (
182 | QtCore.QCoreApplication(context, sourceText,
183 | disambiguation, None, n)))
184 | _add(PyQt4, "setSectionResizeMode", QtGui.QHeaderView.setResizeMode)
185 |
186 | _maintain_backwards_compatibility(PyQt4)
187 |
188 | return PyQt4
189 |
190 |
191 | def _pyside2():
192 | import PySide2
193 | from PySide2 import QtGui, QtWidgets, QtCore, QtUiTools
194 |
195 | _remap(QtCore, "QStringListModel", QtGui.QStringListModel)
196 |
197 | _add(PySide2, "__binding__", PySide2.__name__)
198 | _add(PySide2, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
199 | _add(PySide2, "translate", lambda context, sourceText, disambiguation, n: (
200 | QtCore.QCoreApplication(context, sourceText,
201 | disambiguation, None, n)))
202 | _add(PySide2,
203 | "setSectionResizeMode",
204 | QtWidgets.QHeaderView.setSectionResizeMode)
205 |
206 | _maintain_backwards_compatibility(PySide2)
207 |
208 | return PySide2
209 |
210 |
211 | def _pyside():
212 | import PySide
213 | from PySide import QtGui, QtCore, QtUiTools
214 |
215 | _remap(PySide, "QtWidgets", QtGui)
216 | _remap(QtCore, "QSortFilterProxyModel", QtGui.QSortFilterProxyModel)
217 | _remap(QtCore, "QStringListModel", QtGui.QStringListModel)
218 | _remap(QtCore, "QItemSelection", QtGui.QItemSelection)
219 | _remap(QtCore, "QItemSelectionModel", QtGui.QItemSelectionModel)
220 | _remap(QtCore, "QAbstractProxyModel", QtGui.QAbstractProxyModel)
221 |
222 | try:
223 | from PySide import QtWebKit
224 | _remap(PySide, "QtWebKitWidgets", QtWebKit)
225 | except ImportError:
226 | # QtWebkit is optional in Qt, therefore might not be available
227 | pass
228 |
229 | _add(PySide, "__binding__", PySide.__name__)
230 | _add(PySide, "load_ui", lambda fname: QtUiTools.QUiLoader().load(fname))
231 | _add(PySide, "translate", lambda context, sourceText, disambiguation, n: (
232 | QtCore.QCoreApplication(context, sourceText,
233 | disambiguation, None, n)))
234 | _add(PySide, "setSectionResizeMode", QtGui.QHeaderView.setResizeMode)
235 |
236 | _maintain_backwards_compatibility(PySide)
237 |
238 | return PySide
239 |
240 |
241 | def _log(text, verbose):
242 | if verbose:
243 | sys.stdout.write(text + "\n")
244 |
245 |
246 | def cli(args):
247 | """Qt.py command-line interface"""
248 | import argparse
249 |
250 | parser = argparse.ArgumentParser()
251 | parser.add_argument("--convert",
252 | help="Path to compiled Python module, e.g. my_ui.py")
253 | parser.add_argument("--compile",
254 | help="Accept raw .ui file and compile with native "
255 | "PySide2 compiler.")
256 | parser.add_argument("--stdout",
257 | help="Write to stdout instead of file",
258 | action="store_true")
259 | parser.add_argument("--stdin",
260 | help="Read from stdin instead of file",
261 | action="store_true")
262 |
263 | args = parser.parse_args(args)
264 |
265 | if args.stdout:
266 | raise NotImplementedError("--stdout")
267 |
268 | if args.stdin:
269 | raise NotImplementedError("--stdin")
270 |
271 | if args.compile:
272 | raise NotImplementedError("--compile")
273 |
274 | if args.convert:
275 | sys.stdout.write("#\n"
276 | "# WARNING: --convert is an ALPHA feature.\n#\n"
277 | "# See https://github.com/mottosso/Qt.py/pull/132\n"
278 | "# for details.\n"
279 | "#\n")
280 |
281 | #
282 | # ------> Read
283 | #
284 | with open(args.convert) as f:
285 | lines = convert(f.readlines())
286 |
287 | backup = "%s_backup%s" % os.path.splitext(args.convert)
288 | sys.stdout.write("Creating \"%s\"..\n" % backup)
289 | shutil.copy(args.convert, backup)
290 |
291 | #
292 | # <------ Write
293 | #
294 | with open(args.convert, "w") as f:
295 | f.write("".join(lines))
296 |
297 | sys.stdout.write("Successfully converted \"%s\"\n" % args.convert)
298 |
299 |
300 | def init():
301 | """Try loading each binding in turn
302 |
303 | Please note: the entire Qt module is replaced with this code:
304 | sys.modules["Qt"] = binding()
305 |
306 | This means no functions or variables can be called after
307 | this has executed.
308 |
309 | For debugging and testing, this module may be accessed
310 | through `Qt.__shim__`.
311 |
312 | """
313 |
314 | preferred = os.getenv("QT_PREFERRED_BINDING")
315 | verbose = os.getenv("QT_VERBOSE") is not None
316 | bindings = (_pyside2, _pyqt5, _pyside, _pyqt4)
317 |
318 | if preferred:
319 | # Internal flag (used in installer)
320 | if preferred == "None":
321 | self.__wrapper_version__ = self.__version__
322 | return
323 |
324 | preferred = preferred.split(os.pathsep)
325 | available = {
326 | "PySide2": _pyside2,
327 | "PyQt5": _pyqt5,
328 | "PySide": _pyside,
329 | "PyQt4": _pyqt4
330 | }
331 |
332 | try:
333 | bindings = [available[binding] for binding in preferred]
334 | except KeyError:
335 | raise ImportError(
336 | "Available preferred Qt bindings: "
337 | "\n".join(preferred)
338 | )
339 |
340 | for binding in bindings:
341 | _log("Trying %s" % binding.__name__, verbose)
342 |
343 | try:
344 | binding = binding()
345 |
346 | except ImportError as e:
347 | _log(" - ImportError(\"%s\")" % e, verbose)
348 | continue
349 |
350 | else:
351 | # Reference to this module
352 | binding.__shim__ = self
353 | binding.QtCompat = self
354 |
355 | sys.modules.update({
356 | __name__: binding,
357 |
358 | # Fix #133, `from Qt.QtWidgets import QPushButton`
359 | __name__ + ".QtWidgets": binding.QtWidgets
360 |
361 | })
362 |
363 | return
364 |
365 | # If not binding were found, throw this error
366 | raise ImportError("No Qt binding were found.")
367 |
368 |
369 | def _maintain_backwards_compatibility(binding):
370 | """Add members found in prior versions up till the next major release
371 |
372 | These members are to be considered deprecated. When a new major
373 | release is made, these members are removed.
374 |
375 | """
376 |
377 | for member in ("__binding__",
378 | "__binding_version__",
379 | "__qt_version__",
380 | "__added__",
381 | "__remapped__",
382 | "__modified__",
383 | "convert",
384 | "load_ui",
385 | "translate"):
386 | setattr(binding, member, getattr(self, member))
387 | self.__added__.append(member)
388 |
389 | setattr(binding, "__wrapper_version__", self.__version__)
390 | self.__added__.append("__wrapper_version__")
391 |
392 |
393 | cli(sys.argv[1:]) if __name__ == "__main__" else init()
394 |
--------------------------------------------------------------------------------
/Qtpy/Qt.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisevans3d/uExport/ae4ecb8a13864e621d90fa779199cd1bcd089a3f/Qtpy/Qt.pyc
--------------------------------------------------------------------------------
/Qtpy/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisevans3d/uExport/ae4ecb8a13864e621d90fa779199cd1bcd089a3f/Qtpy/__init__.py
--------------------------------------------------------------------------------
/Qtpy/__init__.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chrisevans3d/uExport/ae4ecb8a13864e621d90fa779199cd1bcd089a3f/Qtpy/__init__.pyc
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | uExport
2 | ============
3 |
4 | Overview
5 | ---------------
6 | uExport is a simple tool to automatically export complex characters from Maya to Unreal Engine 4. It works by marking up the scene with metadata related to what rendermeshes and skeletons compose a 'character'.
7 |
8 | When scenes are saved with this markup, you can open any Maya scene and automatically export exactly what content is supposed to be going to UE4, even in a batch process.
9 |
10 | Here's a simple test where one uExport node has been created; which is exposed to the user like so:
11 | 
12 |
13 | __Features:__
14 | * Serialize export info into Maya scene
15 | * exported path
16 | * user friendly asset name
17 | * fbx name
18 | * fbx export settings
19 | * export skeleton
20 | * rendermeshes
21 | * LODs
22 | * blendshapes
23 | * UI to create and edit export markup
24 | * Fire arbitrary python scripts assoc with any export (useful for LODs)
25 | * Simple sanity checking
26 | * No skeleton set
27 | * Meshes not skinned
28 |
29 | Under the Hood
30 | ---------------
31 | Under the hood, this data is serialized to disk using this network node:
32 | 
33 |
34 | Advanced Usage
35 | ---------------
36 | You can have multiple uExport nodes in one scene, not only to represent each character, but even in one export file when breaking up characters into multiple exported FBX files on disk:
37 | 
38 |
39 | __Batching__
40 | If you are using uExport to markup your characters, there are sample server tasks in the [mayaTaskServer repo](https://github.com/chrisevans3d/mayaTaskServer) to batch exports and animations.
41 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | from uExport import *
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Christopher Evans chris.evans@gmail.com
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 | 2. Altered source versions must be plainly marked as such, and must not be
16 | misrepresented as being the original software.
17 | 3. This notice may not be removed or altered from any source distribution.
18 |
--------------------------------------------------------------------------------
/uExport.py:
--------------------------------------------------------------------------------
1 | '''
2 | uExport
3 | Christopher Evans, Version 0.1, Feb 2014
4 | @author = Chris Evans
5 | version = 0.85
6 |
7 | Add this to a shelf:
8 | import uExport as ue
9 | uExportToolWindow = ue.show()
10 |
11 | '''
12 |
13 | import os
14 | import re
15 | import time
16 | import stat
17 | import types
18 |
19 | from cStringIO import StringIO
20 | import xml.etree.ElementTree as xml
21 | import json
22 | import functools
23 |
24 | import maya.cmds as cmds
25 | import maya.OpenMayaUI as openMayaUI
26 | import maya.mel as mel
27 |
28 | # legacy support
29 | from Qtpy.Qt import QtWidgets, QtCore, QtGui
30 |
31 | mayaApi = cmds.about(api=True)
32 | if mayaApi >= 201700:
33 | import shiboken2 as shiboken
34 | import pyside2uic as pysideuic
35 | else:
36 | import shiboken
37 | import pysideuic
38 |
39 | def show():
40 | global uExportToolWindow
41 | try:
42 | uExportToolWindow.close()
43 | except:
44 | pass
45 |
46 | uExportToolWindow = uExportTool()
47 | uExportToolWindow.show()
48 | return uExportToolWindow
49 |
50 | def loadUiType(uiFile):
51 | """
52 | Pyside lacks the "loadUiType" command, so we have to convert the ui file to py code in-memory first
53 | and then execute it in a special frame to retrieve the form_class.
54 | http://tech-artists.org/forum/showthread.php?3035-PySide-in-Maya-2013 (ChrisE)
55 | """
56 | parsed = xml.parse(uiFile)
57 | widget_class = parsed.find('widget').get('class')
58 | form_class = parsed.find('class').text
59 |
60 | with open(uiFile, 'r') as f:
61 | o = StringIO()
62 | frame = {}
63 |
64 | pysideuic.compileUi(f, o, indent=0)
65 | pyc = compile(o.getvalue(), '', 'exec')
66 | exec pyc in frame
67 |
68 | #Fetch the base_class and form class based on their type in the xml from designer
69 | form_class = frame['Ui_%s'%form_class]
70 | base_class = eval('QtWidgets.%s'%widget_class)
71 | return form_class, base_class
72 |
73 | def getMayaWindow():
74 | ptr = openMayaUI.MQtUtil.mainWindow()
75 | if ptr is not None:
76 | return shiboken.wrapInstance(long(ptr), QtWidgets.QWidget)
77 |
78 | try:
79 | selfDirectory = os.path.dirname(__file__)
80 | uiFile = selfDirectory + '/uExport.ui'
81 | except:
82 | uiFile = 'D:\\Build\\usr\\jeremy_ernst\\MayaTools\\General\\Scripts\\epic\\rigging\\uExport\\uExport.ui'
83 | form_class, base_class = loadUiType(uiFile)
84 |
85 | if os.path.isfile(uiFile):
86 | form_class, base_class = loadUiType(uiFile)
87 | else:
88 | cmds.error('Cannot find UI file: ' + uiFile)
89 |
90 |
91 |
92 | #these are used by both classes below, this method is usually in the utils lib at Epic
93 | def attrExists(attr):
94 | if '.' in attr:
95 | node, att = attr.split('.')
96 | return cmds.attributeQuery(att, node=node, ex=1)
97 | else:
98 | cmds.warning('attrExists: No attr passed in: ' + attr)
99 | return False
100 |
101 | def msgConnect(attribFrom, attribTo, debug=0):
102 | # TODO needs a mode to dump all current connections (overwrite/force)
103 | objFrom, attFrom = attribFrom.split('.')
104 | objTo, attTo = attribTo.split('.')
105 | if debug: print 'msgConnect>>> Locals:', locals()
106 | if not attrExists(attribFrom):
107 | cmds.addAttr(objFrom, longName=attFrom, attributeType='message')
108 | if not attrExists(attribTo):
109 | cmds.addAttr(objTo, longName=attTo, attributeType='message')
110 |
111 | # check that both atts, if existing are msg atts
112 | for a in (attribTo, attribFrom):
113 | if cmds.getAttr(a, type=1) != 'message':
114 | cmds.warning('msgConnect: Attr, ' + a + ' is not a message attribute. CONNECTION ABORTED.')
115 | return False
116 |
117 | try:
118 | return cmds.connectAttr(attribFrom, attribTo, f=True)
119 | except Exception as e:
120 | print e
121 | return False
122 |
123 | def findRelatedSkinCluster(skinObject):
124 | '''Python implementation of MEL command: http://takkun.nyamuuuu.net/blog/archives/592'''
125 |
126 | skinShape = None
127 | skinShapeWithPath = None
128 | hiddenShape = None
129 | hiddenShapeWithPath = None
130 |
131 | cpTest = cmds.ls( skinObject, typ="controlPoint" )
132 | if len( cpTest ):
133 | skinShape = skinObject
134 |
135 | else:
136 | rels = cmds.listRelatives( skinObject )
137 | if rels == None: return False
138 | for r in rels :
139 | cpTest = cmds.ls( "%s|%s" % ( skinObject, r ), typ="controlPoint" )
140 | if len( cpTest ) == 0:
141 | continue
142 |
143 | io = cmds.getAttr( "%s|%s.io" % ( skinObject, r ) )
144 | if io:
145 | continue
146 |
147 | visible = cmds.getAttr( "%s|%s.v" % ( skinObject, r ) )
148 | if not visible:
149 | hiddenShape = r
150 | hiddenShapeWithPath = "%s|%s" % ( skinObject, r )
151 | continue
152 |
153 | skinShape = r
154 | skinShapeWithPath = "%s|%s" % ( skinObject, r )
155 | break
156 |
157 | if skinShape:
158 | if len( skinShape ) == 0:
159 | if len( hiddenShape ) == 0:
160 | return None
161 |
162 | else:
163 | skinShape = hiddenShape
164 | skinShapeWithPath = hiddenShapeWithPath
165 |
166 | clusters = cmds.ls( typ="skinCluster" )
167 | for c in clusters:
168 | geom = cmds.skinCluster( c, q=True, g=True )
169 | for g in geom:
170 | if g == skinShape or g == skinShapeWithPath:
171 | return c
172 |
173 | return None
174 |
175 | def getParents(item):
176 | parents = []
177 | current_item = item
178 | current_parent = current_item.parent()
179 |
180 | # Walk up the tree and collect all parent items of this item
181 | while not current_parent is None:
182 | parents.append(current_parent)
183 | current_item = current_parent
184 | current_parent = current_item.parent()
185 | return parents
186 |
187 | ########################################################################
188 | ## UEXPORT CLASS
189 | ########################################################################
190 |
191 | class uExport(object):
192 | '''
193 | Just a little basket to store things.
194 | TODO: Add properties to get/set values
195 | TODO: add logic to check that meshes exist across LODs
196 | '''
197 | def __init__(self, node):
198 |
199 | #update for new LOD attrs instead of just rendermeshes
200 | if not attrExists(node + '.rendermeshes_LOD0'):
201 | if attrExists(node + '.rendermesh'):
202 | if cmds.listConnections(node + '.rendermesh'):
203 | lod0meshes = cmds.listConnections(node + '.rendermesh')
204 | for mesh in lod0meshes:
205 | msgConnect(node + '.rendermeshes_LOD0', mesh + '.uExport')
206 | cmds.deleteAttr(node, at='rendermesh')
207 | else:
208 | cmds.addAttr(node, longName='rendermeshes_LOD0', attributeType='message')
209 | else:
210 | cmds.addAttr(node, longName='rendermeshes_LOD0', attributeType='message')
211 |
212 | #add the other lod attrs
213 | cmds.addAttr(node, longName='rendermeshes_LOD1', attributeType='message')
214 | cmds.addAttr(node, longName='rendermeshes_LOD2', attributeType='message')
215 | cmds.addAttr(node, longName='rendermeshes_LOD3', attributeType='message')
216 | cmds.addAttr(node, longName='rendermeshes_LOD4', attributeType='message')
217 |
218 | #TODO: don't assume 4 LODs
219 | if not attrExists(node + '.export_script_LOD0'):
220 | cmds.addAttr(node, longName='export_script_LOD0', dt='string')
221 | cmds.addAttr(node, longName='export_script_LOD1', dt='string')
222 | cmds.addAttr(node, longName='export_script_LOD2', dt='string')
223 | cmds.addAttr(node, longName='export_script_LOD3', dt='string')
224 | cmds.addAttr(node, longName='export_script_LOD4', dt='string')
225 |
226 | if not attrExists(node + '.fbx_name_LOD0'):
227 | cmds.addAttr(node, longName='fbx_name_LOD0', dt='string')
228 | cmds.addAttr(node, longName='fbx_name_LOD1', dt='string')
229 | cmds.addAttr(node, longName='fbx_name_LOD2', dt='string')
230 | cmds.addAttr(node, longName='fbx_name_LOD3', dt='string')
231 | cmds.addAttr(node, longName='fbx_name_LOD4', dt='string')
232 |
233 | self.export_root = cmds.listConnections(node + '.export_root')
234 |
235 | self.version = cmds.getAttr(node + '.uexport_ver')
236 | self.node = node
237 | self.name = node.split('|')[-1]
238 | self.asset_name = node
239 | self.folder_path = None
240 | self.fbxPropertiesDict = None
241 |
242 |
243 | if attrExists(node + '.asset_name'):
244 | self.asset_name = cmds.getAttr(node + '.asset_name')
245 | if attrExists(node + '.fbx_name'):
246 | self.fbx_name = cmds.getAttr(node + '.fbx_name')
247 | if attrExists(node + '.folder_path'):
248 | self.folder_path = cmds.getAttr(node + '.folder_path')
249 |
250 | #ART MetaData
251 | #TO DO: Move to properties
252 | if attrExists(node + '.joint_mover_template'):
253 | self.joint_mover_template = cmds.getAttr(node + '.joint_mover_template')
254 | if attrExists(node + '.skeleton_template'):
255 | self.skeleton_template = cmds.getAttr(node + '.skeleton_template')
256 | if attrExists(node + '.pre_script'):
257 | self.pre_script = cmds.getAttr(node + '.pre_script')
258 | if attrExists(node + '.post_script'):
259 | self.post_script = cmds.getAttr(node + '.post_script')
260 | if attrExists(node + '.export_file'):
261 | self.export_file = cmds.getAttr(node + '.export_file')
262 | if attrExists(node + '.anim_file'):
263 | self.anim_file = cmds.getAttr(node + '.anim_file')
264 | if attrExists(node + '.skeleton_uasset'):
265 | self.skeleton_uasset = cmds.getAttr(node + '.skeleton_uasset')
266 | if attrExists(node + '.skelmesh_uasset'):
267 | self.skelmesh_uasset = cmds.getAttr(node + '.skelmesh_uasset')
268 | if attrExists(node + '.physics_uasset'):
269 | self.physics_uasset = cmds.getAttr(node + '.physics_uasset')
270 | if attrExists(node + '.thumbnail_large'):
271 | self.thumbnail_large = cmds.getAttr(node + '.thumbnail_large')
272 | if attrExists(node + '.thumbnail_small'):
273 | self.thumbnail_small = cmds.getAttr(node + '.thumbnail_small')
274 |
275 | ## Built in methods
276 | ########################################################################
277 | def getLodDicts(self):
278 | lodDicts = {}
279 | lodDicts[0] = {'meshes':self.rendermeshes_LOD0, 'export_script':self.export_script_LOD0, 'fbx_name':self.fbx_name_LOD0}
280 | lodDicts[1] = {'meshes':self.rendermeshes_LOD1, 'export_script':self.export_script_LOD1, 'fbx_name':self.fbx_name_LOD1}
281 | lodDicts[2] = {'meshes':self.rendermeshes_LOD2, 'export_script':self.export_script_LOD2, 'fbx_name':self.fbx_name_LOD2}
282 | lodDicts[3] = {'meshes':self.rendermeshes_LOD3, 'export_script':self.export_script_LOD3, 'fbx_name':self.fbx_name_LOD3}
283 | lodDicts[4] = {'meshes':self.rendermeshes_LOD4, 'export_script':self.export_script_LOD4, 'fbx_name':self.fbx_name_LOD4}
284 | return lodDicts
285 |
286 | def getShaderDict(self):
287 | shaderDict = {}
288 | for mesh in self.rendermeshes_ALL:
289 | for shader in self.getAssocShaders(mesh):
290 | if shader not in shaderDict.keys():
291 | shaderDict[shader] = [mesh]
292 | else:
293 | shaderDict[shader].append(mesh)
294 | if shaderDict:
295 | return shaderDict
296 | else:
297 | return False
298 |
299 | def getAssocShaders(self, mesh):
300 | shapes = cmds.listRelatives(mesh, shapes=1, f=True)
301 | shadingGrps = cmds.listConnections(shapes,type='shadingEngine')
302 | shaders = cmds.ls(cmds.listConnections(shadingGrps),materials=1)
303 | return shaders
304 |
305 | def connectRenderMeshes(self, renderMeshes, LOD=0):
306 | try:
307 | cmds.undoInfo(openChunk=True)
308 | lodAttr = None
309 | if LOD >=0 or LOD <=4:
310 | lodAttr = self.node + '.rendermeshes_LOD' + str(LOD)
311 | conns = cmds.listConnections(lodAttr, plugs=1, destination=1)
312 | if conns:
313 | for conn in cmds.listConnections(lodAttr, plugs=1, destination=1):
314 | cmds.disconnectAttr(lodAttr, conn)
315 | if lodAttr:
316 | for mesh in renderMeshes:
317 | msgConnect(lodAttr, mesh + '.uExport')
318 | else:
319 | cmds.error('connectRenderMeshes>>> please specify a LOD integer (0-4) for your meshes')
320 |
321 | except Exception as e:
322 | print e
323 | finally:
324 | cmds.undoInfo(closeChunk=True)
325 |
326 | def getFbxExportPropertiesDict(self):
327 | if not attrExists(self.node + '.fbxPropertiesDict'):
328 | cmds.addAttr(self.node, longName='fbxPropertiesDict', dt='string')
329 | self.fbxPropertiesDict = {'animInterpolation':'quaternion', 'upAxis':'default', 'triangulation':False}
330 | cmds.setAttr(self.node + '.fbxPropertiesDict', json.dumps(self.fbxPropertiesDict), type='string')
331 | return self.fbxPropertiesDict
332 | else:
333 | self.fbxPropertiesDict = json.loads(cmds.getAttr(self.node + '.fbxPropertiesDict'))
334 | return self.fbxPropertiesDict
335 |
336 | ## Properties
337 | ########################################################################
338 |
339 | #return and set the rendermeshes per LOD
340 | @property
341 | def rendermeshes_LOD0(self):
342 | conns = cmds.listConnections(self.node + '.rendermeshes_LOD0')
343 | if conns:
344 | return conns
345 | else: return []
346 | @rendermeshes_LOD0.setter
347 | def rendermeshes_LOD0(self, meshes):
348 | self.connectRenderMeshes(meshes, LOD=0)
349 |
350 | @property
351 | def rendermeshes_LOD1(self):
352 | conns = cmds.listConnections(self.node + '.rendermeshes_LOD1')
353 | if conns:
354 | return conns
355 | else: return []
356 | @rendermeshes_LOD1.setter
357 | def rendermeshes_LOD1(self, meshes):
358 | self.connectRenderMeshes(meshes, LOD=1)
359 |
360 | @property
361 | def rendermeshes_LOD2(self):
362 | conns = cmds.listConnections(self.node + '.rendermeshes_LOD2')
363 | if conns:
364 | return conns
365 | else: return []
366 | @rendermeshes_LOD2.setter
367 | def rendermeshes_LOD2(self, meshes):
368 | self.connectRenderMeshes(meshes, LOD=2)
369 |
370 | @property
371 | def rendermeshes_LOD3(self):
372 | conns = cmds.listConnections(self.node + '.rendermeshes_LOD3')
373 | if conns:
374 | return conns
375 | else: return []
376 | @rendermeshes_LOD3.setter
377 | def rendermeshes_LOD3(self, meshes):
378 | self.connectRenderMeshes(meshes, LOD=3)
379 |
380 | @property
381 | def rendermeshes_LOD4(self):
382 | conns = cmds.listConnections(self.node + '.rendermeshes_LOD4')
383 | if conns:
384 | return conns
385 | else: return []
386 | @rendermeshes_LOD4.setter
387 | def rendermeshes_LOD4(self, meshes):
388 | self.connectRenderMeshes(meshes, LOD=4)
389 |
390 | #number of lods
391 | @property
392 | def lodNum(self):
393 | att = self.node + '.lodNum'
394 | if attrExists(att):
395 | return cmds.getAttr(att)
396 | else:
397 | cmds.addAttr(self.node, ln='lodNum', at='byte')
398 | cmds.setAttr(att, 4)
399 | return cmds.getAttr(att)
400 |
401 | @lodNum.setter
402 | def lodNum(self, meshes):
403 | att = self.node + '.lodNum'
404 | if attrExists(att):
405 | return cmds.setAttr(att)
406 | else:
407 | cmds.addAttr(self.node, ln='lodNum', at='byte')
408 | cmds.setAttr(att, 4)
409 | return cmds.setAttr(att)
410 |
411 | #return ALL lod geometry
412 | @property
413 | def rendermeshes_ALL(self):
414 | meshes = []
415 | meshes.extend(self.rendermeshes_LOD0)
416 | meshes.extend(self.rendermeshes_LOD1)
417 | meshes.extend(self.rendermeshes_LOD2)
418 | meshes.extend(self.rendermeshes_LOD3)
419 | meshes.extend(self.rendermeshes_LOD4)
420 | return meshes
421 |
422 | #return and set export script paths
423 | @property
424 | def export_script_LOD0(self):
425 | return cmds.getAttr(self.node + '.export_script_LOD0')
426 | @export_script_LOD0.setter
427 | def export_script_LOD0(self, path):
428 | cmds.setAttr(self.node + '.export_script_LOD0', path, type='string')
429 |
430 | @property
431 | def export_script_LOD1(self):
432 | return cmds.getAttr(self.node + '.export_script_LOD1')
433 | @export_script_LOD1.setter
434 | def export_script_LOD1(self, path):
435 | cmds.setAttr(self.node + '.export_script_LOD1', path, type='string')
436 |
437 | @property
438 | def export_script_LOD2(self):
439 | return cmds.getAttr(self.node + '.export_script_LOD2')
440 | @export_script_LOD2.setter
441 | def export_script_LOD2(self, path):
442 | cmds.setAttr(self.node + '.export_script_LOD2', path, type='string')
443 |
444 | @property
445 | def export_script_LOD3(self):
446 | return cmds.getAttr(self.node + '.export_script_LOD3')
447 | @export_script_LOD3.setter
448 | def export_script_LOD3(self, path):
449 | cmds.setAttr(self.node + '.export_script_LOD3', path, type='string')
450 |
451 | @property
452 | def export_script_LOD4(self):
453 | return cmds.getAttr(self.node + '.export_script_LOD4')
454 | @export_script_LOD4.setter
455 | def export_script_LOD4(self, path):
456 | cmds.setAttr(self.node + '.export_script_LOD4', path, type='string')
457 |
458 |
459 | #return and set fbx export names
460 | @property
461 | def fbx_name_LOD0(self):
462 | return cmds.getAttr(self.node + '.fbx_name_LOD0')
463 | @fbx_name_LOD0.setter
464 | def fbx_name_LOD0(self, name):
465 | cmds.setAttr(self.node + '.fbx_name_LOD0', name, type='string')
466 |
467 | @property
468 | def fbx_name_LOD1(self):
469 | return cmds.getAttr(self.node + '.fbx_name_LOD1')
470 | @fbx_name_LOD1.setter
471 | def fbx_name_LOD1(self, name):
472 | cmds.setAttr(self.node + '.fbx_name_LOD1', name, type='string')
473 |
474 | @property
475 | def fbx_name_LOD2(self):
476 | return cmds.getAttr(self.node + '.fbx_name_LOD2')
477 | @fbx_name_LOD2.setter
478 | def fbx_name_LOD2(self, name):
479 | cmds.setAttr(self.node + '.fbx_name_LOD2', name, type='string')
480 |
481 | @property
482 | def fbx_name_LOD3(self):
483 | return cmds.getAttr(self.node + '.fbx_name_LOD3')
484 | @fbx_name_LOD3.setter
485 | def fbx_name_LOD3(self, name):
486 | cmds.setAttr(self.node + '.fbx_name_LOD3', name, type='string')
487 |
488 | @property
489 | def fbx_name_LOD4(self):
490 | return cmds.getAttr(self.node + '.fbx_name_LOD4')
491 | @fbx_name_LOD4.setter
492 | def fbx_name_LOD4(self, path):
493 | cmds.setAttr(self.node + '.fbx_name_LOD4', path, type='string')
494 |
495 |
496 | #return joints
497 | @property
498 | def joints(self):
499 | if self.export_root:
500 | returnMe = []
501 | children = cmds.listRelatives(self.export_root, type='joint',allDescendents=True)
502 | if children:
503 | returnMe.extend(children)
504 |
505 | returnMe.append(self.export_root[0])
506 | return returnMe
507 | @joints.setter
508 | def joints(self):
509 | print 'Joints returned by walking hierarchy from the root, not directly settable.'
510 |
511 | #return fbxExportDict
512 | @property
513 | def fbxExportProperties(self):
514 | return self.getFbxExportPropertiesDict()
515 | @fbxExportProperties.setter
516 | def fbxExportProperties(self, dict):
517 | self.fbxPropertiesDict = dict
518 | cmds.setAttr(self.node + '.fbxPropertiesDict', json.dumps(self.fbxPropertiesDict), type='string')
519 |
520 | ########################################################################
521 | ## UEXPORT TOOL
522 | ########################################################################
523 |
524 |
525 | class uExportTool(base_class, form_class):
526 | title = 'uExportTool 0.8'
527 |
528 | currentMesh = None
529 | currentSkin = None
530 | currentInf = None
531 | currentVerts = None
532 | currentNormalization = None
533 |
534 | scriptJobNum = None
535 | copyCache = None
536 |
537 | jointLoc = None
538 |
539 | iconLib = {}
540 | iconPath = os.environ.get('MAYA_LOCATION', None) + '/icons/'
541 | iconLib['joint'] = QtGui.QIcon(QtGui.QPixmap(iconPath + 'kinJoint.png'))
542 | iconLib['ikHandle'] = QtGui.QIcon(QtGui.QPixmap(iconPath + 'kinHandle.png'))
543 | iconLib['transform'] = QtGui.QIcon(QtGui.QPixmap(iconPath + 'orientJoint.png'))
544 |
545 | def __init__(self, parent=getMayaWindow()):
546 | self.closeExistingWindow()
547 | super(uExportTool, self).__init__(parent)
548 |
549 |
550 | self.setupUi(self)
551 | self.setWindowTitle(self.title)
552 | self.fbxVerLbl.setText('fbx plugin ' + str(self.fbxVersion()) + ' ')
553 |
554 | wName = openMayaUI.MQtUtil.fullName(long(shiboken.getCppPointer(self)[0]))
555 |
556 | ## Connect UI
557 | ########################################################################
558 | self.export_BTN.clicked.connect(self.export_FN)
559 | self.createUexportNode_BTN.clicked.connect(self.createUexportNode_FN)
560 | self.replaceUnknownNodes.clicked.connect(self.replaceUnknownNodes_FN)
561 | self.refreshBTN.clicked.connect(self.refreshUI)
562 | self.getTexturesP4BTN.clicked.connect(self.getTexturesP4_FN)
563 |
564 | # TODO: Add save settings, setting p4 menu for now
565 | self.p4CHK.setChecked(False)
566 |
567 | self.workSpaceCMB.currentIndexChanged.connect(self.workspaceSelected)
568 |
569 | #context menu
570 | self.export_tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
571 | self.export_tree.customContextMenuRequested.connect(self.openMenu)
572 |
573 | self.export_tree.itemClicked.connect(self.check_status)
574 | self.export_tree.itemClicked.connect(self.itemClicked)
575 | self.missingFilesTree.itemClicked.connect(self.itemClicked)
576 |
577 |
578 | #check for p4 lib
579 | yourP4Module = 'p4python.P4'
580 | try:
581 | __import__(yourP4Module)
582 | except ImportError:
583 | print 'Perforce lib not found.'
584 | self.perforce = False
585 | else:
586 | self.perforce = True
587 | print 'Perforce lib found.'
588 |
589 | #Connect event filter to grab right/left click
590 | self.export_tree.viewport().installEventFilter(self)
591 | self.mousePress = None
592 |
593 | self.snapRoot_CMB.setHidden(True)
594 | self.refreshUI()
595 |
596 | ## GENERAL
597 | ########################################################################
598 |
599 | #quick mesh check
600 | def isMesh(self, node):
601 | rels = cmds.listRelatives(node, children=True, s=True)
602 | if rels:
603 | for shp in rels:
604 | if cmds.nodeType(shp)=='mesh':
605 | return True
606 | return False
607 |
608 | def setOutlinerToShowAssetContents(self):
609 | mel.eval('outlinerEditor -e -showContainerContents 1 outlinerPanel1; outlinerEditor -e \
610 | -showContainedOnly 0 outlinerPanel1;')
611 |
612 | def closeExistingWindow(self):
613 | for qt in QtWidgets.qApp.topLevelWidgets():
614 | try:
615 | if qt.__class__.__name__ == self.__class__.__name__:
616 | qt.deleteLater()
617 | print 'uExport: Closed ' + str(qt.__class__.__name__)
618 | except:
619 | pass
620 |
621 | def convertSkelSettingsToNN(delete=1):
622 | orig = 'SkeletonSettings_Cache'
623 | if cmds.objExists(orig):
624 | if cmds.nodeType(orig) == 'unknown':
625 | new = cmds.createNode('network')
626 | for att in cmds.listAttr(orig):
627 | if not cmds.attributeQuery(att, node=new, exists=1):
628 | typ = cmds.attributeQuery(att, node=orig, at=1)
629 | if typ == 'typed':
630 | cmds.addAttr(new, longName=att, dt='string')
631 | if cmds.getAttr(orig + '.' + att):
632 | cmds.setAttr(new + '.' + att, cmds.getAttr(orig + '.' + att), type='string')
633 | elif typ == 'enum':
634 | cmds.addAttr(new, longName=att, at='enum', enumName=cmds.attributeQuery(att, node=orig, listEnum=1)[0])
635 | cmds.delete(orig)
636 | cmds.rename(new, 'SkeletonSettings_Cache')
637 |
638 | def fbxVersion(self):
639 | for plugin in cmds.pluginInfo(q=True, listPlugins=True):
640 | if "fbxmaya" in plugin:
641 | return cmds.pluginInfo(plugin, q=True, version=True)
642 |
643 | def replaceUnknownNodes_FN(self):
644 | self.convertSkelSettingsToNN()
645 |
646 | def isBlendshape(self, mesh):
647 | future = cmds.listHistory(mesh, future=1)
648 | isShape = False
649 | for node in future:
650 | if cmds.nodeType(node) == 'blendShape':
651 | return True
652 | return False
653 |
654 | def LOD_transferWeights(meshes, jointsToRemove, jointToTransferTo, debug=1, pruneWeights=0.001, *args):
655 | '''
656 | Original function by Charles Anderson @ Epic Games
657 | '''
658 | for mesh in meshes:
659 |
660 | # Find the skin cluster for the current mesh
661 | cluster = findCluster(mesh)
662 |
663 | if debug:
664 | print "MESH: ", mesh
665 | print "CLUSTER: ", cluster
666 |
667 | # Prune weights on the current mesh
668 | if pruneWeights:
669 | cmds.skinPercent(cluster, mesh, prw=pruneWeights)
670 |
671 | # Find all of the current influences on the current skin cluster.
672 | meshInfluences = cmds.skinCluster(cluster, q=True, inf=True)
673 | #print "Current Influences: ", meshInfluences
674 |
675 | for joint in jointsToRemove:
676 | if joint in meshInfluences:
677 | #print "Current Joint: ", joint
678 |
679 | # If the jointToTransferTo is not already an influence on the current mesh then add it.
680 | currentInfluences = cmds.skinCluster(cluster, q=True, inf=True)
681 | if jointToTransferTo not in currentInfluences:
682 | cmds.skinCluster(cluster, e=True, wt=0, ai=jointToTransferTo)
683 |
684 | # Now transfer all of the influences we want to remove onto the jointToTransferTo.
685 | for x in range(cmds.polyEvaluate(mesh, v=True)):
686 | #print "TRANSFERRING DATA....."
687 | value = cmds.skinPercent(cluster, (mesh+".vtx["+str(x)+"]"), t=joint, q=True)
688 | if value > 0:
689 | cmds.skinPercent(cluster, (mesh+".vtx["+str(x)+"]"), tmw=[joint, jointToTransferTo])
690 |
691 | # Remove unused influences
692 | currentInfluences = cmds.skinCluster(cluster, q=True, inf=True)
693 | #print "Current Influences: ", currentInfluences
694 | influencesToRemove = []
695 | weightedInfs = cmds.skinCluster(cluster, q=True, weightedInfluence=True)
696 | #print "Weighted Influences: ", weightedInfs
697 | for inf in currentInfluences:
698 | #print "Influence: ", inf
699 | if inf not in weightedInfs:
700 | #print "Update Influences to Remove List: ", inf
701 | influencesToRemove.append(inf)
702 |
703 | #print "ToRemove Influences: ", influencesToRemove
704 | if influencesToRemove != []:
705 | for inf in influencesToRemove:
706 | cmds.skinCluster(cluster, e=True, ri=inf)
707 |
708 | ## UI RELATED
709 | ########################################################################
710 |
711 | #event filter to grab and discern right/left click
712 | def eventFilter(self, source, event):
713 | if (event.type() == QtCore.QEvent.MouseButtonPress and event.button() == QtCore.Qt.RightButton and source is self.export_tree.viewport()):
714 | self.mousePress = 'right'
715 | elif (event.type() == QtCore.QEvent.MouseButtonPress and event.button() == QtCore.Qt.LeftButton and source is self.export_tree.viewport()):
716 | self.mousePress = 'left'
717 | return super(uExportTool, self).eventFilter(source, event)
718 |
719 | #contextual menus
720 | def openMenu(self, position):
721 | menu = QtWidgets.QMenu()
722 | clickedWid = self.export_tree.itemAt(position)
723 |
724 | if 'P4_FILE_LIST' in self.export_tree.itemAt(position).text(0):
725 | checkOut = menu.addAction("Check out files")
726 | pos = self.export_tree.mapToGlobal(position)
727 | action = menu.exec_(pos)
728 |
729 | if action:
730 | print 'checkin gout files'
731 |
732 | if 'LOD ' in clickedWid.text(0):
733 | addMeshes = menu.addAction("Add selected meshes")
734 | removeMeshes = menu.addAction("Remove selected meshes")
735 | resetMeshes = menu.addAction("Set only selected meshes")
736 |
737 | pos = self.export_tree.mapToGlobal(position)
738 | action = menu.exec_(pos)
739 |
740 | if action:
741 | if action == addMeshes:
742 | meshes = [mesh for mesh in cmds.ls(sl=1) if self.isMesh(mesh)]
743 | uNode = clickedWid.uExport
744 | lod = clickedWid.lod
745 | existingMeshes = eval('uNode.rendermeshes_LOD' + str(lod))
746 | existingMeshes.extend(meshes)
747 | uNode.connectRenderMeshes(existingMeshes, LOD=clickedWid.lod)
748 | self.refreshUI()
749 | elif action == removeMeshes:
750 | meshes = [mesh for mesh in cmds.ls(sl=1) if self.isMesh(mesh)]
751 | uNode = clickedWid.uExport
752 | lod = clickedWid.lod
753 | existingMeshes = eval('uNode.rendermeshes_LOD' + str(lod))
754 | newMeshes = [mesh for mesh in existingMeshes if mesh not in meshes]
755 | uNode.connectRenderMeshes(newMeshes, LOD=lod)
756 | self.refreshUI()
757 | elif action == resetMeshes:
758 | newMeshes = cmds.ls(sl=1)
759 | uNode = clickedWid.uExport
760 | lod = clickedWid.lod
761 | uNode.connectRenderMeshes(newMeshes, LOD=lod)
762 | self.refreshUI()
763 |
764 | if not self.export_tree.itemAt(position).parent():
765 | rootRewire = menu.addAction("Re-wire root joint attr to current selected joint")
766 |
767 | meshSubmenu = menu.addMenu('EDIT RENDERMESHES >>')
768 |
769 | addLOD0 = meshSubmenu.addAction("Add selected as LOD0 render meshes")
770 | addLOD1 = meshSubmenu.addAction("Add selected as LOD1 render meshes")
771 | addLOD2 = meshSubmenu.addAction("Add selected as LOD2 render meshes")
772 | addLOD3 = meshSubmenu.addAction("Add selected as LOD3 render meshes")
773 | addLOD4 = meshSubmenu.addAction("Add selected as LOD4 render meshes")
774 |
775 | scriptSubmenu = menu.addMenu('ADD EXPORT SCRIPTS >>')
776 |
777 | addScriptLOD0 = scriptSubmenu.addAction("Add LOD0 export script")
778 | addScriptLOD1 = scriptSubmenu.addAction("Add LOD1 export script")
779 | addScriptLOD2 = scriptSubmenu.addAction("Add LOD2 export script")
780 | addScriptLOD3 = scriptSubmenu.addAction("Add LOD3 export script")
781 | addScriptLOD4 = scriptSubmenu.addAction("Add LOD4 export script")
782 |
783 | pos = self.export_tree.mapToGlobal(position)
784 | action = menu.exec_(pos)
785 |
786 |
787 | if action:
788 | if action == rootRewire:
789 | index = self.export_tree.selectedIndexes()[0]
790 | uExportNode = self.export_tree.itemFromIndex(index).uExport.node
791 | root = cmds.ls(sl=1)
792 | if len(root) == 1:
793 | self.connectRoot(uExportNode, root[0])
794 | else:
795 | cmds.error('Select a single joint, you == fail, bro. ' + str(root))
796 |
797 | elif action in (addLOD0, addLOD1, addLOD2, addLOD3, addLOD4):
798 | for index in self.export_tree.selectedIndexes():
799 | uExportNode = self.export_tree.itemFromIndex(index).uExport
800 | meshes = cmds.ls(sl=1)
801 | #TODO: check if theyre actually meshes
802 | if action == addLOD0: uExportNode.rendermeshes_LOD0 = meshes
803 | if action == addLOD1: uExportNode.rendermeshes_LOD1 = meshes
804 | if action == addLOD2: uExportNode.rendermeshes_LOD2 = meshes
805 | if action == addLOD3: uExportNode.rendermeshes_LOD3 = meshes
806 | if action == addLOD4: uExportNode.rendermeshes_LOD4 = meshes
807 |
808 | elif action in (addScriptLOD0, addScriptLOD1, addScriptLOD2, addScriptLOD3, addScriptLOD4):
809 | for index in self.export_tree.selectedIndexes():
810 | uExportNode = self.export_tree.itemFromIndex(index).uExport
811 |
812 | fileName,_ = QtWidgets.QFileDialog.getOpenFileName(self,
813 | "Choose a Python Script", '',
814 | "Python (*.py);;All Files (*)")
815 |
816 | if fileName:
817 | if action == addScriptLOD0: uExportNode.export_script_LOD0 = fileName
818 | if action == addScriptLOD1: uExportNode.export_script_LOD1 = fileName
819 | if action == addScriptLOD2: uExportNode.export_script_LOD2 = fileName
820 | if action == addScriptLOD3: uExportNode.export_script_LOD3 = fileName
821 | if action == addScriptLOD4: uExportNode.export_script_LOD4 = fileName
822 |
823 | self.refreshUI()
824 |
825 | def refreshUI(self):
826 | start = time.time()
827 |
828 | self.export_tree.clear()
829 | self.missingFilesTree.clear()
830 | self.uNodes = []
831 | for node in self.getExportNodes():
832 | self.uNodes.append(uExport(node))
833 | self.buildExportTree(self.uNodes)
834 | self.buildMissingFilesTree()
835 |
836 |
837 | if self.p4CHK.isChecked():
838 | if self.perforce:
839 | self.getTexturesP4BTN.setEnabled(True)
840 | self.getP4Workspaces()
841 |
842 | # put export into the uExport class later
843 |
844 | elapsed = (time.time() - start)
845 | print 'uExport>>> Refreshed in %.2f seconds.' % elapsed
846 |
847 |
848 | def buildExportTree(self, uNodes):
849 | for uNode in uNodes:
850 |
851 | red = QtGui.QColor(200, 75, 75, 255)
852 | widRed = QtGui.QColor(200, 75, 75, 100)
853 | blue = QtGui.QColor(50, 130, 210, 255)
854 | widBlue = QtGui.QColor(50, 130, 210, 100)
855 |
856 | #top level
857 | wid1 = QtWidgets.QTreeWidgetItem()
858 | font = wid1.font(0)
859 | font.setPointSize(15)
860 |
861 | wid1.setText(0,uNode.asset_name)
862 | wid1.uExport = uNode
863 |
864 | wid1.setText(1, uNode.version)
865 | self.export_tree.addTopLevelItem(wid1)
866 | wid1.setExpanded(True)
867 | wid1.setFont(0,font)
868 |
869 | font = wid1.font(0)
870 | font.setPointSize(10)
871 |
872 | wid1.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsSelectable)
873 | wid1.setCheckState(0, QtCore.Qt.Checked)
874 |
875 | wid1.setBackground(0, QtGui.QColor(widBlue))
876 |
877 | #mesh branch
878 | meshTop = QtWidgets.QTreeWidgetItem()
879 | meshes = uNode.rendermeshes_LOD0
880 | if meshes:
881 | meshTop.setText(0, 'RENDER MESHES: (' + str(len(uNode.rendermeshes_ALL)) + ') LODS: (' + str(uNode.lodNum) + ')')
882 | else:
883 | meshTop.setText(0, 'RENDER MESHES: NONE')
884 | meshTop.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable)
885 | meshTop.setCheckState(0, QtCore.Qt.Checked)
886 | wid1.addChild(meshTop)
887 | meshTop.setExpanded(True)
888 |
889 | allMeshRelated = []
890 |
891 | #get mat info
892 | start = time.time()
893 | shaderDict = uNode.getShaderDict()
894 | elapsed = (time.time() - start)
895 | print 'uExport>>> Built shader dict for ' + uNode.asset_name + ' in %.2f seconds.' % elapsed
896 |
897 | #meshes
898 | lodDicts = uNode.getLodDicts()
899 | if lodDicts:
900 | for lodNum in lodDicts:
901 | lodWid = QtWidgets.QTreeWidgetItem()
902 |
903 | #get mesh info
904 | meshes = lodDicts[lodNum]['meshes']
905 | numMeshes = '0'
906 | if meshes:
907 | numMeshes = str(len(meshes))
908 | else:
909 | lodWid.setForeground(0,red)
910 | continue
911 |
912 | numShaders = 0
913 | usedShaders = []
914 |
915 | if shaderDict:
916 | numShaders = len(shaderDict.keys())
917 | for mat in shaderDict.keys():
918 | for mesh in meshes:
919 | if mesh in shaderDict[mat]:
920 | usedShaders.append(mat)
921 | else:
922 | lodWid.setForeground(0,red)
923 | wid1.setBackground(0, widRed)
924 |
925 | usedShaders = set(usedShaders)
926 |
927 | widText = 'LOD ' + str(lodNum) + ' (' + numMeshes + ' meshes) (' + str(len(usedShaders)) + ' materials)'
928 | if lodDicts[lodNum]['export_script']:
929 | widText += ' > Export Script: ' + lodDicts[lodNum]['export_script'].split('/')[-1]
930 |
931 | lodWid.setText(0, widText)
932 |
933 | #add metadata for later use
934 | lodWid.lod = lodNum
935 | lodWid.uExport = uNode
936 |
937 | meshTop.addChild(lodWid)
938 |
939 | #create mesh top widget
940 | meshLodTop = QtWidgets.QTreeWidgetItem()
941 | meshLodTop.setText(0, 'MESHES (' + numMeshes + ')')
942 | lodWid.addChild(meshLodTop)
943 |
944 | matLodTop = QtWidgets.QTreeWidgetItem()
945 |
946 | if meshes:
947 | for mesh in meshes:
948 | meshWid = QtWidgets.QTreeWidgetItem()
949 | meshWid.setText(0, mesh)
950 | meshLodTop.addChild(meshWid)
951 |
952 | if not findRelatedSkinCluster(mesh):
953 | if self.isBlendshape(mesh):
954 | meshWid.setForeground(0, blue)
955 | meshWid.setText(0, mesh + ' (blendshape)')
956 | else:
957 | meshWid.setForeground(0, red)
958 | wid1.setBackground(0, widRed)
959 | meshWid.setText(0, mesh + ': NO SKINCLUSTER')
960 | for item in getParents(meshWid):
961 | item.setExpanded(True)
962 | meshWid.selectMe = [mesh]
963 | meshLodTop.selectMe = meshes
964 |
965 | else:
966 | meshWid = QtWidgets.QTreeWidgetItem()
967 | meshWid.setText(0, 'NONE')
968 | meshLodTop.addChild(meshWid)
969 |
970 | #create mat top widget
971 | matLodTop = QtWidgets.QTreeWidgetItem()
972 | lodWid.addChild(matLodTop)
973 |
974 | usedShaders = list(set(usedShaders))
975 |
976 | for shader in usedShaders:
977 | matWid = QtWidgets.QTreeWidgetItem()
978 | matWid.setText(0, shader)
979 | matWid.setText(1, str(shaderDict[shader]))
980 | matLodTop.addChild(matWid)
981 | matWid.selectMe = [shader]
982 | self.export_tree.sortItems(0, QtCore.Qt.SortOrder(0))
983 |
984 | matLodTop.setText(0, 'MATERIALS (' + str(len(usedShaders)) + ')')
985 | matLodTop.selectMe = usedShaders
986 |
987 | lodWid.selectMe = usedShaders + meshes
988 |
989 | allMeshRelated.extend(lodWid.selectMe)
990 |
991 | meshTop.selectMe = allMeshRelated
992 |
993 |
994 |
995 | #anim branch
996 | animTop = QtWidgets.QTreeWidgetItem()
997 |
998 | animTop.selectMe = uNode.joints
999 |
1000 | if uNode.export_root:
1001 | jnts = uNode.joints
1002 | if jnts:
1003 | animTop.setText(0, 'ANIMATION: (' + str(len(uNode.joints)) + ' JOINTS)')
1004 | else:
1005 | animTop.setText(0, 'ANIMATION: NO SKELETON ROOT SET')
1006 | animTop.setForeground(0, red)
1007 | wid1.setBackground(0, widRed)
1008 | animTop.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable)
1009 | animTop.setCheckState(0, QtCore.Qt.Checked)
1010 | wid1.addChild(animTop)
1011 |
1012 | #anims
1013 | animWid = QtWidgets.QTreeWidgetItem()
1014 | animWid.setText(0, '<< CURRENT TIME RANGE >>')
1015 | animTop.addChild(animWid)
1016 |
1017 |
1018 | #meta branch
1019 | metaTop = QtWidgets.QTreeWidgetItem()
1020 | metaTop.setText(0, 'METADATA')
1021 | wid1.addChild(metaTop)
1022 |
1023 | # uexport node meta
1024 | ueWid = QtWidgets.QTreeWidgetItem()
1025 | ueWid.setText(0, 'UEXPORT NODE: ' + str(uNode.node))
1026 | metaTop.addChild(ueWid)
1027 | ueWid.selectMe = uNode.node
1028 |
1029 | #export path meta
1030 | fpathWid = QtWidgets.QTreeWidgetItem()
1031 | fpathWid.setText(0, 'EXPORT_FOLDER_PATH: ' + str(uNode.folder_path))
1032 | metaTop.addChild(fpathWid)
1033 |
1034 | fbxInfoRoot = QtWidgets.QTreeWidgetItem()
1035 | fbxInfoRoot.setText(0, 'FBX_INFO ')
1036 | metaTop.addChild(fbxInfoRoot)
1037 | fbxInfoRoot.setExpanded(True)
1038 | #fbx files meta
1039 | fbxFiles = QtWidgets.QTreeWidgetItem()
1040 | fbxFiles.setText(0, 'files ')
1041 | fbxInfoRoot.addChild(fbxFiles)
1042 | fileNum = 0
1043 | for lodNum in lodDicts:
1044 | if lodDicts[lodNum]['fbx_name']:
1045 | fbxMeshWid = QtWidgets.QTreeWidgetItem()
1046 | fbxMeshWid.setText(0, lodDicts[lodNum]['fbx_name'])
1047 | fbxFiles.addChild(fbxMeshWid)
1048 | fileNum += 1
1049 | fbxFiles.setText(0, 'files on disk (' + str(fileNum) + ')')
1050 |
1051 | #fbx settings meta
1052 | fbxProps = uNode.fbxExportProperties
1053 | fbxDefault = 1
1054 |
1055 | fbxSettings = QtWidgets.QTreeWidgetItem()
1056 | fbxSettings.setText(0, 'export settings (default)')
1057 | if fbxProps != {u'animInterpolation':u'quaternion', u'upAxis':u'default', u'triangulation':False}:
1058 | fbxSettings.setText(0, 'export settings (non-default)')
1059 | fbxDefault = 0
1060 | fbxInfoRoot.addChild(fbxSettings)
1061 |
1062 | #fbx settings
1063 | interpType = QtWidgets.QTreeWidgetItem()
1064 | interpType_CMB = self.makeTreeCmb(['INTERP TYPE: Quaternion','INTERP TYPE: Euler', 'INTERP TYPE: Resample'], 160)
1065 | interpType_CMB.currentIndexChanged.connect(functools.partial(self.setAnimInterpolation, interpType_CMB, uNode))
1066 |
1067 | if fbxProps['animInterpolation'].lower() == 'quaternion':
1068 | interpType_CMB.setCurrentIndex(0)
1069 | elif fbxProps['animInterpolation'].lower() == 'euler':
1070 | interpType_CMB.setCurrentIndex(1)
1071 | else:
1072 | interpType_CMB.setCurrentIndex(2)
1073 |
1074 |
1075 | upAxis = QtWidgets.QTreeWidgetItem()
1076 | upAxis_CMB = self.makeTreeCmb(['UP AXIS: Default','UP AXIS: Y', 'UP AXIS: Z'], 150)
1077 | upAxis_CMB.currentIndexChanged.connect(functools.partial(self.setUpAxis, upAxis_CMB, uNode))
1078 |
1079 | if fbxProps['upAxis'].lower() == 'default':
1080 | upAxis_CMB.setCurrentIndex(0)
1081 | elif fbxProps['upAxis'].lower() == 'y':
1082 | upAxis_CMB.setCurrentIndex(1)
1083 | else:
1084 | upAxis_CMB.setCurrentIndex(2)
1085 |
1086 | #triangulate checkbox and logic to write properties
1087 | triangulate = QtWidgets.QTreeWidgetItem()
1088 | triangulate_CHK = QtWidgets.QCheckBox(parent=self.export_tree)
1089 | triangulate_CHK.setText('Triangulate mesh')
1090 | triangulate_CHK.setChecked(fbxProps['triangulation'])
1091 | triangulate_CHK.stateChanged.connect(functools.partial(self.setTriangulation, triangulate_CHK, uNode))
1092 |
1093 | fbxSettings.addChild(interpType)
1094 | fbxSettings.addChild(upAxis)
1095 | fbxSettings.addChild(triangulate)
1096 | self.export_tree.setItemWidget(interpType, 0, interpType_CMB)
1097 | self.export_tree.setItemWidget(upAxis, 0, upAxis_CMB)
1098 | self.export_tree.setItemWidget(triangulate, 0, triangulate_CHK)
1099 |
1100 | #if the settings don't match the defaults, set expanded for the user to keep an eye
1101 | if not fbxDefault:
1102 | fbxSettings.setExpanded(True)
1103 |
1104 | #p4
1105 | if self.p4CHK.isChecked():
1106 | p4FileList = QtWidgets.QTreeWidgetItem()
1107 | p4FileList.setText(0, 'P4_FILE_LIST')
1108 | metaTop.addChild(p4FileList)
1109 | for lodNum in lodDicts:
1110 | if lodDicts[lodNum]['fbx_name']:
1111 | p4DepotFile = self.getP4Location(lodDicts[lodNum]['fbx_name'])
1112 | if p4DepotFile:
1113 | p4FileWid = QtWidgets.QTreeWidgetItem()
1114 | p4FileWid.setText(0, p4DepotFile[1])
1115 | p4FileList.addChild(p4FileWid)
1116 |
1117 | #set tool tip with p4 info
1118 | p4FileWid.setToolTip(0, p4DepotFile[0] + '\n' + p4DepotFile[2] + '\n' + p4DepotFile[3])
1119 | wid1.sortChildren(0, QtCore.Qt.SortOrder(0))
1120 |
1121 | def makeTreeCmb(self, items, width, select=None):
1122 | if items:
1123 | cmb = QtWidgets.QComboBox(parent=self.export_tree)
1124 | cmb.addItems(items)
1125 | cmb.setMaximumWidth(width)
1126 | cmb.setMaximumHeight(15)
1127 | return cmb
1128 | else:
1129 | return False
1130 |
1131 | #functions for auto-generated UI in the tree
1132 | def setTriangulation(self, chk, uNode, *args):
1133 | fbxDict = uNode.fbxExportProperties
1134 | if chk.isChecked():
1135 | fbxDict['triangulation'] = True
1136 | uNode.fbxExportProperties = fbxDict
1137 | else:
1138 | fbxDict['triangulation'] = False
1139 | uNode.fbxExportProperties = fbxDict
1140 |
1141 | def setUpAxis(self, cmb, uNode, *args):
1142 | fbxDict = uNode.fbxExportProperties
1143 | index = cmb.currentIndex()
1144 | if index == 0:
1145 | fbxDict['upAxis'] = 'default'
1146 | uNode.fbxExportProperties = fbxDict
1147 | elif index == 1:
1148 | fbxDict['upAxis'] = 'y'
1149 | uNode.fbxExportProperties = fbxDict
1150 | else:
1151 | fbxDict['upAxis'] = 'z'
1152 | uNode.fbxExportProperties = fbxDict
1153 |
1154 | def setAnimInterpolation(self, cmb, uNode, *args):
1155 | fbxDict = uNode.fbxExportProperties
1156 | index = cmb.currentIndex()
1157 | if index == 0:
1158 | fbxDict['animInterpolation'] = 'quaternion'
1159 | uNode.fbxExportProperties = fbxDict
1160 | elif index == 1:
1161 | fbxDict['animInterpolation'] = 'euler'
1162 | uNode.fbxExportProperties = fbxDict
1163 | else:
1164 | fbxDict['animInterpolation'] = 'resample'
1165 | uNode.fbxExportProperties = fbxDict
1166 |
1167 | def buildMissingFilesTree(self):
1168 | missingFileDict = self.missingNodes()
1169 | for f in missingFileDict.keys():
1170 | wid1 = QtWidgets.QTreeWidgetItem()
1171 | font = wid1.font(0)
1172 | font.setPointSize(15)
1173 |
1174 | wid1.setText(0,f)
1175 |
1176 | wid1.setText(2, missingFileDict[f]['path'])
1177 | wid1.setText(3, missingFileDict[f]['node'])
1178 | self.missingFilesTree.addTopLevelItem(wid1)
1179 | wid1.selectMe = missingFileDict[f]['node']
1180 |
1181 | self.missingFilesTree.header().resizeSections(QtWidgets.QHeaderView.ResizeToContents)
1182 |
1183 | def createUexportNode_FN(self):
1184 | if cmds.ls(sl=1):
1185 | if self.useRoot_CHK.isChecked():
1186 | rootName = self.rootName_CMB.currentText()
1187 | self.create(renderMeshes=cmds.ls(sl=1), rootJoint=rootName, lods=self.lodNum_SPIN.value())
1188 | else:
1189 | #TODO: modal picker with joint filter
1190 | pass
1191 |
1192 | def getTexturesP4_FN(self):
1193 | missingFileDict = self.missingNodes()
1194 | self.rePathFileNodesP4(missingFileDict)
1195 |
1196 | def export_FN(self):
1197 | #get what should be export in an exportTask fn
1198 | exportWidgets = self.getExportNodeWidgets()
1199 | for wid in exportWidgets:
1200 | snapConst = None
1201 | if self.snapRoot_CHK.isChecked():
1202 | node = self.snapRoot_CMB.currentText()
1203 | try:
1204 | if cmds.objExists(node):
1205 | snapConst = cmds.parentConstraint(node, wid.uExport.export_root)
1206 | else:
1207 | cmds.error('Unable to find node: ' + node)
1208 | except:
1209 | cmds.warning('Could not constrain root' + wid.uExport.export_root + ', is it already constrained?')
1210 | #export
1211 | mpath = cmds.file(sceneName=1, q=1)
1212 | fname = mpath.split('/')[-1]
1213 | fpath = mpath.replace(fname,'').replace('/','\\')
1214 | if wid.uExport.fbx_name:
1215 | if wid.uExport.fbx_name != '':
1216 | fname = wid.uExport.fbx_name
1217 | if wid.uExport.folder_path:
1218 | if wid.uExport.folder_path != '':
1219 | fpath = wid.uExport.folder_path
1220 |
1221 | #prompt user for save location
1222 | userPath = str()
1223 | if self.suppressSaveCHK.isChecked():
1224 | userPath = "{0}/{1}".format(wid.uExport.folder_path, wid.uExport.fbx_name)
1225 | if not os.path.isfile(userPath):
1226 | userPath = QtWidgets.QFileDialog.getSaveFileName(caption = 'Export ' + wid.uExport.name + ' to:', filter='FBX Files (*.fbx)', dir=fpath + '\\' + fname)[0]
1227 | else:
1228 | userPath = QtWidgets.QFileDialog.getSaveFileName(caption = 'Export ' + wid.uExport.name + ' to:', filter='FBX Files (*.fbx)', dir=fpath + '\\' + fname)[0]
1229 |
1230 | if userPath:
1231 | wid.uExport.fbx_name = userPath.split('/')[-1]
1232 | wid.uExport.folder_path = userPath.replace(wid.uExport.fbx_name,'')
1233 |
1234 | #if set to save loc, save it
1235 | if self.rememberSaveCHK.isChecked():
1236 | cmds.setAttr(wid.uExport.name + '.folder_path', wid.uExport.folder_path, type='string')
1237 | cmds.setAttr(wid.uExport.name + '.fbx_name', wid.uExport.fbx_name, type='string')
1238 |
1239 | #set initial lod save names
1240 | if not wid.uExport.fbx_name_LOD0:
1241 | FbxBaseName = wid.uExport.fbx_name.split('.')[0]
1242 | wid.uExport.fbx_name_LOD0 = wid.uExport.fbx_name
1243 | wid.uExport.fbx_name_LOD1 = FbxBaseName + '_LOD1.fbx'
1244 | wid.uExport.fbx_name_LOD2 = FbxBaseName + '_LOD2.fbx'
1245 | wid.uExport.fbx_name_LOD3 = FbxBaseName + '_LOD3.fbx'
1246 | wid.uExport.fbx_name_LOD4 = FbxBaseName + '_LOD4.fbx'
1247 |
1248 |
1249 | meshChk = 1
1250 | animChk = 1
1251 | for c in range(0, wid.childCount()):
1252 | if 'MESH' in wid.child(c).text(0):
1253 | if wid.child(c).checkState(0) == QtCore.Qt.Checked:
1254 | pass
1255 | else:
1256 | meshChk = 0
1257 |
1258 | start = time.time()
1259 | #put export into the uExport class later
1260 | export_success = self.export(wid.uExport, path=userPath, mesh=meshChk)
1261 | if export_success:
1262 | elapsed = (time.time() - start)
1263 | print 'uExport>>> Exported ', wid.text(0), 'to', userPath ,'in %.2f seconds.' % elapsed
1264 |
1265 | else:
1266 | cmds.warning('Invalid path specified: [' + str(userPath) + ']')
1267 | #cleanup constraint
1268 | if snapConst: cmds.delete(snapConst)
1269 |
1270 | def getExportNodeWidgets(self):
1271 | nodes = []
1272 | for i in range(0, self.export_tree.topLevelItemCount()):
1273 | if self.export_tree.topLevelItem(i).checkState(0) == QtCore.Qt.Checked:
1274 | nodes.append(self.export_tree.topLevelItem(i))
1275 | return nodes
1276 |
1277 |
1278 | def check_status(self):
1279 | for i in range(0, self.export_tree.topLevelItemCount()):
1280 | if self.export_tree.topLevelItem(i).checkState(0) == QtCore.Qt.Unchecked:
1281 | for c in range(0, self.export_tree.topLevelItem(i).childCount()):
1282 | self.export_tree.topLevelItem(i).child(c).setCheckState(0,QtCore.Qt.Unchecked)
1283 |
1284 | def itemClicked(self, *args):
1285 | #select nodes if there is selection metadata
1286 | if self.mousePress is 'left':
1287 | if hasattr(args[0], 'selectMe'):
1288 | cmds.select(args[0].selectMe)
1289 |
1290 |
1291 | ## P4 CRAP
1292 | ########################################################################
1293 | #check that P4 exists
1294 | def getP4Location(self, asset, root='//depot/ArtSource/', debug=1):
1295 | from p4python.P4 import P4, P4Exception
1296 |
1297 | if asset:
1298 | p4 = P4()
1299 | try: p4.connect()
1300 | except:
1301 | print 'Cannot connect to P4!'
1302 | return False
1303 |
1304 | try:
1305 | file = p4.run_files(root + '...' + asset)
1306 | depotLoc = file[0]['depotFile']
1307 | describe = p4.run_describe(file[0]['change'])
1308 | return [describe[0]['user'], depotLoc, describe[0]['desc'], file[0]['change']]
1309 | except Exception as e:
1310 | print "findFileP4>>>> Cannot find file.", asset
1311 | if debug: print e
1312 | return False
1313 | finally:
1314 | p4.disconnect()
1315 |
1316 | def workspaceSelected(self):
1317 | self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,QtCore.QSettings.SystemScope, 'uExport', 'settings')
1318 | self.settings.setFallbacksEnabled(False)
1319 | # setPath() to try to save to current working directory
1320 | self.settings.setPath(QtCore.QSettings.IniFormat,QtCore.QSettings.SystemScope, './uExport_settings.ini')
1321 | self.settings.value('workspace', self.workSpaceCMB.currentText())
1322 |
1323 | def getP4Workspaces(self):
1324 | import socket
1325 | from p4python.P4 import P4, P4Exception
1326 |
1327 | #get computer name
1328 | host = socket.gethostname()
1329 |
1330 | p4 = P4()
1331 | try: p4.connect()
1332 | except:
1333 | print 'Cannot connect to P4!'
1334 | return False
1335 |
1336 | for ws in p4.run('clients', '-u', p4.user):
1337 | try:
1338 | if ws['Host'] == host:
1339 | self.workSpaceCMB.addItem(ws['client'])
1340 | except Exception as e:
1341 | print e
1342 | p4.disconnect()
1343 |
1344 | def colorTreeWidgetItemByName(self, tree, text, color):
1345 | root = tree.invisibleRootItem()
1346 | child_count = root.childCount()
1347 | retWid = None
1348 | for i in range(child_count):
1349 | wid = root.child(i)
1350 | if wid.text(0) == text:
1351 | wid.setForeground(0, color)
1352 | wid.setForeground(1, color)
1353 | wid.setForeground(2, color)
1354 | wid.setForeground(3, color)
1355 | retWid = wid
1356 | if retWid:
1357 | return retWid
1358 |
1359 | def rePathFileNodesP4(self, missingFileDict, debug=1, checkHash=False):
1360 | from p4python.P4 import P4, P4Exception
1361 |
1362 | if missingFileDict:
1363 | p4 = P4()
1364 | print self.workSpaceCMB.currentText()
1365 | p4.client = str(self.workSpaceCMB.currentText())
1366 | try: p4.connect()
1367 | except:
1368 | print 'Cannot connect to P4!'
1369 | return False
1370 |
1371 | for f in missingFileDict.keys():
1372 | floppedPath = missingFileDict[f]['path'].replace('\\', '/') + f
1373 | pathBreak = floppedPath.split('/')
1374 |
1375 | #find parents since there are dupe files in p4 often
1376 | parent = None
1377 | if len(pathBreak) > 1:
1378 | parent = pathBreak[-2]
1379 | parentParent = None
1380 | if len(pathBreak) > 2:
1381 | parentParent = pathBreak[-3]
1382 |
1383 | depotFileToGrab = None
1384 |
1385 | files = None
1386 | try:
1387 | files = p4.run_files(self.p4RootLINE.text() + '...' + f)
1388 | except:
1389 | pass
1390 | if files:
1391 | if len(files) > 1:
1392 | if debug:
1393 | print 'rePathFileNodesP4>>>> Multiple files [', len(files), '] found in depot search path with the name: ' + f
1394 | print 'P4 file paths:'
1395 | for i in range(0, len(files)):
1396 | print files[i]['depotFile']
1397 | for i in range(0, len(files)):
1398 | try:
1399 | newParent = files[i]['depotFile'].split('/')[-2]
1400 | if newParent == parent:
1401 | if 'delete' in p4.run('fstat', files[i]['depotFile'])[0]['headAction']:
1402 | print 'INVALID PATH: p4 asset head revision is deleted or moved. depotFile:', files[i]['depotFile']
1403 | continue
1404 | depotFileToGrab = files[i]['depotFile']
1405 | continue
1406 | if checkHash:
1407 | dupeCheck(files[i]['depotFile'])
1408 |
1409 | #color widget red, change INFO
1410 | widc = self.colorTreeWidgetItemByName(self.missingFilesTree, f, QtGui.QColor(200, 75, 75, 255))
1411 | widc.setText(1, 'NOT FOUND')
1412 |
1413 | except Exception as e:
1414 | if debug:
1415 | print e
1416 | print 'rePathFileNodesP4>>>> No file path similarities to path:', floppedPath
1417 | else:
1418 | print 'rePathFileNodesP4>>>> File found on depot: ', files[0]['depotFile']
1419 | depotFileToGrab = files[0]['depotFile']
1420 |
1421 | else:
1422 | print 'rePathFileNodesP4>>>> FILE NOT FOUND IN PERFORCE SEARCH PATH - fName:', f, 'searchPath:', self.p4RootLINE.text()
1423 | #color widget red, change INFO
1424 | widc = self.colorTreeWidgetItemByName(self.missingFilesTree, f, QtGui.QColor(200, 75, 75, 255))
1425 | widc.setText(1, 'NOT FOUND')
1426 |
1427 |
1428 | #if we found it in the depot
1429 | if depotFileToGrab:
1430 | print 'GRABBING: ', depotFileToGrab
1431 | try:
1432 | p4.run('sync', ' -f', depotFileToGrab)
1433 | except Exception as e:
1434 | if 'up-to-date' in e.message:
1435 | pass
1436 | else:
1437 | print e
1438 |
1439 | fileFstat = p4.run('fstat', depotFileToGrab)[0]
1440 | print fileFstat
1441 | newPath = fileFstat['clientFile']
1442 | newPath = newPath.replace('\\','/')
1443 |
1444 | if debug: print 'rePathFileNodesP4>>>> NEW PATH>>', newPath, '\n\n'
1445 | cmds.setAttr((missingFileDict[f]['node'] + '.fileTextureName'), newPath, type='string')
1446 | #color widget green, change info
1447 | widc = self.colorTreeWidgetItemByName(self.missingFilesTree, f, QtGui.QColor(40, 230, 160, 255))
1448 | widc.setText(1, 'FOUND')
1449 |
1450 | self.repaint()
1451 |
1452 | p4.disconnect()
1453 |
1454 |
1455 | ## UEXPORT NODE
1456 | ########################################################################
1457 | def getExportNodes(self):
1458 | return cmds.ls('*.uexport_ver', o=1, r=1)
1459 |
1460 | def connectRoot(self, uNode, root, rewire=1):
1461 | try:
1462 | cmds.undoInfo(openChunk=True)
1463 | if rewire:
1464 | conns = cmds.listConnections(uNode + '.export_root', plugs=1, source=1)
1465 | if conns:
1466 | for conn in conns:
1467 | cmds.disconnectAttr(conn, uNode + '.export_root')
1468 |
1469 | if not attrExists(root+'.export'):
1470 | cmds.addAttr(root, longName='export', attributeType='message')
1471 | cmds.connectAttr(root + '.export', uNode + '.export_root' )
1472 | except Exception as e:
1473 | print e
1474 | finally:
1475 | cmds.undoInfo(closeChunk=True)
1476 |
1477 | def create(self, renderMeshes=None, rootJoint=None, strName='uExport', lods=1):
1478 |
1479 | uExportNode = None
1480 | if cmds.objExists(strName):
1481 | #later re-hook up
1482 | #set uExport
1483 | cmds.warning('uExport>>>>> uExport node already exists with name: ' + strName)
1484 | pass
1485 | else:
1486 | try:
1487 | cmds.undoInfo(openChunk=True)
1488 |
1489 | text, ok = QtWidgets.QInputDialog.getText(None, 'Creating uExport Node', 'Enter node name:', text='uExport')
1490 | if text:
1491 | strName = text
1492 |
1493 | uExportNode = cmds.group(em=1, name=strName)
1494 | cmds.addAttr(uExportNode, ln='export_root', at='message')
1495 | cmds.addAttr(uExportNode, ln='materials', at='message')
1496 | cmds.addAttr(uExportNode, ln='uexport_ver', dt='string')
1497 | cmds.setAttr(uExportNode + '.uexport_ver', '1.0', type='string')
1498 | cmds.addAttr(uExportNode, ln='folder_path', dt='string')
1499 | cmds.addAttr(uExportNode, ln='asset_name', dt='string')
1500 | cmds.addAttr(uExportNode, ln='fbx_name', dt='string')
1501 | cmds.addAttr(uExportNode, ln='lodNum', at='byte')
1502 |
1503 | cmds.setAttr(uExportNode + '.lodNum', lods)
1504 |
1505 | if self.createArtMetadata_CHK.isChecked():
1506 | #not used atm
1507 | cmds.addAttr(uExportNode, ln='joint_mover_template', dt='string')
1508 | cmds.addAttr(uExportNode, ln='skeleton_template', dt='string')
1509 | cmds.addAttr(uExportNode, ln='pre_script', dt='string')
1510 | cmds.addAttr(uExportNode, ln='post_script', dt='string')
1511 | cmds.addAttr(uExportNode, ln='export_file', dt='string')
1512 | cmds.addAttr(uExportNode, ln='anim_file', dt='string')
1513 | cmds.addAttr(uExportNode, ln='skeleton_uasset', dt='string')
1514 | cmds.addAttr(uExportNode, ln='skelmesh_uasset', dt='string')
1515 | cmds.addAttr(uExportNode, ln='physics_uasset', dt='string')
1516 | cmds.addAttr(uExportNode, ln='thumbnail_large', dt='string')
1517 | cmds.addAttr(uExportNode, ln='thumbnail_small', dt='string')
1518 |
1519 | text, ok = QtWidgets.QInputDialog.getText(None, 'Defining Asset', 'Enter asset name:', text='myAsset')
1520 |
1521 | if text:
1522 | #internal ART stuff
1523 | cmds.setAttr(uExportNode + '.asset_name', text, type='string')
1524 |
1525 | except Exception as e:
1526 | cmds.warning(e)
1527 | print 'Locals: ' + str(locals())
1528 | finally:
1529 | cmds.undoInfo(closeChunk=True)
1530 |
1531 | if uExportNode:
1532 |
1533 | uNode = uExport(uExportNode)
1534 |
1535 | try:
1536 | if renderMeshes:
1537 | uNode.rendermeshes_LOD0 = renderMeshes
1538 |
1539 | #rootJoint
1540 | if rootJoint:
1541 | if not attrExists(rootJoint+'.export'):
1542 | cmds.addAttr(rootJoint, longName='export', attributeType='message')
1543 | cmds.connectAttr(rootJoint + '.export', uExportNode + '.export_root')
1544 | else:
1545 | cmds.warning('No root joint or could not find root: ' + str(rootJoint))
1546 |
1547 | except Exception as e:
1548 | print cmds.warning(e)
1549 | print 'Locals: ' + str(locals())
1550 |
1551 | self.tabWidget.setCurrentIndex(0)
1552 | self.refreshUI()
1553 |
1554 |
1555 | ## MISSING TEXTURES
1556 | ########################################################################
1557 |
1558 | def missingNodes(self):
1559 | fileNodes = cmds.ls(type='file')
1560 | missingFiles = {}
1561 | for f in fileNodes:
1562 | filePath = cmds.getAttr(f + '.fileTextureName')
1563 | if filePath != '':
1564 | if not cmds.file(filePath, exists=1, q=1):
1565 | fileName = filePath.split('/')[-1]
1566 | path = filePath.replace(fileName,'')
1567 | missingFiles[fileName] = {'path':path, 'node':f}
1568 | return missingFiles
1569 |
1570 |
1571 | ## SANITY CHECK
1572 | ########################################################################
1573 |
1574 | #general methods
1575 | def hasPrefix(self, obj, prefix, fix=1):
1576 | if obj.startswith(prefix):
1577 | return True
1578 | else:
1579 | return False
1580 |
1581 | #triggers
1582 | #TODO: remove issue class usage, hook up
1583 | '''
1584 | def matCheck(self, meshes, debug=1, mNum=1):
1585 | import re
1586 |
1587 | issues = []
1588 | allShaders = []
1589 |
1590 | for mesh in meshes:
1591 |
1592 | shaders = []
1593 | shaders.extend(getAssocShaders(mesh))
1594 |
1595 | shaders = set(shaders)
1596 | if debug:
1597 | print 'Shaders found:', shaders
1598 |
1599 | for shader in shaders:
1600 |
1601 | allShaders.append(shader)
1602 |
1603 | shaderNum = re.search(r'\d+$', shader).group()
1604 | if not shaderNum:
1605 | issue = Issue(shader, 'Shader [' + shader + '] does not end in a number', assocNode=mesh)
1606 | issues.append(issue)
1607 |
1608 | if not hasPrefix(shader,'M_'):
1609 | issue = Issue(shader, 'Shader [' + shader + '] does not have the prefix \'M_\'', assocNode=mesh)
1610 | issues.append(issue)
1611 | #FIX
1612 | if shader.startswith('m_'):
1613 | print shader
1614 | shader[0:1] = 'M_'
1615 | #cmds.rename(shader, )
1616 |
1617 | if '_skin' not in shader:
1618 | issue = Issue(shader, 'Shader [' + shader + '] does not have the suffix \'_skin\'', assocNode=mesh)
1619 | issues.append(issue)
1620 |
1621 | shaderDict = {}
1622 | for shader in set(allShaders):
1623 | shaderNum = re.search(r'\d+$', shader).group()
1624 | if shaderNum in shaderDict.keys():
1625 | issue = Issue(shader, 'Shader [' + shader + '] shader with slot number already exists: ' + shaderDict[shaderNum], assocNode=shaderDict[shaderNum])
1626 | issues.append(issue)
1627 | else:
1628 | shaderDict[shaderNum] = shader
1629 |
1630 | if mNum:
1631 | for key in shaderDict.keys():
1632 | pass
1633 | #cmds.rename(shaderDict[key], )
1634 |
1635 | return issues
1636 | '''
1637 |
1638 |
1639 | ## EXPORT
1640 | ########################################################################
1641 |
1642 | #TODO: Find and export blendshape meshes!
1643 | def setExportFlags(self, uNode):
1644 |
1645 | # set export properties from the fbxExportPropertiesDict of the uNode
1646 | fbxDict = uNode.fbxExportProperties
1647 | if fbxDict['triangulation'] == True:
1648 | mel.eval("FBXExportTriangulate -v true")
1649 | else:
1650 | mel.eval("FBXExportTriangulate -v false")
1651 |
1652 | # Mesh
1653 | mel.eval("FBXExportSmoothingGroups -v true")
1654 | mel.eval("FBXExportHardEdges -v false")
1655 | mel.eval("FBXExportTangents -v true")
1656 | mel.eval("FBXExportInstances -v false")
1657 | mel.eval("FBXExportInAscii -v true")
1658 | mel.eval("FBXExportSmoothMesh -v false")
1659 |
1660 | # Animation
1661 | mel.eval("FBXExportBakeResampleAnimation -v true")
1662 | mel.eval("FBXExportBakeComplexAnimation -v true")
1663 | mel.eval("FBXExportBakeComplexStart -v "+str(cmds.playbackOptions(minTime=1, q=1)))
1664 | mel.eval("FBXExportBakeComplexEnd -v "+str(cmds.playbackOptions(maxTime=1, q=1)))
1665 | mel.eval("FBXExportReferencedAssetsContent -v true")
1666 | mel.eval("FBXExportBakeComplexStep -v 1")
1667 | mel.eval("FBXExportUseSceneName -v false")
1668 | mel.eval("FBXExportQuaternion -v quaternion")
1669 | mel.eval("FBXExportShapes -v true")
1670 | mel.eval("FBXExportSkins -v true")
1671 |
1672 | if fbxDict['animInterpolation'] == 'euler':
1673 | mel.eval("FBXExportQuaternion -v euler")
1674 | elif fbxDict['animInterpolation'] == 'resample':
1675 | mel.eval("FBXExportQuaternion -v resample")
1676 |
1677 | if fbxDict['upAxis'].lower() == 'y':
1678 | print 'FBX EXPORT OVERRIDE: setting y up axis'
1679 | mel.eval("FBXExportUpAxis y")
1680 | elif fbxDict['upAxis'].lower() == 'z':
1681 | print 'FBX EXPORT OVERRIDE: setting Z up axis'
1682 | mel.eval("FBXExportUpAxis z")
1683 |
1684 | #garbage we don't want
1685 | # Constraints
1686 | mel.eval("FBXExportConstraints -v false")
1687 | # Cameras
1688 | mel.eval("FBXExportCameras -v false")
1689 | # Lights
1690 | mel.eval("FBXExportLights -v false")
1691 | # Embed Media
1692 | mel.eval("FBXExportEmbeddedTextures -v false")
1693 | # Connections
1694 | mel.eval("FBXExportInputConnections -v false")
1695 |
1696 | def export(self, uNode, mesh=1, anim=1, path=None,bake=False):
1697 |
1698 | # check if the file is checked out/writeable
1699 | if os.path.isfile(path):
1700 | file_stat = os.stat(path)[0]
1701 | if not file_stat & stat.S_IWRITE:
1702 | message = "Please ensure you have {0} checked out".format(path)
1703 | return QtWidgets.QMessageBox.information(QtWidgets.QWidget(), "Export Warning", message)
1704 |
1705 | toExport = []
1706 |
1707 | if not path:
1708 | oldPath = path
1709 | path = cmds.file(sceneName=1, q=1)
1710 | cmds.warning('No valid path set for export [' + str(oldPath) + ']/nExporting to Maya file loc: ' + path)
1711 | toExport.extend(cmds.listRelatives(uNode.export_root, type='joint',allDescendents=True,f=1))
1712 |
1713 |
1714 | #kvassey -- adding support for baking to root in rig before export
1715 | if self.bakeRoot_CHK.isChecked():
1716 | print "Bake Root Checked"
1717 | #copy root skeleton with input connections under new group
1718 | cmds.select(cl=True)
1719 | currRoot = uNode.export_root
1720 | exportSkel = cmds.duplicate(currRoot, un=True, rc=False, po=False)
1721 | tempGrp = cmds.group(exportSkel[0])
1722 | #rename root joint
1723 | dupRoot = cmds.rename(exportSkel[0], currRoot)
1724 | #bake
1725 | startTime = cmds.playbackOptions(min=True, q=True)
1726 | endTime = cmds.playbackOptions(max=True, q=True)
1727 | cmds.bakeResults(dupRoot, sm=True, hi="below", s=True, sb=1, dic=True, t=(startTime, endTime))
1728 |
1729 | #move FBX export inside here, skip rest.
1730 | #toExport.extend(cmds.listRelatives(cmds.listConnections(dupRoot), type='joint',allDescendents=True))
1731 | #cmds.select(toExport)
1732 | cmds.select(dupRoot, r=True, hi=True)
1733 | toExport = cmds.ls(sl=True, type='joint')
1734 | cmds.select(toExport, r=True)
1735 | self.setExportFlags(uNode)
1736 | # Export!
1737 | print "FBXExport -f \""+ path +"\" -s"
1738 | mel.eval("FBXExport -f \""+ path +"\" -s")
1739 |
1740 | cmds.delete(tempGrp)
1741 | toExport = []
1742 |
1743 |
1744 | #Assuming the meshes are skinned, maybe work with static later
1745 | else:
1746 | exportScript = False
1747 |
1748 | if mesh:
1749 | #we're not exporting LODs
1750 | if not self.exportLODs_CHK.isChecked():
1751 | meshes = uNode.rendermeshes_LOD0
1752 | if meshes:
1753 | toExport.extend(uNode.rendermeshes_LOD0)
1754 | else:
1755 | cmds.warning('uExport>>> export: No rendermeshes found.')
1756 | #ok, we're exporting LODs
1757 | else:
1758 | lodDicts = uNode.getLodDicts()
1759 | if lodDicts:
1760 | if self.exportLODs_CHK.isChecked():
1761 | for lodNum in lodDicts:
1762 | #check that there are meshes to be exported at this LOD
1763 | if lodDicts[lodNum]['meshes']:
1764 | #check if there is a script
1765 | if lodDicts[lodNum]['export_script']:
1766 | if self.resetAfterExport_CHK.isChecked():
1767 | # Warn the user that this operation will save their file.
1768 | saveResult = 'Yes'
1769 | if not self.suppressSaveCHK.isChecked():
1770 | saveResult = cmds.confirmDialog(title = "Save Warning", message="In order to continue your file must be saved. Would you like to save it? If yes it will be saved and after the operation is complete your file will be re-opened.", button = ["Save and Continue", "Continue Without Saving"], defaultButton='Save and Continue', cancelButton='Continue Without Saving', dismissString='Continue Without Saving')
1771 | if saveResult == 'Yes':
1772 | cmds.file(save=True)
1773 | else:
1774 | cmds.warning('You chose not to save changes. Big-boy pants.')
1775 |
1776 | filePath = lodDicts[lodNum]['export_script']
1777 |
1778 | exportScript = True
1779 | if os.path.isfile(filePath):
1780 | uExportNode = uNode
1781 | execfile(filePath)
1782 | else:
1783 | cmds.error('UEXPORT>> Cannot find export script: ' + filePath)
1784 |
1785 | #add items for export
1786 | toExport.extend(lodDicts[lodNum]['meshes'])
1787 | toExport.extend(cmds.listRelatives(uNode.export_root, type='joint',allDescendents=True, f=1))
1788 |
1789 | #setup export
1790 | cmds.select(toExport)
1791 | self.setExportFlags(uNode)
1792 | new_fpath = path[:-4] + '_LOD' + str(lodNum) + '.fbx'
1793 |
1794 | #look for lod name overrides
1795 | if lodDicts[lodNum]['fbx_name']:
1796 | justFilePath = path.replace(path.split('/')[-1],'')
1797 | new_fpath = justFilePath + lodDicts[lodNum]['fbx_name']
1798 |
1799 | # Export!
1800 | print "FBXExport -f \"" + new_fpath + "\" -s"
1801 | mel.eval("FBXExport -f \"" + new_fpath + "\" -s")
1802 |
1803 | if exportScript:
1804 | # Re-open the file without saving.
1805 | fullPath = cmds.file(q = True, sceneName = True)
1806 | cmds.file(fullPath, open=True, f=True)
1807 |
1808 | #clear sel and export list
1809 | cmds.select(d=1)
1810 | toExport = []
1811 |
1812 | if not self.exportLODs_CHK.isChecked():
1813 | if anim:
1814 | toExport.extend(cmds.listRelatives(uNode.export_root, type='joint', allDescendents=True, f=1) or [])
1815 |
1816 | cmds.select(toExport)
1817 | self.setExportFlags(uNode)
1818 | # Export!
1819 | print "FBXExport -f \""+ path +"\" -s"
1820 | mel.eval("FBXExport -f \""+ path +"\" -s")
1821 | return True
1822 |
1823 |
1824 | if __name__ == '__main__':
1825 | show()
1826 |
--------------------------------------------------------------------------------
/uExport.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 464
10 | 572
11 |
12 |
13 |
14 | uExport v1
15 |
16 |
17 |
18 | 2
19 |
20 |
21 | 3
22 |
23 | -
24 |
25 |
26 | 0
27 |
28 |
29 |
30 | EXPORT
31 |
32 |
33 |
34 | 2
35 |
36 |
37 | 3
38 |
39 |
-
40 |
41 |
42 | QFrame::Plain
43 |
44 |
45 | true
46 |
47 |
48 |
49 | 1
50 |
51 |
52 |
53 |
54 | -
55 |
56 |
-
57 |
58 |
59 | Export LODs
60 |
61 |
62 |
63 | -
64 |
65 |
66 | Qt::Horizontal
67 |
68 |
69 |
70 | 40
71 | 20
72 |
73 |
74 |
75 |
76 | -
77 |
78 |
79 | Qt::Horizontal
80 |
81 |
82 |
83 | 40
84 | 20
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 | false
93 |
94 |
95 |
96 | 7
97 | 75
98 | true
99 |
100 |
101 |
102 | fbxVer
103 |
104 |
105 |
106 | -
107 |
108 |
109 | p4connect
110 |
111 |
112 | true
113 |
114 |
115 |
116 |
117 |
118 | -
119 |
120 |
121 |
122 | 16
123 | 75
124 | true
125 |
126 |
127 |
128 | EXPORT SELECTED ITEMS
129 |
130 |
131 |
132 | -
133 |
134 |
135 | 3
136 |
137 |
138 | QLayout::SetMinimumSize
139 |
140 |
-
141 |
142 |
143 |
144 | 67
145 | 16777215
146 |
147 |
148 |
149 |
150 | 10
151 | 75
152 | true
153 |
154 |
155 |
156 | REFRESH
157 |
158 |
159 |
160 | -
161 |
162 |
163 | Qt::Horizontal
164 |
165 |
166 |
167 | 40
168 | 20
169 |
170 |
171 |
172 |
173 | -
174 |
175 |
176 | Remember save locations
177 |
178 |
179 | true
180 |
181 |
182 |
183 | -
184 |
185 |
186 | Suppress save dlg
187 |
188 |
189 | true
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 | ADV
200 |
201 |
202 | -
203 |
204 |
205 |
206 | 11
207 | 75
208 | true
209 |
210 |
211 |
212 | UEXPORT NODE MANAGEMENT
213 |
214 |
215 |
216 | 2
217 |
218 |
219 | 3
220 |
221 |
-
222 |
223 |
-
224 |
225 |
226 | false
227 |
228 |
229 |
230 | 10
231 | 50
232 | false
233 |
234 |
235 |
236 | Ask me to choose root
237 |
238 |
239 |
240 | -
241 |
242 |
243 |
244 | 10
245 | 50
246 | false
247 |
248 |
249 |
250 | Use root name:
251 |
252 |
253 | true
254 |
255 |
256 |
257 | -
258 |
259 |
260 |
261 | 16777215
262 | 21
263 |
264 |
265 |
266 |
267 | 10
268 | 50
269 | false
270 |
271 |
272 |
273 | true
274 |
275 |
-
276 |
277 | root
278 |
279 |
280 |
281 |
282 | -
283 |
284 |
285 |
286 | 10
287 | 50
288 | false
289 |
290 |
291 |
292 | LOD#
293 |
294 |
295 |
296 | -
297 |
298 |
299 |
300 | 33
301 | 21
302 |
303 |
304 |
305 |
306 | 10
307 | 50
308 | false
309 |
310 |
311 |
312 | 10
313 |
314 |
315 | 4
316 |
317 |
318 |
319 | -
320 |
321 |
322 | Qt::Horizontal
323 |
324 |
325 |
326 | 40
327 | 20
328 |
329 |
330 |
331 |
332 |
333 |
334 | -
335 |
336 |
-
337 |
338 |
339 |
340 | 8
341 | 75
342 | true
343 |
344 |
345 |
346 | Stamp ART mData
347 |
348 |
349 |
350 | -
351 |
352 |
353 | false
354 |
355 |
356 |
357 | 8
358 | 50
359 | false
360 |
361 |
362 |
363 | D:\Build\usr\jeremy_ernst\MayaTools
364 |
365 |
366 |
367 |
368 |
369 | -
370 |
371 |
372 |
373 | 8
374 |
375 |
376 |
377 | Stamp Perforce metadata
378 |
379 |
380 |
381 | -
382 |
383 |
-
384 |
385 |
386 | CREATE NODE
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 | -
396 |
397 |
398 |
399 | 11
400 | 75
401 | true
402 |
403 |
404 |
405 | FBX EXPORT OPTIONS
406 |
407 |
408 |
409 | 2
410 |
411 |
412 | 3
413 |
414 |
-
415 |
416 |
-
417 |
418 |
419 | Animation Interpolation:
420 |
421 |
422 |
423 | -
424 |
425 |
426 |
427 | 10
428 | 50
429 | false
430 |
431 |
432 |
433 | Quaternion
434 |
435 |
436 | true
437 |
438 |
439 |
440 | -
441 |
442 |
443 |
444 | 10
445 | 50
446 | false
447 |
448 |
449 |
450 | Euler
451 |
452 |
453 |
454 | -
455 |
456 |
457 |
458 | 10
459 | 50
460 | false
461 |
462 |
463 |
464 | Resample
465 |
466 |
467 |
468 | -
469 |
470 |
471 | Qt::Horizontal
472 |
473 |
474 |
475 | 40
476 | 20
477 |
478 |
479 |
480 |
481 |
482 |
483 | -
484 |
485 |
-
486 |
487 |
488 | Stated Up Axis:
489 |
490 |
491 |
492 | -
493 |
494 |
495 |
496 | 10
497 | 50
498 | false
499 |
500 |
501 |
-
502 |
503 | Scene Default
504 |
505 |
506 | -
507 |
508 | Y
509 |
510 |
511 | -
512 |
513 | Z
514 |
515 |
516 |
517 |
518 | -
519 |
520 |
521 | Qt::Horizontal
522 |
523 |
524 |
525 | 40
526 | 20
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 | -
537 |
538 |
539 |
540 | 11
541 | 75
542 | true
543 |
544 |
545 |
546 | LODSCRIPT EXPORT OPTIONS
547 |
548 |
549 |
-
550 |
551 |
552 |
553 | 10
554 | 50
555 | false
556 |
557 |
558 |
559 | Reset scenes after export scripts
560 |
561 |
562 | true
563 |
564 |
565 |
566 | -
567 |
568 |
569 |
570 | 10
571 | 50
572 | false
573 |
574 |
575 |
576 | Suppress LOD save dlgs
577 |
578 |
579 | true
580 |
581 |
582 |
583 |
584 |
585 |
586 | -
587 |
588 |
589 | Qt::Vertical
590 |
591 |
592 |
593 | 20
594 | 40
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 | TOOLS
604 |
605 |
606 |
607 | 3
608 |
609 |
610 | 3
611 |
612 |
613 | 3
614 |
615 |
616 | 2
617 |
618 | -
619 |
620 |
621 |
622 | 75
623 | true
624 |
625 |
626 |
627 | CINEMATICS
628 |
629 |
630 |
631 | 2
632 |
633 |
634 | 3
635 |
636 |
-
637 |
638 |
-
639 |
640 |
641 | true
642 |
643 |
644 |
645 | 50
646 | false
647 |
648 |
649 |
650 | Snap root:
651 |
652 |
653 |
654 | -
655 |
656 |
657 |
658 | 155
659 | 0
660 |
661 |
662 |
663 |
664 | 50
665 | false
666 |
667 |
668 |
669 | true
670 |
671 |
-
672 |
673 | NONE
674 |
675 |
676 | -
677 |
678 | Kite_Boy:master_anim
679 |
680 |
681 |
682 |
683 | -
684 |
685 |
686 | Qt::Horizontal
687 |
688 |
689 |
690 | 40
691 | 20
692 |
693 |
694 |
695 |
696 |
697 |
698 | -
699 |
700 |
701 |
702 | 50
703 | false
704 |
705 |
706 |
707 | Bake Root (exporting attrs on joints pre Maya 2016 Ext 2)
708 |
709 |
710 |
711 |
712 |
713 |
714 | -
715 |
716 |
717 |
718 | 75
719 | true
720 |
721 |
722 |
723 | A.R.T. UTILITIES
724 |
725 |
726 |
727 | 2
728 |
729 |
730 | 3
731 |
732 |
-
733 |
734 |
735 |
736 | 10
737 | 75
738 | true
739 |
740 |
741 |
742 | SWAP UNKNOWN ART NODES WITH NETWORK NODES
743 |
744 |
745 |
746 |
747 |
748 |
749 | -
750 |
751 |
752 |
753 | 75
754 | true
755 |
756 |
757 |
758 | PERFORCE OPTIONS
759 |
760 |
761 |
762 | 2
763 |
764 |
765 | 3
766 |
767 |
-
768 |
769 |
770 | 3
771 |
772 |
-
773 |
774 |
775 | 2
776 |
777 |
-
778 |
779 |
780 |
781 | 50
782 | false
783 |
784 |
785 |
786 | Project Root:
787 |
788 |
789 |
790 | -
791 |
792 |
793 |
794 | 50
795 | false
796 |
797 |
798 |
799 | //depot/ArtSource/Orion/
800 |
801 |
802 |
803 |
804 |
805 | -
806 |
807 |
808 | 3
809 |
810 |
-
811 |
812 |
813 |
814 | 1
815 | 0
816 |
817 |
818 |
819 |
820 | 71
821 | 16777215
822 |
823 |
824 |
825 | WorkSpace:
826 |
827 |
828 |
829 | -
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 | -
840 |
841 |
842 |
843 | 75
844 | true
845 |
846 |
847 |
848 | MISSING TEXTURES
849 |
850 |
851 |
852 | 2
853 |
854 |
855 | 3
856 |
857 |
-
858 |
859 |
860 | 4
861 |
862 |
863 |
864 | FILE
865 |
866 |
867 |
868 |
869 | INFO
870 |
871 |
872 |
873 |
874 | PATH
875 |
876 |
877 |
878 |
879 | NODE
880 |
881 |
882 |
883 |
884 | -
885 |
886 |
-
887 |
888 |
889 | false
890 |
891 |
892 |
893 | 11
894 | 75
895 | true
896 |
897 |
898 |
899 | FIND, SYNC, AND REPATH FILES (P4)
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 | createArtMetadata_CHK
918 | toggled(bool)
919 | artPath
920 | setEnabled(bool)
921 |
922 |
923 | 104
924 | 421
925 |
926 |
927 | 193
928 | 423
929 |
930 |
931 |
932 |
933 |
934 |
--------------------------------------------------------------------------------