-
-
Notifications
You must be signed in to change notification settings - Fork 22.8k
Fix "Create Custom Bone2D(s) from Node(s)" to create working bone chains #107797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
f3606c0
to
82a7e68
Compare
I'm getting a crash in the MRP from #92227:
Seems like you are using wrong index in the |
Looks like I was too hasty in editing after failed checks, but I've changed it to a range loop now, thank you. |
@@ -4875,14 +4877,25 @@ void CanvasItemEditor::_popup_callback(int p_op) { | |||
case SKELETON_MAKE_BONES: { | |||
HashMap<Node *, Object *> &selection = editor_selection->get_selection(); | |||
Node *editor_root = get_tree()->get_edited_scene_root(); | |||
Vector<Node2D *> selected_nodes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vector<Node2D *> selected_nodes; | |
LocalVector<Node2D *> selected_nodes; | |
selected_nodes.reserve(selection.size()); |
Also move this after the condition below.
undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)")); | ||
// Store the objects from `get_selection()` because otherwise the references | ||
// to the selected objects would disappear, because we're repeatedly | ||
// reparenting them. | ||
for (const KeyValue<Node *, Object *> &E : selection) { | ||
Node2D *n2d = Object::cast_to<Node2D>(E.key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a null check, otherwise it will crash if a non-Node2D is selected.
} | ||
|
||
undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)")); | ||
for (int i = 0; i < selected_nodes.size(); i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can also be a range loop.
|
||
undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)")); | ||
for (int i = 0; i < selected_nodes.size(); i++) { | ||
undo_redo->create_action(TTR("Create Custom Bone2D from Node")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this action for? Hitting the continue
below will result in invalid undo state. Nested actions don't make sense if they are always nested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this action for? Hitting the
continue
below will result in invalid undo state. Nested actions don't make sense if they are always nested.
The problem with how I set it up is, every action declared in an iteration in that for loop should be executed before the next iteration, so I create a new action within the loop which I execute at the end of that iteration.
So if I were to remove the nested actions, I'd have to remove the outer action (line 4894). This would mean the case SKELETON_MAKE_BONES
contains multiple smaller actions, and not one big 'umbrella' action. Is that acceptable? Is there a better way to do this?
So this:
undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)"));
for nodes:
undo_redo->create_action(TTR("Create Custom Bone2D from Node"));
// Add_do_methods
undo_redo->commit_action()
undo_redo->create_action(TTR("Connect bones to Marker2D end"));
for nodes:
// Add_do_methods
undo_redo->commit_action()
undo_redo->commit_action()
becomes:
for nodes:
undo_redo->create_action(TTR("Create Custom Bone2D from Node"));
// Add_do_methods
undo_redo->commit_action()
undo_redo->create_action(TTR("Connect bones to Marker2D end"));
for nodes:
// Add_do_methods
undo_redo->commit_action()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commit_action()
does not execute the action if it's nested, so your code should have the same effect without the inner actions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commit_action()
does not execute the action if it's nested
I'm not sure I understand. So nested actions are not supported? Because the result really is different if I removed the inner commit_action()
.
In my case, I need to execute the
for nodes:
undo_redo->create_action(TTR("Create Custom Bone2D from Node"));
// Add_do_methods
undo_redo->commit_action()
nested commit_action()
. This is because the loop is essentially:
for nodes:
undo_redo->create_action(TTR("Create Custom Bone2D from Node"));
// Find the parent of the current node
// Use that in add_do_method(do_some_reparenting)
undo_redo->commit_action()
The thing is, if I don't commit per loop iteration, the queued add_do_method()
s use outdated variables. For example, the n2d_parent
variable would be wrong after the 1st iteration. This is because the next node
in the loop is moved by the previous when the action is commited. If I don't commit the action per loop, the parent referenced in
undo_redo->add_do_method(n2d_parent, "add_child", new_bone);
undo_redo->add_do_reference(new_bone);
undo_redo->add_do_method(n2d_parent, "remove_child", n2d);
undo_redo->add_do_method(new_bone, "add_child", n2d);
are outdated.
Am I explaining this right?
I tried with and without nested actions. This is the result. Left side only has one 'big' action, "Create Custom Bone2D(s) from Node(s)"
from my commit. The right side has both the 'big' action and the nested "Create Custom Bone2D from Node"
action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand. So nested actions are not supported?
They are supported, but they are only executed after the topmost action is committed.
godot/core/object/undo_redo.cpp
Lines 311 to 316 in 53be3b7
void UndoRedo::commit_action(bool p_execute) { | |
ERR_FAIL_COND(action_level <= 0); | |
action_level--; | |
if (action_level > 0) { | |
return; //still nested | |
} |
Though it's weird that it would make difference in your case 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is weird... I've tried looking at the undo_redo
& manager logic but this is going over my head.
In any case, as far as I understand it, what I'm trying to do shouldn't be supported. The alternative would be to pre-determine what the entire chain should look like after each node's actions would be executed, without actually executing them... I'm not sure if that's the way to go here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try to look into it more, but for now just fix the continue
issue I mentioned initially. Every create_actions()
needs a corresponding commit_action()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found it. It's a bug in EditorUndoRedoManager. Your actions aren't getting nested at all.
I'll clean this up myself in a follow-up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I'll wait and see how my implementation behaves when the bug is fixed. I'll probably have to predetermine the entire chain in that case.
// Otherwise leave it to the default bone2D behavior. | ||
// Bone2D without Bone2D children in a chain need their angle and length manually adjusted, | ||
// `SKELETON_MAKE_BONES` should reduce manual input to create a chain. | ||
undo_redo->create_action(TTR("Connect bones to Marker2D end")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another unnecessary nested action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above comment
// `SKELETON_MAKE_BONES` should reduce manual input to create a chain. | ||
undo_redo->create_action(TTR("Connect bones to Marker2D end")); | ||
TypedArray<Marker2D> new_bone_children_marker2d = selected_nodes[0]->find_children("", "Marker2D", true, true); | ||
for (int k = 0; k < selected_nodes.size(); k++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also can be range loop. I think TypedArray uses Variant as iteration type.
before the new bone- and IK system in 4.0, the "Create Custom Bone2D(s) from Node(s)" editor button created an out-of-the-box working chain of bones. This can be seen in the pre-4.0 docs. Since 4.0, that has been broken.
This pull request aims to bring back that functionality.
When using the "Create Custom Bone2D(s) from Node(s)" button, the selected nodes should be used to create a correctly setup Bone2D chain, where Bone2Ds without Bone2D children or siblings should connect to an 'end' marker2D.
Fixes #92227
This is my first 'real' pull request to the Godot engine. I did my best to follow the docs. Please be really critical of my changes.
Notes: