├── docs └── pbUDK.jpg ├── prefs └── icons │ └── udk.png ├── plug-ins ├── DDConvexHull-2012_64bit.mll ├── DDConvexHull-2013_64bit.mll ├── DDConvexHull-2014_64bit.mll ├── DDConvexHull-2015_64bit.mll ├── DDConvexHull-2016_64bit.mll └── DDConvexHull-2017_64bit.mll ├── .gitignore ├── LICENSE ├── README.md └── scripts ├── UDKexport └── UDK-FBX.fbxexportpreset └── pbUDK.py /docs/pbUDK.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/docs/pbUDK.jpg -------------------------------------------------------------------------------- /prefs/icons/udk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/prefs/icons/udk.png -------------------------------------------------------------------------------- /plug-ins/DDConvexHull-2012_64bit.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/plug-ins/DDConvexHull-2012_64bit.mll -------------------------------------------------------------------------------- /plug-ins/DDConvexHull-2013_64bit.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/plug-ins/DDConvexHull-2013_64bit.mll -------------------------------------------------------------------------------- /plug-ins/DDConvexHull-2014_64bit.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/plug-ins/DDConvexHull-2014_64bit.mll -------------------------------------------------------------------------------- /plug-ins/DDConvexHull-2015_64bit.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/plug-ins/DDConvexHull-2015_64bit.mll -------------------------------------------------------------------------------- /plug-ins/DDConvexHull-2016_64bit.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/plug-ins/DDConvexHull-2016_64bit.mll -------------------------------------------------------------------------------- /plug-ins/DDConvexHull-2017_64bit.mll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmcpasserby/pbUDK/HEAD/plug-ins/DDConvexHull-2017_64bit.mll -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | bin/ 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Installer logs 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | .tox/ 29 | .coverage 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Rope 43 | .ropeproject 44 | 45 | # PyCharm 46 | .idea/ 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Chris Cunningham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pbUDK 2 | ===== 3 | 4 | pbUDK is a toolset for working with and exporting content from Maya to UDK, Includes one click export tools, and collision hull generation tools with future plans for exporting transformations via unrealText to udk. 5 | 6 | Installation 7 | ------------ 8 | Clone or extract contents to the parent of your Maya scripts folder. Than enable the DDConvexHull plug-in for your version of Maya in the Maya plugin manager 9 | 10 | Once this is done simply just open a python console and run these commands. 11 | ``` 12 | import pbUDK 13 | pbUDK.UI() 14 | ``` 15 | Optionally you could create a shelf button containing the same python code as above. 16 | 17 | Usage 18 | ----- 19 | 20 | ### Physics 21 | Once you got the UI running you can add adjust the amount of verts in the collision hull and add hulls with the add hull button. If using the convex hull mode instead of box collision you can update the amount of verts in real time on a selected object with a hull attached, up to the point where you delete history. 22 | 23 | ### Export Meshes 24 | Export path just sets a directory to dump all export fbx files into, the file name it’s self id set from the name of the objects transform in the maya scene. 25 | Move to origin, moves objects to 0 0 0 XYZ in the scene, before export and places them back to their old locations after export. Export children exports any meshes parented to the selected mesh I put this option in here so it will auto export collision hulls made with this tool. 26 | The tool comes with a default FBX export preset file that has good settings for UDK, but this can be set to use one made by the user with the FBXPreset button. 27 | 28 | ![pbUDK](docs/pbUDK.jpg) 29 | 30 | Credits 31 | ------- 32 | The convex hull [DDConvexHull]( https://github.com/digitaldestructo/DDConvexHull) generation plug-in was written by [Jonathan Tilden]( https://github.com/digitaldestructo), who created a Maya Node that uses the StanHull method to create a convex hull in real-time. The open source code StanHull code in the plugin was written by Stan Melax and John Ratcliff. 33 | -------------------------------------------------------------------------------- /scripts/UDKexport/UDK-FBX.fbxexportpreset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /scripts/pbUDK.py: -------------------------------------------------------------------------------- 1 | from pymel.util.common import path 2 | import os 3 | import json 4 | import pymel.core as pm 5 | 6 | # Patch for the broken getTransform in older pymel versions 7 | pm.nt.Shape.getTransform = lambda x: x.getParent(generations=1) 8 | 9 | title = 'Unreal Pipeline' 10 | version = '1.07' 11 | 12 | 13 | def UI(): 14 | if pm.window('pbudk', exists=True): 15 | pm.deleteUI('pbudk') 16 | 17 | optspath = '%s/pbUDK.json' % pm.internalVar(usd=True) 18 | defaultdata = {'phyType': 1, 19 | 'maxVerts': 32, 20 | 'center': True, 21 | 'child': True, 22 | 'fbxPath': '%sdata/' % pm.workspace(q=True, rd=True), 23 | 'presetFile': '%s/UDKexport/UDK-FBX.fbxexportpreset' % pm.internalVar(usd=True), 24 | 'version': version, 25 | 'prefix': False, 26 | 'prefix_text': '', 27 | 'suffix': False, 28 | 'suffix_text': ''} 29 | 30 | with pm.window('pbudk', title="{0} - {1}".format(title, version), width=250, sizeable=True) as window: 31 | with pm.columnLayout(): 32 | opts = JSONDict(optspath, defaultdata) 33 | PhyUI(opts) 34 | FbxUI(opts) 35 | window.show() 36 | 37 | 38 | class PhyUI(object): 39 | def __init__(self, opts): 40 | self.opts = opts 41 | with pm.frameLayout('Physics', collapsable=True, cl=False, bs='out'): 42 | with pm.columnLayout(width=250): 43 | pm.text(l='Collision Type:') 44 | self.phyType = pm.radioButtonGrp(labelArray3=['Convex Hull', 'Box', 'Sphere'], 45 | sl=self.opts['phyType'], nrb=3, cc=self.save, 46 | cw3=[94, 64, 64], width=250) 47 | self.maxVerts = pm.intSliderGrp(field=True, l='Max Vertices:', v=self.opts['maxVerts'], 48 | cl3=['left', 'left', 'left'], cw3=[64, 48, 128], cc=self.save) 49 | pm.button(l='Add Hull', w=250, c=self._addHull) 50 | self.save() 51 | 52 | def _addHull(self, *args): 53 | if not pm.selected(): 54 | return 55 | i = self.phyType.getSelect() 56 | if i == 1: 57 | self.convexHull() 58 | elif i == 2: 59 | self.boxHull() 60 | elif i == 3: 61 | self.sphereHull() 62 | 63 | def convexHull(self): 64 | sel = pm.selected() 65 | if not isinstance(sel[0], pm.nt.Transform): 66 | oldSel = sel 67 | sel = sel[0].node().getParent() 68 | else: 69 | oldSel = sel 70 | sel = sel[0] 71 | 72 | inputMesh = sel.getShape() 73 | hullNode = pm.createNode('DDConvexHull') 74 | outputNode = pm.createNode('mesh', n='UCX_{0}Shape_01'.format(sel)) 75 | 76 | pm.connectAttr('%s.outMesh' % inputMesh, '%s.input[0].inputPolymesh' % hullNode) 77 | pm.connectAttr('%s.output' % hullNode, '%s.inMesh' % outputNode) 78 | 79 | hullNode.maxVertices.set(self.maxVerts.getValue()) 80 | outputNode.getParent().setParent(sel) 81 | outputNode.getParent().translate.set(0, 0, 0) 82 | if not isinstance(oldSel[0], pm.nt.Transform): 83 | self.setComponents(oldSel, hullNode) 84 | 85 | def setComponents(self, sel, hullNode): 86 | coms = [str(i.name().split('.')[1]) for i in sel] 87 | hullNode.input[0].inputComponents.set(len(coms), *coms, type='componentList') 88 | 89 | @staticmethod 90 | def _get_bounds(sel): 91 | if sel > 1 and isinstance(sel[0], pm.Component): 92 | transform = sel[0].node().getTransform() 93 | t = pm.polyEvaluate(bc=True) 94 | bb = pm.dt.BoundingBox(pm.dt.Point(t[0][0], t[1][0], t[2][0]), pm.dt.Point(t[0][1], t[1][1], t[2][1])) 95 | verts = [i.getPosition() for i in pm.ls(pm.polyListComponentConversion(sel, tv=True), fl=True)] 96 | center = sum(verts) / len(verts) 97 | else: 98 | transform = sel[0] 99 | bb = sel[0].getBoundingBox() 100 | center = pm.objectCenter(sel[0]) 101 | return bb, center, transform 102 | 103 | def boxHull(self): 104 | sel = pm.ls(sl=True, fl=True) 105 | bb, cnt, transform = self._get_bounds(sel) 106 | hull = pm.polyCube(w=bb.width(), h=bb.height(), d=bb.depth(), n='UCX_{0}_01'.format(transform)) 107 | hull[0].setTranslation(cnt) 108 | sg = pm.PyNode('initialShadingGroup') 109 | sg.remove(hull[0].getShape()) 110 | hull[0].setParent(transform) 111 | 112 | def sphereHull(self): 113 | sel = pm.ls(sl=True, fl=True) 114 | bb, cnt, transform = self._get_bounds(sel) 115 | hull = pm.polySphere(radius=max(bb.width(), bb.height(), bb.depth()) / 2, sx=8, sy=8, 116 | n='UCX_{0}_01'.format(transform)) 117 | hull[0].setTranslation(cnt) 118 | sg = pm.PyNode('initialShadingGroup') 119 | sg.remove(hull[0].getShape()) 120 | hull[0].setParent(transform) 121 | 122 | def save(self, *args): 123 | self.opts['phyType'] = self.phyType.getSelect() 124 | self.opts['maxVerts'] = self.maxVerts.getValue() 125 | if self.phyType.getSelect() == 1: 126 | self.maxVerts.setEnable(True) 127 | else: 128 | self.maxVerts.setEnable(False) 129 | 130 | 131 | class FbxUI(object): 132 | def __init__(self, opts): 133 | self.opts = opts 134 | with pm.frameLayout('Export Meshes (.FBX)', collapsable=True, cl=False, bs='out'): 135 | with pm.columnLayout(width=250): 136 | pm.text(l='Export List:') 137 | pm.separator(height=4) 138 | self.meshList = pm.textScrollList(height=250, width=250, ams=True, dkc=self._remove) 139 | with pm.rowColumnLayout(nc=3, cw=[(1, 82), (2, 82), (3, 82)]): 140 | pm.button(l='Add', c=self._add) 141 | pm.button(l='Remove', c=self._remove) 142 | pm.button(l='Clear', c=self._clear) 143 | 144 | with pm.rowColumnLayout(nc=2, cw=[(1, 124), (2,124)]): 145 | self.prefix = pm.checkBox(label='Prefix', value=self.opts['prefix'], cc=self.save) 146 | self.suffix = pm.checkBox(label='Suffix', value=self.opts['suffix'], cc=self.save) 147 | self.prefix_text = pm.textField(en=self.prefix.getValue(), text=self.opts['prefix_text'], cc=self.save) 148 | self.suffix_text = pm.textField(en=self.suffix.getValue(), text=self.opts['suffix_text'], cc=self.save) 149 | 150 | pm.text(l='Export Path:') 151 | with pm.rowColumnLayout(nc=2, cw=[(1, 215), (2, 32)]): 152 | self.fbxPath = pm.textField(text=self.opts['fbxPath'], cc=self._pathRefreash) 153 | pm.button(l='...', c=self._path) 154 | 155 | with pm.rowColumnLayout(nc=3): 156 | self.center = pm.checkBox(label='Move to Orgin', v=self.opts['center'], cc=self.save) 157 | self.child = pm.checkBox(label='Export Childern', v=self.opts['child'], cc=self.save) 158 | pm.button(l='FBXPreset', c=self._fbxPreset) 159 | 160 | with pm.rowColumnLayout(nc=2, cw=[(1, 124), (2, 124)]): 161 | pm.button(l='Selected', c=self._selected) 162 | pm.button(l='All', c=self._all) 163 | 164 | self._refresh() 165 | 166 | def _path(self, *args): 167 | exportPath = pm.fileDialog2(dir=path, fm=3, okc='Select Folder', cap='Select Export Folder') 168 | if exportPath: 169 | self.fbxPath.setText(exportPath[0]) 170 | self.opts['fbxPath'] = exportPath[0] 171 | 172 | def _pathRefreash(self, text): 173 | self.opts['fbxPath'] = text 174 | 175 | def _selected(self, *args): 176 | self.export(self.fbxPath.getText(), all=False, center=self.center.getValue(), child=self.child.getValue()) 177 | 178 | def _all(self, *args): 179 | self.export(self.fbxPath.getText(), all=True, center=self.center.getValue(), child=self.child.getValue()) 180 | 181 | def _fbxPreset(self, *args): 182 | tempData = pm.fileDialog2(dir=path(self.opts['presetFile']).parent, fm=1, okc='Select Preset File', 183 | cap='Sselect FPXExportPreset File', ff='FBX Export Presets (*.fbxexportpreset)') 184 | if tempData: 185 | self.opts['presetFile'] = tempData[0] 186 | 187 | def _add(self, *args): 188 | sel = pm.selected() 189 | for i in sel: 190 | if isinstance(i, pm.nt.Transform): 191 | try: 192 | i.pbExport.set(True) 193 | except: 194 | i.addAttr('pbExport', at='bool') 195 | i.pbExport.set(True) 196 | self._refresh() 197 | 198 | def _remove(self, *args): 199 | sel = self.meshList.getSelectItem() 200 | for i in sel: 201 | i = pm.PyNode(i) 202 | i.pbExport.delete() 203 | self._refresh() 204 | 205 | def _clear(self, *args): 206 | sel = pm.ls() 207 | for i in sel: 208 | if hasattr(i, 'pbExport'): 209 | i.pbExport.delete() 210 | self._refresh() 211 | 212 | def _refresh(self): 213 | self.meshList.removeAll() 214 | sel = pm.ls(type=pm.nt.Transform) 215 | for i in sel: 216 | if hasattr(i, 'pbExport') and i.pbExport.get() is True: 217 | self.meshList.append(i) 218 | 219 | def export(self, dirpath, all=False, center=True, child=True): 220 | if os.path.isfile(self.opts['presetFile']): 221 | pm.mel.FBXLoadExportPresetFile(f=self.opts['presetFile']) 222 | ext = '.fbx' 223 | 224 | if all: 225 | pm.select(self.meshList.getAllItems()) 226 | sel = pm.selected() 227 | else: 228 | sel = pm.selected() 229 | 230 | if len(sel) == 0: 231 | pm.warning('Nothing is selected!') 232 | else: 233 | for obj in sel: 234 | pm.select(obj) 235 | if center: 236 | oldLoc = obj.getRotatePivot() 237 | self.centerPiv(obj) 238 | exportPath = self._get_filename(dirpath, obj.name(), ext) 239 | if child: 240 | children = obj.getChildren() 241 | for i in children: 242 | if isinstance(i, pm.nt.Transform): 243 | pm.select(i, add=True) 244 | pm.mel.FBXExport(f=exportPath, s=True) 245 | if center: 246 | obj.setTranslation([oldLoc[0], oldLoc[1], oldLoc[2]]) 247 | 248 | def _get_filename(self, dir, name, ext): 249 | file_name = "{0}{1}{2}{3}".format( 250 | self.prefix_text.getText() if self.prefix.getValue() else "", 251 | name, 252 | self.suffix_text.getText() if self.suffix.getValue() else "", 253 | ext 254 | ) 255 | return dir + os.sep + file_name; 256 | 257 | def centerPiv(self, obj): 258 | pm.select(obj) 259 | pm.makeIdentity(apply=True, t=True, r=True, s=True, n=False) 260 | pos = obj.getRotatePivot() 261 | obj.translate.set(-1 * pos.x, -1 * pos.y, -1 * pos.z) 262 | pm.makeIdentity(apply=True, t=True, r=True, s=True, n=False) 263 | 264 | def save(self, *args): 265 | self.prefix_text.setEnable(self.prefix.getValue()) 266 | self.suffix_text.setEnable(self.suffix.getValue()) 267 | 268 | self.opts['center'] = self.center.getValue() 269 | self.opts['child'] = self.child.getValue() 270 | self.opts['fbxPath'] = self.fbxPath.getText() 271 | self.opts['prefix'] = self.prefix.getValue() 272 | self.opts['prefix_text'] = self.prefix_text.getText() 273 | self.opts['suffix'] = self.suffix.getValue() 274 | self.opts['suffix_text'] = self.suffix_text.getText() 275 | 276 | 277 | class JSONDict(dict): 278 | def __init__(self, filename, defaults, *args, **kwargs): 279 | super(JSONDict, self).__init__(**kwargs) 280 | self.filename = filename 281 | self.defaults = defaults 282 | self._load() 283 | self.update(*args, **kwargs) 284 | 285 | def _load(self): 286 | if os.path.isfile(self.filename) and os.path.getsize(self.filename) > 0: 287 | with open(self.filename, 'r') as f: 288 | data = json.load(f) 289 | if 'version' in data and data['version'] == version: 290 | self.update(data) 291 | else: 292 | self._dumpdefaults() 293 | else: 294 | self._dumpdefaults() 295 | 296 | def _dump(self): 297 | with open(self.filename, 'w') as f: 298 | json.dump(self, f, sort_keys=True, indent=4) 299 | 300 | def _dumpdefaults(self): 301 | with open(self.filename, 'w') as f: 302 | json.dump(self.defaults, f, sort_keys=True, indent=4) 303 | self._load() 304 | 305 | def __getitem__(self, key): 306 | return dict.__getitem__(self, key) 307 | 308 | def __setitem__(self, key, value): 309 | dict.__setitem__(self, key, value) 310 | self._dump() 311 | 312 | def __repr__(self): 313 | dictrepr = dict.__repr__(self) 314 | return '%s(%s)' % (type(self).__name__, dictrepr) 315 | 316 | def update(self, *args, **kwargs): 317 | for k, v in dict(*args, **kwargs).items(): 318 | self[k] = v 319 | self._dump() 320 | 321 | if __name__ == "__main__": 322 | UI() 323 | --------------------------------------------------------------------------------