|
| 1 | +from PySide import QtGui, QtCore |
| 2 | +import pysideuic |
| 3 | +import xml.etree.ElementTree as xml |
| 4 | +from cStringIO import StringIO |
| 5 | +from pymel.core import * |
| 6 | +import pymel.core as pm |
| 7 | +from pymel import * |
| 8 | +import maya.OpenMayaUI as apiUI |
| 9 | +import maya.OpenMaya as om |
| 10 | +import maya.cmds as cmds |
| 11 | +from itertools import izip |
| 12 | +import math |
| 13 | + |
| 14 | +def pairwise(iterable): |
| 15 | + a = iter(iterable) |
| 16 | + return izip(a, a) |
| 17 | + |
| 18 | +# Helper for loading Qt ui files from nathanhorne.com/?p=451 |
| 19 | +def loadUiType(uiFile): |
| 20 | + """ |
| 21 | + Pyside lacks the "loadUiType" command, so we have to convert the ui file to py code in-memory first |
| 22 | + and then execute it in a special frame to retrieve the form_class. |
| 23 | + """ |
| 24 | + parsed = xml.parse(uiFile) |
| 25 | + widget_class = parsed.find('widget').get('class') |
| 26 | + form_class = parsed.find('class').text |
| 27 | + |
| 28 | + with open(uiFile, 'r') as f: |
| 29 | + o = StringIO() |
| 30 | + frame = {} |
| 31 | + |
| 32 | + pysideuic.compileUi(f, o, indent=0) |
| 33 | + pyc = compile(o.getvalue(), '<string>', 'exec') |
| 34 | + exec pyc in frame |
| 35 | + |
| 36 | + #Fetch the base_class and form class based on their type in the xml from designer |
| 37 | + form_class = frame['Ui_%s'%form_class] |
| 38 | + base_class = eval('QtGui.%s'%widget_class) |
| 39 | + return form_class, base_class |
| 40 | + |
| 41 | +# UE4 Static Mesh strings |
| 42 | +ue4_static_header = "Begin Map\r\n Begin Level\r\n" |
| 43 | +ue4_static_footer = " End Level\r\nBegin Surface\r\nEnd Map\r\n" |
| 44 | +ue4_static_template_body = " Begin Actor Class=StaticMeshActor Name=$MAYANAME$_1 Archetype=StaticMeshActor'/Script/Engine.Default__StaticMeshActor'\r\n Begin Object Class=StaticMeshComponent Name=\"StaticMeshComponent0\" Archetype=StaticMeshComponent'Default__StaticMeshActor:StaticMeshComponent0'\r\n End Object\r\n Begin Object Name=\"StaticMeshComponent0\"\r\n StaticMesh=$UNREALNAME$\r\n RelativeLocation=(X=$LOC_X$,Y=$LOC_Y$,Z=$LOC_Z$)\r\n RelativeScale3D=(X=$SCALE_X$,Y=$SCALE_Y$,Z=$SCALE_Z$)\r\n BodyInstance=(Scale3D=(X=$SCALE_X$,Y=$SCALE_Y$,Z=$SCALE_Z$))\r\n RelativeRotation=(Pitch=$ROT_Y$,Yaw=$ROT_Z$,Roll=$ROT_X$)\r\n End Object\r\n StaticMeshComponent=StaticMeshComponent0\r\n RootComponent=StaticMeshComponent0\r\n ActorLabel=\"$MAYANAME$\"\r\n End Actor\r\n" |
| 45 | + |
| 46 | +def GetTemplatedExportBody(MayaName, UnrealName, LocX, LocY, LocZ, RotX, RotY, RotZ, ScaleX, ScaleY, ScaleZ): |
| 47 | + body = ue4_static_template_body.replace("$MAYANAME$", MayaName) |
| 48 | + body = body.replace("$UNREALNAME$", UnrealName) |
| 49 | + body = body.replace("$LOC_X$", str(LocX)) |
| 50 | + body = body.replace("$LOC_Y$", str(LocY)) |
| 51 | + body = body.replace("$LOC_Z$", str(LocZ)) |
| 52 | + body = body.replace("$ROT_X$", str(RotX)) |
| 53 | + body = body.replace("$ROT_Y$", str(RotY)) |
| 54 | + body = body.replace("$ROT_Z$", str(RotZ)) |
| 55 | + body = body.replace("$SCALE_X$", str(ScaleX)) |
| 56 | + body = body.replace("$SCALE_Y$", str(ScaleY)) |
| 57 | + body = body.replace("$SCALE_Z$", str(ScaleZ)) |
| 58 | + return body |
| 59 | + |
| 60 | +# Paths to Files |
| 61 | +homedir = os.environ['UE4_PRODUCTIVITY'].replace("\\","/") + "/" |
| 62 | +usersettings = homedir + "Maya/batchplacer.config" |
| 63 | +main_ui_filename = homedir + "Maya/batchplacer.ui" |
| 64 | +main_form_class, main_base_class = loadUiType(main_ui_filename) |
| 65 | +slot_ui_filename = homedir + "Maya/batchplacer_slotrow.ui" |
| 66 | +slot_form_class, slot_base_class = loadUiType(slot_ui_filename) |
| 67 | + |
| 68 | +# Slot Class |
| 69 | +class BatchPlacerExportSlotUI(slot_form_class, slot_base_class): |
| 70 | + def __init__(self, mainui, parent=None): |
| 71 | + super(BatchPlacerExportSlotUI, self).__init__(parent) |
| 72 | + self.setupUi(self) |
| 73 | + self.connectInterface() |
| 74 | + self.parentui = mainui |
| 75 | + self.setLayout(self.horizontalLayout) |
| 76 | + |
| 77 | + def connectInterface(self): |
| 78 | + QtCore.QObject.connect(self.btnRemoveSlot, QtCore.SIGNAL("clicked()"), self.removeFromParent) |
| 79 | + QtCore.QObject.connect(self.btnMoveUpSlot, QtCore.SIGNAL("clicked()"), self.moveUpInParent) |
| 80 | + QtCore.QObject.connect(self.btnMoveDownSlot, QtCore.SIGNAL("clicked()"), self.moveDownInParent) |
| 81 | + QtCore.QObject.connect(self.lineObjName, QtCore.SIGNAL("textEdited(QString)"), self.saveAllSlots) |
| 82 | + QtCore.QObject.connect(self.lineUnrealName, QtCore.SIGNAL("textEdited(QString)"), self.saveAllSlots) |
| 83 | + |
| 84 | + def removeFromParent(self): |
| 85 | + for i in xrange(self.parentui.listWidget.count()): |
| 86 | + slotWidget = self.parentui.listWidget.itemWidget(self.parentui.listWidget.item(i)) |
| 87 | + if (self == slotWidget): |
| 88 | + self.parentui.listWidget.takeItem(i) |
| 89 | + self.parentui.saveAllSlots() |
| 90 | + return |
| 91 | + |
| 92 | + def moveUpInParent(self): |
| 93 | + self.parentui.moveUpSlot(self) |
| 94 | + |
| 95 | + def moveDownInParent(self): |
| 96 | + self.parentui.moveDownSlot(self) |
| 97 | + |
| 98 | + def saveAllSlots(self, newtext): |
| 99 | + self.parentui.saveAllSlots() |
| 100 | + |
| 101 | + |
| 102 | +# Interface Class |
| 103 | +class BatchPlacerUI(main_form_class, main_base_class): |
| 104 | + def __init__(self, parent=None): |
| 105 | + super(BatchPlacerUI, self).__init__(parent) |
| 106 | + self.setupUi(self) |
| 107 | + self.setObjectName('UE4BatchPlacer') |
| 108 | + self.connectInterface() |
| 109 | + |
| 110 | + lines = [line.strip() for line in open(usersettings)] |
| 111 | + |
| 112 | + for x, y in pairwise(lines): |
| 113 | + item_widget = self.addExportSlot() |
| 114 | + item_widget.lineObjName.setText(x) |
| 115 | + item_widget.lineUnrealName.setText(y) |
| 116 | + |
| 117 | + def connectInterface(self): |
| 118 | + QtCore.QObject.connect(self.btnExportSelected, QtCore.SIGNAL("clicked()"), self.exportSelected) |
| 119 | + QtCore.QObject.connect(self.btnAddExportSlot, QtCore.SIGNAL("clicked()"), self.addExportSlot) |
| 120 | + |
| 121 | + def getUnrealMesh(self, inputName): |
| 122 | + if inputName is None: |
| 123 | + return None; |
| 124 | + for i in xrange(self.listWidget.count()): |
| 125 | + slotWidget = self.listWidget.itemWidget(self.listWidget.item(i)) |
| 126 | + if slotWidget.lineObjName.text() not in inputName: continue |
| 127 | + return slotWidget.lineUnrealName.text() |
| 128 | + |
| 129 | + def moveUpSlot(self, slotitem): |
| 130 | + taken = None |
| 131 | + foundId = 0 |
| 132 | + mayaName = None |
| 133 | + unrealName = None |
| 134 | + for i in xrange(self.listWidget.count()): |
| 135 | + slotWidget = self.listWidget.itemWidget(self.listWidget.item(i)) |
| 136 | + if (slotitem == slotWidget): |
| 137 | + if (i is not 0): |
| 138 | + mayaName = slotitem.lineObjName.text() |
| 139 | + unrealName = slotitem.lineUnrealName.text() |
| 140 | + taken = self.listWidget.takeItem(i) |
| 141 | + foundId = i |
| 142 | + break |
| 143 | + if (taken is not None): |
| 144 | + self.listWidget.insertItem(foundId-1,taken) |
| 145 | + item_widget = BatchPlacerExportSlotUI(self, self) |
| 146 | + item_widget.lineObjName.setText(mayaName) |
| 147 | + item_widget.lineUnrealName.setText(unrealName) |
| 148 | + taken.setSizeHint(item_widget.sizeHint()) |
| 149 | + self.listWidget.setItemWidget(taken, item_widget) |
| 150 | + self.saveAllSlots() |
| 151 | + |
| 152 | + def moveDownSlot(self, slotitem): |
| 153 | + taken = None |
| 154 | + foundId = 0 |
| 155 | + mayaName = None |
| 156 | + unrealName = None |
| 157 | + for i in xrange(self.listWidget.count()): |
| 158 | + slotWidget = self.listWidget.itemWidget(self.listWidget.item(i)) |
| 159 | + if (slotitem == slotWidget): |
| 160 | + if (i is not self.listWidget.count()-1): |
| 161 | + mayaName = slotitem.lineObjName.text() |
| 162 | + unrealName = slotitem.lineUnrealName.text() |
| 163 | + taken = self.listWidget.takeItem(i) |
| 164 | + foundId = i |
| 165 | + break |
| 166 | + if (taken is not None): |
| 167 | + self.listWidget.insertItem(foundId+1,taken) |
| 168 | + item_widget = BatchPlacerExportSlotUI(self, self) |
| 169 | + item_widget.lineObjName.setText(mayaName) |
| 170 | + item_widget.lineUnrealName.setText(unrealName) |
| 171 | + taken.setSizeHint(item_widget.sizeHint()) |
| 172 | + self.listWidget.setItemWidget(taken, item_widget) |
| 173 | + self.saveAllSlots() |
| 174 | + |
| 175 | + def addExportSlot(self): |
| 176 | + item = QtGui.QListWidgetItem(self.listWidget) |
| 177 | + item_widget = BatchPlacerExportSlotUI(self, self) |
| 178 | + item_widget.setLayout(item_widget.horizontalLayout) |
| 179 | + item.setSizeHint(item_widget.sizeHint()) |
| 180 | + self.listWidget.addItem(item) |
| 181 | + self.listWidget.setItemWidget(item, item_widget) |
| 182 | + return item_widget |
| 183 | + |
| 184 | + def exportSelected(self): |
| 185 | + exportable = False |
| 186 | + export_body = "" |
| 187 | + selection = om.MSelectionList() |
| 188 | + om.MGlobal.getActiveSelectionList(selection) |
| 189 | + selection_iter = om.MItSelectionList(selection) |
| 190 | + while not selection_iter.isDone(): |
| 191 | + obj = om.MObject() |
| 192 | + dagPath = om.MDagPath() |
| 193 | + selection_iter.getDependNode(obj) |
| 194 | + selection_iter.getDagPath(dagPath) |
| 195 | + node = om.MFnDependencyNode(obj) |
| 196 | + unrealName = self.getUnrealMesh(node.name()) |
| 197 | + if (unrealName is not None): |
| 198 | + exportable = True |
| 199 | + mt = om.MTransformationMatrix(dagPath.inclusiveMatrix()) |
| 200 | + loc = mt.translation(om.MSpace.kWorld) |
| 201 | + rot = mt.rotation().asEulerRotation() |
| 202 | + scaleUtil = om.MScriptUtil() |
| 203 | + scaleUtil.createFromList([0,0,0],3) |
| 204 | + scaleVec = scaleUtil.asDoublePtr() |
| 205 | + mt.getScale(scaleVec, om.MSpace.kWorld) |
| 206 | + scale = [om.MScriptUtil.getDoubleArrayItem(scaleVec, i) for i in range(0,3)] |
| 207 | + if (cmds.upAxis(q=True,axis=True) == "y"): |
| 208 | + export_body += GetTemplatedExportBody(node.name(), unrealName, loc.x, loc.z, loc.y, math.degrees(rot.x), math.degrees(rot.z), math.degrees(rot.y), scale[0], scale[2], scale[1]) |
| 209 | + else: |
| 210 | + export_body += GetTemplatedExportBody(node.name(), unrealName, loc.x, loc.y, loc.z, math.degrees(rot.x), math.degrees(rot.y), math.degrees(rot.z), scale[0], scale[1], scale[2]) |
| 211 | + selection_iter.next() |
| 212 | + if (exportable is True): |
| 213 | + export = ue4_static_header + export_body + ue4_static_footer |
| 214 | + clipboard = QtGui.QApplication.clipboard() |
| 215 | + clipboard.setText(export) |
| 216 | + |
| 217 | + |
| 218 | + def saveAllSlots(self): |
| 219 | + f = open(usersettings, 'w') |
| 220 | + for i in xrange(self.listWidget.count()): |
| 221 | + slotWidget = self.listWidget.itemWidget(self.listWidget.item(i)) |
| 222 | + f.write(slotWidget.lineObjName.text() + '\n') |
| 223 | + f.write(slotWidget.lineUnrealName.text() + '\n') |
| 224 | + f.close() |
| 225 | + |
| 226 | +# main |
| 227 | +def main(): |
| 228 | + global ui |
| 229 | + ui = BatchPlacerUI() |
| 230 | + ui.show() |
| 231 | + |
| 232 | +def show(): |
| 233 | + global ui |
| 234 | + if (ui != None): |
| 235 | + ui.show() |
| 236 | + else: |
| 237 | + main() |
| 238 | + |
| 239 | +if __name__ == "__main__": |
| 240 | + main() |
0 commit comments