Animation retargeting WIP

Unused animator validator check
This commit is contained in:
JannisX11 2024-07-13 13:20:07 +02:00
parent e89e6549d7
commit 954e7d7658
3 changed files with 137 additions and 1 deletions

View File

@ -467,6 +467,7 @@ class Animation extends AnimationItem {
Animator.preview();
updateInterface();
}
Blockbench.dispatchEvent('select_animation', {animation: this})
return this;
}
setLength(len = this.length) {
@ -975,6 +976,49 @@ Blockbench.addDragHandler('animation', {
}
})
new ValidatorCheck('unused_animators', {
condition: { features: ['animation_mode'], selected: {animation: true} },
update_triggers: ['select_animation'],
run() {
let animation = Animation.selected;
if (!animation) return;
let animators = [];
for (let id in animation.animators) {
let animator = animation.animators[id];
if (animator instanceof BoneAnimator && animator.keyframes.length) {
if (!animator.getGroup()) {
animators.push(animator);
}
}
}
if (animators.length) {
let buttons = [
{
name: 'Retarget Animators',
icon: 'rebase',
click() {
Validator.dialog.close()
BarItems.retarget_animators.click();
},
},
{
name: 'Reveal in Timeline',
icon: 'fa-sort-amount-up',
click() {
for (let animator of animators) {
animator.addToTimeline();
}
Validator.dialog.close();
},
}
];
this.warn({
message: `The animation "${animation.name}" contains ${animators.length} animated nodes that do not exist in the current model.`,
buttons,
})
}
}
})
BARS.defineActions(function() {
new NumSlider('slider_animation_length', {
@ -1566,6 +1610,87 @@ BARS.defineActions(function() {
}
}
})
new Action('retarget_animators', {
icon: 'rebase',
category: 'animation',
condition: () => Animation.selected,
click: async function() {
let animation = Animation.selected;
let form = {};
let unassigned_animators = [];
let assigned_animators = [];
for (let id in animation.animators) {
let animator = animation.animators[id];
if (animator instanceof BoneAnimator && animator.keyframes.length) {
if (!animator.getGroup()) {
unassigned_animators.push(animator);
} else {
assigned_animators.push(animator);
}
}
}
let all_animators = unassigned_animators.slice();
if (unassigned_animators.length && assigned_animators.length) {
all_animators.push('_');
}
all_animators.push(...assigned_animators);
for (let animator of all_animators) {
if (animator == '_') {
form._ = '_';
continue;
}
let is_assigned = assigned_animators.includes(animator);
let options = {};
let nodes;
if (animator.type == 'bone') {
nodes = Group.all;
} else {
nodes = Outliner.all.filter(element => element.type == animator.type);
}
if (!is_assigned) options[animator.uuid] = '-';
for (let node of nodes) {
options[node.uuid] = node.name;
}
form[animator.uuid] = {
label: animator.name,
type: 'select',
value: animator.uuid,
options
}
}
let form_result = await new Promise(resolve => {
new Dialog('retarget_animators', {
name: 'action.retarget_animators',
form,
onConfirm(result) {
resolve(result);
},
onCancel() {
resolve(false);
}
}).show();
})
if (!form_result) return;
Undo.initEdit({animations: [animation]});
for (let animator of all_animators) {
if (animator == '_') continue;
let new_target = form_result[animator.uuid];
if (new_target == animator.uuid) continue;
delete animation.animators[animator.uuid];
animation.animators[new_target] = animator;
animator.uuid = new_target;
}
Undo.finishEdit('Retarget animations');
}
})
})

View File

@ -4,7 +4,14 @@ const Validator = {
warnings: [],
errors: [],
_timeout: null,
accumulated_triggers: [],
wildcard_trigger: false,
validate(trigger) {
if (trigger) {
this.accumulated_triggers.safePush(trigger);
} else {
this.wildcard_trigger = true;
}
if (this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
@ -20,7 +27,7 @@ const Validator = {
try {
if (!Condition(check.condition)) return;
if (!trigger || check.update_triggers.includes(trigger)) {
if (this.wildcard_trigger || check.update_triggers.includes(trigger) || this.accumulated_triggers.find(t => check.update_triggers.includes(t))) {
check.update();
}
Validator.warnings.push(...check.warnings);
@ -30,6 +37,8 @@ const Validator = {
console.error(error);
}
})
this.accumulated_triggers.empty();
this.wildcard_trigger = false;
}, 40)
},
openDialog() {

View File

@ -1779,6 +1779,8 @@
"action.export_animation_file.desc": "Export a selection of animations into a new file",
"action.optimize_animation": "Optimize Animation",
"action.optimize_animation.desc": "Optimize the current animation and reduce the keyframe count",
"action.retarget_animators": "Retarget Animations",
"action.retarget_animators.desc": "Select which animator targets which bone or element",
"action.merge_animation": "Merge Animation",
"action.merge_animation.desc": "Optimize the current animation and reduce the keyframe count",
"action.bake_ik_animation": "Bake Inverse Kinematics",