diff --git a/frame_editor/CMakeLists.txt b/frame_editor/CMakeLists.txt
index 33d6972..57d4c23 100644
--- a/frame_editor/CMakeLists.txt
+++ b/frame_editor/CMakeLists.txt
@@ -65,6 +65,7 @@ add_service_files(
GetFrame.srv
GetFrameNames.srv
RemoveFrame.srv
+ RenameFrame.srv
SetFrame.srv
SetParentFrame.srv
CopyFrame.srv
diff --git a/frame_editor/src/frame_editor/FrameEditorGUI.ui b/frame_editor/src/frame_editor/FrameEditorGUI.ui
index 3b517ee..655fcc3 100644
--- a/frame_editor/src/frame_editor/FrameEditorGUI.ui
+++ b/frame_editor/src/frame_editor/FrameEditorGUI.ui
@@ -123,6 +123,17 @@
+ -
+
+
+ Rename
+
+
+
+ ..
+
+
+
@@ -174,9 +185,7 @@
-
-
true
-
-
diff --git a/frame_editor/src/frame_editor/commands.py b/frame_editor/src/frame_editor/commands.py
index 6c6ece6..62a047b 100644
--- a/frame_editor/src/frame_editor/commands.py
+++ b/frame_editor/src/frame_editor/commands.py
@@ -188,7 +188,56 @@ def undo(self):
self.element.hidden = True
self.editor.add_undo_level(1, [self.element])
+class Command_RenameElement(QUndoCommand):
+ '''Copys a source frame's transformation and sets a new parent
+ '''
+
+ def __init__(self, editor, element, new_name):
+ QUndoCommand.__init__(self, "Rename")
+ self.editor = editor
+ self.element = element
+
+ self.was_active = editor.active_frame is element
+
+ self.new_name = new_name
+ self.old_name = element.name
+
+ self.element.name = self.new_name
+
+ def redo(self):
+ if self.was_active:
+ self.editor.active_frame = self.element
+ self.editor.add_undo_level(2)
+
+ # Remove old frame
+ self.editor.frames.pop(self.old_name, None)
+
+ # Add new frame
+ self.element.name = self.new_name
+ self.editor.frames[self.new_name] = self.element
+ self.editor.add_undo_level(1, [self.element])
+
+ # Update parent names
+ for frame in self.editor.frames.values():
+ if frame.parent == self.old_name:
+ frame.parent = self.new_name
+ self.editor.add_undo_level(4, [frame])
+
+ def undo(self):
+ if self.was_active:
+ self.editor.active_frame = self.element
+ self.editor.add_undo_level(2)
+
+ self.editor.frames.pop(self.new_name, None)
+
+ self.element.name = self.old_name
+ self.editor.frames[self.old_name] = self.element
+ self.editor.add_undo_level(1, [self.element])
+ for frame in self.editor.frames.values():
+ if frame.parent == self.new_name:
+ frame.parent = self.old_name
+ self.editor.add_undo_level(4, [frame])
class Command_RebaseElement(QUndoCommand):
'''Copys a source frame's transformation and sets a new parent
diff --git a/frame_editor/src/frame_editor/interface_services.py b/frame_editor/src/frame_editor/interface_services.py
index 4d6a641..7d767a7 100644
--- a/frame_editor/src/frame_editor/interface_services.py
+++ b/frame_editor/src/frame_editor/interface_services.py
@@ -30,6 +30,7 @@ def __init__(self, frame_editor):
rospy.Service("~set_frame", SetFrame, self.callback_set_frame)
rospy.Service("~set_parent", SetParentFrame, self.callback_set_parent_frame)
rospy.Service("~copy_frame", CopyFrame, self.callback_copy_frame)
+ rospy.Service("~rename_frame", RenameFrame, self.callback_rename_frame)
rospy.Service("~load_yaml", LoadYaml, self.callback_load_yaml)
rospy.Service("~save_yaml", SaveYaml, self.callback_save_yaml)
@@ -276,5 +277,39 @@ def callback_copy_frame(self, request):
response.error_code = 9
return response
+
+ def callback_rename_frame(self, request):
+ rospy.loginfo("> Request to rename frame '{}' with new name '{}'".format(request.source_name, request.new_name))
+
+ response = RenameFrameResponse()
+ response.error_code = 0
+
+ if request.new_name == "":
+ rospy.logerr(" Error: No name given")
+ response.error_code = 1
+
+ elif request.source_name == "":
+ rospy.logerr(" Error: No source name given")
+ response.error_code = 3
+
+ elif request.source_name not in self.editor.frames:
+ rospy.logerr(f" Error: Source Frame not found: {request.source_name}")
+ response.error_code = 2
+
+ elif request.new_name in self.editor.frames:
+ rospy.logerr(f" Error: New frame name already exists: {request.new_name}")
+ response.error_code = 4
+
+ else:
+ try:
+ self.editor.command(Command_RenameElement(self.editor, self.editor.frames.get(request.source_name), request.new_name))
+ except AttributeError as e:
+ rospy.logerr("Error: The source frame was not found. {}".format(e))
+ response.error_code = 9
+ except Exception as e:
+ rospy.logerr("Error: unhandled exception {}".format(e))
+ response.error_code = 9
+
+ return response
# eof
diff --git a/frame_editor/src/frame_editor/rqt_editor.py b/frame_editor/src/frame_editor/rqt_editor.py
index feb2499..fcd5d9b 100755
--- a/frame_editor/src/frame_editor/rqt_editor.py
+++ b/frame_editor/src/frame_editor/rqt_editor.py
@@ -116,6 +116,7 @@ def create_main_widget(self):
##
widget.btn_add.clicked.connect(self.btn_add_clicked)
widget.btn_delete.clicked.connect(self.btn_delete_clicked)
+ widget.btn_rename.clicked.connect(self.btn_rename_clicked)
widget.btn_duplicate.clicked.connect(self.btn_duplicate_clicked)
widget.list_frames.currentItemChanged.connect(self.selected_frame_changed)
widget.list_tf.currentItemChanged.connect(self.update_measurement)
@@ -151,6 +152,7 @@ def create_main_widget(self):
widget.txt_c.editingFinished.connect(self.c_valueChanged)
widget.txt_group.editingFinished.connect(self.group_valueChanged)
+ widget.txt_name.editingFinished.connect(self.frameName_valueChanged)
widget.btn_rad.toggled.connect(self.update_fields)
@@ -559,6 +561,24 @@ def btn_duplicate_clicked(self, checked):
self.editor.command(Command_CopyElement(self.editor, name, source_name, parent_name))
self.signal_update_tf.emit(False, False)
+
+ @Slot(bool)
+ def btn_rename_clicked(self, checked):
+ item = self.widget.list_frames.currentItem()
+ if not item:
+ return
+
+ # Check if the item is a frame. Early returns if selected item is not a frame (e.g. a group)
+ source_name = item.text(0)
+ if self.editor.frames.get(source_name) is None:
+ return
+
+ new_name = self.get_valid_frame_name("Rename Frame", default_name=source_name)
+ if not new_name:
+ return
+
+ self.editor.command(Command_RenameElement(self.editor, self.editor.frames[source_name], new_name))
+ self.signal_update_tf.emit(True, True)
def get_sleep_time(self):
return max(5.0 / self.editor.hz, 0.1)
@@ -585,7 +605,13 @@ def btn_delete_clicked(self, checked):
item = self.widget.list_frames.currentItem()
if not item:
return
- self.editor.command(Command_RemoveElement(self.editor, self.editor.frames[item.text(0)]))
+
+ # Check if the item is a frame. Early returns if selected item is not a frame (e.g. a group)
+ source_name = item.text(0)
+ if self.editor.frames.get(source_name) is None:
+ return
+
+ self.editor.command(Command_RemoveElement(self.editor, self.editor.frames[source_name]))
self.signal_update_tf.emit(True, True)
## PARENTING ##
@@ -714,7 +740,26 @@ def group_valueChanged(self):
if self.editor.active_frame.group != value:
self.editor.command(Command_SetGroup(self.editor, self.editor.active_frame, value))
+ @Slot()
+ def frameName_valueChanged(self):
+ item = self.widget.list_frames.currentItem()
+ if not item:
+ return
+ source_name = item.text(0)
+
+ new_name = self.widget.txt_name.text()
+ existing_tf_frames = set(self.editor.all_frame_ids())
+ existing_editor_frames = set(self.editor.all_editor_frame_ids())
+ # allow recreating if frame was published by frameditor node originally
+ if source_name != new_name and (new_name in existing_editor_frames or (new_name in existing_tf_frames and not Frame.was_published_by_frameeditor(new_name))):
+ self.widget.txt_name.setText(source_name)
+ QtWidgets.QMessageBox.warning(self.widget, "Invalid Frame Name",
+ f"The frame name {new_name} already exists. Cannot create a new frame with the same name.")
+ return None
+
+ self.editor.command(Command_RenameElement(self.editor, self.editor.frames[source_name], new_name))
+ self.signal_update_tf.emit(True, True)
## FRAME STYLE ##
##
diff --git a/frame_editor/srv/RenameFrame.srv b/frame_editor/srv/RenameFrame.srv
new file mode 100644
index 0000000..c3b0ba8
--- /dev/null
+++ b/frame_editor/srv/RenameFrame.srv
@@ -0,0 +1,4 @@
+string source_name
+string new_name
+---
+int32 error_code
\ No newline at end of file