# NOTE: follow these steps https://mstevetodd.com/anyrail-export-jmri for setup details # AnyRailExportAdditions.py - SteveT - 05/15/2025 # Intended to be run ONCE after opening a file generated by the AnyRail export # The AnyRail export outputs a background .jpg, and a set of internal Block objects # for all AnyRail "Sections" which have been assigned a "Name". # This script does some cleanup of the generated Block and Turnout definitions, creates Sensors # and assigns them to the corresponding Blocks # Both Sensors and Turnouts are created to match your default hardware Connection. # Requirements: # AnyRail "Section" names must contain a unique Block number, this number is used to generate # the JMRI Block and Sensor systemNames. # AnyRail Turnouts must have "Label" names entered with a unique Turnout number or numbers. # If desired, you can include a unique "User Name" after the unique number, such as "186 Yard Track 1" # The number will be used for the SystemName, and the trailing text for the UserName # Be sure to check the JMRI System Console for any WARN or ERROR messages after running this script. # Pre-export suggestions: set AnyRail Zoom to 1:6, disable "Show Visible track" # # Next steps: Run CreateSectionsFromBlocks.py and AddOccupancyIconsToPanel.py # # Thanks to Cliff Anderson's AnyRailBuildSensorList.py and Bill Fitch's CreateSignalLogicAndSections.py # for some of the code and much of the inspiration # TODO: handle underscore in AR SectionName # TODO: "rename" the Block systemName to match (Block "move" currently causes NPE in JMRI) # # NOTE: to enable jython logging, see https://www.jmri.org/help/en/html/apps/Debug.shtml # Add the Logger Category name "jmri.jmrit.jython.exec" at DEBUG Level. import jmri import java import org.slf4j.LoggerFactory import re import sys from java.awt import Color log = org.slf4j.LoggerFactory.getLogger("jmri.jmrit.jython.exec.script.AnyRailExportAdditions") connectionPrefix = turnouts.getSystemPrefix() # get the "Connection Prefix" in use for the default JMRI connection if (connectionPrefix == 'I') : log.error('AnyRailExportAdditions.py Error - Default system cannot be Internal, correct and rerun.') exit() log.info( "AnyRailExportAdditions.py Tweaking LayoutEditor appearance" ) panels = jmri.InstanceManager.getDefault(jmri.jmrit.display.EditorManager) layoutPanels = panels.getList(jmri.jmrit.display.layoutEditor.LayoutEditor) if (len(layoutPanels) != 1) : log.error('AnyRailExportAdditions.py - Error finding single LayoutEditor panel, terminating.') exit() layoutEditor = layoutPanels[0] log.debug("tweaking some display values for panel '{}'".format(layoutEditor.getLayoutName())) ltdo = layoutEditor.getLayoutTrackDrawingOptions() ltdo.setMainRailWidth(2) ltdo.setMainBlockLineWidth(3) layoutEditor.setTurnoutDrawUnselectedLeg(False) layoutEditor.setTurnoutCircleSize(3) layoutEditor.setTurnoutCircleColor(Color.LIGHT_GRAY) layoutEditor.setTurnoutCircleThrownColor(Color.LIGHT_GRAY) layoutEditor.redrawPanel() log.info( "AnyRailExportAdditions.py Create and Assign Sensors to blocks" ) blocksUpdated = 0 #count successes blocksSkipped = 0 # and failures sensorsCreated = 0 blocksSet = set(blocks.getNamedBeanSet()) # use a copy for the loop for blockBean in blocksSet : #if (blocksUpdated > 0) : continue #debugging, comment out before committing blockSystemName = str ( blockBean ) block = blocks.getBlock(blockSystemName) anyRailUserName = block.getUserName() if (anyRailUserName == None) : log.warn('AnyRail SectionName "{}" is empty for block {}, skipped.'.format(blockSystemName, anyRailUserName)) blocksSkipped += 1 continue ra = anyRailUserName.split("_"); #break B_15_LS223 South Staging Lead_mainline into parts on underscore if (len(ra)<3 or ra[0]!="B") : log.warn("Unexpected syntax '{}', skipping.".format(anyRailUserName)) blocksSkipped += 1 continue anyRailDescription = None if (len(ra) == 4) : # user defined, "Unspecified", "mainline", "secondary", etc. anyRailDescription = ra[3] userName = ra[2] # e.g. LS223 South Staging Lead # extract the block number from this username, e.g. LS223 South Staging Lead -> 223 result = re.search("(\d+)", userName) if (result == None) : log.warn('BlockNumber not found in "{}", skipping.'.format(userName)) blocksSkipped += 1 continue blockNumber = result.group(1) blockName = "B" + blockNumber # block Name will be Bnnn result = blocks.getByUserName(blockName) # prevent duplicates in username and systemname if (result != None) : log.warn('Block "{}" already exists, skipping.'.format(blockName)) blocksSkipped += 1 continue result = re.search("[\d]+[\s]*(\D+.*)*", userName) # whatever follows the first number is userName if (result != None) : userName = result.group(1) result = blocks.getByUserName(userName) # prevent duplicate userNames if (result != None) : log.warn('Block userName "{}" already exists, skipping.'.format(userName)) blocksSkipped += 1 continue log.debug('Processing blockSystemName={}, anyRailUserName={}, blockName={}, userName={}'.format(blockSystemName, anyRailUserName, blockName, userName)) block.setUserName(userName if userName else blockName) blocksUpdated += 1 # create and attach sensor for this block sensorName = connectionPrefix + "S" + blockNumber check = sensors.getBySystemName(sensorName) # insure no duplicate systemnames if (check) : log.warn("Sensor systemName '{}' is duplicate, skipping".format(sensorName)) continue newSensor = None try: newSensor = sensors.newSensor(sensorName, None) # create the hardware sensor using systemName except: log.error("Failed to create sensor '{}', {}", sensorName, sys.exc_info()[1]); continue if (userName) : newSensor.setUserName(userName) newSensor.setUseDefaultTimerSettings(True) # not sure why this isn't the default block.setSensor(sensorName) #set the Sensor for the block sensorsCreated += 1 log.debug("Created Sensor={} and assigned it to Block {} (renamed from {})".format(sensorName, blockName, anyRailUserName)) log.info("Created {} Sensors, updated {} Blocks, skipped {} invalid Blocks".format(sensorsCreated, blocksUpdated, blocksSkipped)) log.info("AnyRailExportAdditions.py Converting Internal Turnouts to Hardware ({})".format(connectionPrefix)) turnoutsCreated = 0 #count successes turnoutsSkipped = 0 # and failures turnoutsSet = set(turnouts.getNamedBeanSet()) # use a copy for the loop for turnoutBean in turnoutsSet : # if (turnoutsCreated > 5) : continue # get the Turnout systemName entered as AnyRail Label, and get reference to that turnout oldSystemName = str ( turnoutBean ) oldTurnout = turnouts.getTurnout(oldSystemName) anyRailUserName = oldTurnout.getUserName() # AnyRail stores entered name as userName if (anyRailUserName == None) : log.warn('Turnout "{}" has no userName, skipping.'.format(oldSystemName)) turnoutsSkipped += 1 continue result = re.search("(\d+).*", anyRailUserName) # get the first number sequence if (result == None) : log.warn('Turnout Number not found in "{}", skipping.'.format(anyRailUserName)) turnoutsSkipped += 1 continue turnoutNumber = result.group(1) turnoutNumber2 = None result = re.search("\d+\+(\d+).*", anyRailUserName) # check for nn+nn for multi-address turnouts if (result != None) : turnoutNumber2 = result.group(1) oldTurnout.setUserName(None) # clear old username to avoid duplicates newSystemName = connectionPrefix + "T" + turnoutNumber # systemName is xTnnn checkTurnout = turnouts.getTurnout(newSystemName) # insure systemname is not already used if (checkTurnout) : if (turnoutNumber2 == None) : log.warn("Turnout systemName '{}' would be duplicate, not replaced".format(newSystemName)) oldTurnout.setUserName(anyRailUserName) # restore username turnoutsSkipped += 1 continue else : # check again with 2nd turnout if found newSystemName = connectionPrefix + "T" + turnoutNumber2 # systemName is xTnnn checkTurnout = turnouts.getTurnout(newSystemName) # insure systemname is not already used if (checkTurnout) : log.warn("Turnout2 systemName '{}' would be duplicate, not replaced".format(newSystemName)) oldTurnout.setUserName(anyRailUserName) # restore username turnoutsSkipped += 1 continue newUserName = None result = re.search("[\d|\+]+\s*(\S.*)", anyRailUserName) # new username is the rest of the string after address(es) if (result != None) : newUserName = result.group(1) checkTurnout = turnouts.getByUserName(newUserName) if (checkTurnout) : log.warn("Turnout '{}' would duplicate userName '{}', clearing".format(newSystemName, newUserName)) newUserName = None log.debug("Replacing Turnout {} with {} {}".format(oldSystemName, newSystemName, '('+newUserName+')' if newUserName else '')) newTurnout = turnouts.provideTurnout(newSystemName) # create new hardware turnout newTurnout.setUserName(anyRailUserName) # move the old username to new turnout beans.moveBean(oldTurnout, newTurnout, anyRailUserName) # move references from old to new beans.updateBeanFromUserToSystem(newTurnout) # move refs to sysname, so we can change userName newTurnout.setUserName(newUserName) # set new userName, can be None turnouts.deleteBean(oldTurnout, 'DoDelete') # delete old turnout turnoutsCreated += 1 log.info('Replaced {} Turnouts, skipped {} Turnouts'.format(turnoutsCreated, turnoutsSkipped))