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
« 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#------------------------------------------------------------------------------------
51import uuid
52import copy
54from ...library import points
55from ...library import dcc_control
57from . import objects_common
58from . import objects_signals
59from . import objects_sensors
60from .. import run_layout
62#------------------------------------------------------------------------------------
63# Default Point Objects (i.e. state at creation)
64#------------------------------------------------------------------------------------
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"] = []
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
154#------------------------------------------------------------------------------------
155# Function to to update a point object after a configuration change
156#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
210#------------------------------------------------------------------------------------
211# Function to Create a new default Point (and draw it on the canvas)
212#------------------------------------------------------------------------------------
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)
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#------------------------------------------------------------------------------------
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)
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#------------------------------------------------------------------------------------
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()
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#------------------------------------------------------------------------------------
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()
293####################################################################################