220 lines
9.9 KiB
Python
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))
|