def addRoutine(self, routineName, routine=None): """Add a Routine to the current list of them. Can take a Routine object directly or will create an empty one if none is given. """ if routine is None: # create a deafult routine with this name self.routines[routineName] = Routine(routineName, exp=self) else: self.routines[routineName] = routine return self.routines[routineName]
def loadFromXML(self, filename): """Loads an xml file and parses the builder Experiment from it """ self._doc.parse(filename) root = self._doc.getroot() # some error checking on the version (and report that this isn't valid # .psyexp)? filenameBase = os.path.basename(filename) if root.tag != "PsychoPy2experiment": logging.error('%s is not a valid .psyexp file, "%s"' % (filenameBase, root.tag)) # the current exp is already vaporized at this point, oops return self.psychopyVersion = root.get('version') # If running an experiment from a future version, send alert to change "Use Version" if Version(psychopy.__version__) < Version(self.psychopyVersion): alert(code=4051, strFields={'version': self.psychopyVersion}) # If versions are either side of 2021, send alert if Version(psychopy.__version__) >= Version("2021.1.0") > Version( self.psychopyVersion): alert(code=4052, strFields={'version': self.psychopyVersion}) # Parse document nodes # first make sure we're empty self.flow = Flow(exp=self) # every exp has exactly one flow self.routines = {} self.namespace = NameSpace(self) # start fresh modifiedNames = [] duplicateNames = [] # fetch exp settings settingsNode = root.find('Settings') for child in settingsNode: self._getXMLparam(params=self.settings.params, paramNode=child, componentNode=settingsNode) # name should be saved as a settings parameter (only from 1.74.00) if self.settings.params['expName'].val in ['', None, 'None']: shortName = os.path.splitext(filenameBase)[0] self.setExpName(shortName) # fetch routines routinesNode = root.find('Routines') allCompons = getAllComponents(self.prefsBuilder['componentsFolders'], fetchIcons=False) allRoutines = getAllStandaloneRoutines(fetchIcons=False) # get each routine node from the list of routines for routineNode in routinesNode: if routineNode.tag == "Routine": routineGoodName = self.namespace.makeValid( routineNode.get('name')) if routineGoodName != routineNode.get('name'): modifiedNames.append(routineNode.get('name')) self.namespace.user.append(routineGoodName) routine = Routine(name=routineGoodName, exp=self) # self._getXMLparam(params=routine.params, paramNode=routineNode) self.routines[routineNode.get('name')] = routine for componentNode in routineNode: componentType = componentNode.tag if componentType in allCompons: # create an actual component of that type component = allCompons[componentType]( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) else: # create UnknownComponent instead component = allCompons['UnknownComponent']( name=componentNode.get('name'), parentName=routineNode.get('name'), exp=self) # check for components that were absent in older versions of # the builder and change the default behavior # (currently only the new behavior of choices for RatingScale, # HS, November 2012) # HS's modification superceded Jan 2014, removing several # RatingScale options if componentType == 'RatingScaleComponent': if (componentNode.get('choiceLabelsAboveLine') or componentNode.get('lowAnchorText') or componentNode.get('highAnchorText')): pass # if not componentNode.get('choiceLabelsAboveLine'): # # this rating scale was created using older version # component.params['choiceLabelsAboveLine'].val=True # populate the component with its various params for paramNode in componentNode: self._getXMLparam(params=component.params, paramNode=paramNode, componentNode=componentNode) compGoodName = self.namespace.makeValid( componentNode.get('name')) if compGoodName != componentNode.get('name'): modifiedNames.append(componentNode.get('name')) self.namespace.add(compGoodName) component.params['name'].val = compGoodName routine.append(component) else: if routineNode.tag in allRoutines: # If not a routine, may be a standalone routine routine = allRoutines[routineNode.tag]( exp=self, name=routineNode.get('name')) else: # Otherwise treat as unknown routine = allRoutines['UnknownRoutine']( exp=self, name=routineNode.get('name')) # Apply all params for paramNode in routineNode: if paramNode.tag == "Param": for key, val in paramNode.items(): setattr(routine.params[paramNode.get("name")], key, val) # Add routine to experiment self.addStandaloneRoutine(routine.name, routine) # for each component that uses a Static for updates, we need to set # that for thisRoutine in list(self.routines.values()): for thisComp in thisRoutine: for thisParamName in thisComp.params: thisParam = thisComp.params[thisParamName] if thisParamName == 'advancedParams': continue # advanced isn't a normal param elif thisParam.updates and "during:" in thisParam.updates: # remove the part that says 'during' updates = thisParam.updates.split(': ')[1] routine, static = updates.split('.') if routine not in self.routines: msg = ("%s was set to update during %s Static " "Component, but that component no longer " "exists") logging.warning(msg % (thisParamName, static)) else: self.routines[routine].getComponentFromName( static).addComponentUpdate( thisRoutine.params['name'], thisComp.params['name'], thisParamName) # fetch flow settings flowNode = root.find('Flow') loops = {} for elementNode in flowNode: if elementNode.tag == "LoopInitiator": loopType = elementNode.get('loopType') loopName = self.namespace.makeValid(elementNode.get('name')) if loopName != elementNode.get('name'): modifiedNames.append(elementNode.get('name')) self.namespace.add(loopName) loop = eval('%s(exp=self,name="%s")' % (loopType, loopName)) loops[loopName] = loop for paramNode in elementNode: self._getXMLparam(paramNode=paramNode, params=loop.params) # for conditions convert string rep to list of dicts if paramNode.get('name') == 'conditions': param = loop.params['conditions'] # e.g. param.val=[{'ori':0},{'ori':3}] try: param.val = eval('%s' % (param.val)) except SyntaxError: # This can occur if Python2.7 conditions string # contained long ints (e.g. 8L) and these can't be # parsed by Py3. But allow the file to carry on # loading and the conditions will still be loaded # from the xlsx file pass # get condition names from within conditionsFile, if any: try: # psychophysicsstaircase demo has no such param conditionsFile = loop.params['conditionsFile'].val except Exception: conditionsFile = None if conditionsFile in ['None', '']: conditionsFile = None if conditionsFile: try: trialList, fieldNames = data.importConditions( conditionsFile, returnFieldNames=True) for fname in fieldNames: if fname != self.namespace.makeValid(fname): duplicateNames.append(fname) else: self.namespace.add(fname) except Exception: pass # couldn't load the conditions file for now self.flow.append(LoopInitiator(loop=loops[loopName])) elif elementNode.tag == "LoopTerminator": self.flow.append( LoopTerminator(loop=loops[elementNode.get('name')])) else: if elementNode.get('name') in self.routines: self.flow.append(self.routines[elementNode.get('name')]) else: logging.error("A Routine called '{}' was on the Flow but " "could not be found (failed rename?). You " "may need to re-insert it".format( elementNode.get('name'))) logging.flush() if modifiedNames: msg = 'duplicate variable name(s) changed in loadFromXML: %s\n' logging.warning(msg % ', '.join(list(set(modifiedNames)))) if duplicateNames: msg = 'duplicate variable names: %s' logging.warning(msg % ', '.join(list(set(duplicateNames)))) # if we succeeded then save current filename to self self.filename = filename