Coverage for /home/pi/Software/model-railway-signalling/model_railway_signals/editor/objects/objects_points.py: 100%

114 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-05 17:29 +0100

1#------------------------------------------------------------------------------------ 

2# This module contains all the functions for managing Point objects 

3#------------------------------------------------------------------------------------ 

4# 

5# External API functions intended for use by other editor modules: 

6# create_point(type) - Create a default point object on the schematic 

7# delete_point(obj_id) - Hard Delete an object when deleted from the schematic 

8# update_point(obj_id,new_obj) - Update the configuration of an existing point object 

9# paste_point(object) - Paste a copy of an object to create a new one (returns new object_id) 

10# delete_point_object(object_id) - Soft delete the drawing object (prior to recreating) 

11# redraw_point_object(object_id) - Redraw the object on the canvas following an update 

12# default_point_object - The dictionary of default values for the object 

13# reset_point_interlocking_tables() - recalculates interlocking tables from scratch 

14# 

15# Makes the following external API calls to other editor modules: 

16# objects_common.set_bbox - to create/update the boundary box for the schematic object 

17# objects_common.find_initial_canvas_position - to find the next 'free' canvas position 

18# objects_common.new_item_id - to find the next 'free' item ID when creating objects 

19# objects_common.point - To get The Object_ID for a given Item_ID 

20# objects_common.signal - To get The Object_ID for a given Item_ID 

21# objects_signals.update_references_to_point - called when the point ID is changed 

22# objects_signals.remove_references_to_point - called when the point is deleted 

23# objects_sensors.update_references_to_point - called when the point ID is changed 

24# objects_sensors.remove_references_to_point - called when the point is deleted 

25# 

26# Accesses the following external editor objects directly: 

27# run_layout.schematic_callback - to set the callbacks when creating/recreating 

28# objects_common.schematic_objects - the master dictionary of Schematic Objects 

29# objects_common.point_index - The index of Point Objects (for iterating) 

30# objects_common.signal_index - The index of Signal Objects (for iterating) 

31# objects_common.default_object - The common dictionary element for all objects 

32# objects_common.object_type - The Enumeration of supported objects 

33# objects_common.canvas - Reference to the Tkinter drawing canvas 

34# 

35# Accesses the following external library objects directly: 

36# points.point_type - for setting the enum value when creating the object 

37# 

38# Makes the following external API calls to library modules: 

39# points.point_exists - Common function to see if a given item exists 

40# points.delete_point(id) - delete library drawing object (part of soft delete) 

41# points.create_point(id) - To create the library object (create or redraw) 

42# points.update_autoswitch(id,autoswitch_id) - to change the config of an existing point 

43# points.get_tags(id) - get the canvas 'tags' for the point drawing objects 

44# points.point_switched(id) - test if a point is switched (when updating dependent objects) 

45# points.toggle_point_state(id) - to toggle point (when updating dependent objects) 

46# dcc_control.delete_point_mapping - delete mappings when deleting point / prior to recreating 

47# dcc_control.map_dcc_point - to create the new DCC mapping (creation or updating) 

48# 

49#------------------------------------------------------------------------------------ 

50 

51import uuid 

52import copy 

53 

54from ...library import points 

55from ...library import dcc_control 

56 

57from . import objects_common 

58from . import objects_signals 

59from . import objects_sensors 

60from .. import run_layout 

61 

62#------------------------------------------------------------------------------------ 

63# Default Point Objects (i.e. state at creation) 

64#------------------------------------------------------------------------------------ 

65 

66default_point_object = copy.deepcopy(objects_common.default_object) 

67default_point_object["item"] = objects_common.object_type.point 

68default_point_object["itemtype"] = points.point_type.LH.value 

69default_point_object["orientation"] = 0 

70default_point_object["colour"] = "black" 

71default_point_object["alsoswitch"] = 0 

72default_point_object["reverse"] = False 

73default_point_object["automatic"] = False 

74default_point_object["hasfpl"] = False 

75default_point_object["dccaddress"] = 0 

76default_point_object["dccreversed"] = False 

77# This is the default signal interlocking table for the point 

78# The Table comprises a variable length list of interlocked signals 

79# Each signal entry in the list comprises [sig_id, [main, lh1, lh2, rh1, rh2]] 

80# Each route element in the list of routes is a boolean value (True or False) 

81default_point_object["siginterlock"] = [] 

82 

83#------------------------------------------------------------------------------------ 

84# Function to recalculate the point interlocking tables for all points 

85# Called following update or delete of a signal object and on layout load 

86# Signal 'pointinterlock' comprises: [main, lh1, lh2, rh1, rh2] 

87# Each route comprises: [[p1, p2, p3, p4, p5, p6, p7], sig_id, block_id] 

88# Each point element (in the list of points) comprises [point_id, point_state] 

89# Point 'siginterlock' comprises a variable length list of interlocked signals 

90# Each list entry comprises [sig_id, [main, lh1, lh2, rh1, rh2]] 

91# Each route element is a boolean value (True or False) 

92#------------------------------------------------------------------------------------ 

93 

94def reset_point_interlocking_tables(): 

95 # Iterate through the points to clear the interlocking tables 

96 for point_id in objects_common.point_index: 

97 objects_common.schematic_objects[objects_common.point(point_id)]["siginterlock"] = [] 

98 # Iterate through the points to re-calculate the interlocking tables 

99 for signal_id in objects_common.signal_index: 

100 # Get the object ID for the signal 

101 signal_object = objects_common.signal(signal_id) 

102 # Iterate through all the points on the schematic 

103 for point_id in objects_common.point_index: 

104 # Get the Object ID of the point 

105 point_object = objects_common.point(point_id) 

106 # Everything is false by default- UNLESS specifically set 

107 point_interlocked_by_signal = False 

108 interlocked_routes = [False, False, False, False, False] 

109 # Iterate through each route in the SIGNAL interlocking table and then the points on each route 

110 interlocking_table = objects_common.schematic_objects[signal_object]["pointinterlock"] 

111 for route_index, route_to_test in enumerate(interlocking_table): 

112 list_of_points_to_test = route_to_test[0] 

113 for point_to_test in list_of_points_to_test: 

114 if point_to_test[0] == int(point_id): 

115 interlocked_routes[route_index] = True 

116 point_interlocked_by_signal = True 

117 if point_interlocked_by_signal: 

118 interlocked_signal = [objects_common.schematic_objects[signal_object]["itemid"], interlocked_routes] 

119 objects_common.schematic_objects[point_object]["siginterlock"].append(interlocked_signal) 

120 return() 

121 

122#------------------------------------------------------------------------------------ 

123# Internal function to update references from points that "also switch" this point. 

124# Note that we use the non-public API function for updating the 'autoswitched' ID 

125# rather than deleting the point and then re-creating it in its new state 

126#------------------------------------------------------------------------------------ 

127 

128def update_references_to_point(old_point_id:int, new_point_id:int): 

129 # Iterate through all the points on the schematic 

130 for point_id in objects_common.point_index: 

131 point_object = objects_common.point(point_id) 

132 if objects_common.schematic_objects[point_object]["alsoswitch"] == old_point_id: 

133 objects_common.schematic_objects[point_object]["alsoswitch"] = new_point_id 

134 points.update_autoswitch(point_id=int(point_id), autoswitch_id=new_point_id) 

135 return() 

136 

137#------------------------------------------------------------------------------------ 

138# Internal function to remove references to this point from points configured to "also 

139# switch" the deleted point. Note that we use the non-public API function for updating 

140# the 'autoswitched' ID rather than deleting the point and then re-creating it in its new 

141# state. The main use case is when bulk deleting objects via the schematic editor, where 

142# we want to avoid interleaving tkinter 'create' commands in amongst the 'delete' commands 

143# outside of the main loop as this can result in with artefacts persisting on the canvas 

144#------------------------------------------------------------------------------------ 

145 

146def remove_references_to_point(deleted_point_id:int): 

147 for point_id in objects_common.point_index: 

148 point_object = objects_common.point(point_id) 

149 if objects_common.schematic_objects[point_object]["alsoswitch"] == deleted_point_id: 

150 objects_common.schematic_objects[point_object]["alsoswitch"] = 0 

151 points.update_autoswitch(point_id=int(point_id), autoswitch_id=0) 

152 return() 

153 

154#------------------------------------------------------------------------------------ 

155# Function to to update a point object after a configuration change 

156#------------------------------------------------------------------------------------ 

157 

158def update_point(object_id, new_object_configuration): 

159 # We need to track whether the Item ID has changed 

160 old_item_id = objects_common.schematic_objects[object_id]["itemid"] 

161 new_item_id = new_object_configuration["itemid"] 

162 # Delete the existing point object, copy across the new configuration and redraw 

163 # Note the point will be created in the unswitched state (we change it later if needed) 

164 delete_point_object(object_id) 

165 objects_common.schematic_objects[object_id] = copy.deepcopy(new_object_configuration) 

166 redraw_point_object(object_id) 

167 # Check to see if the Type-specific ID has been changed 

168 if old_item_id != new_item_id: 

169 # Update the type-specific index 

170 del objects_common.point_index[str(old_item_id)] 

171 objects_common.point_index[str(new_item_id)] = object_id 

172 # Update any other point that "also switches" this point to use the new ID 

173 update_references_to_point(old_item_id,new_item_id) 

174 # Update any affected signal / track sensor tables to reference the new point ID 

175 objects_signals.update_references_to_point(old_item_id, new_item_id) 

176 objects_sensors.update_references_to_point(old_item_id, new_item_id) 

177 return() 

178 

179#------------------------------------------------------------------------------------ 

180# Function to redraw a Point object on the schematic. Called when the object is first 

181# created or after the object configuration has been updated. 

182#------------------------------------------------------------------------------------ 

183 

184def redraw_point_object(object_id): 

185 # Create the new DCC Mapping for the point 

186 dcc_control.map_dcc_point (objects_common.schematic_objects[object_id]["itemid"], 

187 objects_common.schematic_objects[object_id]["dccaddress"], 

188 objects_common.schematic_objects[object_id]["dccreversed"]) 

189 # Turn the point type value back into the required enumeration type 

190 point_type = points.point_type(objects_common.schematic_objects[object_id]["itemtype"]) 

191 # Create the new point object 

192 canvas_tags = points.create_point ( 

193 canvas = objects_common.canvas, 

194 point_id = objects_common.schematic_objects[object_id]["itemid"], 

195 pointtype = point_type, 

196 x = objects_common.schematic_objects[object_id]["posx"], 

197 y = objects_common.schematic_objects[object_id]["posy"], 

198 callback = run_layout.schematic_callback, 

199 colour = objects_common.schematic_objects[object_id]["colour"], 

200 orientation = objects_common.schematic_objects[object_id]["orientation"], 

201 also_switch = objects_common.schematic_objects[object_id]["alsoswitch"], 

202 reverse = objects_common.schematic_objects[object_id]["reverse"], 

203 auto = objects_common.schematic_objects[object_id]["automatic"], 

204 fpl = objects_common.schematic_objects[object_id]["hasfpl"]) 

205 # Create/update the canvas "tags" and selection rectangle for the point 

206 objects_common.schematic_objects[object_id]["tags"] = canvas_tags 

207 objects_common.set_bbox (object_id, objects_common.canvas.bbox(canvas_tags)) 

208 return() 

209 

210#------------------------------------------------------------------------------------ 

211# Function to Create a new default Point (and draw it on the canvas) 

212#------------------------------------------------------------------------------------ 

213 

214def create_point(item_type): 

215 # Generate a new object from the default configuration with a new UUID  

216 object_id = str(uuid.uuid4()) 

217 objects_common.schematic_objects[object_id] = copy.deepcopy(default_point_object) 

218 # Find the initial canvas position for the new object and assign the item ID 

219 x, y = objects_common.find_initial_canvas_position() 

220 item_id = objects_common.new_item_id(exists_function=points.point_exists) 

221 # Add the specific elements for this particular instance of the point 

222 objects_common.schematic_objects[object_id]["itemid"] = item_id 

223 objects_common.schematic_objects[object_id]["itemtype"] = item_type 

224 objects_common.schematic_objects[object_id]["posx"] = x 

225 objects_common.schematic_objects[object_id]["posy"] = y 

226 # Add the new object to the index of points 

227 objects_common.point_index[str(item_id)] = object_id 

228 # Draw the object on the canvas 

229 redraw_point_object(object_id) 

230 return(object_id) 

231 

232#------------------------------------------------------------------------------------ 

233# Function to paste a copy of an existing point - returns the new Object ID 

234# Note that only the basic point configuration is used. Underlying configuration 

235# such as signal interlocking, dcc addresses etc is set back to the default 

236# values as it will need to be configured specific to the new point 

237#------------------------------------------------------------------------------------ 

238 

239def paste_point(object_to_paste, deltax:int, deltay:int): 

240 # Create a new UUID for the pasted object 

241 new_object_id = str(uuid.uuid4()) 

242 objects_common.schematic_objects[new_object_id] = copy.deepcopy(object_to_paste) 

243 # Assign a new type-specific ID for the object and add to the index 

244 new_id = objects_common.new_item_id(exists_function=points.point_exists) 

245 objects_common.schematic_objects[new_object_id]["itemid"] = new_id 

246 objects_common.point_index[str(new_id)] = new_object_id 

247 # Set the position for the "pasted" object (offset from the original position) 

248 objects_common.schematic_objects[new_object_id]["posx"] += deltax 

249 objects_common.schematic_objects[new_object_id]["posy"] += deltay 

250 # Now set the default values for all elements we don't want to copy: 

251 objects_common.schematic_objects[new_object_id]["alsoswitch"] = default_point_object["alsoswitch"] 

252 objects_common.schematic_objects[new_object_id]["dccaddress"] = default_point_object["dccaddress"] 

253 objects_common.schematic_objects[new_object_id]["dccreversed"] = default_point_object["dccreversed"] 

254 objects_common.schematic_objects[new_object_id]["siginterlock"] = default_point_object["siginterlock"] 

255 # Set the Boundary box for the new object to None so it gets created on re-draw 

256 objects_common.schematic_objects[new_object_id]["bbox"] = None 

257 # Create/draw the new object on the canvas 

258 redraw_point_object(new_object_id) 

259 return(new_object_id) 

260 

261#------------------------------------------------------------------------------------ 

262# Function to "soft delete" the point object from the canvas together with any accociated 

263# dcc mapping. Primarily used to delete the point in its current configuration prior to 

264# re-creating in its new configuration - also used as part of a hard delete (below) 

265#------------------------------------------------------------------------------------ 

266 

267def delete_point_object(object_id): 

268 # Delete the point drawing objects and associated DCC mapping 

269 points.delete_point(objects_common.schematic_objects[object_id]["itemid"]) 

270 dcc_control.delete_point_mapping(objects_common.schematic_objects[object_id]["itemid"]) 

271 return() 

272 

273#------------------------------------------------------------------------------------ 

274# Function to 'hard delete' a point (drawing objects, DCC mappings, and the main 

275# dictionary entry). Function called when object is deleted from the schematic. 

276#------------------------------------------------------------------------------------ 

277 

278def delete_point(object_id): 

279 # Soft delete the associated library objects from the canvas 

280 delete_point_object(object_id) 

281 # Remove any references to the point from other points ('also switch' points). 

282 remove_references_to_point(objects_common.schematic_objects[object_id]["itemid"]) 

283 # Remove any references to the point from the signal / track sensor tables 

284 objects_signals.remove_references_to_point(objects_common.schematic_objects[object_id]["itemid"]) 

285 objects_sensors.remove_references_to_point(objects_common.schematic_objects[object_id]["itemid"]) 

286 # "Hard Delete" the selected object - deleting the boundary box rectangle and deleting 

287 # the object from the dictionary of schematic objects (and associated dictionary keys) 

288 objects_common.canvas.delete(objects_common.schematic_objects[object_id]["bbox"]) 

289 del objects_common.point_index[str(objects_common.schematic_objects[object_id]["itemid"])] 

290 del objects_common.schematic_objects[object_id] 

291 return() 

292 

293####################################################################################