├── GoZ ├── __init__.py ├── start_zbrush.py ├── errs.py ├── utils.py ├── zbrushgui.py ├── mayagui.py ├── maya_tools.py └── zbrush_tools.py ├── .gitignore └── README.md /GoZ/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /GoZ/start_zbrush.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import zbrushgui 4 | zbg = zbrushgui.ZBrushGUI() 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | <<<<<<< HEAD 3 | .swp 4 | .zsc 5 | ======= 6 | *.pyc 7 | *.swp 8 | >>>>>>> 43559e50075ee93cf7f5bfb0c2f0c87a58f14f02 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | =================== 2 | ZBrush-Command-Port 3 | =================== 4 | 5 | Connect Maya with ZBrush via Python 6 | 7 | 8 | Install 9 | ======= 10 | 11 | - Download a copy of the repo, place in your site packages 12 | - Alternativly sym link it to your site-packages folders 13 | - Currently this only is tested on OSX/Linux, (FC 18, OSX 10.8.2), (Maya2013,ZBrush 4R4P2) 14 | - For the stand alone ZBrushServer TkInter might be needed, should work on python 2.7+ 15 | 16 | ```bash 17 | ln -s /Users/name/GoZ/ /your/python/site-packages/GoZ 18 | ln -s /USers/name/GoZ/ /maya/default/site-packages/GoZ 19 | ``` 20 | - Create a shelf button in Maya simmilar to: 21 | 22 | ```python 23 | import GoZ.mayagui 24 | mayagui=GoZ.mayagui.Win() 25 | ``` 26 | 27 | - Create a 'send' shelf button in Maya: 28 | 29 | ```python 30 | mayagui.send() 31 | ``` 32 | 33 | - Start ZBrushServer config with: 34 | 35 | ```python 36 | ./start_zbrush.py 37 | ``` 38 | 39 | - Create a folder ZBrush and Maya have acess to (network drive) 40 | - set up the shared enviromental variable on each machine: 41 | - set up the desired server/client host/ports in a env var 42 | 43 | ```bash 44 | export ZDOCS = /path/to/goz_default 45 | export MNET = 127.0.0.1:6667 46 | export ZNET = 127.0.0.1:6668 47 | ``` 48 | -------------------------------------------------------------------------------- /GoZ/errs.py: -------------------------------------------------------------------------------- 1 | """custom exceptions for GoZ""" 2 | 3 | 4 | class IpError(Exception): 5 | 6 | """ 7 | Exception raised for invalid IP addresses 8 | 9 | Attribitues 10 | host -- input host address 11 | msg -- gui message 12 | 13 | """ 14 | 15 | def __init__(self, host, msg): 16 | Exception.__init__(self, msg) 17 | self.host = host 18 | self.msg = msg 19 | 20 | 21 | class ZBrushServerError(Exception): 22 | 23 | """ 24 | Exception raised for connection failure 25 | 26 | Attribitues 27 | msg -- gui message 28 | 29 | """ 30 | 31 | def __init__(self, msg): 32 | Exception.__init__(self, msg) 33 | self.msg = msg 34 | 35 | 36 | class PortError(Exception): 37 | 38 | """ 39 | Exception raised for invalid socket ports 40 | 41 | Attributes 42 | port -- input port 43 | msg -- gui msg 44 | """ 45 | 46 | def __init__(self, port, msg): 47 | Exception.__init__(self, msg) 48 | self.port = port 49 | self.msg = msg 50 | self.message = msg 51 | 52 | 53 | class SelectionError(Exception): 54 | 55 | """ 56 | Exception raise for no file mesh selected 57 | 58 | Attributes 59 | msg -- gui msg 60 | 61 | """ 62 | 63 | def __init__(self, msg): 64 | Exception.__init__(self, msg) 65 | self.msg = msg 66 | -------------------------------------------------------------------------------- /GoZ/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities for managing validation and enviromental variables 3 | 4 | CONSTANTS: 5 | 6 | MAYA_ENV -- default maya network info env var 7 | ZBRUSH_ENV -- default zbrush network info env var 8 | 9 | SHARED_DIR_ENV -- ZDOCS default file path, /some/path/goz_default 10 | DEFAULT_NET -- Contains fallbacks for host/port (local) 11 | OS -- platform check 12 | currently only osx is supported 13 | 14 | SHARED_DIR_* -- default OSX file plath for 'localmode' 15 | SHARED_DIR_* -- default WIN file path for 'localmode' 16 | 17 | """ 18 | 19 | import os 20 | import socket 21 | from GoZ import errs 22 | from contextlib import contextmanager 23 | import sys 24 | 25 | SHARED_DIR_ENV = 'ZDOCS' 26 | 27 | # currently only OSX is supported due to apple script usage 28 | SHARED_DIR_DEFAULT_OSX = '/Users/Shared/Pixologic/GoZProjects' 29 | # win32 api could be used on windows 30 | SHARED_DIR_DEFAULT_WIN = 'C:\\Users\\Public\\Pixologic\\GoZProjects' 31 | 32 | OS = sys.platform 33 | 34 | # maya network info env 35 | MAYA_ENV = 'MNET' 36 | # zbrush network info env 37 | ZBRUSH_ENV = 'ZNET' 38 | 39 | # default network info 40 | DEFAULT_NET = {MAYA_ENV: 'localhost:6667', ZBRUSH_ENV: 'localhost:6668'} 41 | 42 | 43 | @contextmanager 44 | def err_handler(gui): 45 | """handles general GoZ errors, raises a gui/logger on err """ 46 | 47 | try: 48 | yield 49 | except (errs.PortError, 50 | errs.IpError, 51 | errs.SelectionError, 52 | errs.ZBrushServerError) as err: 53 | print err.msg 54 | gui(err.msg) 55 | except Exception as err: 56 | print err 57 | gui(err) 58 | finally: 59 | pass 60 | 61 | 62 | def validate_port(port): 63 | """ checks port is valid,or raises an error """ 64 | 65 | try: 66 | port = int(port) 67 | except ValueError: 68 | raise errs.PortError(port, 'Please specify a valid port: %s' % (port)) 69 | 70 | 71 | def validate_host(host): 72 | """ validates IP/host, or raises and error """ 73 | 74 | try: 75 | host = socket.gethostbyname(host) 76 | except socket.error: 77 | raise errs.IpError(host, 'Please specify a valid host: %s' % (host)) 78 | 79 | 80 | def validate(net_string): 81 | """runs host/port validation on a string""" 82 | 83 | print net_string 84 | 85 | host, port = net_string.split(':') 86 | validate_host(host) 87 | validate_port(port) 88 | return (host, port) 89 | 90 | 91 | def get_net_info(net_env): 92 | """ 93 | check for enviromental variabels, 94 | or defaults network info (local) 95 | net_env is MNET or ZNET 96 | 97 | missing SHARED_DIR_ENV forces local mode 98 | """ 99 | 100 | # check the shared dir first. it could force us into local mode 101 | shared_dir = os.getenv(SHARED_DIR_ENV) 102 | 103 | # check for empty but existing env var 104 | if shared_dir is '': 105 | shared_dir = None 106 | 107 | if shared_dir is None: 108 | # if no shared directory is set, start in local modee 109 | print "No shared directory set. Defaulting to local mode" 110 | if OS == 'darwin': 111 | print "working on OSX" 112 | os.environ[SHARED_DIR_ENV] = SHARED_DIR_DEFAULT_OSX 113 | elif OS == 'win32' or OS == 'win64': 114 | print "working on Windows" 115 | os.environ[SHARED_DIR_ENV] = SHARED_DIR_DEFAULT_WIN 116 | else: 117 | net_string = os.environ.get(net_env) 118 | 119 | if net_string: 120 | host, port = validate(net_string) 121 | return host, port 122 | 123 | # finally default to local mode 124 | net_string = DEFAULT_NET[net_env] 125 | 126 | if net_string: 127 | return validate(net_string) 128 | 129 | 130 | def split_file_name(file_path): 131 | """ recovers 'name' from file, strips ext and dir """ 132 | file_name = os.path.splitext(file_path)[0] 133 | file_name = os.path.split(file_name)[1] 134 | 135 | return file_name 136 | 137 | 138 | def make_file_name(name): 139 | """ makes a full resolved file path for zbrush """ 140 | expanded_path = os.path.expandvars(make_fp_rel(name)) 141 | return expanded_path 142 | 143 | 144 | def make_fp_rel(name): 145 | """ makes a relative file path to use in maya""" 146 | name = os.path.relpath(name + '.ma') 147 | return os.path.join('$' + SHARED_DIR_ENV, name) 148 | 149 | 150 | def send_osa(script_path): 151 | """ sends a zscript file for zbrush to open """ 152 | cmd = ['osascript -e', 153 | '\'tell app "ZBrush"', 154 | 'to open', 155 | '"' + script_path + '"\''] 156 | 157 | cmd = ' '.join(cmd) 158 | print cmd 159 | os.system(cmd) 160 | 161 | 162 | def open_osa(): 163 | """ 164 | opens zbrush 165 | 166 | blocks untill ZBrush is ready for addional commands 167 | makes sure ZBrush is ready to install the GUI 168 | 169 | launches ZBrush 170 | loop to check if ZBrush is 'ready' 171 | brings ZBrush to front/focus 172 | clears any crash messages 173 | 174 | """ 175 | 176 | osa = "osascript "\ 177 | + "-e 'tell application \"ZBrush\" to launch' "\ 178 | + "-e 'tell application \"System Events\"' "\ 179 | + "-e 'repeat until visible of process \"ZBrushOSX\" is false' "\ 180 | + "-e 'set visible of process \"ZBrushOSX\" to false' "\ 181 | + "-e 'end repeat' "\ 182 | + "-e 'end tell' "\ 183 | + "-e 'tell application \"System Events\"' "\ 184 | + "-e 'tell application process \"ZBrushOSX\"' "\ 185 | + "-e 'set frontmost to true' "\ 186 | + "-e 'keystroke return' "\ 187 | + "-e 'end tell' "\ 188 | + "-e 'end tell'" 189 | 190 | print osa 191 | os.system(osa) 192 | -------------------------------------------------------------------------------- /GoZ/zbrushgui.py: -------------------------------------------------------------------------------- 1 | """ZBrushGUI uses zbrushtools, starts ZBrushServer and MayaClient """ 2 | 3 | from GoZ import zbrush_tools as zbrush_tools 4 | import Tkinter 5 | import tkMessageBox 6 | import GoZ.utils as utils 7 | 8 | 9 | class ZBrushGUI(object): 10 | 11 | """ 12 | 13 | GUI for zbrush_tools 14 | 15 | build a UI using Tkinter, gets network info from utils.get_net_info 16 | starts ZBrushServer and MayaClient from zbrush_tools 17 | 18 | Also installs a zscript GUI in ZBrush using zbrush_tools.zscript_ui 19 | 20 | attributes: 21 | self.serv -- ZBrushServer instance 22 | self.client -- MayaClient instance 23 | 24 | self.maya_status_ui -- status lines 25 | self.zbrush_status_ui -- 26 | 27 | self.maya_host_ui -- maya host 28 | self.maya_port_ui -- maya port 29 | 30 | self.zbrush_port_ui -- zbrush port 31 | 32 | """ 33 | 34 | def __init__(self): 35 | zhost, zport = utils.get_net_info('ZNET') 36 | mhost, mport = utils.get_net_info('MNET') 37 | 38 | self.serv = zbrush_tools.ZBrushServer(zhost, zport) 39 | self.client = zbrush_tools.MayaClient(mhost, mport) 40 | 41 | self.maya_status_ui = None 42 | self.maya_host_ui = None 43 | self.maya_port_ui = None 44 | 45 | self.zbrush_port_ui = None 46 | self.zbrush_status_ui = None 47 | self.win = None 48 | 49 | self.build() 50 | self.serv_start() 51 | self.test_client() 52 | self.zscript_ui() 53 | self.win.mainloop() 54 | 55 | def serv_start(self): 56 | """ 57 | starts sever 58 | 59 | gets network info from UI (port) 60 | 61 | sets status line 62 | """ 63 | self.serv.port = self.zbrush_port_ui.get() 64 | 65 | with zbrush_tools.utils.err_handler(self.error_gui): 66 | self.serv.start() 67 | 68 | if self.serv.status: 69 | status_line = 'ZBrush Server Status: %s:%s' % ( 70 | self.serv.host, self.serv.port) 71 | 72 | self.zbrush_status_ui.config(text=status_line, background='green') 73 | else: 74 | self.zbrush_status_ui.config( 75 | text='ZBrush Server Status: down', 76 | background='red') 77 | 78 | def serv_stop(self): 79 | """ 80 | stops server 81 | 82 | sets status line 83 | 84 | """ 85 | 86 | self.serv.stop() 87 | self.zbrush_status_ui.config( 88 | text='ZBrush Server Status: down', 89 | background='red') 90 | 91 | def zscript_ui(self): 92 | """install UI in ZBrush """ 93 | 94 | self.client.activate_zbrush() 95 | self.client.zscript_ui() 96 | 97 | def test_client(self): 98 | """tests conn to MayaSever """ 99 | 100 | self.client.host = self.maya_host_ui.get() 101 | self.client.port = self.maya_port_ui.get() 102 | 103 | self.maya_status_ui.config( 104 | text='MayaClient Status: conn refused', 105 | background='red') 106 | 107 | with zbrush_tools.utils.err_handler(self.error_gui): 108 | ret = self.client.test_client() 109 | 110 | if ret: 111 | print 'connected to maya' 112 | self.maya_status_ui.config( 113 | text='MayaClient Status: connected', 114 | background='green') 115 | 116 | def build(self): 117 | """Creates tkinter UI """ 118 | self.win = Tkinter.Tk() 119 | self.win.title('GoZ GUI') 120 | Tkinter.Label( 121 | self.win, 122 | text='GoZ - Basic Setup:').pack( 123 | pady=5, 124 | padx=25) 125 | 126 | Tkinter.Label( 127 | self.win, 128 | text='Set MNET/ZNET/ZDOCS envs').pack( 129 | pady=0, 130 | padx=25) 131 | Tkinter.Label( 132 | self.win, 133 | text='like: ZNET=127.0.0.1:6668').pack( 134 | pady=0, 135 | padx=25) 136 | Tkinter.Label( 137 | self.win, 138 | text='set ZDOCS to your network path').pack( 139 | pady=0, 140 | padx=25) 141 | 142 | zb_cfg = Tkinter.LabelFrame(self.win, text="ZBrush Server") 143 | zb_cfg.pack(pady=15, fill="both", expand="yes") 144 | 145 | Tkinter.Label(zb_cfg, text='ZBrush Port:').pack(pady=5, padx=5) 146 | self.zbrush_port_ui = Tkinter.Entry(zb_cfg, width=15) 147 | self.zbrush_port_ui.pack() 148 | self.zbrush_port_ui.insert(0, self.serv.port) 149 | 150 | Tkinter.Button( 151 | zb_cfg, 152 | text='Start', 153 | command=self.serv_start).pack() 154 | Tkinter.Button( 155 | zb_cfg, 156 | text='Stop', 157 | command=self.serv_stop).pack() 158 | 159 | self.zbrush_status_ui = Tkinter.Label( 160 | zb_cfg, 161 | text='ZBrush Server Status: down', 162 | background='red') 163 | self.zbrush_status_ui.pack(pady=5, padx=5) 164 | 165 | maya_cfg = Tkinter.LabelFrame(self.win, text="Maya Client") 166 | maya_cfg.pack(pady=15, fill="both", expand="yes") 167 | 168 | Tkinter.Label(maya_cfg, text='Maya Host:').pack(pady=5, padx=5) 169 | self.maya_host_ui = Tkinter.Entry(maya_cfg, width=15) 170 | self.maya_host_ui.insert(0, self.client.host) 171 | self.maya_host_ui.pack() 172 | 173 | Tkinter.Label(maya_cfg, text='Maya Port:').pack(pady=5, padx=5) 174 | self.maya_port_ui = Tkinter.Entry(maya_cfg, width=15) 175 | self.maya_port_ui.insert(0, self.client.port) 176 | self.maya_port_ui.pack() 177 | 178 | Tkinter.Button( 179 | maya_cfg, 180 | text='Make ZBrush UI', 181 | command=self.zscript_ui).pack() 182 | Tkinter.Button( 183 | maya_cfg, 184 | text='Test Connection', 185 | command=self.test_client).pack() 186 | self.maya_status_ui = Tkinter.Label( 187 | maya_cfg, 188 | text='Maya Client Status: conn refused', 189 | background='red') 190 | self.maya_status_ui.pack(pady=5, padx=5) 191 | 192 | @staticmethod 193 | def error_gui(message): 194 | """simple tkinter gui for displaying errors""" 195 | tkMessageBox.showwarning('GoZ Error:', message) 196 | -------------------------------------------------------------------------------- /GoZ/mayagui.py: -------------------------------------------------------------------------------- 1 | """Class to create a gui within maya uses GoZ.maya_tools""" 2 | 3 | from pymel.core import window 4 | from pymel.core import button 5 | from pymel.core import rowColumnLayout 6 | from pymel.core import text 7 | from pymel.core import textField 8 | from pymel.core import separator 9 | from pymel.core import deleteUI 10 | from pymel.core import confirmDialog 11 | from GoZ import maya_tools as maya_tools 12 | 13 | 14 | class Win(object): 15 | 16 | """ 17 | 18 | GUI for maya_tools 19 | 20 | attributes: 21 | self.serv -- MayaServer instance 22 | self.client -- ZBrushClient instance 23 | self.user_* -- user defined network info 24 | 25 | self.maya_status_ui -- connection status 26 | self.zbrush_status_ui -- connection status 27 | 28 | self.gui/btn -- various pymel UI elements 29 | """ 30 | 31 | def __init__(self): 32 | """ new server/client, make gui, start server """ 33 | 34 | self.serv = maya_tools.MayaServer() 35 | self.client = maya_tools.ZBrushClient() 36 | self.gui_window = None 37 | self.send_btn = None 38 | self.user_zbrush_host = None 39 | self.user_zbrush_port = None 40 | self.listen_btn = None 41 | self.user_maya_port = None 42 | self.maya_status_ui = None 43 | self.conn_btn = None 44 | self.zbrush_status_gui = None 45 | 46 | # make the gui 47 | self.build() 48 | self.buttons() 49 | # start MayaServer 50 | self.listen() 51 | # check ZBrushClient connection to ZBrushServer 52 | self.connect() 53 | 54 | self.client.check_socket() 55 | self.check_status_ui() 56 | 57 | def update_network(self): 58 | """ sends host/port back to client/server instances""" 59 | 60 | self.client.host = self.user_zbrush_host.getText() 61 | self.client.port = self.user_zbrush_port.getText() 62 | 63 | self.serv.port = self.user_maya_port.getText() 64 | 65 | def connect(self, *args): 66 | """ connects to ZBrushServer using ZBrushClient instance""" 67 | print args 68 | 69 | self.update_network() 70 | with maya_tools.utils.err_handler(self.error_gui): 71 | self.client.connect() 72 | self.check_status_ui() 73 | 74 | def check_status_ui(self): 75 | """ updates statuslines, connected/disconnected for zbrush """ 76 | # check if client is connected, set gui accordingly 77 | if self.client.status: 78 | self.zbrush_status_ui.setBackgroundColor((0.0, 1.0, 0.5)) 79 | self.zbrush_status_ui.setLabel( 80 | 'Status: connected (' + 81 | self.client.host + ':' + 82 | str(self.client.port) + ')') 83 | else: 84 | self.zbrush_status_ui.setBackgroundColor((1, 0, 0)) 85 | self.zbrush_status_ui.setLabel('Status: not connected') 86 | 87 | def send(self, *args): 88 | """ 89 | send to zbrush using client instance 90 | 91 | assists in some handling of GoZBrushID name mistmatches, 92 | this is done here to easliy create GUI boxes for create/relink 93 | 94 | client.get_gozid_mistmatches returns a list of GoZBrushID mistmatches 95 | that need to resolved before sending the object to ZBrushServer 96 | 97 | """ 98 | 99 | self.client.check_socket() 100 | try: 101 | self.check_status_ui() 102 | except: 103 | pass 104 | 105 | if self.client.status is False: 106 | # try last socket, or fail 107 | with maya_tools.utils.err_handler(self.error_gui): 108 | self.client.connect() 109 | self.check_status_ui() 110 | 111 | # construct list of selection, filter meshes 112 | if self.client.parse_objs(): 113 | # check for any GoZBrushIDs, and relink/create 114 | for obj, goz_id in self.client.get_gozid_mismatches(): 115 | # relinked objs are removed from self.client.objs 116 | # this prevents relinking 2 previous tool histories 117 | # it stops relinking after the 1st match/relink 118 | # so pSphere1 contains both meshes, but pSphere2 still exists 119 | # this prevents overwriting 2 zbrush tools with the same obj 120 | 121 | # the 'skip' option during in the relink gui keeps the obj to look 122 | # for an alternative history, for example relink the 2nd obj history 123 | # if skip fails to relink, it will default to 'create' 124 | 125 | if obj in self.client.objs: 126 | self.client.goz_id = goz_id 127 | self.client.goz_obj = obj 128 | self.rename_gui() 129 | with maya_tools.utils.err_handler(self.error_gui): 130 | self.client.send() 131 | else: 132 | self.error_gui('Please select a mesh to send') 133 | 134 | def listen(self, *args): 135 | """ writes back host/port to MayaServer, starts listening """ 136 | 137 | print args 138 | 139 | self.update_network() 140 | self.serv.status = False 141 | 142 | with maya_tools.utils.err_handler(self.error_gui): 143 | self.serv.start() 144 | 145 | # check if server is up, set gui accordingly 146 | if self.serv.status: 147 | self.maya_status_ui.setBackgroundColor((0.0, 1.0, 0.5)) 148 | self.maya_status_ui.setLabel( 149 | 'Status: listening (' + 150 | self.serv.host + ':' + 151 | str(self.serv.port) + ')') 152 | else: 153 | self.maya_status_ui.setBackgroundColor((1, 0, 0)) 154 | self.maya_status_ui.setLabel('Status: not listening') 155 | 156 | def rename_gui(self): 157 | """ 158 | confirms object rename, 159 | triggers create or relink 160 | then revises objlist 161 | """ 162 | gui_message = """%s has a old ZBrush ID, of %s, try to relink? 163 | 164 | NOTE! relinking will 165 | remove objects named "%s" 166 | selected mesh as the new one!! 167 | """ % (self.client.goz_obj, self.client.goz_id, self.client.goz_id) 168 | 169 | choice = confirmDialog(title="ZBrush Name Conflict", 170 | message=gui_message, 171 | button=['Relink', 'Create', 'Skip']) 172 | if 'Relink' in choice: 173 | # relink to past GoZBrushID 174 | self.client.relink() 175 | # remove any corrected IDs from list 176 | self.client.parse_objs() 177 | if 'Create' in choice: 178 | # new object for zbrush 179 | self.client.create() 180 | # remove any corrected IDs from list 181 | self.client.parse_objs() 182 | print 'time make a new one' 183 | 184 | def build(self): 185 | """ constructs gui """ 186 | if window('goz', exists=True): 187 | deleteUI('goz', window=True) 188 | 189 | self.gui_window = window('goz', title="send to zbrush") 190 | layout = rowColumnLayout( 191 | numberOfColumns=3, 192 | columnAttach=(1, 'right', 0), 193 | columnWidth=[(1, 100), (2, 240), (3, 60)]) 194 | text(label='ZBrush IP') 195 | self.user_zbrush_host = textField(text=self.client.host) 196 | self.spacer(1) 197 | text(label='ZBrush PORT') 198 | self.user_zbrush_port = textField(text=self.client.port) 199 | self.spacer(2) 200 | self.send_btn = button(label="Send Meshes to ZBrush", parent=layout) 201 | self.spacer(2) 202 | self.conn_btn = button(label="Connect to ZBrush", parent=layout) 203 | self.spacer(2) 204 | self.zbrush_status_ui = text(label='Status: not connected', 205 | height=30, 206 | enableBackground=True, 207 | backgroundColor=(1.0, 0.0, 0.0)) 208 | self.spacer(2) 209 | separator(style='double', height=30) 210 | self.spacer(1) 211 | text(label='Maya PORT') 212 | self.user_maya_port = textField(text=self.serv.port) 213 | self.spacer(2) 214 | self.listen_btn = button( 215 | label="Listen for Meshes from ZBrush", 216 | parent=layout) 217 | self.spacer(2) 218 | self.maya_status_ui = text(label='Status: not listening', 219 | height=30, 220 | enableBackground=True, 221 | backgroundColor=(1.0, 0.0, 0.0)) 222 | self.spacer(1) 223 | self.gui_window.show() 224 | 225 | def buttons(self): 226 | """ attaches methods to callbacks """ 227 | self.send_btn.setCommand(self.send) 228 | self.conn_btn.setCommand(self.connect) 229 | self.listen_btn.setCommand(self.listen) 230 | 231 | @staticmethod 232 | def error_gui(message): 233 | """ simple gui for displaying errors """ 234 | confirmDialog( 235 | title=str('GoZ Error:'), 236 | message='\n' + str(message), 237 | button=['Ok']) 238 | 239 | @staticmethod 240 | def spacer(num): 241 | """ creates a spacer """ 242 | for _ in range(0, num): 243 | separator(style='none') 244 | -------------------------------------------------------------------------------- /GoZ/maya_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | Maya Server and ZBrush client classes 3 | 4 | MayaServer is used to start a commandPort, 5 | and listen for objects from ZBrush 6 | 7 | Objects are loaded when ZBrushServer calls 8 | client.load funcitons with name/path and tool parent 9 | 10 | If the ZDOCS env is missing MayaServer/ZBrushClient 11 | will start in a local mode 12 | 13 | ZBrushClient is used for sending ascii files to ZBrush 14 | from Maya, it also manges GoZBrushIDs, and GoZParent attributes 15 | These attributes are used to keep track of name changes in maya 16 | 17 | Conflicts in the attributes result in renaming on export 18 | or creating new attributes to fit name changes 19 | 20 | CONSTANTS: 21 | 22 | GARBAGE_NODES -- nodes marked for removal in maya 23 | used to prevent duplicates 24 | 25 | """ 26 | 27 | import socket 28 | import errno 29 | import GoZ.errs as errs 30 | import GoZ.utils as utils 31 | import maya.cmds as cmds 32 | import os 33 | 34 | # nodes marked for removal from maya on import from ZBrush 35 | GARBAGE_NODES = ['blinn', 36 | 'blinnSG', 37 | 'materialInfo', 38 | 'ZBrushTexture', 39 | 'place2dTexture2'] 40 | 41 | 42 | class MayaServer(object): 43 | 44 | """ 45 | 46 | Maya server using commandPort, gets meshes from zbrush 47 | 48 | start/stop(host,port) functions open/close the maya commandPort 49 | 50 | attributes: 51 | self.status -- current server status (up/down) 52 | self.host -- current host for serving on from utils.get_net_info 53 | self.port -- current port for serving on from utils.get_net_info 54 | self.cmdport_name -- formated command port name (xxx.xxx.xxx.xxx:port) 55 | self.file_path -- current file loaded from ZBrush (full path) 56 | self.file_name -- current object loaded from ZBrush (name only no ext) 57 | 58 | """ 59 | 60 | def __init__(self): 61 | """gets networking info, creates command port name """ 62 | self.host, self.port = utils.get_net_info('MNET') 63 | 64 | self.cmdport_name = "%s:%s" % (self.host, self.port) 65 | self.status = False 66 | 67 | def start(self): 68 | """ starts a command port""" 69 | 70 | # check network info 71 | utils.validate_host(self.host) 72 | utils.validate_port(self.port) 73 | 74 | self.cmdport_name = "%s:%s" % (self.host, self.port) 75 | self.status = cmds.commandPort(self.cmdport_name, query=True) 76 | 77 | # if down, start a new command port 78 | if self.status is False: 79 | cmds.commandPort(name=self.cmdport_name, sourceType='python') 80 | self.status = cmds.commandPort(self.cmdport_name, query=True) 81 | print 'listening %s' % self.cmdport_name 82 | 83 | def stop(self): 84 | """ stop command port """ 85 | cmds.commandPort(name=self.cmdport_name, 86 | sourceType='python', close=True) 87 | self.status = cmds.commandPort(self.cmdport_name, 88 | query=True) 89 | print 'closing %s' % self.cmdport_name 90 | 91 | # Maya-side callbacks 92 | 93 | 94 | def load(file_path, obj_name, parent_name): 95 | """ 96 | get file name from file path 97 | remove matching nodes 98 | import file 99 | """ 100 | file_name = utils.split_file_name(file_path) 101 | cleanup(file_name) 102 | cmds.file(file_path, i=True, 103 | usingNamespaces=False, 104 | removeDuplicateNetworks=True) 105 | cmds.addAttr(obj_name, longName='GoZParent', dataType='string') 106 | cmds.setAttr(obj_name + '.GoZParent', parent_name, type='string') 107 | 108 | 109 | def cleanup(name): 110 | """ removes un-used nodes on import of obj""" 111 | 112 | if cmds.objExists(name): 113 | cmds.delete(name) 114 | 115 | for node in GARBAGE_NODES: 116 | node = name + '_' + node 117 | if cmds.objExists(node): 118 | cmds.delete(node) 119 | 120 | 121 | class ZBrushClient(object): 122 | 123 | """ 124 | ZBrush client used for sending meshes to zbrush 125 | 126 | methods of this class handle: 127 | Object name management between zbrush/maya 128 | Connections to ZBrushServer 129 | Cleaning and exporting mayaAscii files 130 | 131 | attributes: 132 | self.status -- status of the connection to ZBrushServer 133 | self.ascii_path -- current maya ascii file export path 134 | self.objs -- list of objects to send to ZBrushServer 135 | self.host -- current host obtained from utils.get_net_info 136 | self.port -- current port obtained from utils.get_net_info 137 | self.sock -- current open socket connection 138 | 139 | """ 140 | 141 | def __init__(self): 142 | """gets networking information, initalizes client""" 143 | 144 | self.host, self.port = utils.get_net_info('ZNET') 145 | self.status = False 146 | self.sock = None 147 | self.objs = None 148 | self.goz_id = None 149 | self.goz_obj = None 150 | self.ascii_path = None 151 | 152 | def connect(self): 153 | """connects to ZBrushServer """ 154 | 155 | try: 156 | # close old socket, might not exist so skip 157 | self.sock.close() 158 | except AttributeError: 159 | print 'no socket to close...' 160 | 161 | self.status = False 162 | 163 | utils.validate_host(self.host) 164 | utils.validate_port(self.port) 165 | 166 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 167 | # time out incase of a bad host/port that actually exists 168 | self.sock.settimeout(45) 169 | 170 | try: 171 | self.sock.connect((self.host, int(self.port))) 172 | except socket.error as err: 173 | self.status = False 174 | if errno.ECONNREFUSED in err: 175 | raise errs.ZBrushServerError( 176 | 'Connection Refused: %s:%s' % (self.host, self.port)) 177 | 178 | self.status = True 179 | 180 | def check_socket(self): 181 | """ verify connection to zbrush """ 182 | 183 | if self.sock is None: 184 | return 185 | 186 | try: 187 | self.sock.send('check') 188 | if self.sock.recv(1024) == 'ok': 189 | # connected 190 | print 'connected!' 191 | else: 192 | # bad connection, clear socket 193 | self.status = False 194 | self.sock.close() 195 | self.sock = None 196 | print 'conn reset!' 197 | 198 | except socket.error as err: 199 | # catches server down errors, resets socket 200 | self.status = False 201 | self.sock.close() 202 | self.sock = None 203 | if errno.ECONNREFUSED in err: 204 | print 'conn ref' 205 | # server probbly down 206 | if errno.EADDRINUSE in err: 207 | # this is fine 208 | print 'already connected...' 209 | if errno.EPIPE in err: 210 | # server down, or unexpected connection interuption 211 | print 'broken pipe, trying to reconnect' 212 | except AttributeError: 213 | print 'need new sock' 214 | 215 | def send(self): 216 | """ 217 | send a file load command to ZBrush via ZBrushServer 218 | 219 | commands are send looking like: 220 | open|object#parentobject:nextobject#nextparent 221 | 222 | this is then parsed by ZBrushServer 223 | """ 224 | 225 | # export, send 226 | if self.status: 227 | self.export() 228 | 229 | sendlist = [] 230 | 231 | # organize lists so top level objects are first 232 | for obj in self.objs: 233 | if obj.split('#')[0] == obj.split('#')[1]: 234 | sendlist.append(obj) 235 | 236 | for obj in self.objs: 237 | if obj.split('#')[0] != obj.split('#')[1]: 238 | sendlist.append(obj) 239 | 240 | self.sock.send('open|' + ':'.join(sendlist)) 241 | # check receipt of objs 242 | self.load_confirm() 243 | else: 244 | raise errs.ZBrushServerError( 245 | 'Please connect to ZBrushServer first') 246 | 247 | def load_confirm(self): 248 | """ 249 | checks with ZBrushServer to make 250 | sure objects are loaded after a send 251 | 252 | 'loaded' will be sent back from ZBrushServer 253 | on load of a object from maya 254 | """ 255 | 256 | if self.sock.recv(1024) == 'loaded': 257 | print 'ZBrush Loaded:' 258 | print ('\n'.join(self.objs)) 259 | else: 260 | self.status = False 261 | self.sock = None 262 | print 'ZBrushServer is down!' 263 | raise errs.ZBrushServerError('ZBrushServer is down!') 264 | 265 | def export(self): 266 | """ 267 | 268 | saves files, also checks for GoZParent attr 269 | 270 | GoZParent is used to import objects in correct order in ZBrush 271 | GoZParent determines the top level tool in ZBrush 272 | 273 | If no instance exists, it is created 274 | 275 | GoZParent is also appended to the export string: objectname#gozparentname 276 | 277 | """ 278 | 279 | print self.objs 280 | 281 | # default pm3d star 282 | 283 | new_objects = [] 284 | 285 | for idx, obj in enumerate(self.objs): 286 | 287 | cmds.select(cl=True) 288 | cmds.select(obj) 289 | cmds.delete(ch=True) 290 | self.ascii_path = utils.make_file_name(obj) 291 | cmds.file(self.ascii_path, 292 | force=True, 293 | options="v=0", 294 | type="mayaAscii", 295 | exportSelected=True) 296 | if cmds.attributeQuery('GoZParent', node=obj, exists=True): 297 | # object existed in zbrush, has 'parent' tool 298 | parent = cmds.getAttr(obj + '.GoZParent') 299 | else: 300 | # construct a list of objects to create 301 | # append all future objects as sub tools 302 | new_objects.append(obj) 303 | parent = new_objects[0] 304 | cmds.addAttr(obj, longName='GoZParent', dataType='string') 305 | cmds.setAttr(obj + '.GoZParent', parent, type='string') 306 | self.objs[idx] = obj + '#' + parent 307 | 308 | # maya is often run as root, this makes sure osx can open/save files 309 | # not needed if maya is run un-privileged 310 | os.chmod(self.ascii_path, 0o777) 311 | 312 | def parse_objs(self): 313 | """ 314 | grab meshes from selection, filters out extraneous dag objects 315 | Also freezes transforms on objects 316 | 317 | """ 318 | self.objs = cmds.ls(selection=True, type='mesh', dag=True) 319 | if self.objs: 320 | xforms = cmds.listRelatives( 321 | self.objs, parent=True, fullPath=True) 322 | # freeze transform 323 | cmds.makeIdentity(xforms, apply=True, t=1, r=1, s=1, n=0) 324 | cmds.select(xforms) 325 | self.objs = cmds.ls(selection=True) 326 | return True 327 | else: 328 | return False 329 | 330 | def get_gozid_mismatches(self): 331 | """ 332 | checks object history for instances of GoZBrushID, 333 | returns a list ofGoZBrushID/name conflicts 334 | 335 | GoZBrushID is created by ZBrush on export and is used to track 336 | name changes that can occur in maya 337 | 338 | this function compares object current name against the ID 339 | and returns a list of conflicts 340 | 341 | this list is handled by the gui to allow for dialog boxes 342 | 343 | """ 344 | 345 | goz_list = [] 346 | 347 | for obj in self.objs: 348 | 349 | goz_check = cmds.attributeQuery( 350 | 'GoZBrushID', node=obj, exists=True) 351 | 352 | if goz_check: 353 | # check for 'rename' 354 | goz_id = cmds.getAttr(obj + '.GoZBrushID') 355 | if obj != goz_id: 356 | goz_list.append((obj, goz_id)) 357 | else: 358 | # check for old ID in history 359 | history = cmds.listHistory(obj) 360 | for old_obj in history: 361 | goz_check = cmds.attributeQuery('GoZBrushID', 362 | node=old_obj, 363 | exists=True) 364 | if goz_check: 365 | goz_id = cmds.getAttr(old_obj + '.GoZBrushID') 366 | if obj != goz_id: 367 | goz_list.append((obj, goz_id)) 368 | 369 | # resulting mismatches to be handled 370 | return goz_list 371 | 372 | def relink(self): 373 | """ relink object name with existing GoZBrushID""" 374 | if self.goz_obj not in self.objs: 375 | return 376 | 377 | # manages re linking GoZBrush IDs, checks for attribute on shape/xform 378 | obj = self.goz_obj 379 | goz_id = self.goz_id 380 | 381 | pre_sel = cmds.ls(sl=True) 382 | cmds.delete(obj, ch=True) 383 | 384 | # in the case of a object being duplicated this removes the duplicate 385 | # to prevent deletion, the 'create' option is prefered 386 | # is only happens when an object was duplicated and merged (original 387 | # still exists) 388 | if cmds.objExists(goz_id): 389 | cmds.delete(goz_id) 390 | 391 | cmds.rename(obj, goz_id) 392 | cmds.select(cl=True) 393 | cmds.select(goz_id) 394 | shape = cmds.ls(selection=True, type='mesh', dag=True)[0] 395 | xform = cmds.listRelatives(shape, parent=True, fullPath=True)[0] 396 | goz_check_xform = cmds.attributeQuery( 397 | 'GoZBrushID', node=xform, exists=True) 398 | goz_check_shape = cmds.attributeQuery( 399 | 'GoZBrushID', node=shape, exists=True) 400 | 401 | if goz_check_shape is False: 402 | cmds.addAttr(shape, longName='GoZBrushID', dataType='string') 403 | if goz_check_xform is False: 404 | cmds.addAttr(xform, longName='GoZBrushID', dataType='string') 405 | 406 | cmds.setAttr(shape + '.GoZBrushID', goz_id, type='string') 407 | cmds.setAttr(xform + '.GoZBrushID', goz_id, type='string') 408 | cmds.select(cl=True) 409 | pre_sel.remove(obj) 410 | pre_sel.append(xform) 411 | print pre_sel 412 | cmds.select(pre_sel) 413 | 414 | def create(self): 415 | """ 416 | changes a GoZBrush ID to match object name 417 | ZBrush then treats this as a new object 418 | 419 | """ 420 | obj = self.goz_obj 421 | pre_sel = cmds.ls(sl=True) 422 | cmds.delete(obj, ch=True) 423 | cmds.select(cl=True) 424 | cmds.select(obj) 425 | shape = cmds.ls(selection=True, type='mesh', dag=True)[0] 426 | xform = cmds.listRelatives(shape, parent=True, fullPath=True)[0] 427 | goz_check_xform = cmds.attributeQuery( 428 | 'GoZBrushID', node=xform, exists=True) 429 | goz_check_shape = cmds.attributeQuery( 430 | 'GoZBrushID', node=shape, exists=True) 431 | 432 | if goz_check_shape: 433 | cmds.setAttr(shape + '.GoZBrushID', obj, type='string') 434 | if goz_check_xform: 435 | cmds.setAttr(xform + '.GoZBrushID', obj, type='string') 436 | cmds.select(pre_sel) 437 | -------------------------------------------------------------------------------- /GoZ/zbrush_tools.py: -------------------------------------------------------------------------------- 1 | """ 2 | starts ZBrushSever, manages MayaClient 3 | 4 | ZbrushServer recived strings such as: 5 | open|objectname#objectparent:anotherobject#anotherparent... 6 | 7 | These are parsed and opened in ZBrush with the use of some apple script 8 | 9 | MayaClient conencts to a open commandPort in maya 10 | 11 | GoZ.maya_tools.load(file,objname,objparent) is used to open files 12 | 13 | 14 | """ 15 | import os 16 | import socket 17 | import SocketServer 18 | from threading import Thread 19 | from GoZ import utils as utils 20 | 21 | 22 | class ZBrushServer(object): 23 | 24 | """ 25 | ZBrush server extending SocketServer module, gets meshes from maya 26 | 27 | attributes: 28 | self.status -- current server status (up/down) 29 | self.host -- current host for serving on from utils.get_net_info 30 | self.port -- current port for serving on from utils.get_net_info 31 | self.cmdport_name -- formated command port name 32 | 33 | """ 34 | 35 | def __init__(self, host, port): 36 | """initializes server with host/port to server on, send from GoZ.zbrushgui """ 37 | 38 | self.host = host 39 | self.port = port 40 | self.server = None 41 | self.server_thread = None 42 | self.status = False 43 | 44 | def start(self): 45 | """ looks for previous server, trys to start a new one""" 46 | 47 | self.status = False 48 | 49 | utils.validate_host(self.host) 50 | utils.validate_port(self.port) 51 | 52 | if self.server is not None: 53 | print 'killing previous server...' 54 | self.server.shutdown() 55 | self.server.server_close() 56 | 57 | print 'starting a new server!' 58 | 59 | self.server = ZBrushSocketServ( 60 | (self.host, int(self.port)), ZBrushHandler) 61 | self.server.allow_reuse_address = True 62 | self.server_thread = Thread(target=self.server.serve_forever) 63 | self.server_thread.daemon = True 64 | self.server_thread.start() 65 | print 'Serving on %s:%s' % (self.host, self.port) 66 | self.status = True 67 | 68 | def stop(self): 69 | """ shuts down ZBrushSever""" 70 | self.server.shutdown() 71 | self.server.server_close() 72 | print 'stoping...' 73 | self.status = False 74 | 75 | 76 | class ZBrushSocketServ(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 77 | 78 | """ 79 | extends socket server with custom settings 80 | 81 | configures daemon mode for socketserv module 82 | """ 83 | timeout = 5 84 | daemon_threads = True 85 | allow_reuse_address = True 86 | 87 | # handler is the RequestHandlerClass 88 | def __init__(self, server_address, handler): 89 | SocketServer.TCPServer.__init__( 90 | self, 91 | server_address, 92 | handler) 93 | 94 | def handle_timeout(self): 95 | print 'TIMEOUT' 96 | 97 | 98 | class ZBrushHandler(SocketServer.BaseRequestHandler): 99 | 100 | """ 101 | custom handler for ZBrushSever 102 | handles loading objects from maya 103 | 104 | splits: 105 | open|objectname#objectparent:anotherobject#anotherparent... 106 | 107 | also response with 'loaded' on sucessful object load 108 | 109 | if 'check' is send from ZBrushClient a 'ok' send back 110 | this is used to check if the server is up/ready 111 | 112 | """ 113 | 114 | def handle(self): 115 | # keep handle open until client/server close 116 | while True: 117 | data = self.request.recv(1024).strip() 118 | if not data: 119 | self.request.close() 120 | break 121 | print data 122 | # check for conn-reset/disconnect by peer (on client) 123 | if data == 'check': 124 | self.request.send('ok') 125 | 126 | # parse object list from maya 127 | if data.split('|')[0] == 'open': 128 | print data 129 | objs = data.split('|')[1].split(':') 130 | for obj in objs: 131 | print 'got: ' + obj 132 | parent = obj.split('#')[1] 133 | obj = obj.split('#')[0] 134 | zs_temp = self.zbrush_open(obj + '.ma', parent) 135 | utils.send_osa(zs_temp) 136 | print 'loaded all objs!' 137 | self.request.send('loaded') 138 | 139 | @staticmethod 140 | def zbrush_open(name, parent): 141 | """open a file with zbrush 142 | -create temp zscript file 143 | -load with file open commands 144 | -replace #TOOLNAME/#FILENAME with maya path/filename 145 | -iterate through current subtools to check for 'matches' 146 | -import if match, append new cloned tool for unique tools 147 | 148 | """ 149 | script_path = os.environ['HOME'] 150 | script_path = os.path.join(script_path, 'zbrush_load.txt') 151 | zs_temp = open(script_path, 'w+') 152 | 153 | env = os.getenv(utils.SHARED_DIR_ENV) 154 | print env 155 | 156 | # zbrush script to iterate through sub tools, 157 | # and open matches, appends new tools 158 | 159 | zscript = """ 160 | 161 | //this is a new set of import functions 162 | //it allows the loop up of top level tools 163 | //routine to locate a tool by name 164 | //ZBrush uses ToolID, SubToolID, and UniqueID's 165 | //All of these are realative per project/session 166 | 167 | //find subtool 168 | 169 | [RoutineDef, findSubTool, 170 | 171 | 172 | //iterate through sub tools 173 | //even though the ui element exists 174 | //it may not be visable 175 | [SubToolSelect,0] 176 | 177 | [Loop,[SubToolGetCount], 178 | 179 | //get currently selected tool name to compare 180 | [VarSet,currentTool,[IgetTitle, Tool:Current Tool]] 181 | [VarSet,subTool, [FileNameExtract, #currentTool, 2]] 182 | [If,([StrLength,"#TOOLNAME"]==[StrLength,#subTool])&&([StrFind,#subTool,"#TOOLNAME"]>-1), 183 | //there was a match, import 184 | //stop looking 185 | [LoopExit] 186 | ,] 187 | //move through each sub tool to make it visable 188 | [If,[IsEnabled,Tool:SubTool:SelectDown], 189 | [IPress, Tool:SubTool:SelectDown] 190 | 191 | , 192 | [IPress, Tool:SubTool:Duplicate] 193 | [IPress, Tool:SubTool:MoveDown] 194 | [IPress, Tool:SubTool:All Low] 195 | [IPress, Tool:Geometry:Del Higher] 196 | [LoopExit] 197 | ] 198 | ] 199 | 200 | ] 201 | 202 | 203 | //find parent 204 | 205 | [RoutineDef, findTool, 206 | 207 | //ToolIDs befor 47 are 'default' tools 208 | //48+ are user loaded tools 209 | //this starts the counter at 48 210 | //also gets the last 'tool' 211 | [VarSet,count,[ToolGetCount]-47] 212 | [VarSet,a, 47] 213 | 214 | //flag for if a object was found 215 | //or a new blank object needs to be made 216 | [VarSet, makeTool,0] 217 | 218 | //shuts off interface update 219 | [IFreeze, 220 | 221 | [Loop, #count, 222 | //increment current tool 223 | [VarSet, a, a+1] 224 | 225 | //select tool to look for matches 226 | [ToolSelect, #a] 227 | [SubToolSelect,0] 228 | 229 | //check for matching tool 230 | //looks in the interface/UI 231 | [VarSet, uiResult, [IExists,Tool:SubTool:#PARENT]] 232 | 233 | [If, #uiResult == 1, 234 | //check to see if tool is a parent tool 235 | //if it is select it, otherwise iterate to find sub tool 236 | //ideally direct selection of the subtool would be posible 237 | //but sub tools can potentially be hidden in the UI 238 | //findSubTool iterates through sub tools to find a match 239 | [If, [IExists,Tool:#PARENT], 240 | [IPress, Tool:#PARENT], 241 | ] 242 | 243 | [RoutineCall, findSubTool] 244 | [VarSet, makeTool,0] 245 | [LoopExit] 246 | , 247 | [VarSet,makeTool,1] 248 | 249 | ] 250 | ] 251 | //check to see if found or needs a new blank mesh 252 | [If, #makeTool==1, 253 | //make a blank PolyMesh3D 254 | [ToolSelect, 41] 255 | [IPress,Tool:Make PolyMesh3D] 256 | 257 | , 258 | //otherwise 259 | //find sub tool 260 | 261 | ] 262 | ] 263 | ] 264 | 265 | 266 | //find 'parent tool 267 | //check for sub tool 268 | //if found import 269 | //if missing make new tool 270 | 271 | [RoutineDef, open_file, 272 | //check if in edit mode 273 | [VarSet, ui,[IExists,Tool:SubTool:All Low]] 274 | 275 | //if no open tool make a new tool 276 | // this could happen if there is no active mesh 277 | [If, ui == 0, 278 | [ToolSelect, 41] 279 | [IPress,Tool:Make PolyMesh3D] 280 | , 281 | ] 282 | 283 | //find parent 284 | [RoutineCall, findTool] 285 | 286 | //lowest sub-d 287 | [IPress, Tool:SubTool:All Low] 288 | [FileNameSetNext,"!:#FILENAME"] 289 | //finally import the tool 290 | [IPress,Tool:Import] 291 | ] 292 | 293 | [RoutineCall,open_file] 294 | 295 | """ 296 | 297 | # swap above zscript #'s with info from maya 298 | # then write to temp file 299 | zscript = zscript.replace( 300 | '#FILENAME', os.path.join(env, name)) 301 | zscript = zscript.replace('#TOOLNAME', name.replace('.ma', '')) 302 | zscript = zscript.replace('#PARENT', parent) 303 | zs_temp.write(zscript) 304 | return zs_temp.name 305 | 306 | 307 | class MayaClient(object): 308 | 309 | """ 310 | Maya client used for sending meshes to maya, also contains methods 311 | to create a UI buttons in ZBrush with MayaClient.zscript_ui() 312 | 313 | Also contains a method to check operation with maya MayaClient.test_client() 314 | 315 | MayaClient.send() is used by the GUI installed in ZBrush by running: 316 | python -m GoZ.zbrush_tools 317 | 318 | this executes this module as a script with command line arguments 319 | the args contain objectname, and object parent tool 320 | 321 | GoZ.utils.osa_send is used to create a gui in ZBrush 322 | 323 | GoZ.utils.osa_open is also used to open ZBrush 324 | 325 | attributes: 326 | self.host -- current host obtained from utils.get_net_info 327 | self.port -- current port obtained from utils.get_net_info 328 | 329 | """ 330 | 331 | def __init__(self, host, port): 332 | """ inits client with values from gui""" 333 | self.host = host 334 | self.port = port 335 | 336 | @staticmethod 337 | def activate_zbrush(): 338 | """ apple script to open ZBrush and bring to front """ 339 | utils.open_osa() 340 | 341 | @staticmethod 342 | def zscript_ui(): 343 | """ assembles a zscript to be loaded by ZBrush to create GUI buttons """ 344 | 345 | # grab the current path of this file, make a temp file in the same 346 | # location 347 | script_path = os.environ['HOME'] 348 | script_path = os.path.join(script_path, 'zbrush_gui.txt') 349 | zs_temp = open(script_path, 'w+') 350 | 351 | # zscript to create the 'send' button 352 | zscript = """ 353 | [RoutineDef, send_file, 354 | 355 | //check if in edit mode 356 | [VarSet, ui,[IExists,Tool:SubTool:All Low]] 357 | 358 | //if no open tool make a new tool 359 | [If, ui == 0, 360 | [ToolSelect, 41] 361 | [IPress,Tool:Make PolyMesh3D] 362 | ,] 363 | 364 | //set lowest subtool resolution 365 | [IPress, Tool:SubTool:All Low] 366 | 367 | //base path for saving files 368 | //'!:' is required to prefix paths in ZBrush 369 | // #ENVPATH is replaced with the expanded SHARED_DIR_ENV 370 | [VarSet, env_path, "!:#ENVPATH/"] 371 | 372 | //extracts the current active tool name 373 | [VarSet, tool_name,[FileNameExtract, [GetActiveToolPath], 2]] 374 | 375 | //appends .ma to the path for export, construct filename 376 | [VarSet, file_name, [StrMerge,tool_name,".ma"]] 377 | 378 | //python module execution command, needs to be abs path 379 | [VarSet, module_path, "/usr/bin/python -m GoZ.zbrush_tools "] 380 | 381 | //append env to file path 382 | [VarSet, export_path, [StrMerge,env_path,file_name] ] 383 | 384 | //set the maya 'tamplate?' I think ofer spelled something wrong 385 | //this sets the file name for the next export \w correct 'tamplate' 386 | [FileNameSetNext, #export_path,"ZSTARTUP_ExportTamplates\Maya.ma"] 387 | 388 | [VarSet, validpath,[FileExists, "!:/Volumes/public/goz_default/"]] 389 | 390 | [If, validpath != 1, 391 | 392 | 393 | //prevents zbrush crash from exporting to a invalid path 394 | //if zbrush exports to a bad path it will lock up 395 | [MessageOK, "Invalid ZDOCS file path for export"] 396 | [MessageOK, #export_path] 397 | [Exit] 398 | , 399 | 400 | 401 | ] 402 | 403 | 404 | //finally export the tool 405 | [IPress,Tool:Export] 406 | 407 | //get base tool 408 | [SubToolSelect,0] 409 | 410 | [VarSet,base_tool,[IgetTitle, Tool:Current Tool]] 411 | [VarSet,base_tool, [FileNameExtract, #base_tool, 2]] 412 | 413 | 414 | //trigger the python module to send maya the load commands 415 | [ShellExecute, 416 | //merge the python command with the tool name 417 | [StrMerge, #module_path, 418 | #tool_name, " ",#base_tool 419 | ] 420 | ] 421 | ] 422 | 423 | //gui button for triggering this script 424 | [IButton, "TOOL:Send to Maya", "Export model as a *.ma to maya", 425 | [RoutineCall, send_file] 426 | ] 427 | 428 | """ 429 | # zscript to create the 'send -all' button 430 | zscript += """ 431 | [RoutineDef, send_all, 432 | 433 | //check if in edit mode 434 | [VarSet, ui,[IExists,Tool:SubTool:All Low]] 435 | 436 | //if no open tool make a new tool 437 | [If, ui == 0, 438 | [ToolSelect, 41] 439 | [IPress,Tool:Make PolyMesh3D] 440 | ,] 441 | 442 | //set all tools to lowest sub-d 443 | [IPress, Tool:SubTool:All Low] 444 | 445 | //iterator variable 446 | [VarSet,t,0] 447 | 448 | //start at the first subtool 449 | [SubToolSelect,0] 450 | 451 | //iterate through all subtools 452 | [Loop,[SubToolGetCount], 453 | 454 | //increment iterator 455 | [VarSet,t,t+1] 456 | 457 | //select current subtool index in loop 458 | [SubToolSelect,t-1] 459 | 460 | //set base export path #ENVPATH is replace with SHARED_DIR_ENV (expanded) 461 | [VarSet, env_path, "!:#ENVPATH/"] 462 | 463 | //current tool name 464 | [VarSet, tool_name, [FileNameExtract, [GetActiveToolPath], 2]] 465 | 466 | //start constructing export file path /some/dir/tool.ma 467 | [VarSet, file_name, [StrMerge,tool_name,".ma"]] 468 | 469 | //base python module shell command, needs to be abs path 470 | [VarSet, module_path, "/usr/bin/python -m GoZ.zbrush_tools "] 471 | 472 | 473 | //full export path 474 | [VarSet, export_path, [StrMerge,env_path,file_name] ] 475 | 476 | //set export path to be used by next command 477 | [FileNameSetNext, #export_path,"ZSTARTUP_ExportTamplates\Maya.ma"] 478 | 479 | 480 | 481 | [VarSet, validpath,[FileExists, "!:/Volumes/public/goz_default/"]] 482 | 483 | [If, validpath != 1, 484 | 485 | 486 | //prevents zbrush crash from exporting to a invalid path 487 | //if zbrush exports to a bad path it will lock up 488 | [MessageOK, "Invalid ZDOCS file path for export"] 489 | [MessageOK, #export_path] 490 | [Exit] 491 | , 492 | 493 | 494 | ] 495 | 496 | 497 | //finally export 498 | [IPress,Tool:Export] 499 | 500 | 501 | //get base tool 502 | [SubToolSelect,0] 503 | [VarSet,base_tool,[IgetTitle, Tool:Current Tool]] 504 | [VarSet,base_tool, [FileNameExtract, #base_tool, 2]] 505 | 506 | [ShellExecute, 507 | //join module_path tool_name for maya to load 508 | [StrMerge, #module_path, #tool_name, " ",#base_tool] 509 | ] 510 | ] 511 | ] 512 | [IButton, "TOOL:Send to Maya -all", "Export model as a *.ma to maya", 513 | [RoutineCall, send_all] 514 | ] 515 | """ 516 | 517 | # zscript to create the 'send -vis' button 518 | zscript += """ 519 | [RoutineDef, send_visable, 520 | 521 | //check if in edit mode 522 | [VarSet, ui,[IExists,Tool:SubTool:All Low]] 523 | 524 | //if no open tool make a new tool 525 | [If, ui == 0, 526 | [ToolSelect, 41] 527 | [IPress,Tool:Make PolyMesh3D] 528 | ,] 529 | 530 | //set all tools to lowest sub-d 531 | [IPress, Tool:SubTool:All Low] 532 | 533 | //iterator variable 534 | [VarSet,t,0] 535 | 536 | //start at the first subtool 537 | [SubToolSelect,0] 538 | 539 | //iterate through all subtools 540 | [Loop,[SubToolGetCount], 541 | 542 | //increment iterator 543 | [VarSet,t,t+1] 544 | 545 | //select current subtool index in loop 546 | [SubToolSelect,t-1] 547 | 548 | //set base export path #ENVPATH is replace with SHARED_DIR_ENV (expanded) 549 | [VarSet, env_path, "!:#ENVPATH/"] 550 | 551 | //current tool name 552 | [VarSet, tool_name, [FileNameExtract, [GetActiveToolPath], 2]] 553 | 554 | //start constructing export file path /some/dir/tool.ma 555 | [VarSet, file_name, [StrMerge,tool_name,".ma"]] 556 | 557 | //base python module shell command, needs to be absolute path 558 | [VarSet, module_path, "/usr/bin/python -m GoZ.zbrush_tools "] 559 | 560 | 561 | //full export path 562 | [VarSet, export_path, [StrMerge,env_path,file_name] ] 563 | 564 | //set export path to be used by next command 565 | [FileNameSetNext, #export_path,"ZSTARTUP_ExportTamplates\Maya.ma"] 566 | 567 | [VarSet, validpath,[FileExists, "!:/Volumes/public/goz_default/"]] 568 | 569 | [If, validpath != 1, 570 | 571 | 572 | //prevents zbrush crash from exporting to a invalid path 573 | //if zbrush exports to a bad path it will lock up 574 | [MessageOK, "Invalid ZDOCS file path for export"] 575 | [MessageOK, #export_path] 576 | [Exit] 577 | , 578 | 579 | 580 | ] 581 | //check visablility 582 | [VarSet,curTool,[IgetTitle, Tool:Current Tool]] 583 | //look at interface mod 584 | [If,[IModGet,[StrMerge,"Tool:SubTool:",curTool]] >= 16, 585 | //finally export if visable 586 | [IPress,Tool:Export] 587 | 588 | //get base tool 589 | [SubToolSelect,0] 590 | [VarSet,base_tool,[IgetTitle, Tool:Current Tool]] 591 | [VarSet,base_tool, [FileNameExtract, #base_tool, 2]] 592 | 593 | [ShellExecute, 594 | //join module_path tool_name for maya to load 595 | [StrMerge, #module_path, #tool_name, " ",#base_tool] 596 | ] 597 | 598 | , 599 | ] 600 | ] 601 | ] 602 | [IButton, "TOOL:Send to Maya -visable", "Export model as a *.ma to maya", 603 | [RoutineCall, send_visable] 604 | ] 605 | """ 606 | 607 | env = os.getenv(utils.SHARED_DIR_ENV) 608 | print env 609 | 610 | zscript = zscript.replace('#ENVPATH', env) 611 | zs_temp.write(zscript) 612 | zs_temp.flush() 613 | zs_temp.close() 614 | 615 | utils.send_osa(script_path) 616 | 617 | def test_client(self): 618 | """ tests connection with maya, creates a sphere and deletes it """ 619 | 620 | utils.validate_host(self.host) 621 | utils.validate_port(self.port) 622 | 623 | maya_cmd = 'import maya.cmds as cmds;' 624 | maya_cmd += 'cmds.sphere(name="goz_server_test;")' 625 | maya_cmd += 'cmds.delete("goz_server_test")' 626 | maya = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 627 | maya.settimeout(5) 628 | try: 629 | maya.connect((self.host, int(self.port))) 630 | except socket.error as err: 631 | print err 632 | print 'connection refused' 633 | return False 634 | except ValueError: 635 | print 'specify a valid port' 636 | return False 637 | else: 638 | maya.send(maya_cmd) 639 | maya.close() 640 | return True 641 | 642 | @staticmethod 643 | def send(obj_name, parent_name): 644 | """ 645 | sends a file to maya 646 | 647 | includes filepath, object name, and the "parent" 648 | 649 | The parent is the top level tool or sub tool 0 of the current tool 650 | this is used to preserve organization when loading back into ZBrush 651 | 652 | connects to maya commandPort and sends the maya commands 653 | 654 | """ 655 | print 'Parent tool: ' + parent_name 656 | 657 | # construct file read path for maya, uses SHARED_DIR_ENV 658 | # make realative path 659 | file_path = utils.make_fp_rel(obj_name) 660 | 661 | print file_path 662 | 663 | # previous import was not looking inside of GoZ package maybe? 664 | # this could have been the error with sending back to maya previously 665 | maya_cmd = 'from GoZ import maya_tools;maya_tools.load("' + \ 666 | file_path + '","' + obj_name + \ 667 | '","' + \ 668 | parent_name + \ 669 | '")' 670 | 671 | print maya_cmd 672 | 673 | maya = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 674 | host, port = utils.get_net_info('MNET') 675 | 676 | print host, port 677 | 678 | maya.connect((host, int(port))) 679 | maya.send(maya_cmd) 680 | maya.close() 681 | 682 | 683 | if __name__ == "__main__": 684 | """grabs args for when this module is run as a script """ 685 | 686 | import sys 687 | # send to maya/save from zbrush 688 | # arg 1: object name ie: pSphere1 689 | MayaClient.send(sys.argv[1], sys.argv[2]) 690 | --------------------------------------------------------------------------------