123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import sys
- import os
- import pymel.core as pm
- import maya.cmds as cmds
- import tempfile
- import json
- import subprocess
- # // SETTINGS
- GEO_EXT = '.obj'
- APP_EXT = '.blend'
- PORT = 6006
- EDITOR = '/Applications/blender.app/Contents/MacOS/blender'
- # shader-independent way of figuring out texture file from mat
- # /// UTILS
- def guess_diffuse_file(node):
- mat = [pm.ls(pm.listConnections(se), materials=True)[0] for se in node.getShape().outputs(type='shadingEngine')]
- textures = [c for c in mat[0].listConnections() if c.type() =='file']
- for t in textures:
- plugs = t.listConnections(plugs=True)
- if any ([p.name() for p in plugs if 'color' in p.name() or 'diff' in p.name()]):
- return t.fileTextureName.get()
- return 'untextured'
- def get_blender_path(root='/Applications'):
- bin_location = 'Contents/MacOS/blender'
- for directory in os.listdir(root):
- full_path = os.path.join(root, directory)
- if os.path.isdir(full_path):
- if directory.endswith('blender.app'):
- return os.path.join(root, directory, bin_location)
- else:
- for subdir in os.listdir(full_path):
- if subdir.endswith('blender.app'):
- return os.path.join(full_path, subdir, bin_location)
- def this_script():
- # does not work - can we get the current script's location?
- return os.path.join(sys.argv[0])
- def replace_mesh(old, new):
- shading_engines = old.getShape().outputs(type='shadingEngine')[0]
- old_mesh = old.listRelatives(shapes=True)[0]
- old_mesh_name = old_mesh.name()
- pm.delete(old_mesh)
- print new
- new_mesh = new.listRelatives(shapes=True)[0]
- pm.rename(new_mesh, old_mesh_name)
- pm.parent(new_mesh, old, shape=True, relative=True)
- pm.delete(new)
-
- pm.sets(shading_engines, edit=True, forceElement=old)
- def load_geofile(filepath):
- try:
- imported_nodes = pm.importFile(filepath, i=True, returnNewNodes=True)
- # print imported_nodes
-
- relevant_nodes = []
- trash = []
- for node in imported_nodes:
- print node, 'TYPE', node.type()
- if node.type() == 'mesh' or node.type() == 'transform':
- relevant_nodes.append(node)
- print '> append', node
- else:
- trash.append(str(node.name()))
-
- # seems like a bug in pymel, materials et al must be
- # explicitly deleted by string name with cmds
- for item in trash:
- try:
- cmds.delete(item)
- except ValueError:
- pass
-
- for node in relevant_nodes:
- if node.getParent() is None:
- return node
-
- except RuntimeError:
- print "> unreadable:", filepath
- return False
- def open_port(number):
- if not cmds.commandPort(':'+str(number), q=True):
- cmds.commandPort('mayaCommand', name=':'+str(number), sourceType='python')
- def get_temp_dir():
- prefix = 'm2b'
- base_tmp = tempfile.gettempdir()
- m2b_tmp = os.path.join(base_tmp, prefix)
- if not os.path.isdir(m2b_tmp):
- os.makedirs(m2b_tmp)
- return m2b_tmp
-
-
- # /// CLASSES
- class AtOrigin(object):
- def __init__(self, node):
- self.node = node
- self.matrix = pm.xform(matrix=True, query=True)
-
- def __enter__(self):
- print '> discard trs'
- mat_neutral = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
- pm.xform(self.node, matrix=mat_neutral)
- def __exit__(self, type, value, traceback):
- print '> restore trs'
- pm.xform(self.node, matrix=self.matrix)
- class KeepSelection(object):
- def __enter__(self):
- print '> store sel'
- self.selection = pm.selected()
-
- def __exit__(self, type, value, traceback):
- print '> restore sel'
- pm.select(self.selection)
- class BridgedNode(object):
- def __init__(self, uuid):
- self.uuid = uuid
- self.node = None
- self.texture = None
- self.geofile = None
- self.metadatafile = None
- self.matrix = None
- self.valid = False
- self.blendfile = False
- self.updatescript = None
-
- self.ingest()
-
- def is_valid(self):
- print '> validating:'
- #print [ (key, self.__dict__[key]) for key in self.__dict__.keys()]
-
- if not any([self.__dict__[key] is None for key in self.__dict__.keys()]):
- print '> valid'
- return True
- else:
- print '> invalid'
- print [self.__dict__[key] for key in self.__dict__.keys()]
-
- def has_history(self):
- if os.path.isfile(self.geofile) and os.path.isfile(self.metadatafile) and os.path.isfile(self.blendfile):
- return True
-
- def ingest(self):
- # get scene object
- for node in pm.ls(transforms=True):
- if cmds.ls(node.name(), uuid=True)[0] == self.uuid:
- self.node = node
- self.geofile = os.path.join(get_temp_dir(), self.uuid + GEO_EXT)
- self.blendfile = os.path.join(get_temp_dir(), self.uuid + APP_EXT)
- self.metadatafile = os.path.join(get_temp_dir(), self.uuid + '.json')
- self.matrix = pm.xform(matrix=True, query=True)
- self.texture = guess_diffuse_file(self.node)
- self.updatescript = this_script()
-
- def dump_metadata(self):
- metadata = {'obj': self.geofile,
- 'tex': self.texture,
- 'blend': self.blendfile,
- 'uuid': self.uuid,
- 'updatescript': self.updatescript,
- 'port': PORT
- }
-
-
- print '> metadata: ', metadata
- with open(self.metadatafile, 'w') as f:
- json.dump(metadata, f, indent=4)
-
- def dump_geo(self):
- print '> dumping mesh'
- # save position
- with AtOrigin(self.node):
- cmds.file(self.geofile,
- force=True,
- pr=1,
- typ="OBJexport",
- exportSelected=True,
- op="groups=0;materials=0;smoothing=0;normals=1")
-
- def edit_externally(self, path_to_app):
- blender_bridge_script = os.path.join(os.path.expanduser('~'), 'Documents', 'lab', 'm2b', 'b2m.py')
- blendfile = self.blendfile
- cmd = [path_to_app, '--python', blender_bridge_script, blendfile]
- print 'CMD', cmd
- subprocess.Popen(cmd)
-
- # /// CONTROLS
- def init():
- print 'init'
- selection = pm.selected()
- if len(selection) != 1:
- print 'Must select one object'
- else:
- selection = selection[0]
- uuid = cmds.ls(selection.name(), uuid=True)[0]
- bn = BridgedNode(uuid)
- bn.ingest()
-
- if bn.is_valid():
- if bn.has_history():
- print '> file has history. opening source.'
- else:
- bn.dump_geo()
- bn.dump_metadata()
- else:
- bn.dump_geo()
- bn.dump_metadata()
- open_port(6006)
- bn.edit_externally(get_blender_path())
- def update(uuid):
- print '> running on', uuid
- bn = BridgedNode(uuid)
- bn.ingest()
- if bn.is_valid():
- with KeepSelection():
- print 'GEO', bn.geofile
- imported_node = load_geofile(bn.geofile)
- replace_mesh(bn.node, imported_node)
- else:
- print '> UUID invalid.'
-
|