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