00001
00002
00003 import os
00004 import sys
00005
00006 import time
00007 import threading
00008 import yaml
00009
00010 import rospy
00011 import rosparam
00012 import rospkg
00013
00014 import tf
00015
00016 from frame_editor.objects import *
00017 from frame_editor.commands import *
00018
00019 from frame_editor.constructors_geometry import *
00020 from frame_editor.constructors_std import *
00021
00022 from python_qt_binding import QtCore
00023 from python_qt_binding.QtGui import QUndoStack
00024
00025
00026 from frame_editor.interface_interactive_marker import FrameEditor_InteractiveMarker
00027 from frame_editor.interface_services import FrameEditor_Services
00028 from frame_editor.interface_markers import FrameEditor_Markers
00029 from frame_editor.interface_tf import FrameEditor_TF
00030
00031
00032 class FrameEditor(QtCore.QObject):
00033
00034 def __init__(self):
00035 super(FrameEditor, self).__init__()
00036
00037 self.frames = {}
00038 self.active_frame = None
00039
00040
00041 self.observers = []
00042 self.undo_level = 0
00043 self.undo_elements = []
00044 self.undo_stack = QUndoStack()
00045 self.undo_stack.indexChanged.connect(self.undo_stack_changed)
00046 self.__command_lock = threading.Lock()
00047
00048 self.namespace = "frame_editor"
00049
00050
00051
00052 @QtCore.Slot(int)
00053 def undo_stack_changed(self, idx):
00054 '''Updates all observers, whenever a command has been undone/redone'''
00055 self.update_obsevers(self.undo_level)
00056
00057 def add_undo_level(self, level, elements=None):
00058 '''Used by commands to add a level for updating'''
00059 self.undo_level = self.undo_level | level
00060 if elements:
00061 self.undo_elements.extend(elements)
00062
00063 def command(self, command):
00064 '''Push a command to the stack (blocking)'''
00065 with self.__command_lock:
00066 self.undo_stack.push(command)
00067
00068
00069 def update_obsevers(self, level):
00070 '''Updates all registered observers and resets the undo_level'''
00071 for observer in self.observers:
00072 observer.update(self, level, self.undo_elements)
00073 self.undo_level = 0
00074 self.undo_elements = []
00075
00076
00077 self.broadcast()
00078
00079 def broadcast(self):
00080 for observer in self.observers:
00081 observer.broadcast(self)
00082
00083 @staticmethod
00084 def tf_dict():
00085 y = Frame.tf_buffer.all_frames_as_yaml()
00086 d = yaml.load(y)
00087 if isinstance(d, dict):
00088 return d
00089 else:
00090 rospy.logwarn('Got invalid yaml from tf2: '+y)
00091 return {}
00092
00093 @staticmethod
00094 def frame_is_temporary(frame_id):
00095 return frame_id.startswith('_')
00096
00097 @staticmethod
00098 def all_frame_ids(include_temp=True):
00099 return [f for f in FrameEditor.tf_dict() if
00100 not FrameEditor.frame_is_temporary(f) or include_temp]
00101
00102 def iter_frames(self, include_temp=True):
00103 for f in self.frames.itervalues():
00104 if not self.frame_is_temporary(f.name) or include_temp:
00105 yield f
00106
00107
00108
00109
00110 def print_all(self):
00111 print "> Printing all frames"
00112
00113 for frame in self.frames:
00114 frame.print_all()
00115
00116
00117
00118
00119 def load_file(self, file_name):
00120 if file_name:
00121 print "> Loading file"
00122 data = rosparam.load_file(file_name, self.namespace)[0][0]
00123 self.load_data(data)
00124 else:
00125
00126 self.command(Command_ClearAll(self))
00127
00128 self.undo_stack.clear()
00129
00130 return True
00131
00132 def load_params(self, namespace):
00133 if not rosparam.list_params(namespace):
00134 print "> No data to load"
00135 else:
00136 data = rosparam.get_param(namespace)
00137 self.load_data(data)
00138
00139 def load_data(self, data):
00140
00141 self.undo_stack.beginMacro("Import file")
00142
00143
00144 for name, frame in data["frames"].items():
00145 t = frame["position"]
00146 o = frame["orientation"]
00147
00148 if "style" in frame:
00149 style = frame["style"]
00150 else:
00151 style = "none"
00152
00153 if "data" in frame:
00154 dat = frame["data"]
00155 if "color" in dat:
00156 color = dat["color"]
00157 else:
00158 color = (0.0, 0.5, 0.5, 0.75)
00159
00160 position = (t["x"], t["y"], t["z"])
00161 orientation = (o["x"], o["y"], o["z"], o["w"])
00162
00163 if style == "plane":
00164 f = Object_Plane(name, position, orientation, frame["parent"], dat["length"], dat["width"])
00165 f.set_color(color)
00166 elif style == "cube":
00167 f = Object_Cube(name, position, orientation, frame["parent"], dat["length"], dat["width"], dat["height"])
00168 f.set_color(color)
00169 elif style == "sphere":
00170 f = Object_Sphere(name, position, orientation, frame["parent"], dat["diameter"])
00171 f.set_color(color)
00172 elif style == "axis":
00173 f = Object_Axis(name, position, orientation, frame["parent"], dat["length"], dat["width"])
00174 f.set_color(color)
00175 elif style == "mesh":
00176 f = Object_Mesh(name, position, orientation, frame["parent"], dat["path"], dat["scale"])
00177 f.set_color(color)
00178 else:
00179 f = Frame(name, position, orientation, frame["parent"])
00180
00181 self.command(Command_AddElement(self, f))
00182
00183 self.undo_stack.endMacro()
00184
00185 print "> Loading done"
00186
00187 def save_file(self, filename):
00188
00189
00190 data = {}
00191 frames = {}
00192
00193 for frame in self.iter_frames(include_temp=False):
00194 t = {}
00195 t["x"] = float(frame.position[0])
00196 t["y"] = float(frame.position[1])
00197 t["z"] = float(frame.position[2])
00198
00199 o = {}
00200 o["x"] = float(frame.orientation[0])
00201 o["y"] = float(frame.orientation[1])
00202 o["z"] = float(frame.orientation[2])
00203 o["w"] = float(frame.orientation[3])
00204
00205 f = {}
00206 f["parent"] = frame.parent
00207 f["position"] = t
00208 f["orientation"] = o
00209
00210 f["style"] = frame.style
00211
00212 if frame.style == "plane":
00213 f["data"] = { "length": frame.length, "width":frame.width, "color": frame.color }
00214
00215 elif frame.style == "cube":
00216 f["data"] = { "length": frame.length, "width": frame.width, "height": frame.height , "color": frame.color}
00217
00218 elif frame.style == "sphere":
00219 f["data"] = { "diameter": frame.diameter, "color": frame.color }
00220
00221 elif frame.style == "axis":
00222 f["data"] = { "length": frame.length, "width": frame.width, "color": frame.color }
00223
00224 elif frame.style == "mesh":
00225 f["data"] = { "path" : frame.path, "scale" : frame.scale, "color": frame.color }
00226
00227 frames[frame.name] = f
00228
00229 data["frames"] = frames
00230
00231
00232 rospy.set_param(self.namespace, data)
00233 print rospy.get_param(self.namespace)
00234
00235
00236 print "Saving to file", filename
00237 rosparam.dump_params(filename, self.namespace)
00238 print "Saving done"
00239
00240 return True
00241
00242
00243 def run(self):
00244 print "> Going for some spins"
00245 rate = rospy.Rate(200)
00246 while not rospy.is_shutdown():
00247 self.broadcast()
00248 rate.sleep()
00249
00250 def parse_args(self, argv):
00251
00252
00253
00254 from argparse import ArgumentParser
00255 parser = ArgumentParser()
00256
00257 parser.add_argument("-q", "--quiet", action="store_true",
00258 dest="quiet",
00259 help="Put plugin in silent mode")
00260 parser.add_argument("-l", "--load", action="append",
00261 dest="file",
00262 help="Load a file at startup. [rospack filepath/file]")
00263
00264 args, unknowns = parser.parse_known_args(argv)
00265 if not args.quiet:
00266 print 'arguments: ', args
00267 print 'unknowns: ', unknowns
00268
00269
00270
00271 if args.file:
00272 arg_path = args.file[0].split()
00273 if len(arg_path) == 1:
00274
00275 filename = arg_path[0]
00276 print "Loading", filename
00277 success = self.load_file(str(filename))
00278 elif len(arg_path) == 2:
00279
00280 rospack = rospkg.RosPack()
00281 filename = os.path.join(rospack.get_path(arg_path[0]), arg_path[1])
00282 print "Loading", filename
00283 success = self.load_file(str(filename))
00284 else:
00285 print "Load argument not understood! --load", arg_path
00286 print "Please use --load 'myRosPackage pathInMyPackage/myYaml.yaml'"
00287 print "or use --load 'fullPathToMyYaml.yaml'"
00288 success = None
00289
00290 if success:
00291 return filename
00292 elif success == False:
00293 print "ERROR LOADING FILE"
00294 return ''
00295
00296 def init_views(self):
00297
00298 self.interface_tf = FrameEditor_TF(self)
00299 self.interactive = FrameEditor_InteractiveMarker(self)
00300 self.services = FrameEditor_Services(self)
00301 self.interface_markers = FrameEditor_Markers(self)
00302
00303 if __name__ == "__main__":
00304
00305 rospy.init_node('frame_editor')
00306
00307 editor = FrameEditor()
00308
00309
00310 editor.parse_args(sys.argv[1:])
00311 editor.init_views()
00312
00313 print "Frame editor ready!"
00314 editor.run()
00315
00316