def openFlixGUI(self, timeout): """Opens Flix's main GUI""" log('##### openFlixGUI') sikuliUtils.newChromeTab('http://127.0.0.1:35980/html/index.html#') self.launchCheck.openFlixGUICheck(timeout)
def psCurrentImage(self, fileNames=["1"]): """Test method to send stills from Photoshop to an open Flix edit :param fileNames: Array of filenames to be open in Photoshop (without extension, files available in assets/psds directory) :return: None """ log("##### psCurrentImage") psScriptPath = "%s/psCurrentImage.jsx" % self.assetsDir filePaths = [] for fileName in fileNames: filePath = "%s/%s.psd" % (self.assetsDir, fileName) if os.path.exists(filePath): filePaths.append(filePath) else: log("psCurrentImage: Could not find %s in PSD assets folder; skipping..." % fileName) if not len(filePaths): self.testInfo.failed("psCurrentImage: No file to open in PS; exiting...") return if not self.__writePsScript(psScriptPath, filePaths, "Current Image"): self.testInfo.failed("psCurrentImage: Failed to write the PS script; exiting...") return self.__executePsScript(psScriptPath) # fromPs.psCurrentImageCheck(self.testInfo, len(filePaths)) self.fromPsCheck.psCurrentImageCheck(len(filePaths))
def replaceStill(self, mayaFile="pyramid.mb", frame=1): """Test method to replace the currently selected Flix panel with a still from Maya. Must make sure the current Flix edit contains at least one panel! Usage: fromMaya.replaceStill(self.testInfo, "pyramid.mb", 15) :param mayaFile: Name of the Maya scene file :param frame: Integer corresponding to the frame from the Maya scene to send to Flix :return: None """ log("##### replaceStill") mayaSettings = checkUtils.readMayaSettings(self.testInfo, mayaFile) if not mayaSettings["inFrame"] <= frame <= mayaSettings["outFrame"]: log( "addStills: Frame %s not in Maya scene (In: %s Out: %s); stopping test..." % (frame, mayaSettings["inFrame"], mayaSettings["outFrame"]), "error", ) return melScriptPath = "%s/replaceStill.mel" % self.assetsPath # Write Mel actions to execute in Maya scriptBody = "currentTime %s;\n" 'evalDeferred("replaceStill");\n\n' % frame self.__writeMelScript(melScriptPath, mayaFile, scriptBody) self.__executeMelScript(melScriptPath) # fromMaya.replaceStillCheck(self.testInfo) self.fromMayaCheck.replaceStillCheck()
def addDialogue(self, dialogue="Adding dialogue to panel [panelIndex].", firstPanel=1, lastPanel=3): """Adds dialogue to all of the panels currently in the sequence :param dialogue: String to add to every panel as the dialogue :param firstPanel: Panel index of first panel to add dialogue to :param lastPanel: Panel index of last panel to add dialogue to :return: None """ log('##### addDialogue') panels = [] for i in range(firstPanel, lastPanel+1): panels.append(i) log("addDialogue: Panel indices to add dialogue to: %s" % panels, "debug") url = "127.0.0.1:35980/html/index.html#show=%s;seq=%s;branch=%s;edit=%s;panel=%s" % \ (self.testInfo.show, self.testInfo.sequence, self.testInfo.currentBranch, self.testInfo.getEditVersion(), panels[0]) sikuliUtils.newChromeTab(url, closeCurrent=True); wait(5) click("dialogueField.png"); wait(1) for panel in panels: # Paste the dialogue (paste better than type as it keeps special characters) dial = dialogue.replace("[panelIndex]", str(panel)) sikuli.paste(unicode(dial, "utf8")); wait(1) sikuli.type(sikuli.Key.ENTER, sikuli.KeyModifier.SHIFT); wait(1) self.editToolsCheck.addDialogueCheck(panels, dialogue)
def replaceSequencerShot(self, mayaFile="pyramidSequencer.mb", shotNumber=1): """Test method to replace the currently selected Flix panel with a shot from a Maya sequencer. Usage: fromMaya.replaceSequencerShot(self.testInfo, "pyramidSequencer.mb", 1) :param mayaFile: Name of the Maya scene file :param shotNumber: Position of the shot in the sequencer :return: None """ log("##### replaceSequencerShot") mayaSettings = checkUtils.readMayaSettings(self.testInfo, mayaFile) if shotNumber > mayaSettings["nShots"]: shot = mayaSettings["shotNames"][0] else: shot = mayaSettings["shotNames"][shotNumber - 1] duration = mayaSettings["outFrame"] - mayaSettings["inFrame"] + 1 melScriptPath = "%s/replaceSequencerShot.mel" % self.assetsPath # Write Mel actions to execute in Maya scriptBody = "select -add %s ;\n" 'evalDeferred("replaceSequencerShot");\n\n' % shot self.__writeMelScript(melScriptPath, mayaFile, scriptBody) self.__executeMelScript(melScriptPath) # fromMaya.replaceSequencerShotCheck(self.testInfo, duration) self.fromMayaCheck.replaceSequencerShotCheck(duration)
def addSequencerShots(self, mayaFile="pyramidSequencer.mb", nShots=1): """Test method to send a number of sequencer shots from Maya to the current Flix edit Usage: fromMaya.addSequencerShot(self.testInfo, "pyramidSequencer.mb", 3) :param mayaFile: Name of the Maya scene file :param nShots: Number of shots to be sent from Maya to Flix :return: None """ log("##### addSequencerShots") mayaSettings = checkUtils.readMayaSettings(self.testInfo, mayaFile) if nShots > mayaSettings["nShots"]: shots = mayaSettings["shotNames"][0 : mayaSettings["nShots"]] else: shots = mayaSettings["shotNames"][0:nShots] log("addSequencerShots: shots to be sent to Flix: %s" % shots, "debug") melScriptPath = "%s/addSequencerShot.mel" % self.assetsPath # Write Mel actions to execute in Maya scriptBody = "" duration = 0 for shot in shots: scriptBody += "select -add %s ;\n" % shot duration += mayaSettings["shotDurations"][shots.index(shot)] scriptBody += 'evalDeferred("addSequencerShot");\n\n' self.__writeMelScript(melScriptPath, mayaFile, scriptBody) self.__executeMelScript(melScriptPath) # fromMaya.addSequencerShotsCheck(self.testInfo, len(shots), duration) self.fromMayaCheck.addSequencerShotsCheck(len(shots), duration)
def addStills(self, mayaFile="pyramid.mb", frames=[1, 12, 24]): """Test method to send stills from Maya to an open Flix edit Usage: fromMaya.addStills(self.testInfo, "pyramid.mb", [1, 12, 24]) :param mayaFile: Name of the Maya scene file :param frames: Array of frames for which to send panels from Maya :return: None """ log("##### addStills") mayaSettings = checkUtils.readMayaSettings(self.testInfo, mayaFile) existingFrames = [] for frame in frames: if mayaSettings["inFrame"] <= frame <= mayaSettings["outFrame"]: existingFrames.append(frame) else: log( "addStills: Frame %s not in Maya scene (In: %s Out: %s); skipping..." % (frame, mayaSettings["inFrame"], mayaSettings["outFrame"]), "error", ) melScriptPath = "%s/addStills.mel" % self.assetsPath # Write Mel actions to execute in Maya scriptBody = "" for frame in existingFrames: # For each frame, set the keyframe, perform mayaAddStill, wait for 3sec scriptBody += "currentTime %s;\n" 'evalDeferred("addStill");\n\n' % frame self.__writeMelScript(melScriptPath, mayaFile, scriptBody) self.__executeMelScript(melScriptPath) # fromMaya.addStillsCheck(self.testInfo, existingFrames) self.fromMayaCheck.addStillsCheck(existingFrames)
def getMarkersFromShotEdit(self): """Returns a list of all the markers in the current shotEdit :return: list of markers' shotLabels """ markerRecipes = [] markers = [] xml = self.getShotEdit() if xml == 0: log('TestInfo: cannot find panels in shot edit (xml not found).', 'error') return markers tree = ET.parse(xml) root = tree.getroot() for shot in root.findall('Shot'): recipe = shot.get('recipeLabelName').split('[')[0] # if '_marker_' in recipe: # markerRecipes.append(recipe) if self.getPanelBeat(recipe) == 'marker': markerRecipes.append(recipe) # Read shotLabel from multitrack for markerRecipe in markerRecipes: multitrack = self.getPanelMultitrack(markerRecipe) shotLabel = pyUtils.parseMultitrack(multitrack, 'Pose', 'dialogue') markers.append(shotLabel) log('TestInfo: getMarkersFromShotEdit: %s' % markers, 'debug') return markers
def __writePsScript(scriptPath, filePaths, action): """Writes a Jsx script to open a PS file, execute actions and quit PS. :param scriptPath: Path to the PS script to be written (.jsx) :param filePaths: Array of filepaths to be opened in PS :param action: Name of the Photoshop action to be executed :return: 0 if failed, 1 if succeeded """ # supportedActions = ["Current Image", "Replace Current Image", "Each Frame", "Each Layer"] # if action not in supportedActions: # log("writePsScript: %s is not an existing or supported Flix Action; exiting...\n" # "Supported Actions: %s" % (action, supportedActions)) # return 0 scriptContents = ( "var files = %s\n" "for (i = 0; i < files.length; i++){\n" "var file = File(files[i])\n" "app.open(file)\n" 'app.doAction("%s", "Flix Actions")\n' "app.activeDocument.close(SaveOptions.DONOTSAVECHANGES)\n" "}\n" "photoshop.quit()\n" % (filePaths, action) ) try: with open(scriptPath, "wb") as f: log("writePsScript: creating following script: %s" % scriptPath, "debug") f.write(scriptContents.replace("\\", "/")) return 1 except Exception, e: log("writePsScript: Could not write PS script.\n%s" % e) return 0
def psEachLayer(self, fileName="5layers", BG=0, FG=0): log("##### psEachLayer") psScriptPath = "%s/psEachLayer.jsx" % self.assetsDir filePath = "%s/%s.psd" % (self.assetsDir, fileName) if not os.path.exists(filePath): self.testInfo.failed("psEachLayer: Could not find %s in PSD assets folder; exiting..." % fileName) return # TODO: Write a method that reads settings for each PSD to figure out how many frames it's got nLayers = 5 if BG and FG: scriptName = "Each Layer + FG/BG" nLayers -= 1 elif BG and not FG: scriptName = "Each Layer + BG" else: if FG and not BG: log("psEachLayer: Can't send FG but not BG, using Flix Each Layer script instead.") scriptName = "Each Layer" nLayers += 1 if not self.__writePsScript(psScriptPath, [filePath], scriptName): self.testInfo.failed("psEachLayer: Failed to write the PS script; exiting...") return self.__executePsScript(psScriptPath) self.fromPsCheck.psEachLayerCheck(nLayers)
def updateMarkers(self, markers): """Updates the markers in the current edit""" for marker in markers: self.editMarkers.append(marker) self.allMarkers.append(marker) log('TestInfo: updateMarkers: %s' % self.editMarkers, 'debug')
def __getAvailablePanels(self, ext): self.importPath = '%s/assets/%ss/' % (self.testInfo.testPath, ext) if not os.path.exists(self.importPath): log('importDrawings: Could not find an asset directory for %s; ' 'try with another file extension.' % ext, 'error') return 0 else: return pyUtils.findFileNames(self.importPath, "*.%s" % ext)
def updateAllPanels(self, panels): """Updates the panels in the sequence""" for beats in panels: for panel in panels[beats]: beat = self.getPanelBeat(panel) if beat != 'unknown': if panel not in self.allPanels[beat]: self.allPanels[beat].append(panel) else: log('TestInfo: updateAllPanels: %s\'s beat determined as unknown.' % panel, 'debug')
def launchFlix(self, timeout): """Launches Flix's main UI via its bat or sh file path -- Path to the Flix launcher timeout -- Time in seconds before the Project Browser will open """ log('##### launchFlix') # Flix is being launched from the Test Suite before Sikuli if self.launchCheck.launchFlixCheck(timeout) == 'login': self.logIn(timeout)
def __publishFromFlix(self, methodName, comment, checkMethod, windowName="fcp", importAAFs=False, createSgSeq=False): """Method that will select the specified editorial plugin, publish with a comment and call the relevant check method :param methodName: Name of the method calling me :param comment: Comment to add to the publish :param checkMethod: Method to call to check the publish results :param windowName: Name of the Explorer window Flix reveals after publishing :param importAAFs: Set to True to import AAFs after Avid publish (not relevant for other editorial publishes) :param createSgSeq: Set to True to create the Flix sequence in Shotgun (only relevant for Shotgun publish) :return: None """ log("##### toEditorial: %s" % methodName) if methodName == "toShotgun" and createSgSeq: sikuliUtils.createShotgunSequence(self.testInfo) if self.testInfo.flixVersion < '5.2': click(sikuliUtils.switchPlugin('editorial', '%s.png' % methodName)); wait(1) else: sikuliUtils.switchPlugin('editorial', '%s.png' % methodName); wait(1) click('publishComment.png'); wait(1) type(comment); wait(1) type(sikuli.Key.TAB); wait(.5) type(sikuli.Key.ENTER) # Assuming .5 sec/frame is enough to publish an edit to editorial # timeout = self.testInfo.getDurationFromShotEdit()/2 # Assuming 2 sec/panel for stills and 1.5 sec/frame for animated panels is enough to publish an edit stillPanels = len(self.testInfo.editPanels["p"]) + len(self.testInfo.editPanels["s"]) animPanels = self.testInfo.editPanels["a"] animFrames = 0 for panel in animPanels: animFrames += self.testInfo.getPanelDurationFromShotEdit(panel) if methodName == "toAvid": stillTimeout = 2 animFrameTimeout = 1.5 else: stillTimeout = 0.5 animFrameTimeout = 0.5 timeout = int((stillTimeout*stillPanels) + (animFrameTimeout*animFrames)) if timeout < 20: timeout = 20 checkMethod(timeout) if methodName in ["toSbp", "toPremiere"]: sikuliUtils.closeExplorerWindow("%s" % windowName) elif importAAFs: self.launch.openEditorialGUI(10) self.editorialProjectBrowser.selectEditorialProject() self.__importToAvid()
def __importToAvid(self): """Imports the AAFs, ALE and marker.txt files that were created when publishing to Avid :return: None """ log('##### importToAvid') if os.path.isdir(self.toAvidOutputDir): try: shutil.rmtree(self.toAvidOutputDir) except OSError, e: log('importToAvid: Could not delete %s.\n%s' % (self.toAvidOutputDir, e), 'debug')
def openEditorialGUI(self, timeout, closeFlix=False): """Opens Flix's Editorial GUI""" log('##### openEditorialGUI') pyUtils.resetEditorialGuiState() if closeFlix: sikuliUtils.closeChromeTab() # Close the main Flix tab sikuliUtils.newChromeTab('http://127.0.0.1:35980/html/editorial.html') self.launchCheck.openEditorialGUICheck(timeout)
def createNewPanels(self, n=3): """Creates n new panels using the New Button panel in Flix :param n: Number of new panels to be created :return: None """ log('##### createNewPanels') for _ in range(0, n): click('newPanelBtn.png'); wait(2) self.editToolsCheck.createNewPanelsCheck(n)
def logIn(self, timeout): log('##### logIn') click('logInScreen.png'); wait(.5) type(sikuli.Key.TAB); wait(.5) type(self.testInfo.user); wait(1) type(sikuli.Key.TAB); wait(.5) type('latte'); wait(1) type(sikuli.Key.ENTER) self.launchCheck.launchFlixCheck(timeout)
def getAvidNew(self): """Returns a list of panels that haven't been published to Avid yet""" avidNew = [] for beat in self.editPanels.iterkeys(): if beat != 'ref': for panel in self.editPanels[beat]: if panel not in self.avidPublished: avidNew.append(panel) log('TestInfo: getAvidNew: New AAFs: %s' % avidNew, 'debug') return avidNew
def getEditPanels(self, ref=False): """Returns the number of non-ref panels in the current testInfo object""" totalPanels = 0 for beat in self.editPanels.iterkeys(): if not ref: if beat != 'ref': # totalPanels += self.editPanels[beat] totalPanels += len(self.editPanels[beat]) else: totalPanels += len(self.editPanels[beat]) log('TestInfo: getEditPanels: %s' % totalPanels, 'debug') return totalPanels
def exportPDF(self, panelsPerPage=9): log("##### exportPDF") if not self.testInfo.getShotEdit(): log("Cannot use an export plugin when no edit is saved for this sequence; exiting...", "error") return sikuliUtils.switchPlugin("pdf", "exportPdf%s.png" % panelsPerPage) self.exportCheck.exportPdfCheck(panelsPerPage) # sikuliUtils.closeExplorerWindow("pdf") sikuli.App.close("pdf")
def getDurationFromShotEdit(self): """Returns the edit duration based on the duration of each panel in the edit :return: int """ duration = 0 panels = self.getPanelsFromShotEdit() for beat in panels: for panel in panels[beat]: duration += self.getPanelDurationFromShotEdit(panel) log('getDurationFromShotEdit: edit duration: %s' % duration, 'debug') return duration
def getPanelDuration(self, panel): """Parses a panel's multitrack to determine its duration :param panel: Panel recipe name (e.g. 'flx_a_0001_v1') :return: int: Duration of the panel """ recipe = self.getPanelRecipe(panel) sDuration = pyUtils.parseRecipe(recipe, 'FrameRange', 'end') try: duration = int(sDuration) except ValueError, e: log('getPanelDuration: %s\'s duration could not be converted to an integer.\n%s' % e, 'error') return 0
def duplicatePanel(self, panel="panel1.png"): """Duplicate a panel, given the name of its screenshot :param panel: Name of the Sikuli screenshot associated with the panel to be copy/pasted :return: None """ log('##### duplicatePanel') if not click(panel): log('duplicatePanel: Could not find %s on screen, exiting test.' % panel, 'error') return click('duplicateBtn.png'); wait(1) self.editToolsCheck.duplicatePanelCheck(panel)
def __init__(self, testInfo, launch): log("Constructing ToEditorial object...", "debug") self.testInfo = testInfo self.launch = launch self.projectBrowser = self.launch.projectBrowser self.editorialProjectBrowser = ProjectBrowser(self.testInfo) self.editorialDir = self.testInfo.getEditorialDir() self.fcpDir = "%s/fcp" % self.editorialDir self.sbpDir = "%s/sbp" % self.editorialDir self.aafDir = "%s/aaf" % self.editorialDir self.aleDir = "%s/ale" % self.editorialDir self.sgDir = '%s/flix/shotgun/published/' % self.testInfo.getSequenceDir() self.toAvidOutputDir = "%s/toAvid/" % self.testInfo.outDir self.toEditorialCheck = ToEditorialCheck(self) log("Done!", "debug")
def importPanels2(self, ext, nPanels=10): """Imports nPanels panels without checking image in Flix :param ext: extension of the files to import :param nPanels: Number of panels to import, 10 by default :return: None """ log('##### importPanels2') if self.testInfo.OS == "mac": log("importPanels only supported on Windows right now. Using importDrawings instead...", "error") self.importDrawings(ext, nPanels) return availablePanels = self.__getAvailablePanels(ext) panelList = [] while len(panelList) < nPanels: rest = nPanels - len(panelList) if rest > len(availablePanels): for panel in availablePanels: panelList.append(panel) else: for panel in availablePanels[0:rest]: panelList.append(panel) # Got to do it in chunks of 10 panels for i in range(0, len(panelList), 10): chunk = panelList[i:i+10] click('importDrawingsButton.png') if not exists('importFileBrowserFilename.png', 20): log('importPanels2: Could not find importFileBrowserFilename after 20 seconds.', 'error') return click('importFileBrowserFilename.png'); wait(1) if i == 0: type(self.importPath.replace('/', '\\')); wait(1) type(sikuli.Key.ENTER); wait(1) for panel in chunk: type("\"" + panel + "\" ") type(sikuli.Key.ENTER) # wait(len(chunk)) # Wait until the 'processing' image for the first panel timeout = int((len(chunk) * 1.5)) if ext == 'mov': timeout = 0 for movie in chunk: timeout += pyUtils.readMovSettings(self.testInfo, movie)['timeout'] log('importPanels2: timeout set to %ssec.' % timeout, 'debug') wait(timeout) self.importDrawingCheck.importPanels2Check(ext, nPanels, panelList)
def getPoseFile(self, ext): """Returns the path to the panel's pose file :return: String """ setup = self.name.split('_')[-2] version = self.name.split('_')[-1] poseLabel = "%s_%s" % (setup, version) if self.isAnimated(): poseDir = "%sposes/%s/" % (self.beatsDir, poseLabel) else: poseDir = "%sposes/" % self.beatsDir poseFile = "%s%s.%s" % (poseDir, poseLabel, ext) log('getPoseFile: poseFile: %s' % poseFile) return poseFile
def openInPhotoshop(self, panels, psMethod): """Opens one or multiple panels in Photoshop using Layer Comps ('layerComp') or Timeline ('timeline') panels -- List of panels to open in Photoshop psMethod -- Either open the panels in 'layerComp' or 'timeline' view in PS """ log('##### openInPhotoshop') click(panels[0]) if len(panels) != 1: for panel in panels[1:]: # click(panel, sikuli.KeyModifier.CTRL) sikuliUtils.ctrlClick(panel) if psMethod == 'layerComp': plugin = 'photoshopLayerComp.png' elif psMethod == 'timeline': plugin = 'photoshopTimeline.png' else: log('openInPhotoshop: %s psMethod is not supported in this test.' % psMethod) return if self.testInfo.flixVersion < '5.2': click(sikuliUtils.switchPlugin('editing', plugin)) else: sikuliUtils.switchPlugin('editing', plugin) wait(1) # type(sikuli.Key.ENTER, sikuli.KeyModifier.CTRL) sikuliUtils.ctrlType('', sikuli.KeyModifier.ENTER) # Need to find a way to make sure the panel is open in Photoshop psState = sikuliUtils.oneOrTheOther('psOpen.png', 'psOpening.png', 30) if psState == 0: wait(5) elif psState == 1: wait(30) elif psState == 2: self.testInfo.failed('openInPhotoshop: Photoshop is not open and not opening.') sikuliUtils.psEdit() sikuli.App.focus('Google Chrome') for panel in panels: toPs.openInPhotoshopCheck(self.testInfo, panel)
def publishSequencer(self, mayaFile="pyramidSequencer.mb"): """Test method to publish the current Maya sequencer as a new version of the current Flix sequence. Usage: fromMaya.publishSequencer(self.testInfo, "pyramidSequencer.mb") :param mayaFile: Name of the Maya scene file :return: None """ log("##### publishSequencer") melScriptPath = "%s/publishSequencer.mel" % self.assetsPath mayaSettings = checkUtils.readMayaSettings(self.testInfo, mayaFile) # Write Mel actions to execute in Maya scriptBody = 'evalDeferred("publishSequencer");\n\n' self.__writeMelScript(melScriptPath, mayaFile, scriptBody) self.__executeMelScript(melScriptPath) # fromMaya.publishSequencerCheck(self.testInfo, mayaSettings) self.fromMayaCheck.publishSequencerCheck(mayaSettings)