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
« 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#------------------------------------------------------------------------------------
72import copy
73import logging
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
84from .. import run_layout
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#######################################################################################################
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#------------------------------------------------------------------------------------
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()
105#------------------------------------------------------------------------------------
106# Internal Function to set (re-create) all schematic objects
107# Called following a file load or re-drawing for undo/redo
108#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
166undo_buffer = [{}]
167undo_pointer = 0
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()
187def undo():
188 global undo_pointer
189 if undo_pointer > 0:
190 undo_pointer = undo_pointer - 1
191 restore_schematic_state()
192 return()
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()
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()
224#------------------------------------------------------------------------------------
225# Functions to Enable and disable editing
226#------------------------------------------------------------------------------------
228def enable_editing():
229 objects_sections.enable_editing()
230 run_layout.enable_editing()
231 return()
233def disable_editing():
234 objects_sections.disable_editing()
235 run_layout.disable_editing()
236 return()
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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)
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
437clipboard=[]
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()
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#------------------------------------------------------------------------------------
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)
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#------------------------------------------------------------------------------------
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()
630#------------------------------------------------------------------------------------
631# Function get the current objects dictionary (for saving to file)
632#------------------------------------------------------------------------------------
634def get_all():
635 return(objects_common.schematic_objects)
637####################################################################################