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

316 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-10 15:08 +0100

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

2# This module contains all the functions for managing layout objects. This is 

3# effectively the "top-level" objects module (with all public API functions) 

4#------------------------------------------------------------------------------------ 

5# 

6# External API functions / objects intended for use by other editor modules: 

7# save_schematic_state(reset_pointer=False) - save the current snapshot ('load' or 'new') 

8# undo() / redo() - Undo and re-do functions as you would expect 

9# set_all(new_objects) - Creates a new dictionary of objects (following a load) 

10# get_all() - returns the current dictionary of objects (for saving to file) 

11# create_object(obj_type, item_type, item_subtype) - create a new object on the canvas 

12# delete_objects(list of obj IDs) - Delete the selected objects from the canvas 

13# rotate_objects(list of obj IDs) - Rotate the selected objects on the canvas 

14# move_objects(list of obj IDs) - Finalises the move of selected objects 

15# copy_objects(list of obj IDs) - Copy the selected objects to the clipboard 

16# paste_objects() - Paste Clipboard objects onto the canvas (returnslist of new IDs) 

17# update_object(object ID, new_object) - update the config of an existing object 

18# reset_objects() - resets all points, signals, instruments and sections to default state 

19# 

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

21# run_layout.initialise_layout() - Re-initiallise the state of schematic objects following a change 

22# run_layout.enable_editing() - To set "edit mode" for processing schematic object callbacks 

23# run_layout.disable_editing() - To set "edit mode" for processing schematic object callbacks 

24# objects_instruments.create_instrument(type) - Create a default object on the schematic 

25# objects_instruments.delete_instrument(object_id) - Hard Delete an object when deleted from the schematic 

26# objects_instruments.update_instrument(obj_id,new_obj) - Update the configuration of an existing instrument object 

27# objects_instruments.paste_instrument(object) - Paste a copy of an object to create a new one (returns new object_id) 

28# objects_instruments.delete_instrument_object(object_id) - Soft delete the drawing object (prior to recreating)) 

29# objects_instruments.redraw_instrument_object(object_id) - Redraw the object on the canvas following an update 

30# objects_instruments.default_instrument_object - The dictionary of default values for the object 

31# objects_lines.create_line() - Create a default object on the schematic 

32# objects_lines.delete_line(object_id) - Hard Delete an object when deleted from the schematic 

33# objects_lines.update_line(obj_id,new_obj) - Update the configuration of an existing line object 

34# objects_lines.paste_line(object) - Paste a copy of an object to create a new one (returns new object_id) 

35# objects_lines.delete_line_object(object_id) - Soft delete the drawing object (prior to recreating)) 

36# objects_lines.redraw_line_object(object_id) - Redraw the object on the canvas following an update 

37# objects_lines.default_line_object - The dictionary of default values for the object 

38# objects_textboxes.create_textbox() - Create a default object on the schematic 

39# objects_textboxes.delete_textbox(object_id) - Hard Delete an object when deleted from the schematic 

40# objects_textboxes.update_textbox(obj_id,new_obj) - Update the configuration of an existing textbox object 

41# objects_textboxes.paste_textbox(object) - Paste a copy of an object to create a new one (returns new object_id) 

42# objects_textboxes.delete_textbox_object(object_id) - Soft delete the drawing object (prior to recreating)) 

43# objects_textboxes.redraw_textbox_object(object_id) - Redraw the object on the canvas following an update 

44# objects_textboxes.default_textbox_object - The dictionary of default values for the object 

45# objects_points.create_point(type) - Create a default object on the schematic 

46# objects_points.delete_point(obj_id) - Hard Delete an object when deleted from the schematic 

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

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

49# objects_points.delete_point_object(object_id) - Soft delete the drawing object (prior to recreating) 

50# objects_points.redraw_point_object(object_id) - Redraw the object on the canvas following an update 

51# objects_points.default_point_object - The dictionary of default values for the object 

52# objects_points.reset_point_interlocking_tables() - recalculates interlocking tables from scratch 

53# objects_sections.create_section(type) - Create a default object on the schematic 

54# objects_sections.delete_section(object_id) - Hard Delete an object when deleted from the schematic 

55# objects_sections.update_section(obj_id,new_obj) - Update the configuration of an existing section object 

56# objects_sections.paste_section(object) - Paste a copy of an object to create a new one (returns new object_id) 

57# objects_sections.delete_section_object(object_id) - Soft delete the drawing object (prior to recreating)) 

58# objects_sections.redraw_section_object(object_id) - Redraw the object on the canvas following an update 

59# objects_sections.default_section_object - The dictionary of default values for the object 

60# objects_sections.enable_editing() - Called when 'Edit' Mode is selected (from Schematic Module) 

61# objects_sections.disable_editing() - Called when 'Run' Mode is selected (from Schematic Module) 

62# objects_signals.create_signal(type,subtype) - Create a default object on the schematic 

63# objects_signals.delete_signal(object_id) - Hard Delete an object when deleted from the schematic 

64# objects_signals.update_signal(obj_id,new_obj) - Update the configuration of an existing signal object 

65# objects_signals.paste_signal(object) - Paste a copy of an object to create a new one (returns new object_id) 

66# objects_signals.delete_signal_object(object_id) - soft delete the drawing object (prior to recreating) 

67# objects_signals.redraw_signal_object(object_id) - Redraw the object on the canvas following an update 

68# objects_signals.default_signal_object - The dictionary of default values for the object 

69# 

70#------------------------------------------------------------------------------------ 

71 

72import copy 

73import logging 

74 

75from . import objects_common 

76from . import objects_signals 

77from . import objects_points 

78from . import objects_lines 

79from . import objects_sections 

80from . import objects_instruments 

81from . import objects_textboxes 

82from . import objects_sensors 

83 

84from .. import run_layout 

85 

86####################################################################################################### 

87### Handle change of sensors being a configuration item in their own right from release 3.6.0 ######### 

88####################################################################################################### 

89from .. import settings 

90####################################################################################################### 

91################################## End of code to handle breaking changes ############################# 

92####################################################################################################### 

93 

94#------------------------------------------------------------------------------------ 

95# Internal function to bring all track sections to the front of the canvas 

96# This insures they are not obscured by any lines drawn on the canvas 

97#------------------------------------------------------------------------------------ 

98 

99def bring_track_sections_to_the_front(): 

100 for object_id in objects_common.schematic_objects: 

101 if objects_common.schematic_objects[object_id]["item"] == objects_common.object_type.section: 

102 objects_common.canvas.tag_raise(objects_common.schematic_objects[object_id]["tags"]) 

103 return() 

104 

105#------------------------------------------------------------------------------------ 

106# Internal Function to set (re-create) all schematic objects 

107# Called following a file load or re-drawing for undo/redo 

108#------------------------------------------------------------------------------------ 

109 

110def redraw_all_objects(create_new_bbox:bool, reset_state:bool): 

111 for object_id in objects_common.schematic_objects: 

112 # Set the bbox reference to none so it will be created on redraw 

113 if create_new_bbox: objects_common.schematic_objects[object_id]["bbox"] = None 

114 this_object_type = objects_common.schematic_objects[object_id]["item"] 

115 if this_object_type == objects_common.object_type.line: 

116 objects_lines.redraw_line_object(object_id) 

117 elif this_object_type == objects_common.object_type.textbox: 

118 objects_textboxes.redraw_textbox_object(object_id) 

119 elif this_object_type == objects_common.object_type.signal: 

120 objects_signals.redraw_signal_object(object_id) 

121 elif this_object_type == objects_common.object_type.point: 

122 objects_points.redraw_point_object(object_id) 

123 elif this_object_type == objects_common.object_type.section: 

124 objects_sections.redraw_section_object(object_id, reset_state=reset_state) 

125 elif this_object_type == objects_common.object_type.instrument: 

126 objects_instruments.redraw_instrument_object(object_id) 

127 elif this_object_type == objects_common.object_type.track_sensor: 127 ↛ 111line 127 didn't jump to line 111, because the condition on line 127 was never false

128 objects_sensors.redraw_track_sensor_object(object_id) 

129 # Ensure all track sections are brought forward on the schematic (in front of any lines) 

130 bring_track_sections_to_the_front() 

131 return() 

132 

133#------------------------------------------------------------------------------------ 

134# Internal function to reset all item-specific indexes from the main schematic_objects 

135# dictionary - called following item load and as part of undo/redo. Note that for 

136# both of these cases, all existing entries will have been deleted when all schematic 

137# objects were selected then deleted as part of the undo/redo or load layout 

138#------------------------------------------------------------------------------------ 

139 

140def reset_all_schematic_indexes(): 

141 for object_id in objects_common.schematic_objects: 

142 this_object_type = objects_common.schematic_objects[object_id]["item"] 

143 this_object_item_id = objects_common.schematic_objects[object_id]["itemid"] 

144 if this_object_type == objects_common.object_type.line: 

145 objects_common.line_index[str(this_object_item_id)] = object_id 

146 elif this_object_type == objects_common.object_type.signal: 

147 objects_common.signal_index[str(this_object_item_id)] = object_id 

148 elif this_object_type == objects_common.object_type.point: 

149 objects_common.point_index[str(this_object_item_id)] = object_id 

150 elif this_object_type == objects_common.object_type.section: 

151 objects_common.section_index[str(this_object_item_id)] = object_id 

152 elif this_object_type == objects_common.object_type.instrument: 

153 objects_common.instrument_index[str(this_object_item_id)] = object_id 

154 elif this_object_type == objects_common.object_type.track_sensor: 

155 objects_common.track_sensor_index[str(this_object_item_id)] = object_id 

156 # Note that textboxes don't have an index as we don't track their IDs 

157 return() 

158 

159#------------------------------------------------------------------------------------ 

160# Undo and redo functions - the 'save_schematic_state' function should be called after 

161# every change the schematic or a change to any object on the schematic to take a snapshot 

162# and add this to the undo buffer. 'undo' and 'redo' then work as you'd expect 

163# 'restore_schematic_state' is the internal function used by 'undo' and 'redo' 

164#------------------------------------------------------------------------------------ 

165 

166undo_buffer = [{}] 

167undo_pointer = 0 

168 

169def save_schematic_state(reset_pointer:bool=False): 

170 global undo_buffer 

171 global undo_pointer 

172 # The undo buffer is reset following 'layout load' or 'new layout' 

173 if reset_pointer: undo_pointer = 0 

174 else:undo_pointer = undo_pointer + 1 

175 # If the undo pointer isn't at the end of the undo buffer when a change is made 

176 # then we need to clear everything from the undo buffer forward of this point 

177 if len(undo_buffer) > undo_pointer: 

178 undo_buffer = undo_buffer[:undo_pointer] 

179 undo_buffer.append({}) 

180 # Save a snapshot of all schematic objects - I had a few issues with copy and 

181 # deepcopy not working as I was expecting but copying one object at a time works 

182 snapshot_objects = objects_common.schematic_objects 

183 for object_id in snapshot_objects: 

184 undo_buffer[undo_pointer][object_id] = copy.deepcopy(snapshot_objects[object_id]) 

185 return() 

186 

187def undo(): 

188 global undo_pointer 

189 if undo_pointer > 0: 

190 undo_pointer = undo_pointer - 1 

191 restore_schematic_state() 

192 return() 

193 

194def redo(): 

195 global undo_pointer 

196 if undo_pointer < len(undo_buffer)-1: 

197 undo_pointer = undo_pointer + 1 

198 restore_schematic_state() 

199 return() 

200 

201def restore_schematic_state(): 

202 global undo_pointer 

203 # Delete all current objects gracefully. We create a list of objects to delete rather than 

204 # just iterating through the main dictionary otherwise the dict would disappear from underneath 

205 objects_to_delete = [] 

206 for object_id in objects_common.schematic_objects: 

207 objects_to_delete.append(object_id) 

208 for object_id in objects_to_delete: 

209 delete_object(object_id) 

210 # Restore the main schematic object dictionary from the snapshot - I had a few issues with 

211 # copy and deepcopy not working as I was expecting but copying one object at a time works 

212 snapshot_objects = undo_buffer[undo_pointer] 

213 for object_id in snapshot_objects: 

214 objects_common.schematic_objects[object_id] = copy.deepcopy(snapshot_objects[object_id]) 

215 # Set the seperate schematic dictionary indexes from the restored schematic objects dict 

216 reset_all_schematic_indexes() 

217 # Re-draw all objects, ensuring a new bbox is created for each object 

218 redraw_all_objects(create_new_bbox=True, reset_state=False) 

219 # Recalculate instrument interlocking tables as a 'belt and braces' measure (on the  

220 # basis they would have successfully been restored with the rest of the snapshot) 

221 objects_points.reset_point_interlocking_tables() 

222 return() 

223 

224#------------------------------------------------------------------------------------ 

225# Functions to Enable and disable editing  

226#------------------------------------------------------------------------------------ 

227 

228def enable_editing(): 

229 objects_sections.enable_editing() 

230 run_layout.enable_editing() 

231 return() 

232 

233def disable_editing(): 

234 objects_sections.disable_editing() 

235 run_layout.disable_editing() 

236 return() 

237 

238#------------------------------------------------------------------------------------ 

239# Function to reset the schematic back to its default state with all signals 'on', 

240# all points 'unswitched', all track sections 'unoccupied' and all block instruments 

241# showing 'line blocked' (by soft deleting all objects and redrawing them) 

242#------------------------------------------------------------------------------------ 

243 

244def reset_objects(): 

245 # Soft delete all point, section, instrument and signal objects (keeping the bbox) 

246 for object_id in objects_common.schematic_objects: 

247 type_of_object = objects_common.schematic_objects[object_id]["item"] 

248 if type_of_object == objects_common.object_type.line: 

249 objects_lines.delete_line_object(object_id) 

250 elif type_of_object == objects_common.object_type.textbox: 

251 objects_textboxes.delete_textbox_object(object_id) 

252 elif type_of_object == objects_common.object_type.signal: 

253 objects_signals.delete_signal_object(object_id) 

254 elif type_of_object == objects_common.object_type.point: 

255 objects_points.delete_point_object(object_id) 

256 elif type_of_object == objects_common.object_type.section: 

257 objects_sections.delete_section_object(object_id) 

258 elif type_of_object == objects_common.object_type.instrument: 

259 objects_instruments.delete_instrument_object(object_id) 

260 elif type_of_object == objects_common.object_type.track_sensor: 260 ↛ 246line 260 didn't jump to line 246, because the condition on line 260 was never false

261 objects_sensors.delete_track_sensor_object(object_id) 

262 # Redraw all point, section, instrument and signal objects in their default state 

263 # We don't need to create a new bbox as soft_delete keeps the tkinter object 

264 redraw_all_objects(create_new_bbox=False, reset_state=True) 

265 # Ensure all track sections are brought forward on the schematic (in front of any lines) 

266 bring_track_sections_to_the_front() 

267 # Process any layout changes (interlocking, signal ahead etc) 

268 # that might be dependent on the object configuration change 

269 run_layout.initialise_layout() 

270 return() 

271 

272#------------------------------------------------------------------------------------ 

273# Function to Create a new schematic object and draw it on the canvas 

274# Called from the Schematic Module when an "add object" button is clicked 

275#------------------------------------------------------------------------------------ 

276 

277def create_object(new_object_type, item_type=None, item_subtype=None): 

278 if new_object_type == objects_common.object_type.line: 

279 object_id = objects_lines.create_line() 

280 elif new_object_type == objects_common.object_type.textbox: 

281 object_id = objects_textboxes.create_textbox() 

282 elif new_object_type == objects_common.object_type.signal: 

283 object_id = objects_signals.create_signal(item_type, item_subtype) 

284 elif new_object_type == objects_common.object_type.point: 

285 object_id = objects_points.create_point(item_type) 

286 elif new_object_type == objects_common.object_type.section: 

287 object_id = objects_sections.create_section() 

288 elif new_object_type == objects_common.object_type.instrument: 

289 object_id = objects_instruments.create_instrument(item_type) 

290 elif new_object_type == objects_common.object_type.track_sensor: 290 ↛ 293line 290 didn't jump to line 293, because the condition on line 290 was never false

291 object_id = objects_sensors.create_track_sensor() 

292 else: 

293 object_id = None 

294 # save the current state (for undo/redo) 

295 save_schematic_state() 

296 # As we are creating 'new' objects we don't need to process layout changes 

297 return(object_id) 

298 

299#------------------------------------------------------------------------------------ 

300# Function to update the configuration of an existing schematic object and re-draw it 

301# in its new configuration (delete the drawing objects then re-draw in the new configuration) 

302#------------------------------------------------------------------------------------ 

303 

304def update_object(object_id, new_object): 

305 type_of_object = objects_common.schematic_objects[object_id]["item"] 

306 if type_of_object == objects_common.object_type.line: 

307 objects_lines.update_line(object_id, new_object) 

308 elif type_of_object == objects_common.object_type.textbox: 

309 objects_textboxes.update_textbox(object_id, new_object) 

310 elif type_of_object == objects_common.object_type.signal: 

311 objects_signals.update_signal(object_id, new_object) 

312 elif type_of_object == objects_common.object_type.point: 

313 objects_points.update_point(object_id, new_object) 

314 elif type_of_object == objects_common.object_type.section: 

315 objects_sections.update_section(object_id, new_object) 

316 elif type_of_object == objects_common.object_type.instrument: 

317 objects_instruments.update_instrument(object_id, new_object) 

318 elif type_of_object == objects_common.object_type.track_sensor: 318 ↛ 321line 318 didn't jump to line 321, because the condition on line 318 was never false

319 objects_sensors.update_track_sensor(object_id, new_object) 

320 # Ensure all track sections are brought forward on the schematic (in front of any lines) 

321 bring_track_sections_to_the_front() 

322 # save the current state (for undo/redo) 

323 save_schematic_state() 

324 # Process any layout changes (interlocking, signal ahead etc) 

325 # that might be dependent on the object configuration change 

326 run_layout.initialise_layout() 

327 return() 

328 

329#------------------------------------------------------------------------------------ 

330# Common Function to permanently Delete an objects from the schematic 

331# Called from the delete_objects and also the undo/redo functions 

332#------------------------------------------------------------------------------------ 

333 

334def delete_object(object_id): 

335 type_of_object = objects_common.schematic_objects[object_id]["item"] 

336 if type_of_object == objects_common.object_type.line: 

337 objects_lines.delete_line(object_id) 

338 elif type_of_object == objects_common.object_type.textbox: 

339 objects_textboxes.delete_textbox(object_id) 

340 elif type_of_object == objects_common.object_type.signal: 

341 objects_signals.delete_signal(object_id) 

342 elif type_of_object == objects_common.object_type.point: 

343 objects_points.delete_point(object_id) 

344 elif type_of_object == objects_common.object_type.section: 

345 objects_sections.delete_section(object_id) 

346 elif type_of_object == objects_common.object_type.instrument: 

347 objects_instruments.delete_instrument(object_id) 

348 elif type_of_object == objects_common.object_type.track_sensor: 348 ↛ 350line 348 didn't jump to line 350, because the condition on line 348 was never false

349 objects_sensors.delete_track_sensor(object_id) 

350 return() 

351 

352#------------------------------------------------------------------------------------ 

353# Function to permanently Delete one or more objects from the schematic 

354# Called from the Schematic Module when selected objects are deleted 

355#------------------------------------------------------------------------------------ 

356 

357def delete_objects(list_of_object_ids, initialise_layout_after_delete:bool=True): 

358 for object_id in list_of_object_ids: 

359 delete_object(object_id) 

360 # save the current state (for undo/redo) 

361 save_schematic_state() 

362 # Process any layout changes (interlocking, signal ahead etc) 

363 # that might need to change following objet deletion 

364 if initialise_layout_after_delete: run_layout.initialise_layout() 

365 return() 

366 

367#------------------------------------------------------------------------------------ 

368# Function to Rotate one or more objects on the schematic 

369# Called from the Schematic Module when selected objects are rotated 

370# Only Points and Signals can be rotated - all other objects are unchanged 

371#------------------------------------------------------------------------------------ 

372 

373def rotate_objects(list_of_object_ids): 

374 # Note that we do all deletions prior to re-drawing as tkinter doesn't seem to like 

375 # processing a load of intermixed deletes/creates when it returns to the main loop 

376 for object_id in list_of_object_ids: 

377 type_of_object = objects_common.schematic_objects[object_id]["item"] 

378 # Delete the drawing objects from the canvas 

379 if type_of_object == objects_common.object_type.signal: objects_signals.delete_signal_object(object_id) 

380 elif type_of_object == objects_common.object_type.point: objects_points.delete_point_object(object_id) 

381 # Re-draw the drawing objects on the canvas in their new position 

382 for object_id in list_of_object_ids: 

383 type_of_object = objects_common.schematic_objects[object_id]["item"] 

384 if type_of_object in (objects_common.object_type.signal,objects_common.object_type.point): 

385 # Work out the orientation change based on the current orientation 

386 orientation = objects_common.schematic_objects[object_id]["orientation"] 

387 if orientation == 0: objects_common.schematic_objects[object_id]["orientation"] = 180 

388 else: objects_common.schematic_objects[object_id]["orientation"] = 0 

389 if type_of_object == objects_common.object_type.signal: objects_signals.redraw_signal_object(object_id) 

390 elif type_of_object == objects_common.object_type.point: objects_points.redraw_point_object(object_id) 

391 # save the current state (for undo/redo) 

392 save_schematic_state() 

393 # As we are just rotating objects we don't need to process layout changes 

394 return() 

395 

396#------------------------------------------------------------------------------------ 

397# Function to finalise the move of selected objects on the schematic. The objects 

398# themselves will have already been moved on the canvas and snapped to the grid 

399# so we just need to update the object configuration to reflect the new positions. 

400# Note that the function also caters for the editing of lines - in this case we will 

401# be given a single object id and either the xdiff1/ydiff1 or xdiff2/ydiff2 will be 

402# passed to signify which end of the line needs to be updated 

403#------------------------------------------------------------------------------------ 

404 

405def move_objects(list_of_object_ids, xdiff1:int=None, 

406 ydiff1:int=None, xdiff2:int=None, ydiff2:int=None): 

407 # Only bother processing the update if there has been a change 

408 if ( (xdiff1 is not None and xdiff1 !=0) or (ydiff1 is not None and ydiff1 !=0) or 

409 (xdiff2 is not None and xdiff2 !=0) or (ydiff2 is not None and ydiff2 !=0) ): 

410 for object_id in list_of_object_ids: 

411 type_of_object = objects_common.schematic_objects[object_id]["item"] 

412 if type_of_object == objects_common.object_type.line: 

413 if xdiff1 is not None and ydiff1 is not None: 

414 objects_common.schematic_objects[object_id]["posx"] += xdiff1 

415 objects_common.schematic_objects[object_id]["posy"] += ydiff1 

416 if xdiff2 is not None and ydiff2 is not None: 

417 objects_common.schematic_objects[object_id]["endx"] += xdiff2 

418 objects_common.schematic_objects[object_id]["endy"] += ydiff2 

419 # Update the boundary box to reflect the new line position 

420 objects_common.set_bbox(object_id,objects_common.canvas.bbox 

421 (objects_common.schematic_objects[object_id]["line"])) 

422 else: 

423 objects_common.schematic_objects[object_id]["posx"] += xdiff1 

424 objects_common.schematic_objects[object_id]["posy"] += ydiff1 

425 # Ensure all track sections are in front of any lines 

426 bring_track_sections_to_the_front() 

427 # save the current state (for undo/redo) 

428 save_schematic_state() 

429 # As we are just moving objects we don't need to process layout changes 

430 return() 

431 

432#------------------------------------------------------------------------------------ 

433# Function to Copy one or more objects on the schematic to the clipboard 

434# Called from the Schematic Module when selected objects are copied 

435#------------------------------------------------------------------------------------ 

436 

437clipboard=[] 

438 

439def copy_objects(list_of_object_ids): 

440 global clipboard 

441 clipboard=[] 

442 for object_id in list_of_object_ids: 

443 # Take a deep copy of the object and add to the clipboard 

444 clipboard.append(copy.deepcopy(objects_common.schematic_objects[object_id])) 

445 return() 

446 

447#------------------------------------------------------------------------------------ 

448# Function to paste copies of the current clipboard objects to the canvas 

449# Called from the Schematic Modulee on 'paste' - returns a list of new object_ids 

450# Note that the object_ids, item_ids and canvas positions are reassigned on 'paste' 

451#------------------------------------------------------------------------------------ 

452 

453def paste_objects(): 

454 list_of_new_object_ids=[] 

455 # New objects are "pasted" at a slightly offset position on the canvas 

456 deltax, deltay = objects_common.canvas_grid, objects_common.canvas_grid 

457 # Create a copy of each object in the clipboard (depending on type) 

458 for object_to_paste in clipboard: 

459 type_of_object = object_to_paste["item"] 

460 if type_of_object == objects_common.object_type.line: 

461 new_object_id = objects_lines.paste_line(object_to_paste, deltax, deltay) 

462 elif type_of_object == objects_common.object_type.textbox: 

463 new_object_id = objects_textboxes.paste_textbox(object_to_paste, deltax, deltay) 

464 elif type_of_object == objects_common.object_type.signal: 

465 new_object_id = objects_signals.paste_signal(object_to_paste, deltax, deltay) 

466 elif type_of_object == objects_common.object_type.point: 

467 new_object_id = objects_points.paste_point(object_to_paste, deltax, deltay) 

468 elif type_of_object == objects_common.object_type.section: 

469 new_object_id = objects_sections.paste_section(object_to_paste, deltax, deltay) 

470 elif type_of_object == objects_common.object_type.instrument: 

471 new_object_id = objects_instruments.paste_instrument(object_to_paste, deltax, deltay) 

472 elif type_of_object == objects_common.object_type.track_sensor: 472 ↛ 476line 472 didn't jump to line 476, because the condition on line 472 was never false

473 new_object_id = objects_sensors.paste_track_sensor(object_to_paste, deltax, deltay) 

474 # Add the new object to the list of clipboard objects 

475 # in case the user wants to paste the same objects again 

476 list_of_new_object_ids.append(new_object_id) 

477 # Ensure all track sections are in front of any lines 

478 bring_track_sections_to_the_front() 

479 # save the current state (for undo/redo) 

480 save_schematic_state() 

481 # As we are just pasting 'new' objects we don't need to process layout changes 

482 return(list_of_new_object_ids) 

483 

484#------------------------------------------------------------------------------------ 

485# Function to set (re-create) all schematic objects (following a file load) 

486# Note that there is a dependancy that the main schematic objects dict is empty 

487# i.e. any legacy objects existing prior to the load will have been deleted first 

488#------------------------------------------------------------------------------------ 

489 

490def set_all(new_objects): 

491 ################################################################################## 

492 ### Code block to Handle breaking changes - see later in the code for details #### 

493 ################################################################################## 

494 list_of_track_sensors_to_create =[] 

495 ################################################################################## 

496 ################ End of code block to handle breaking changes #################### 

497 ################################################################################## 

498 # For each loaded object, create a new default object of the same type 

499 # and then copy across each element in turn. This is defensive programming 

500 # to populate the objects gracefully whilst handling changes to an object 

501 # structre (e.g. new element introduced since the file was last saved) 

502 for object_id in new_objects: 

503 # Get the item id of the new object - for error reporting 

504 # The 'item' and 'itemid' elements are MANDATORY for all objects  

505 item_id = str(new_objects[object_id]["itemid"]) 

506 # Set the type-specific default object 

507 new_object_type = new_objects[object_id]["item"] 

508 if new_object_type == objects_common.object_type.line: 

509 default_object = objects_lines.default_line_object 

510 elif new_object_type == objects_common.object_type.textbox: 

511 default_object = objects_textboxes.default_textbox_object 

512 elif new_object_type == objects_common.object_type.signal: 

513 default_object = objects_signals.default_signal_object 

514 elif new_object_type == objects_common.object_type.point: 

515 default_object = objects_points.default_point_object 

516 elif new_object_type == objects_common.object_type.section: 

517 default_object = objects_sections.default_section_object 

518 elif new_object_type == objects_common.object_type.instrument: 

519 default_object = objects_instruments.default_instrument_object 

520 elif new_object_type == objects_common.object_type.track_sensor: 

521 default_object = objects_sensors.default_track_sensor_object 

522 else: 

523 default_object = {} 

524 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ 

525 " - Unrecognised object type - DISCARDED") 

526 # Populate each element at a time and report any elements not recognised 

527 if default_object != {}: 

528 objects_common.schematic_objects[object_id] = copy.deepcopy(default_object) 

529 for element in new_objects[object_id]: 

530 if element not in default_object.keys(): 

531 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ 

532 " - Unexpected element: '"+element+"' - DISCARDED") 

533 ################################################################################################# 

534 ## Handle breaking change of tracksections now a list of 3 sections from release 4.0.0 ########## 

535 ## The 'tracksections' element is a list of [section_behind, sections_ahead] #################### 

536 ## The sections_ahead element is a list of the available signal routes [MAIN,LH1,LH2,RH1,RH2] ### 

537 ## Before release 4.0.0, each route element was a single track section (integer value) ########## 

538 ## From Release 4.0.0 onwards, each element comprises a list of track sections [T1, T2, T3] ##### 

539 ################################################################################################# 

540 elif new_object_type == objects_common.object_type.signal and element == "tracksections": 

541 objects_common.schematic_objects[object_id][element][0] = new_objects[object_id][element][0] 

542 if type(new_objects[object_id][element][1][0]) == int: 542 ↛ 543line 542 didn't jump to line 543, because the condition on line 542 was never true

543 for index, route in enumerate(new_objects[object_id][element][1]): 

544 list_of_sections = [new_objects[object_id][element][1][index],0,0] 

545 objects_common.schematic_objects[object_id][element][1][index] = list_of_sections 

546 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ 

547 " - Handling version 4.0.0 breaking change to : '"+element+"'") 

548 else: 

549 objects_common.schematic_objects[object_id][element][1] = new_objects[object_id][element][1] 

550 ################################################################################## 

551 ### Handle change of sensor IDs being strings from Release 3.6.0 onwards ######### 

552 ### This is something we can resolve without affecting the user so we resolve #### 

553 ### it silently without an Log message or load warning message - unless the ###### 

554 ### track sensors - in which case we need to list them for the user to resolve ### 

555 ################################################################################## 

556 elif new_object_type == objects_common.object_type.signal and element == "passedsensor": 

557 objects_common.schematic_objects[object_id][element][0] = new_objects[object_id][element][0] 

558 if new_objects[object_id][element][1] == 0: 558 ↛ 559line 558 didn't jump to line 559, because the condition on line 558 was never true

559 objects_common.schematic_objects[object_id][element][1] = "" 

560 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ 

561 " - Handling version 3.6.0 breaking change to : '"+element+"'") 

562 elif isinstance(new_objects[object_id][element][1],int): 562 ↛ 563line 562 didn't jump to line 563, because the condition on line 562 was never true

563 objects_common.schematic_objects[object_id][element][1] = str(new_objects[object_id][element][1]) 

564 list_of_track_sensors_to_create.append([new_objects[object_id][element][1],new_objects[object_id][element][1]]) 

565 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ 

566 " - Handling version 3.6.0 breaking change to : '"+element+"'") 

567 else: 

568 objects_common.schematic_objects[object_id][element] = new_objects[object_id][element] 

569 elif new_object_type == objects_common.object_type.signal and element == "approachsensor": 

570 objects_common.schematic_objects[object_id][element][0] = new_objects[object_id][element][0] 

571 if new_objects[object_id][element][1] == 0: 571 ↛ 572line 571 didn't jump to line 572, because the condition on line 571 was never true

572 objects_common.schematic_objects[object_id][element][1] = "" 

573 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ 

574 " - Handling version 3.6.0 breaking change to : '"+element+"'") 

575 elif isinstance(new_objects[object_id][element][1],int): 575 ↛ 576line 575 didn't jump to line 576, because the condition on line 575 was never true

576 objects_common.schematic_objects[object_id][element][1] = str(new_objects[object_id][element][1]) 

577 list_of_track_sensors_to_create.append([new_objects[object_id][element][1],new_objects[object_id][element][1]]) 

578 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+ 

579 " - Handling version 3.6.0 breaking change to : '"+element+"'") 

580 else: 

581 objects_common.schematic_objects[object_id][element] = new_objects[object_id][element] 

582 ############################################################################################# 

583 ## End of Handle breaking change for track sections and sensor IDs ########################## 

584 ############################################################################################# 

585 ######################################################################################################## 

586 ## Handle bugfix for Signal point interlocking tables (i.e. length point list was wrongly getting 7 #### 

587 ## points assigned on point deletion whereas the list should only ever include 6 points ################  

588 ######################################################################################################## 

589 elif new_object_type == objects_common.object_type.signal and element == "pointinterlock": 

590 for index, route in enumerate (new_objects[object_id][element]): 

591 objects_common.schematic_objects[object_id][element][index][0] = route[0][0:6] 

592 objects_common.schematic_objects[object_id][element][index][1] = route[1] 

593 objects_common.schematic_objects[object_id][element][index][2] = route[2] 

594 ######################################################################################################## 

595 ## End of Handle bugfix for Signal point interlocking tables ########################################### 

596 ######################################################################################################## 

597 else: 

598 objects_common.schematic_objects[object_id][element] = new_objects[object_id][element] 

599 ################################################################################## 

600 ### Handle change of sensor IDs being strings from Release 3.6.0 onwards ######### 

601 ################################################################################## 

602 if len(list_of_track_sensors_to_create) > 0: 602 ↛ 603line 602 didn't jump to line 603, because the condition on line 602 was never true

603 logging.debug("LOAD LAYOUT - Populating track sensor mappings to handle version 3.6.0 breaking change") 

604 settings.set_gpio(mappings = list_of_track_sensors_to_create) 

605 ################################################################################## 

606 ## End of Handle breaking change for sensor IDs ################################## 

607 ################################################################################## 

608 # Now report any elements missing from the new object - intended to provide a 

609 # level of backward capability (able to load old config files into an extended config) 

610 for element in default_object: 

611 if element not in new_objects[object_id].keys(): 

612 default_value = objects_common.schematic_objects[object_id][element] 

613 logging.debug("LOAD LAYOUT - "+new_object_type+" "+str(item_id)+" - Missing element: '" 

614 +element+"' - Asigning default values: "+str(default_value)) 

615 # Reset the signal/point/section/instrument indexes 

616 reset_all_schematic_indexes() 

617 # Redraw (re-create) all items on the schematic with a new bbox 

618 redraw_all_objects(create_new_bbox=True, reset_state=False) 

619 # Ensure all track sections are in front of any lines 

620 bring_track_sections_to_the_front() 

621 # Recalculate point interlocking tables as a 'belt and braces' measure (on the  

622 # basis they would have successfully been loaded with the rest of the configuration) 

623 objects_points.reset_point_interlocking_tables() 

624 # Initialise the layout (interlocking changes, signal aspects etc) 

625 run_layout.initialise_layout() 

626 # save the current state (for undo/redo) - deleting all previous history 

627 save_schematic_state(reset_pointer=True) 

628 return() 

629 

630#------------------------------------------------------------------------------------ 

631# Function get the current objects dictionary (for saving to file) 

632#------------------------------------------------------------------------------------ 

633 

634def get_all(): 

635 return(objects_common.schematic_objects) 

636 

637####################################################################################