Files
JIMRI/jython/anyrail/AnyRailExportAdditions.py
T
2026-06-17 14:00:51 +02:00

220 lines
9.9 KiB
Python

# 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))