# Copyright 2005-2008 Nanorex, Inc. See LICENSE file for details. """ NanoHiveUtils.py @author: Brian, Mark @version: $Id$ @copyright: 2005-2008 Nanorex, Inc. See LICENSE file for details. History: Brian wrote the NH_Connection class. Mark wrote everything else. Module classification: Mostly control & io code. Some model & ui code (via assy arg & assy.w). Probably "simulation". [bruce 071214] """ import foundation.env as env, os, sys, time from platform_dependent.PlatformDependent import find_or_make_Nanorex_subdir from utilities.prefs_constants import nanohive_path_prefs_key, nanohive_enabled_prefs_key from PyQt4.Qt import Qt, QApplication, QCursor from widgets.StatusBar import NanoHiveProgressReporter def get_nh_simspec_filename(basename): """ Return the full path of the Nano-Hive simulation specification file. """ if basename: nhdir = find_or_make_Nanorex_subdir("Nano-Hive") fn = os.path.normpath(os.path.join(nhdir,str(basename)+"-sim.xml")) #print "get_nh_simspec_filename(): filename=", fn return fn else: return None def get_nh_workflow_filename(basename): """ Return the full path of the Nano-Hive workflow file. """ if basename: nhdir = find_or_make_Nanorex_subdir("Nano-Hive") fn = os.path.normpath(os.path.join(nhdir,str(basename)+"-flow.tcl")) #print "get_nh_workflow_filename(): filename=", fn return fn else: return None def get_nh_mmp_filename(basename): """ Return the full path of the Nano-Hive MMP input file. """ if basename: nhdir = find_or_make_Nanorex_subdir("Nano-Hive") fn = os.path.normpath(os.path.join(nhdir,str(basename)+".mmp")) #print "get_nh_mmp_filename(): filename=", fn return fn else: return None def get_nh_espimage_filename(assy, jigname): """ Returns the filename of the ESP Image's png given assy and ESP Image's jigname, to be stored in the ESP Image's MMP info record. The filename format is "assyname-jigname.png" """ cwd = os.path.join(assy.get_cwd(), assy.name + "-" + jigname + ".png") return os.path.normpath(cwd) def get_nh_home_ORIG(): """ Return the Nano-Hive home directory """ nanohive_exe = env.prefs[nanohive_path_prefs_key] # On Windows, the default location of the Nano-Hive executable is # C:\Program Files\Nano-Hive\bin\win32-x86\NanoHive.exe # The home directory is retreived by stripping off the last two directories # from the Nano-Hive executable path, which is # C:\Program Files\Nano-Hive if sys.platform == 'win32': head, tail = os.path.split(nanohive_exe) head, tail = os.path.split(head) nh_home, tail = os.path.split(head) return nh_home def get_nh_home(): """ Returns the Nano-Hive home (base) directory for each platform, if it exists. Otherwise, return None. """ if sys.platform == "win32": # Windows basedir = "C:\Program Files\Nano-Hive" else: # Linux and MacOS basedir = "/usr/local/share/Nano-Hive" if not os.path.exists(basedir): return None return basedir def get_nh_config_filename(): """ Return the full path of the Nano-Hive config.txt file. """ if sys.platform == "win32": # Windows fn = os.path.normpath(get_nh_home() + "/conf/configs.txt") else: # Linxus and MacOS fn = os.path.normpath(os.path.expanduser("~/.Nano-Hive/conf/configs.txt")) if not os.path.exists(fn): return None return fn def run_nh_simulation(assy, sim_id, sim_parms, sims_to_run, results_to_save): """ Run a Nano-Hive simulation on the part (assy). Only the MPQC_ESP plug-in used for creating an ESP Image file is supported in A7. sim_id is the simulation id of the simulation. It is used to construct the basename of all the simulation files and is the name of the Nano-Hive simulation run. sims_to_run is a list of simulations to run, where: MPQC_ESP = MPQC ESP Plane MPQC_GD = MPQC Gradient Dynamics (not supported in A7) AIREBO = AIREBO (not supported in A7) results_to_save is a list of results to save, where: MEASUREMENTS = Measurements to file POVRAYVIDEO = POVRay Video NETCDF = NetCDF Return values: 0 = successful 1 = Nano-Hive plug-in not enabled 2 = Nano-Hive plug-in path is empty 3 = Nano-Hive plug-in path points to a file that does not exist 4 = Nano-Hive plug-in is not Version 1.2b 5 = Couldn't connect to Nano-Hive instance 6 = "load" command failed 7 = "run" command failed 8 = Simulation aborted """ if not sims_to_run: return # No simulations to run in the list. # Validate that the Nano-Hive plug-in is enabled. if not env.prefs[nanohive_enabled_prefs_key]: r = activate_nh_plugin(assy.w) if r: return 1 # Nano-Hive plug-in not enabled. if not env.prefs[nanohive_path_prefs_key]: return 2 # Nano-Hive plug-in path is empty if not os.path.exists(env.prefs[nanohive_path_prefs_key]): return 3 # Nano-Hive plug-in path points to a file that does not exist r = verify_nh_program() if r: return 4 # Nano-Hive plug-in is not Version 1.2b if not sim_id: sim_id = get_sim_id() output_dir = find_or_make_Nanorex_subdir("Nano-Hive") # ~/Nanorex/Nano-Hive # Put up the wait cursor. The cursor will be restored by exit_nh(). QApplication.setOverrideCursor( assy.w.ArrowWaitCursor ) # 1. Try to connect to Nano-Hive, get socket. # 99% of the time, no socket will be returned since Nano-Hive hasn't been started yet. # Let's check, just in case this is a Nano-Hive instance running. nh_socket = connect_to_nh() if nh_socket: kill_nh = False else: # No Nano-Hive instance is running. Start it. kill_nh = True r = start_nh() # Start Nano-Hive server (instance). if r: print "Nano-Hive startup aborted." # It may take a second or two to connect to the new Nano-Hive instance. # Keep trying until we get a socket. Give up if we haven't connected within 4 seconds. start = time.time() while not nh_socket: time.sleep(0.25) nh_socket = connect_to_nh() duration = time.time() - start if duration > 4.0: # Give up after 4 seconds exit_nh(nh_socket, kill_nh) # Only restore's the cursor return 5 # Couldn't connect to Nano-Hive socket. # 2. Write the MMP file that Nano-Hive will use for the sim run. assy.writemmpfile(get_nh_mmp_filename(sim_id)) # 3. Write the sim-spec file using the parameters from the Nano-Hive dialog widgets from analysis.ESP.files_nh import write_nh_simspec_file write_nh_simspec_file(sim_id, sim_parms, sims_to_run, results_to_save, output_dir) # 4. Write the Sim Workflow file from analysis.ESP.files_nh import write_nh_workflow_file write_nh_workflow_file(sim_id) # 5. Send commands to Nano-Hive. There can be no spaces in partname. Need to fix this. cmd = 'load simulation -f "' + get_nh_simspec_filename(sim_id) + '" -n ' + sim_id #print "NanoHiveUtils.run_nh_simulation(): N-H load command: ", cmd success, response = nh_socket.sendCommand(cmd) # Send "load" command. if not success: print success, response exit_nh(nh_socket, kill_nh) return 6 # "load" command failed cmd = "run " + sim_id #print "NanoHiveUtils.run_nh_simulation(): N-H run command: ", cmd success, response = nh_socket.sendCommand(cmd) # Send "run" command. if not success: print success, response exit_nh(nh_socket, kill_nh) return 7 # "run" command failed statusBar = assy.w.statusBar() progressReporter = NanoHiveProgressReporter(nh_socket, sim_id) r = statusBar.show_progressbar_and_stop_button(progressReporter, "NanoHive", True) if r: stop_nh_sim(sim_id) exit_nh(nh_socket, kill_nh) return 8 # simulation aborted exit_nh(nh_socket, kill_nh) return 0 def activate_nh_plugin(win): """ Opens a message box informing the user that the Nano-Hive plugin needs to be enabled and asking if they wish to do so. win is the main window object. """ from PyQt4.Qt import QMessageBox ret = QMessageBox.warning( win, "Activate Nano-Hive Plug-in", "Nano-Hive plug-in not enabled. Please select OK to \n" \ "activate the Nano-Hive plug-in from the Preferences dialog.", "&OK", "Cancel", "", 0, 1 ) if ret == 0: # OK win.userPrefs.showDialog('Plug-ins') # Show Preferences | Plug-in. if not env.prefs[nanohive_enabled_prefs_key]: return 1 # Nano-Hive was not enabled by user. elif ret == 1: # Cancel return 1 return 0 def verify_nh_program(): """ Returns 0 if nanohive_path_prefs_key is the path to the Nano-Hive v1.2b executable. Otherwise, returns 1. """ vstring = "Nano-Hive version 1.2.0-beta-1 Copyright (C) 2004,2005 Nano-Hive, LLC" r = verify_program(env.prefs[nanohive_path_prefs_key], '-v', vstring) return r # This is a general function that should be moved to utilities def verify_program(program, version_flag, vstring): """ Verifies a program by running it with the version_flag and matching the output to vstring. Returns 0 if there is a match. Otherwise, returns 1 """ if not program: return 1 if not os.path.exists(program): return 1 args = [version_flag] from processes.Process import Process arguments = [] for arg in args: if arg != "": arguments.append(arg) print print "arguments:", arguments p = Process() p.start(program, arguments) if not p.waitForFinished (10000): # Wait for 10000 milliseconds = 10 seconds return 1 output = 'Not vstring' output = str(p.readAllStandardOutput()) #print "output=", output #print "vstring=", vstring if output.find(vstring) == -1: return 1 else: return 0 # Match found. def start_nh(): """ Starts Nano-Hive server in the background. Returns 1 if Nano-Hive path is not set or if the path does not exist. Returns 2 if Nano-Hive config file does not exist. """ # Get Nano-Hive executable path from the prefs db. nanohive_exe = env.prefs[nanohive_path_prefs_key] if not nanohive_exe: return 1 if not os.path.exists(nanohive_exe): return 1 nanohive_config = get_nh_config_filename() if not nanohive_config: return 2 args = ['-f', nanohive_config] #print "start_nh(): args=", args from processes.Process import Process arguments = [] for arg in args: if arg != "": arguments.append(arg) nhProcess = Process() nhProcess.start(nanohive_exe, args) return 0 def stop_nh_sim(nh_socket, sim_id=None): """ Stops a running Nano-Hive simulation. - the id (name) of the simulator to stop. """ if nh_socket: if sim_id: success, response = nh_socket.sendCommand("stop " + sim_id) # Send "stop" command. QApplication.restoreOverrideCursor() # Restore the cursor def exit_nh(nh_socket, kill_nh): """ Exits (kills) the Nano-Hive instance if kill_nh is True, closes the socket and restores the cursor." """ if nh_socket: if kill_nh: success, response = nh_socket.sendCommand("exit") # Send "exit" command. nh_socket.close() QApplication.restoreOverrideCursor() # Restore the cursor def connect_to_nh(): """ Connects to a Nano-Hive instance. Returns NH_Connection socket if successful, None if failure. """ hostIP = "127.0.0.1" port = 3000 # Nano-Hive 1.2b uses port 3000, not 3002 as 1.2a did. mark 2006-01-04. serverTimeout = 5.0 clientTimeout = 30.0 method = "NanoHiveUtils.connect_to_nh()" nh_conn = NH_Connection() # Obtain connection object # Try connecting the Nano-Hive instance. success, msg = nh_conn.connect(hostIP, port, serverTimeout, clientTimeout) if success: success, result = nh_conn.sendCommand("status 1") # Send status command. if success: msg = method + ": Success: " + str(success) + "\nMessage: " + str(result) #env.history.message(msg) #print msg return nh_conn else: msg = method + ": Command Error:\nSuccess=" + str(success) + "\nMessage: " + str(result) #env.history.message(msg) #print msg return None else: msg = method + ": Connection Failed:\nSuccess=" + str(success) + "\nMessage: " + str(msg) #env.history.message(msg) #print msg return None # Version 2 import socket class NH_Connection: """ A sockets connection to a Nano-Hive instance. """ def connect(self, hostIP, port, serverTimeout, clientTimeout): """ Connects to the specified N-H instance. Parameters: hostIP The IP address of the N-H host port The port listened to by the SocketsControl plugin of the N-H host serverTimeout A float specifying how long, in seconds, to give when reading from, and writing to, N-H clientTimeout A float specifying the timeout period of the SocketsControl plugin of the N-H host. Not implemented yet, but this will keep the N-H connection alive with "ping" commands Returns: success indicator A one if the N-H instance was successfully connected to, or a zero if an error occurred and connection failed error message If connection failed, describes the problem """ try: self.connectionHandle = \ socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connectionHandle.connect((hostIP, port)) self.connectionHandle.settimeout(serverTimeout) except socket.error, errorMessage: return 0, errorMessage return 1, "" def close(self): """ Closes the connection to the N-H instance. """ self.connectionHandle.close() def sendCommand(self, command): """ Sends a command to the N-H instance and returns the response. Parameters: command The command to send to the N-H instance Returns: success indicator A one if the command was successfully sent and a response was received response or error message If all was successful, the encoded N-H instance response, or a description of the error if unsuccessful. The encoded response can be decoded with the Nano-Hive/data/local/en_resultCodes.txt file """ # Send command success = 1 try: self.connectionHandle.send(command) except socket.error, errorMessage: success = 0 # Read response length if success: try: responseLengthChars = self.connectionHandle.recv(4) except socket.error, errorMessage: success = 0 if success: responseLength = ord(responseLengthChars[0]) responseLength |= ord(responseLengthChars[1]) << 8 responseLength |= ord(responseLengthChars[2]) << 16 responseLength |= ord(responseLengthChars[3]) << 24; # Read response try: response = self.connectionHandle.recv(responseLength) except socket.error, errorMessage: success = 0 if success: return 1, response else: return 0, errorMessage