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

101 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 Line objects 

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

4# 

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

6# create_line() - Create a default line object on the schematic 

7# delete_line(object_id) - Hard Delete an object when deleted from the schematic 

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

9# delete_line_object(object_id) - Soft delete the drawing object (prior to recreating) 

10# redraw_line_object(object_id) - Redraw the object on the canvas following an update 

11# default_line_object - The dictionary of default values for the object 

12# get_endstop_offsets(x1,y1,x2,y2)- used by the schematics module to get the offsets 

13# for the 'end stops' so they can be moved with the line end during editing 

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#  

20# Accesses the following external editor objects directly: 

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

22# objects_common.line_index - The index of Line Objects (for iterating) 

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

24# objects_common.object_type - The Enumeration of supported objects 

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

26# 

27#------------------------------------------------------------------------------------ 

28 

29import uuid 

30import copy 

31import math 

32import tkinter as Tk 

33 

34from . import objects_common 

35 

36#------------------------------------------------------------------------------------ 

37# Default Line Objects (i.e. state at creation) 

38#------------------------------------------------------------------------------------ 

39 

40default_line_object = copy.deepcopy(objects_common.default_object) 

41default_line_object["item"] = objects_common.object_type.line 

42default_line_object["endx"] = 0 

43default_line_object["endy"] = 0 

44default_line_object["colour"] = "black" 

45default_line_object["arrowtype"] = [0,0,0] # eg: [15,15,10],[10,15,10],[15,15,5] 

46default_line_object["arrowends"] = 0 # 0=none, 1=start, 2=end, 3=both 

47default_line_object["line"] = None # Tkinter canvas object 

48default_line_object["end1"] = None # Tkinter canvas object 

49default_line_object["end2"] = None # Tkinter canvas object 

50default_line_object["stop1"] = None # Tkinter canvas object 

51default_line_object["stop2"] = None # Tkinter canvas object 

52 

53#------------------------------------------------------------------------------------ 

54# Function to to update a line object following a configuration change 

55#------------------------------------------------------------------------------------ 

56 

57def update_line(object_id, new_object_configuration): 

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

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

60 new_item_id = new_object_configuration["itemid"] 

61 # Delete the existing line object, copy across the new config and redraw 

62 delete_line_object(object_id) 

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

64 # The line remains selected after a configuration, so we need to re-draw 

65 # the selection circles at each end by passing in state='normal' 

66 redraw_line_object(object_id, state="normal") 

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

68 if old_item_id != new_item_id: 

69 # Update the type-specific index 

70 del objects_common.line_index[str(old_item_id)] 

71 objects_common.line_index[str(new_item_id)] = object_id 

72 ######################################################################### 

73 ## TODO - This will be important if we use these IDs for route display ## 

74 ######################################################################### 

75 return() 

76 

77#------------------------------------------------------------------------------------ 

78# Function to get the x and y deltas for a line 'end stop' - a short line  

79# perpendicular to the main line - normally used to represent a buffer stop 

80# Used in the re-drawing function here and also used by the schematic editor 

81# for moving the 'end stops' with the line end the line is edited 

82#------------------------------------------------------------------------------------ 

83 

84def get_endstop_offsets(x1,y1,x2,y2): 

85 if x2 != x1: slope = (y2-y1)/(x2-x1) 

86 else: slope = 1000000 

87 dy = math.sqrt(9**2/(slope**2+1)) 

88 dx = -slope*dy 

89 return(dx,dy) 

90 

91#------------------------------------------------------------------------------------ 

92# Function to re-draw a Line object on the schematic. Called when the object 

93# is first created or after the object attributes have been updated. 

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

95 

96def redraw_line_object(object_id,state:str="hidden"): 

97 # Create new drawing objects - note that the ovals at each end are for schematic editing 

98 # normally hidden, but displayed when the line is selected so they can be selected/moved 

99 # the state is set to 'normal' only when called from 'update_line' as the line remains 

100 # selected - in all other cases the line is re-created with the selection ends hidden 

101 x1 = objects_common.schematic_objects[object_id]["posx"] 

102 y1 = objects_common.schematic_objects[object_id]["posy"] 

103 x2 = objects_common.schematic_objects[object_id]["endx"] 

104 y2 = objects_common.schematic_objects[object_id]["endy"] 

105 colour = objects_common.schematic_objects[object_id]["colour"] 

106 arrow_ends = objects_common.schematic_objects[object_id]["arrowends"] 

107 arrow_type = objects_common.schematic_objects[object_id]["arrowtype"] 

108 tags = "line"+str(objects_common.schematic_objects[object_id]["itemid"] ) 

109 # Draw the line (with arrow heads if these are selected). An arrow_type configuration 

110 # of [1,1,1] is used to select 'end stops' over 'arrows'. Note that we store this 

111 # configuration as a list rather than the tuple needed by the tkinter create_line 

112 # function so it is serialisable to json for save and load. 

113 if arrow_type != [0,0,0] and arrow_type != [1,1,1] and arrow_ends == 1: 

114 line_object = objects_common.canvas.create_line(x1,y1,x2,y2, 

115 fill=colour,width=3,arrow=Tk.FIRST,arrowshape=tuple(arrow_type),tags=tags) 

116 elif arrow_type != [0,0,0] and arrow_type != [1,1,1] and arrow_ends == 2: 

117 line_object = objects_common.canvas.create_line(x1,y1,x2,y2, 

118 fill=colour,width=3,arrow=Tk.LAST,arrowshape=tuple(arrow_type),tags=tags) 

119 elif arrow_type != [0,0,0] and arrow_type != [1,1,1] and arrow_ends == 3: 

120 line_object = objects_common.canvas.create_line(x1,y1,x2,y2, 

121 fill=colour,width=3,arrow=Tk.BOTH,arrowshape=tuple(arrow_type),tags=tags) 

122 else: 

123 line_object = objects_common.canvas.create_line(x1,y1,x2,y2,fill=colour,width=3,tags=tags) 

124 # Draw the line end selection circles (i.e displayed when line selected) 

125 end1_object = objects_common.canvas.create_oval(x1-5,y1-5,x1+5,y1+5,state=state,tags=tags) 

126 end2_object = objects_common.canvas.create_oval(x2-5,y2-5,x2+5,y2+5,state=state,tags=tags) 

127 # Draw the line 'end stops' - these are only displayed if selected (otherwise hidden) 

128 # An arrow_type configuration of [1,1,1] is used to select 'end stops' over 'arrows' 

129 if arrow_type == [1,1,1] and arrow_ends == 1: stop1, stop2 = "normal", "hidden" 

130 elif arrow_type == [1,1,1] and arrow_ends == 2: stop1, stop2 = "hidden", "normal" 

131 elif arrow_type == [1,1,1] and arrow_ends == 3: stop1, stop2 = "normal", "normal" 

132 else: stop1, stop2 = "hidden", "hidden" 

133 dx, dy = get_endstop_offsets(x1,y1,x2,y2) 

134 stop1_object = objects_common.canvas.create_line(x1+dx,y1+dy,x1-dx,y1-dy,fill=colour,width=3,tags=tags,state=stop1) 

135 stop2_object = objects_common.canvas.create_line(x2+dx,y2+dy,x2-dx,y2-dy,fill=colour,width=3,tags=tags,state=stop2) 

136 # Save the references to the tkinter drawing objects 

137 objects_common.schematic_objects[object_id]["line"] = line_object 

138 objects_common.schematic_objects[object_id]["end1"] = end1_object 

139 objects_common.schematic_objects[object_id]["end2"] = end2_object 

140 objects_common.schematic_objects[object_id]["stop1"] = stop1_object 

141 objects_common.schematic_objects[object_id]["stop2"] = stop2_object 

142 # Create/update the canvas "tags" and selection rectangle for the line 

143 objects_common.schematic_objects[object_id]["tags"] = tags 

144 objects_common.set_bbox (object_id, objects_common.canvas.bbox(tags)) 

145 return() 

146 

147#------------------------------------------------------------------------------------ 

148# Function to Create a new default Line (and draw it on the canvas) 

149#------------------------------------------------------------------------------------ 

150 

151def create_line(): 

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

153 object_id = str(uuid.uuid4()) 

154 objects_common.schematic_objects[object_id] = copy.deepcopy(default_line_object) 

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

156 x, y = objects_common.find_initial_canvas_position() 

157 item_id = objects_common.new_item_id(exists_function=objects_common.line_exists) 

158 # Add the specific elements for this particular instance of the object 

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

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

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

162 objects_common.schematic_objects[object_id]["endx"] = x + 50 

163 objects_common.schematic_objects[object_id]["endy"] = y 

164 # Add the new object to the index of lines 

165 objects_common.line_index[str(item_id)] = object_id 

166 # Draw the Line on the canvas 

167 redraw_line_object(object_id) 

168 return(object_id) 

169 

170#------------------------------------------------------------------------------------ 

171# Function to paste a copy of an existing line - returns the new Object ID 

172#------------------------------------------------------------------------------------ 

173 

174def paste_line(object_to_paste, deltax:int, deltay:int): 

175 # Create a new UUID for the pasted object 

176 new_object_id = str(uuid.uuid4()) 

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

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

179 new_id = objects_common.new_item_id(exists_function=objects_common.line_exists) 

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

181 objects_common.line_index[str(new_id)] = new_object_id 

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

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

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

185 objects_common.schematic_objects[new_object_id]["endx"] += deltax 

186 objects_common.schematic_objects[new_object_id]["endy"] += deltay 

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

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

189 # Draw the new object 

190 redraw_line_object(new_object_id) 

191 return(new_object_id) 

192 

193#------------------------------------------------------------------------------------ 

194# Function to "soft delete" the section object from the canvas - Primarily used to 

195# delete the line in its current configuration prior to re-creating in its 

196# new configuration - also called as part of a hard delete (below). 

197#------------------------------------------------------------------------------------ 

198 

199def delete_line_object(object_id): 

200 # Delete the tkinter drawing objects assoviated with the line object 

201 objects_common.canvas.delete(objects_common.schematic_objects[object_id]["tags"]) 

202 return() 

203 

204#------------------------------------------------------------------------------------ 

205# Function to 'hard delete' a schematic line object (drawing objects and the main 

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

207#------------------------------------------------------------------------------------ 

208 

209def delete_line(object_id): 

210 # Soft delete the associated library objects from the canvas 

211 delete_line_object(object_id) 

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

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

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

215 del objects_common.line_index[str(objects_common.schematic_objects[object_id]["itemid"])] 

216 del objects_common.schematic_objects[object_id] 

217 return() 

218 

219####################################################################################