# Script to automatically Generate Icons on Panel for automation purposes # # Author: Dave Sand, Bill Fitch, copyright 2022 # Part of the JMRI distribution from javax.swing import JOptionPane from java.awt.geom import Point2D from java.awt import Color # from jython.DispatcherSystem.Startup import OptionDialog # IS:DSCT:nnn Control sensors # IS:DSMT:nnn Move TO sensors # IS:DSMP:nnn Move Progress sensors # IX:DSLX:1 Logix # Remove Icons # Block content labels # Labels # Sensors # Remove Logix # Remove Transits # Remove Sections # Remove SML # Remove Sensors # # Add sensors # Generate SML and Sections # Add Logix # Add Icons # Sensors # Labels # Block content labels class processPanels(jmri.jmrit.automat.AbstractAutomaton): if "list" in globals() and type(globals()["list"]).__name__ != "type": # print(" Detected shadowed 'list' type: ", type(globals()["list"])) # list is being used in JMRI. This enables us to use list in Jython del globals()["list"] if "set" in globals() and type(globals()["set"]).__name__ != "type": # print(" Detected shadowed 'set' type: ", type(globals()["set"])) # set is being used in JMRI. This enables us to use set in Jython del globals()["set"] logLevel = 0 version_no = 0.2 #used to delete DispatcherPanel for new versions if the number of controlsensors/icons has changed list_of_stopping_points = [] blockPoints1 = {} blockPoints = {} # Block center points used by direct access process editorManager = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager) # row number, user name, label name, x offset, y offset i = 1 controlSensors = [] controlSensors.append([i, 'startDispatcherSensor', 'Run Dispatcher System', 0, 0]); i += 1 controlSensors.append([i, 'stopMasterSensor', 'Stop Dispatcher System', 0, 0]); i += 1 controlSensors.append([i, 'modifyMasterSensor', 'Modify Dispatcher System', 0, 0]); i += 1 controlSensors.append([i, 'Express', 'Express Train (no stopping)', 10, 5]); i += 1 controlSensors.append([i, 'newTrainSensor', 'Setup Train in Section', 10, 5]); i += 1 controlSensors.append([i, 'soundSensor', 'Enable Announcements', 10, 5]); i += 1 controlSensors.append([i, 'simulateSensor', 'Simulate Dispatched Trains', 10, 5]); i += 1 controlSensors.append([i, 'checkRouteSensor', 'Dispatch Path must be clear', 10, 5]); i += 1 controlSensors.append([i, 'stopAtStopSensor', 'Stop at Stop Sensors (Default)', 10, 5]); i += 1 controlSensors.append([i, 'setDispatchSensor', 'Run Dispatch', 0, 5]); i += 1 controlSensors.append([i, 'setRouteSensor', 'Setup Route', 0, 5]); i += 1 controlSensors.append([i, 'setStoppingDistanceSensor', 'Set Stopping Length', 0, 5]); i += 1 controlSensors.append([i, 'setStopSensor', 'Set Stop Sensor', 0, 5]); i += 1 controlSensors.append([i, 'setStationWaitTimeSensor', 'Set Station Wait Time', 0, 5]); i += 1 controlSensors.append([i, 'setStationDirectionSensor', 'Set Station Direction', 0, 5]); i += 1 controlSensors.append([i, 'setTransitBlockRestrictionSensor', 'Restrict Transit Operation', 0, 5]); i += 1 controlSensors.append([i, 'runRouteSensor', 'Run Route', 10, 5]); i += 1 controlSensors.append([i, 'editRoutesSensor', 'View/Edit Routes', 10, 5]); i += 1 controlSensors.append([i, 'viewScheduledSensor', 'View/Edit Scheduled Trains', 10, 5]); i += 1 controlSensors.append([i, 'showClockSensor', 'Show Analog Clock', 10, 5]); i += 1 controlSensors.append([i, 'startSchedulerSensor', 'Start Scheduler', 10, 5]); i += 1 controlSensors.append([i, 'timetableSensor', 'Show Timetable', 10, 5]); i += 1 controlSensors.append([i, 'departureTimeSensor', 'Setup Departure Times', 10, 5]); i += 1 controlSensors.append([i, 'helpSensor', 'Help', 0, 5]); i += 1 def __init__(self): self.result = "Success" #value is returned in __str__ and set to "Failure" in self.tryme() self.define_DisplayProgress_global() if self.perform_initial_checks(): self.show_progress(0) self.tryme(self.saveForwardStoppingSensors, "Cannot save Forward Stopping Sensors: Contact Developer") self.tryme(self.removeIconsAndLabels, "Cannot remove Icons And Labels: Contact Developer") self.tryme(self.removeLogix, "Cannot remove startup Logix: Contact Developer") self.tryme(self.removeTransits, "Cannot remove Transits: Contact Developer") self.tryme(self.removeSML, "Cannot remove SML: Contact Developer") # do before removeSections in case direction sensors have been added to the SML self.tryme(self.removeSections, "Cannot remove Sections: Contact Developer") self.show_progress(20) self.tryme(self.removeSensors, "Cannot generate startup Logix: Contact Developer") self.show_progress(40) # print "A" self.tryme(self.updatePanels, "Cannot update Panels: Contact Developer") # print "B" self.get_list_of_stopping_points() # print "C" # self.tryme(self.get_list_of_stopping_points, "Cannot get list of stopping points, Contact Developer") self.addSensors() # print "D" self.generateSML() # print "E" self.show_progress(60) self.generateSections() # if it fails need a better fail message # print "F" # self.tryme(self.generateSections, "Cannot generate Sections: Signal Masts not set up correctly. Needs to be fixed before using Dispatcher System.") self.show_progress(80) self.tryme(self.addLogix, "Cannot generate startup Logix: Contact Developer") # print "G" self.addIcons() self.tryme(self.retrieveForwardStoppingSensors, "Cannot retrieve Stopping Sensors: Contact Developer") self.setVersionNo() self.stop_all_threads() self.end_show_progress() else: self.result = "Failure" def __str__(self): return self.result def setVersionNo(self): memory = memories.provideMemory('IS:ISMEM:' + "versionNo") if memory is not None: memory.setValue(self.version_no) def version_number_changed(self): memory = memories.getMemory('IMIS:ISMEM:' + "versionNo") # print "memory", memory, type(memory) if memory is None: # print "version_no changed", "memory:", "version", self.version_no return True elif memory.getValue() != self.version_no: # print "version_no changed", "memory:", memory.getValue(), "version", self.version_no return True else: # print "version_no not changed", "memory:", memory.getValue(), "version", self.version_no return False def stop_all_threads(self): summary = jmri.jmrit.automat.AutomatSummary.instance() automatsList = java.util.concurrent.CopyOnWriteArrayList() for automat in summary.getAutomats(): automatsList.add(automat) for automat in automatsList: automat.stop() def tryme(self, func, failure_message): try: func() except: title = "Error in Routine" Query().displayMessage(failure_message,title) self.result = "Failure" pass def define_DisplayProgress_global(self): global dpg dpg = DisplayProgress() def show_progress(self, progress): global dpg dpg.Update("creating icons: " + str(progress)+ "% complete") def end_show_progress(self): global dpg dpg.killLabel() # ************************************************** # perform initial checks # ************************************************** def perform_initial_checks(self): sensors_OK = False block_sensors_OK = False stops_OK = False lengths_OK = False speed_profiles_OK = False #JOptionPane.showMessageDialog(None, "Performing some preliminary checks to ensure the trains run correctly\nAll errors will need to be fixed for Dispatcher to run correctly\nSome errors will cause the panel to be set up incorrectly in this stage", 'Checks', JOptionPane.WARNING_MESSAGE) # check all blocks have sensors if self.check_all_blocks_have_sensors() == False: self.msg = self.msg + "\n***********************\n Do you wish to continue\n***********************" myAnswer = JOptionPane.showConfirmDialog(None, self.msg) if myAnswer == JOptionPane.YES_OPTION: #JOptionPane.showMessageDialog(None, 'OK continuing', "As you wish", JOptionPane.WARNING_MESSAGE) pass elif myAnswer == JOptionPane.NO_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, 'Stopping', "Fix Error" , JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CANCEL_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, 'Stopping', "Have a cup of Tea", JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CLOSED_OPTION: if self.logLevel > 0: print "You closed the window. How rude!" else: sensors_OK = True if self.check_no_blocks_have_same_sensor() == False: self.msg1 = self.msg1 + "\n***********************\n Do you wish to continue\n***********************" myAnswer = JOptionPane.showConfirmDialog(None, self.msg1) if self.logLevel > 0: print(1) if myAnswer == JOptionPane.YES_OPTION: #JOptionPane.showMessageDialog(None, 'OK continuing', "As you wish", JOptionPane.WARNING_MESSAGE) pass elif myAnswer == JOptionPane.NO_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, 'Stopping', "You need a cup of Tea" , JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CANCEL_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, 'Stopping', "Have a cup of Tea", JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CLOSED_OPTION: if self.logLevel > 0: print "You closed the window. How rude!" else: block_sensors_OK = True if self.check_sufficient_number_of_blocks() == False: self.msg2 = "There are insufficient stopping points for Dispatcher to work\n" + self.msg2 + "\n***********************\n Do you wish to continue? You are advised to stop as the current stage cannot perfom correctly\n***********************" if self.logLevel > 0: print(3) myAnswer = JOptionPane.showConfirmDialog(None, self.msg2) if myAnswer == JOptionPane.YES_OPTION: #JOptionPane.showMessageDialog(None, 'OK continuing', "As you wish", JOptionPane.WARNING_MESSAGE) pass elif myAnswer == JOptionPane.NO_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, "Specify the stopping points by inserting 'stop' in the comment fields of the blocks" , "Stopping" , JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CANCEL_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, 'Stopping', "Have a cup of Tea", JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CLOSED_OPTION: if self.logLevel > 0: print "You closed the window. How rude!" else: stops_OK = True if self.check_all_blocks_have_lengths() == False: self.msg5 = "Not all blocks have lengths\n" + self.msg5 + "\n***********************\n Do you wish to continue? Trains will not run correctly.\n***********************" if self.logLevel > 0: print(3) myAnswer = JOptionPane.showConfirmDialog(None, self.msg5) if myAnswer == JOptionPane.YES_OPTION: #JOptionPane.showMessageDialog(None, 'OK continuing', "As you wish", JOptionPane.WARNING_MESSAGE) pass elif myAnswer == JOptionPane.NO_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, "Specify the stopping points by inserting 'stop' in the comment fields of the blocks" , "Stopping" , JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CANCEL_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, 'Stopping', "Have a cup of Tea", JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CLOSED_OPTION: if self.logLevel > 0: print "You closed the window. How rude!" else: lengths_OK = True if self.check_engines_with_speed_profiles_exist() == False: self.msg5 = "There are no engines with speed profiles\n" + self.msg5 + "\n***********************\n To continue either set up speed profiles for a train, \nor for a quick examination of Dispatcher System you can install the speed profiles stored in the Dispatcher System Folder\n*********Do you wish to continue?**************" if self.logLevel > 0: print(3) myAnswer = JOptionPane.showConfirmDialog(None, self.msg5) if myAnswer == JOptionPane.YES_OPTION: JOptionPane.showMessageDialog(None, 'Please install some of the speed profiles provided', "Look in the speed profile folder in the Dispatcher System Folder", JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.NO_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, "Please run your trains over a suitable track with 3 blocks - see help" , "Install speed profiles" , JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CANCEL_OPTION: msg = 'Stopping' JOptionPane.showMessageDialog(None, 'Stopping', "Have a cup of Tea", JOptionPane.WARNING_MESSAGE) return False elif myAnswer == JOptionPane.CLOSED_OPTION: if self.logLevel > 0: print "You closed the window. How rude!" else: speed_profiles_OK = True msg = "" some_checks_OK = False if sensors_OK: msg = msg + "All blocks have lengths\n" some_checks_OK = True if block_sensors_OK: msg = msg + "All blocks have lengths\n" some_checks_OK = True if stops_OK: msg = msg + "All blocks have lengths\n" some_checks_OK = True if lengths_OK: msg = msg + "All blocks have lengths\n" some_checks_OK = True if speed_profiles_OK: msg = msg + "You have engine(s) with speed profile\n" some_checks_OK = True if some_checks_OK: msg = "Performed some preliminary checks to ensure the trains run correctly\n\nAll Checks OK" title = "Checks" opt1 = "Continue" opt2 = "Look in more detail" reply = Query().customQuestionMessage2str(msg, title, opt1, opt2) if reply == opt2: if sensors_OK: Message = "All blocks have sensors" JOptionPane.showMessageDialog(None, Message, 'Message', JOptionPane.INFORMATION_MESSAGE) if block_sensors_OK: Message = "no two blocks have the same sensor\nPassed check OK" JOptionPane.showMessageDialog(None, Message, 'Message', JOptionPane.INFORMATION_MESSAGE) if stops_OK: Message = "The following blocks have been specified as stopping points\n" + self.msg2 + "\n there are sufficient blocks set up" JOptionPane.showMessageDialog(None, Message, 'Message', JOptionPane.INFORMATION_MESSAGE) if lengths_OK: Message = "All blocks have lengths\n OK to continue \nNote that trains should also be set up with a speed profile to stop correctly" JOptionPane.showMessageDialog(None, Message, 'Message', JOptionPane.INFORMATION_MESSAGE) if speed_profiles_OK: msg = "" for engine in self.get_all_roster_entries_with_speed_profile(): msg += "\n" + str(engine) Message = "You have the following trains with speed profiles" + msg JOptionPane.showMessageDialog(None, Message, 'Message', JOptionPane.INFORMATION_MESSAGE) return True def check_all_blocks_have_sensors(self): LayoutBlockManager=jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager) list_of_errors = [] success = True for block in blocks.getNamedBeanSet(): if block.getSensor() == None: if LayoutBlockManager.getLayoutBlock(block) != None: #only include blocks included in a layout panel if block.getUserName() != None: #all layout blocks have usernames, should not need this check msg = "block {} does not have a sensor".format(block.getUserName()) else: msg = "block {} does not have a sensor".format(block.getSystemName()) msg = msg + "\nblock {} does not have a username".format(block.getSystemName()) list_of_errors.append(msg) self.msg = "" for message in list_of_errors: self.msg = self.msg +"\n" + message success = False return success def check_no_blocks_have_same_sensor(self): LayoutBlockManager=jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager) dict = {} success = True for block in blocks.getNamedBeanSet(): if LayoutBlockManager.getLayoutBlock(block) != None: #only include blocks included in a layout panel if block.getUserName() != None: #all layout blocks have usernames, should not need this check block_name = block.getUserName() else: block_name = block.getSystemName() sensor = block.getSensor() if sensor != None: if sensor.getUserName() != None: sensor_name = sensor.getUserName() else: sensor_name = sensor.getSystemName() dict[block_name] = sensor_name list_of_errors = self.get_duplicate_values_in_dict(dict) if self.logLevel > 0: print list_of_errors if list_of_errors == []: success = True else: success = False self.msg1 = "" for message in list_of_errors: self.msg1 = self.msg1 + "\n" + message return success def get_duplicate_values_in_dict(self, input_dict): # finding duplicate values # from dictionary # using a naive approach rev_dict = {} for key, value in input_dict.items(): rev_dict.setdefault(value, set()).add(key) result = ["blocks " +', '.join(values) + " have the same sensor " + str( key) for key, values in rev_dict.items() if len(values) > 1] return result def check_sufficient_number_of_blocks(self): LayoutBlockManager=jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager) list_of_stops = [] list_of_blocks = [] for block in blocks.getNamedBeanSet(): if LayoutBlockManager.getLayoutBlock(block) != None: #only include blocks included in a layout panel if block.getUserName() != None: #all layout blocks have usernames, should not need this check block_name = block.getUserName() else: block_name = block.getSystemName() comment = str(block.getComment()) if comment !=None: if "stop" in comment.lower(): list_of_stops.append("block " + block_name + " has a stop") if self.logLevel > 0: print list_of_stops else: list_of_blocks.append("block " + block_name + " has no stop") if self.logLevel > 0: print list_of_blocks else: list_of_blocks.append("block " + block_name + " has no stop") #countthe number of blocks in dictionary no_stops = len(list_of_stops) if self.logLevel > 0: print "no_stops", no_stops no_blocks = len(list_of_blocks) if self.logLevel > 0: print "no blocks", no_blocks if no_stops < 2: success = False else: success = True self.msg2 = " - " self.msg2 = self.msg2 + '\n - '.join(list_of_stops) if self.logLevel > 0: print self.msg2 self.msg3 = "" self.msg3 = '\n - '.join(list_of_blocks) if self.logLevel > 0: print self.msg3 if no_stops == 0: self.msg2 = " - there are no stops" return success def check_all_blocks_have_lengths(self): LayoutBlockManager=jmri.InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager) list_of_errors = [] success = True for block in blocks.getNamedBeanSet(): if LayoutBlockManager.getLayoutBlock(block) != None: #only include blocks included in a layout panel if block.getLengthMm() < 0.01: if block.getUserName() != None: #all layout blocks have usernames, should not need this check msg = "block {} does not have a length".format(block.getUserName()) else: msg = "block {} does not have a length".format(block.getSystemName()) msg = msg + "\nblock {} does not have a username".format(block.getSystemName()) list_of_errors.append(msg) success = False self.msg5 = " - " self.msg5 = self.msg5 + '\n - '.join(list_of_errors) return success def check_engines_with_speed_profiles_exist(self): roster_entries_with_speed_profile = self.get_all_roster_entries_with_speed_profile() if roster_entries_with_speed_profile == []: return False else: return True # return True def get_all_roster_entries_with_speed_profile(self): roster_entries_with_speed_profile = [] r = jmri.jmrit.roster.Roster.getDefault() for roster_entry in jmri.jmrit.roster.Roster.getAllEntries(r): if self.logLevel > 0: print "roster_entry.getSpeedProfile()",roster_entry,roster_entry.getSpeedProfile() if roster_entry.getSpeedProfile() != None: roster_entries_with_speed_profile.append(roster_entry.getId()) if self.logLevel > 0: print "roster_entry.getId()",roster_entry.getId() return roster_entries_with_speed_profile def updatePanels(self): for panel in self.editorManager.getAll(jmri.jmrit.display.layoutEditor.LayoutEditor): if panel.getTitle() != 'Dispatcher System': panel.invalidate() panel.validate() panel.repaint() pass # ************************************************** # remove icons and labels from panels # ************************************************** def removeIconsAndLabels(self): for panel in self.editorManager.getAll(jmri.jmrit.display.layoutEditor.LayoutEditor): if panel.getTitle() == 'Dispatcher System': if self.version_number_changed(): # print "removing panel, version number changed" self.editorManager.remove(panel) panel.dispose() # Skip the Dispatcher System control panel if it exists continue self.removeBlockContentIcons(panel) self.removeLabels(panel) self.removeSensorIcons(panel) def removeBlockContentIcons(self, panel): deleteList = [] # Prevent concurrent modification icons = panel.getBlockContentsLabelList() for icon in icons: blk = icon.getBlock() if blk is not None: deleteList.append(icon) for item in deleteList: panel.removeFromContents(item) def removeLabels(self, panel): labelText = [] for control in self.controlSensors: labelText.append(control[2]) deleteList = [] # Prevent concurrent modification for label in panel.getLabelImageList(): if label.isText(): if label.getText() in labelText: deleteList.append(label) for item in deleteList: panel.removeFromContents(item) def removeSensorIcons(self, panel): blockSensors = [] for block in blocks.getNamedBeanSet(): sensor = block.getSensor() if sensor is not None: blockSensors.append(sensor) deleteList = [] # Prevent concurrent modification icons = panel.getSensorList() for icon in icons: sensor = icon.getSensor() if sensor is not None: name = sensor.getDisplayName() if 'MoveTo' in name or 'MoveInProgress' in name: # dispatcher system sensors deleteList.append(icon) else: # block sensors if sensor in blockSensors: deleteList.append(icon) for item in deleteList: panel.removeFromContents(item) # ************************************************** # remove Logix # ************************************************** def removeLogix(self): logixManager = jmri.InstanceManager.getDefault(jmri.LogixManager) logix = logixManager.getLogix('Run Dispatcher') if logix is not None: logix.deActivateLogix() logixManager.deleteLogix(logix) # ************************************************** # remove Transits # ************************************************** def removeTransits(self): deleteList = [] # Prevent concurrent modification for transit in transits.getNamedBeanSet(): deleteList.append(transit) for item in deleteList: transits.deleteBean(item, 'DoDelete') # ************************************************** # remove Sections # ************************************************** def removeSections(self): #remove sections deleteList = [] # Prevent concurrent modification directionSensorDeleteList = [] for section in sections.getNamedBeanSet(): deleteList.append(section) forward_sensor = section.getForwardBlockingSensor() if forward_sensor is not None: directionSensorDeleteList.append(forward_sensor) reverse_sensor = section.getReverseBlockingSensor() if reverse_sensor is not None: directionSensorDeleteList.append(reverse_sensor) for item in deleteList: sections.deleteBean(item, 'DoDelete') for item in directionSensorDeleteList: sensors.deleteBean(item, 'DoDelete') deleteList = [] directionSensorDeleteList = [] # ************************************************** # remove signal mast logic # ************************************************** def removeSML(self): smlManger = jmri.InstanceManager.getDefault(jmri.SignalMastLogicManager) deleteList = [] # Prevent concurrent modification for sml in smlManger.getNamedBeanSet(): deleteList.append(sml) for item in deleteList: smlManger.deleteBean(item, 'DoDelete') # ************************************************** # remove sensors # ************************************************** def removeSensors(self): controlName = [] if self.editorManager.get("Dispatcher System") is None: # OK to delete control sensors for control in self.controlSensors: controlName.append(control[1]) deleteList = [] # Prevent concurrent modification for sensor in sensors.getNamedBeanSet(): userName = sensor.getUserName() sysName = sensor.getSystemName() if userName is not None: if 'MoveTo' in userName or 'MoveInProgress' in userName: deleteList.append(sensor) elif userName in controlName: deleteList.append(sensor) for item in deleteList: #print 'remove sensor {}'.format(item.getDisplayName()) sensors.deleteBean(item, 'DoDelete') # *********************************************************** # gets the list of stopping points (stations, sidings etc.) # *********************************************************** def get_list_of_stopping_points(self): if self.logLevel > 0: print "in get_list_of_stopping_points" stopping_points_set = set() # First, get stopping points from block comments for block in blocks.getNamedBeanSet(): comment = block.getComment() if comment != None: if "stop" in comment.lower(): stopping_points_set.add(block.getUserName()) # Second, automatically add blocks associated with LayoutTurntables and LayoutTraversers editorManager = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager) for editor in editorManager.getAll(): if isinstance(editor, jmri.jmrit.display.layoutEditor.LayoutEditor): # The returned object is a Java Set, which needs to be converted to a list for safe iteration in Jython for turntable in list(editor.getLayoutTurntables()): layout_block = turntable.getLayoutBlock() if layout_block is not None and layout_block.getUserName() is not None: stopping_points_set.add(layout_block.getUserName()) # The returned object is a Java Set, which needs to be converted to a list for safe iteration in Jython for traverser in list(editor.getLayoutTraversers()): layout_block = traverser.getLayoutBlock() if layout_block is not None and layout_block.getUserName() is not None: stopping_points_set.add(layout_block.getUserName()) self.list_of_stopping_points = sorted(list(stopping_points_set)) if self.logLevel > 0: print "list of stopping points", self.list_of_stopping_points # ************************************************** # add sensors # ************************************************** def addSensors(self): # Create the control sensors for control in self.controlSensors: sensor = sensors.provideSensor('IS:DSCT:' + str(control[0])) if sensor is not None: sensor.setUserName(control[1]) # Create a dummy sensor sensor = sensors.provideSensor('IS:DSCT:' + str(0)) sensor.setUserName("DummyControlSensor") sensor = sensors.provideSensor('IS:DSCTA:' + str(0)) sensor.setUserName("Jdialog_closed") # used when setting nonmodal dialogs closed sensor = sensors.provideSensor('IS:DSCTB:' + str(0)) sensor.setUserName("stoppingDistanceSet") # used when setting stopping distances # Create the stop sensors index = 0 for stop in self.list_of_stopping_points: block = blocks.getBlock(stop) if block is not None: index += 1 moveto = sensors.provideSensor('IS:DSMT:' + str(index)) if moveto is not None: moveto.setUserName('MoveTo' + block.getDisplayName().replace(" ","_") + '_stored') inproc = sensors.provideSensor('IS:DSMP:' + str(index)) if inproc is not None: inproc.setUserName('MoveInProgress' + block.getDisplayName().replace(" ","_")) # ************************************************** # generate SML # ************************************************** def generateSML(self): layoutblocks.enableAdvancedRouting(True) layoutblocks.setRoutingStabilised() if self.logLevel > 0: print "Generating Signal Mast Logic" smlManager = jmri.InstanceManager.getDefault(jmri.SignalMastLogicManager) smlManager.automaticallyDiscoverSignallingPairs() if self.logLevel > 0: print "Signal Mast Logic Generated" # ************************************************** # generate sections # ************************************************** def generateSections(self): if self.logLevel > 0: print "Generating Sections" smlManager = jmri.InstanceManager.getDefault(jmri.SignalMastLogicManager) # generate sections() smlManager.generateSection() if self.logLevel > 0: print "Sections Generated" self.show_progress(80) # print "+++++++++++++++++++++++ generate block sections ++++++++++++++++++++++++++++++" sections.generateBlockSections() # print "+++++++++++++++++++++++ end generate block sections ++++++++++++++++++++++++++++++" # ************************************************** # add Logix # ************************************************** def addLogix(self): lgxManager = jmri.InstanceManager.getDefault(jmri.LogixManager) cdlManager = jmri.InstanceManager.getDefault(jmri.ConditionalManager) lgx = lgxManager.createNewLogix('IX:DSLX:1', 'Run Dispatcher') cdl = cdlManager.createNewConditional('IX:DSLX:1C1', 'Run Dispatcher') lgx.addConditional('IX:DSLX:1C1', 0) if cdl is not None: cdl.setUserName('Run Dispatcher') vars = [] vars.append(jmri.ConditionalVariable(False, jmri.Conditional.Operator.AND, jmri.Conditional.Type.SENSOR_ACTIVE, 'startDispatcherSensor', True)) cdl.setStateVariables(vars) actions = [] actions.append(jmri.implementation.DefaultConditionalAction(1, jmri.Conditional.Action.RUN_SCRIPT, '', -1, 'program:jython/DispatcherSystem/Startup.py')) cdl.setAction(actions) lgx.activateLogix() # ************************************************** # add Icons # ************************************************** def addIcons(self): for panel in self.editorManager.getAll(jmri.jmrit.display.layoutEditor.LayoutEditor): self.getBlockCenterPoints(panel) self.addStopIcons(panel) self.addOccupancyIconsAndLabels(panel) #add control icons in separate editor panel self.addControlIconsAndLabels() def getCenterPointOfNearestBlockToMid(self, panel): if self.logLevel > 0: print "in getCenterPointOfNearestBlockToMid" self.index = 0 self.blockPoints.clear() blocks_with_track_segments = set() # reassign the blockpoints to the nearest track segment to mid if one exists for tsv in panel.getTrackSegmentViews(): blk = tsv.getBlockName() pt1 = panel.getCoords(tsv.getConnect1(), tsv.getType1()) pt2 = panel.getCoords(tsv.getConnect2(), tsv.getType2()) [x1,y1] = [pt1.getX(), pt1.getY()] [x2,y2] = [pt2.getX(), pt2.getY()] if blk in self.list_of_stopping_points: if abs(float(y1)-float(y2)) < 15.0: # East-West place icon to right of circle x_reqd = int((float(x1)+float(x2))/2.0)+25 # to put to right of circle y_reqd = int((float(y1)+float(y2))/2.0) # to put just under track else: # North south place icon under circle x_reqd = int((float(x1)+float(x2))/2.0)-20 # to centralise y_reqd = int((float(y1)+float(y2))/2.0)+15 # to put under circle blocks_with_track_segments.add(blk) else: x_reqd = int((float(x1)+float(x2))/2.0) # to put to right of circle y_reqd = int((float(y1)+float(y2))/2.0) pt_to_try = Point2D.Double(x_reqd, y_reqd) if blk in self.blockPoints1: pt_mid = self.blockPoints1[blk] self.updateCoords1(blk, pt_to_try, pt_mid) # Handle turntables for turntableView in panel.getLayoutTurntableViews(): turntable = turntableView.getTurntable() layoutBlock = turntable.getLayoutBlock() if layoutBlock is not None: blk = layoutBlock.getUserName() if blk in self.blockPoints1: pt_mid = self.blockPoints1[blk] # For a turntable, the icon position is calculated relative to its own center [x_reqd, y_reqd] = self.get_turntable_icon_position(turntable, turntableView) pt_to_try = Point2D.Double(x_reqd, y_reqd) self.updateCoords1(blk, pt_to_try, pt_mid) # Handle traversers for traverserView in panel.getLayoutTraverserViews(): traverser = traverserView.getTraverser() layoutBlock = traverser.getLayoutBlock() if layoutBlock is not None: blk = layoutBlock.getUserName() if blk in self.blockPoints1: pt_mid = self.blockPoints1[blk] # For a traverser, the icon position is calculated relative to its own center [x_reqd, y_reqd] = self.get_traverser_icon_position(traverser, traverserView) pt_to_try = Point2D.Double(x_reqd, y_reqd) self.updateCoords1(blk, pt_to_try, pt_mid) # Handle turnouts for turnoutView in panel.getLayoutTurnoutViews(): turnout = turnoutView.getTurnout() layoutBlock = turnoutView.getLayoutBlock() if layoutBlock is not None: blk = layoutBlock.getUserName() if blk in blocks_with_track_segments: continue if blk in self.blockPoints1: pt_mid = self.blockPoints1[blk] coords = turnoutView.getCoordsCenter() pt_to_try = Point2D.Double(coords.getX(), coords.getY()) self.updateCoords1(blk, pt_to_try, pt_mid) def get_turntable_icon_position(self, turntable, turntableView): import math # print "--- Calculating icon position for turntable:", turntable.getName(), "---" turntable_center = turntableView.getCoordsCenter() radius = turntable.getRadius() rays = turntable.getRayTrackList() if len(rays) < 2: # Fallback for turntables with 0 or 1 ray: place icon bottom-right # print " Fewer than 2 rays, using fallback position." x_reqd = int(turntable_center.getX()) + 30 y_reqd = int(turntable_center.getY()) + 25 return [x_reqd, y_reqd] # Get all ray angles in degrees and sort them angles = sorted([ray.getAngle() for ray in rays]) # print " Sorted ray angles (degrees):", angles # Calculate angular gaps between adjacent rays gaps = [] for i in range(len(angles) - 1): gap = angles[i+1] - angles[i] gaps.append(gap) # Add the wrap-around gap between the last and first ray wrap_around_gap = (360.0 - angles[-1]) + angles[0] gaps.append(wrap_around_gap) # print " Calculated gaps:", gaps max_gap = max(gaps) # print " Max gap found:", max_gap # Find all gaps that are the largest best_mid_angle = -1 min_angle_diff = 361 # Larger than any possible angle difference # Define a preferred angle for tie-breaking (bottom-right quadrant) preferred_angle = math.degrees(math.atan2(25, 30)) # atan2(y, x) # print " Preferred angle for tie-breaking:", preferred_angle for i in range(len(gaps)): if abs(gaps[i] - max_gap) < 1e-6: # Compare floats with a tolerance # print " Found a max gap at index", i if i < len(angles) - 1: mid_angle = angles[i] + max_gap / 2.0 else: # Wrap-around case mid_angle = (angles[-1] + max_gap / 2.0) % 360.0 # print " Mid-angle of this gap:", mid_angle # Normalize angle difference for tie-breaking angle_diff = abs(mid_angle - preferred_angle) # print " Difference from preferred angle:", angle_diff if angle_diff < min_angle_diff: # print " This is the new best candidate." min_angle_diff = angle_diff best_mid_angle = mid_angle # print " Final chosen mid-angle:", best_mid_angle # Convert the final angle to radians for trig functions final_angle_rad = math.radians(best_mid_angle) if best_mid_angle > 180.0 and best_mid_angle < 360.0: icon_distance = radius + 70 # Place icon 20 pixels out from the turntable radius plus a bit to allow for the icon length else: icon_distance = radius + 30 # Place icon 20 pixels out from the turntable radius x_reqd = int(turntable_center.getX() + icon_distance * math.sin(final_angle_rad)) y_reqd = int(turntable_center.getY() - icon_distance * math.cos(final_angle_rad)) # print " Turntable Centre (x, y):", int(turntable_center.getX()), ",", int(turntable_center.getY()) # print " Calculated position (x, y):", x_reqd, ",", y_reqd # print "----------------------------------------------------" return [x_reqd, y_reqd] def get_traverser_icon_position(self, traverser, traverserView): import math if self.logLevel > 0: print "--- Calculating icon position for traverser:", traverser.getName(), "---" traverser_center = traverserView.getCoordsCenter() if self.logLevel > 0: print "traverser_center", traverser_center slotOffset = traverser.getSlotOffset() numberOfSlots = traverser.getNumberSlots() if self.logLevel > 0: print "numberOfSlots", numberOfSlots deckWidth = traverser.getDeckWidth() # deck_height = ((deckWidth/4.0) + (slotOffset * numberOfSlots))/4.0 deck_height = traverser.getDeckLength() # offset = -1.5 * deckWidth/4.0 offset = 0.0 # Check if all RHS (even index) slots are disabled rhs_disabled = True slots = traverser.getSlotList() for i in range(len(slots)): if i % 2 == 0: # Even index = RHS if not slots[i].isDisabled(): rhs_disabled = False break # Check if all LHS (odd index) slots are disabled lhs_disabled = True for i in range(len(slots)): if i % 2 != 0: # Odd index = LHS if not slots[i].isDisabled(): lhs_disabled = False break if rhs_disabled: if traverser.getOrientation() == traverser.HORIZONTAL: x_reqd = int(traverser_center.getX() - deckWidth/2.0 - 70.0 ) y_reqd = int(traverser_center.getY()) else: x_reqd = int(traverser_center.getX() - 25.0) y_reqd = int(traverser_center.getY() - deckWidth/2.0 - 25.0) elif lhs_disabled: if traverser.getOrientation() == traverser.HORIZONTAL: x_reqd = int(traverser_center.getX() + deckWidth/2.0 + 20.0) y_reqd = int(traverser_center.getY()) else: x_reqd = int(traverser_center.getX() - 25.0) y_reqd = int(traverser_center.getY() + deckWidth/2.0 + 15.0) else: if traverser.getOrientation() == traverser.HORIZONTAL: x_reqd = int(traverser_center.getX()) - 25 y_reqd = int(traverser_center.getY()) + deck_height/2.0 + offset else: x_reqd = int(traverser_center.getX()) + deck_height/2.0 + 20.0 y_reqd = int(traverser_center.getY()) if self.logLevel > 0: print "finished calculating icon position" return [x_reqd, y_reqd] def updateCoords1(self, blk, pt_to_try, pt_mid): if blk is not None: if blk in self.blockPoints: if (jmri.util.MathUtil.distance(pt_mid, pt_to_try) < \ jmri.util.MathUtil.distance(pt_mid, self.blockPoints[blk])): self.blockPoints[blk] = pt_to_try else: self.blockPoints[blk] = pt_to_try def updateCoords(self, blk, xy): if blk is not None: if blk in self.blockPoints1: self.blockPoints1[blk] = jmri.util.MathUtil.midPoint(self.blockPoints1[blk], xy) else: self.blockPoints1[blk] = xy def getBlockCenterPoints(self, panel): self.index = 0 self.blockPoints1.clear() for tsv in panel.getTrackSegmentViews(): blk = tsv.getBlockName() pt1 = panel.getCoords(tsv.getConnect1(), tsv.getType1()) pt2 = panel.getCoords(tsv.getConnect2(), tsv.getType2()) mid = jmri.util.MathUtil.midPoint(pt1, pt2) self.updateCoords(blk, mid) for tov in panel.getLayoutTurnoutAndSlipViews(): blkA = tov.getBlockName() blkB = tov.getBlockBName() blkC = tov.getBlockCName() blkD = tov.getBlockDName() xyA = tov.getCoordsA() xyB = tov.getCoordsB() xyC = tov.getCoordsC() xyD = tov.getCoordsD() self.updateCoords(blkA, xyA) self.updateCoords(blkB, xyB) self.updateCoords(blkC, xyC) self.updateCoords(blkD, xyD) for lxv in panel.getLevelXingViews(): blkAC = lxv.getBlockNameAC() blkBD = lxv.getBlockNameBD() # A level crossing has 4 points but only two blocks. To prevent both points being in the # middle, use the A and D points. xyA = lxv.getCoordsA() xyD = lxv.getCoordsD() self.updateCoords(blkAC, xyA) self.updateCoords(blkBD, xyD) # Handle turntables for turntableView in panel.getLayoutTurntableViews(): turntable = turntableView.getTurntable() layoutBlock = turntable.getLayoutBlock() if layoutBlock is not None: blockName = layoutBlock.getUserName() # The center of the turntable is its main coordinate, get it from the View centerCoords = turntableView.getCoordsCenter() self.updateCoords(blockName, centerCoords) # Handle traversers for traverserView in panel.getLayoutTraverserViews(): traverser = traverserView.getTraverser() layoutBlock = traverser.getLayoutBlock() if layoutBlock is not None: blockName = layoutBlock.getUserName() # The center of the traverser is its main coordinate, get it from the View centerCoords = traverserView.getCoordsCenter() self.updateCoords(blockName, centerCoords) # place the stations at the block nearest the mid-point self.getCenterPointOfNearestBlockToMid(panel) # ************************************************** # stop icons # ************************************************** def addStopIcons(self, panel): for blockName in self.list_of_stopping_points: if blockName in self.blockPoints.keys(): # print "addStopIcons blockName", blockName x = self.blockPoints[blockName].getX() y = self.blockPoints[blockName].getY() mtSensor = sensors.getSensor('MoveTo' + blockName.replace(" ","_") + '_stored') if mtSensor is not None: self.addMarkerIcon(panel, mtSensor, blockName, x, y) mpSensor = sensors.getSensor('MoveInProgress' + blockName.replace(" ","_")) if mpSensor is not None: self.addSmallIcon(panel, mpSensor.getDisplayName(), x - 10, y) # ************************************************** # occupancy sensor icons and block content labels # ************************************************** def addOccupancyIconsAndLabels(self, panel): for blockName in self.blockPoints.keys(): x = self.blockPoints[blockName].getX() - 10 y = self.blockPoints[blockName].getY() + 10 block = blocks.getBlock(blockName) if block is not None: sensor = block.getSensor() if sensor is not None: self.addSmallIcon(panel, sensor.getDisplayName(), x, y) y = int(y) - 30 if int(y) > 35 else 5 self.addBlockContentLabel(panel, block, x, y) # ************************************************** # control sensor icons and label # ************************************************** def addControlIconsAndLabels(self): if (not self.version_number_changed()) and self.dispatcher_system_panel_exists(): if self.logLevel > 0: print "not adding control Icons and labels" return # Create the Dispatcher System control panel panel = jmri.jmrit.display.layoutEditor.LayoutEditor("Dispatcher System") self.editorManager.add(panel) for control in self.controlSensors: sensor = sensors.getSensor('IS:DSCT:' + str(control[0])) if sensor is not None: x = 20 + control[3] y = (control[0] * 20) + 0 + control[4] self.addMediumIcon(panel, sensor, x, y) x += 20 self.addTextLabel(panel, control[2], x, y) panel.setSize(300,600) panel.setAllEditable(False) panel.setVisible(True) def dispatcher_system_panel_exists(self): for frame1 in java.awt.Frame.getFrames(): # print "frame", frame1.getName() if frame1.getName() == "Dispatcher System": if frame1.isVisible(): return True # print "Dispatcher System Panel does not exist" return False # ************************************************** # small icon # ************************************************** def addSmallIcon(self, panel, sensorName, x, y): icn = jmri.jmrit.display.SensorIcon(panel) icn.setIcon("SensorStateActive", jmri.jmrit.catalog.NamedIcon("resources/icons/smallschematics/tracksegments/circuit-occupied.gif", "active")); icn.setIcon("SensorStateInactive", jmri.jmrit.catalog.NamedIcon("resources/icons/smallschematics/tracksegments/circuit-empty.gif", "inactive")); icn.setIcon("BeanStateInconsistent", jmri.jmrit.catalog.NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif", "incons")); icn.setIcon("BeanStateUnknown", jmri.jmrit.catalog.NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif", "unknown")); # Assign the sensor and set the location icn.setSensor(sensorName) icn.setLocation(int(x), int(y)) # Add the icon to the layout editor panel panel.putSensor(icn) # ************************************************** # medium icon # ************************************************** def addMediumIcon(self, panel, sensor, x, y): icn = jmri.jmrit.display.SensorIcon(panel) icn.setIcon("SensorStateActive", jmri.jmrit.catalog.NamedIcon("resources/icons/mediumschematics/LEDs/AMBERLED.gif", "active")); icn.setIcon("SensorStateInactive", jmri.jmrit.catalog.NamedIcon("resources/icons/mediumschematics/LEDs/GRAYLED.gif", "inactive")); icn.setIcon("BeanStateInconsistent", jmri.jmrit.catalog.NamedIcon("resources/icons/mediumschematics/LEDs/REDLED.gif", "incons")); icn.setIcon("BeanStateUnknown", jmri.jmrit.catalog.NamedIcon("resources/icons/mediumschematics/LEDs/REDLED.gif", "unknown")); # Assign the sensor and set the location icn.setSensor(sensor.getDisplayName()) icn.setLocation(int(x), int(y)) # Add the icon to the layout editor panel panel.putSensor(icn) # ************************************************** # marker icon # ************************************************** def addMarkerIcon(self, panel, sensor, blockName, x, y): icn = jmri.jmrit.display.SensorIcon(panel) icn.setIcon("SensorStateActive", jmri.jmrit.catalog.NamedIcon("resources/icons/markers/loco-green.gif", "active")); icn.setIcon("SensorStateInactive", jmri.jmrit.catalog.NamedIcon("resources/icons/markers/loco-red.gif", "inactive")); icn.setIcon("BeanStateInconsistent", jmri.jmrit.catalog.NamedIcon("resources/icons/markers/loco-yellow.gif", "incons")); icn.setIcon("BeanStateUnknown", jmri.jmrit.catalog.NamedIcon("resources/icons/markers/loco-gray.gif", "unknown")); if len(blockName) > 9: icn.setText(blockName[:11]) icn.getPopupUtility().setFontSize(9) else: icn.setText(blockName[:9]) icn.getPopupUtility().setFontSize(11) icn.setTextActive(Color.RED) icn.setTextInActive(Color.YELLOW) icn.setTextInconsistent(Color.BLACK) icn.setTextUnknown(Color.BLUE) # Assign the sensor and set the location icn.setSensor(sensor.getDisplayName()) icn.setLocation(int(x), int(y)) # Add the icon to the layout editor panel panel.putSensor(icn) # ************************************************** # text label # ************************************************** def addTextLabel(self, panel, text, x, y): label = jmri.jmrit.display.PositionableLabel(text, panel) label.setLocation(int(x), int(y)) label.setSize(label.getPreferredSize().width, label.getPreferredSize().height); label.setDisplayLevel(4) panel.putItem(label) # ************************************************** # block content label # ************************************************** def addBlockContentLabel(self, panel, block, x, y): label = jmri.jmrit.display.BlockContentsIcon(block.getDisplayName(), panel) label.setBlock(block.getDisplayName()) label.setLocation(int(x), int(y)) panel.putItem(label) def saveForwardStoppingSensors(self): forward_stop_sensors = \ [["section: " , str(section.getUserName()), " stop sensor: " , str(section.getForwardStoppingSensor().getUserName())] \ for section in sections.getNamedBeanSet() if section.getForwardStoppingSensor() != None] self.write_list(forward_stop_sensors) def retrieveForwardStoppingSensors(self): forward_stop_sensors = self.read_list() if forward_stop_sensors != []: [sections.getSection(section_name).setForwardStoppingSensorName(forward_stopping_sensor_name) \ for [sn_prompt, section_name, fss_prompt, forward_stopping_sensor_name] in forward_stop_sensors \ if forward_stop_sensors is not [] and sections.getSection(section_name) is not None] def directory(self): path = jmri.util.FileUtil.getUserFilesPath() + "dispatcher" + java.io.File.separator + "forwardStoppingSensors" if not os.path.exists(path): os.makedirs(path) return path + java.io.File.separator def write_list(self, a_list): # store list in binary file so 'wb' mode file = self.directory() + "forwardStoppingSensors.txt" #print "block_info" , a_list #print "file" +file with open(file, 'wb') as fp: for items in a_list: i = 0 for item in items: fp.write('%s' %item) if i < 3 : fp.write(",") i+=1 fp.write('\n') # Read list to memory def read_list(self): # for reading also binary mode is important file = self.directory() + "forwardStoppingSensors.txt" n_list = [] try: with open(file, 'rb') as fp: for line in fp: x = line[:-1] #print x y = x.split(",") #print "y" , y n_list.append(y) return n_list except: return [] class DisplayProgress: def __init__(self): #labels don't seem to work. This is the only thing I could get to work. Improvements welcome self.frame1 = JFrame('Starting Processing!', defaultCloseOperation=JFrame.DISPOSE_ON_CLOSE, size=(500, 50), locationRelativeTo=None) self.frame1.setVisible(True) def Update(self,msg): self.frame1.setTitle(msg) def killLabel(self): self.frame1.setVisible(False) self.frame1 = None class Query: def customQuestionMessage2str(self, msg, title, opt1, opt2): self.CLOSED_OPTION = False options = [opt1, opt2] s = JOptionPane.showOptionDialog(None, msg, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, None, options, options[1]) if s == JOptionPane.CLOSED_OPTION: self.CLOSED_OPTION = True return if s == JOptionPane.YES_OPTION: s1 = opt1 else: s1 = opt2 return s1 def displayMessage(self, msg, title = ""): self.CLOSED_OPTION = False s = JOptionPane.showOptionDialog(None, msg, title, JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, None, ["OK"], None) #JOptionPane.showMessageDialog(None, msg, 'Message', JOptionPane.WARNING_MESSAGE) if s == JOptionPane.CLOSED_OPTION: self.CLOSED_OPTION = True return return s