Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions RAPPORT_CORRECTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Rapport des corrections – Plugin PoseKey

## 1. Undo (CTRL+Z) – `posekey.js`

### Note originale
> Undo currently does not work. "purpose" is not a valid property. Instead, you need to init the edit with the empty list of keyframes, and then finish it with all the newly created keyframes.

### Correction appliquée
- **Avant** : `Undo.initEdit({purpose: 'PoseKey Single'})` et `Undo.finishEdit()` sans paramètres.
- **Après** : `Undo.initEdit({animations: [anim]})` et `Undo.finishEdit('PoseKey', {animations: [anim]})`.

L’aspect `animations` enregistre l’état complet de l’animation (y compris les keyframes) avant et après les modifications, ce qui permet à CTRL+Z de fonctionner correctement.

---

## 2. Description du plugin – `posekey.js`

### Note originale
> Could you please rephrase the description so that it is a bit clearer what this plugin does and what it can be used for?

### Correction appliquée
- **Avant** : `"Poses a unique keyframe on the entire group."`
- **Après** : `"Adds a single keyframe (position, rotation, scale) at the current timeline position for all selected groups and their descendants. Useful for quickly setting key poses without affecting other keyframes."`

La nouvelle description précise :
- ce que fait le plugin (ajout d’une keyframe P+R+S),
- où elle est placée (position actuelle de la timeline),
- sur quels éléments (groupes sélectionnés et descendants),
- et son utilité (poses rapides sans modifier les autres keyframes).

---

## 3. Champ invalide dans `plugins.json`

### Note originale
> This field does not exist

### Champ concerné
Le champ **`file`** n’existe pas dans le schéma du registre de plugins BlockBench. Le fichier du plugin est déduit de l’ID du plugin (ex. `posekey` → `posekey.js`).

### Correction à appliquer
Supprimer la ligne `"file": "posekey.js"` de l’entrée `posekey` dans `plugins.json`.

### Entrée corrigée pour `plugins.json`

```json
"posekey": {
"title": "PoseKey",
"author": "FroXaL",
"description": "Adds a single keyframe (position, rotation, scale) at the current timeline position for all selected groups and their descendants. Useful for quickly setting key poses without affecting other keyframes.",
"icon": "key",
"variant": "both",
"version": "3.0.0",
"min_version": "4.0.0",
"tags": ["animation", "keyframe", "workflow"]
}
```

---

## Résumé

| Élément | Statut |
|--------|--------|
| Undo (CTRL+Z) | Corrigé dans `posekey.js` |
| Description | Corrigée dans `posekey.js` |
| Champ `file` dans plugins.json | À supprimer lors de la soumission |
11 changes: 11 additions & 0 deletions plugins.json
Original file line number Diff line number Diff line change
Expand Up @@ -1391,5 +1391,16 @@
"website": "https://pastelito.dev",
"has_changelog": true,
"repository": "https://github.com/PasteDev/blockbench-plugins/tree/master/plugins/hytale_avatar_loader"
},
"posekey": {
"title": "PoseKey",
"author": "FroXaL",
"description": "Poses a unique keyframe on the entire group.",
"icon": "key",
"variant": "both",
"version": "1.0.0",
"min_version": "4.0.0",
"tags": ["animation", "keyframe", "workflow"],
"file": "posekey.js"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field does not exist

}
}
103 changes: 103 additions & 0 deletions plugins/posekey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
(function() {
let action_posekey;

function getAllDescendants(groups) {
let all_groups = [];
function recurse(arr) {
arr.forEach(item => {
if (item.type === 'group') {
if (!all_groups.includes(item)) all_groups.push(item);
if (item.children && item.children.length > 0) recurse(item.children);
}
});
}
recurse(groups);
return all_groups;
}

BBPlugin.register('posekey', {
title: 'PoseKey',
author: 'FroXaL',
icon: 'key',
description: 'Poses a unique keyframe on the entire group.',
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please rephrase the description so that it is a bit clearer what this plugin does and what it can be used for?

version: '3.0.0',
variant: 'both',

onload() {
action_posekey = new Action('pose_key_action_id', {
name: 'PoseKey',
description: 'Unique P+R+S keyframe at cursor',
icon: 'key',
category: 'animation',
condition: () => Animator.open,

click: function() {
let anim = Animator.animation;
if (!anim && Animation.selected) anim = Animation.selected;

if (!anim) {
Blockbench.showQuickMessage("⚠️ No active animation!");
return;
}

let selection_base = [];
if (Group.selected.length > 0) selection_base.push(...Group.selected);

if (Cube.selected.length > 0) {
Cube.selected.forEach(cube => {
if (cube.parent && cube.parent.type === 'group') {
if (!selection_base.includes(cube.parent)) selection_base.push(cube.parent);
}
});
}

if (selection_base.length === 0) {
Blockbench.showQuickMessage("⚠️ Select an object!");
return;
}

let final_targets = getAllDescendants(selection_base);

Undo.initEdit({purpose: 'PoseKey Single'});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo currently does not work. "purpose" is not a valid property. Instead, you need to init the edit with the empty list of keyframes, and then finish it with all the newly created keyframes.


let currentTime = Timeline.time;

final_targets.forEach(group => {
let uuid = group.uuid;

let bone_animator = anim.animators[uuid];
if (!bone_animator) {
bone_animator = new GeneralAnimator(uuid, anim);
bone_animator.init();
anim.animators[uuid] = bone_animator;
}

['position', 'rotation', 'scale'].forEach(channel => {

let channel_array = bone_animator[channel];
let was_empty = channel_array.length === 0;

bone_animator.getOrMakeKeyframe(channel, currentTime);

if (was_empty && currentTime > 0 && channel_array.length > 1) {
let key_at_zero = channel_array.find(k => k.time === 0);
if (key_at_zero) {
key_at_zero.remove();
}
}
});
});

Undo.finishEdit();
if (Animator.open) Animator.preview();

Blockbench.showQuickMessage(`🔑 Unique keyframe set on ${final_targets.length} groups!`);
}
});
},

onunload() {
if (action_posekey) action_posekey.delete();
}
});
})();
103 changes: 103 additions & 0 deletions posekey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
(function() {
let action_posekey;

function getAllDescendants(groups) {
let all_groups = [];
function recurse(arr) {
arr.forEach(item => {
if (item.type === 'group') {
if (!all_groups.includes(item)) all_groups.push(item);
if (item.children && item.children.length > 0) recurse(item.children);
}
});
}
recurse(groups);
return all_groups;
}

BBPlugin.register('posekey', {
title: 'PoseKey',
author: 'FroXaL',
icon: 'key',
description: 'Adds a single keyframe (position, rotation, scale) at the current timeline position for all selected groups and their descendants. Useful for quickly setting key poses without affecting other keyframes.',
version: '3.0.0',
variant: 'both',

onload() {
action_posekey = new Action('pose_key_action_id', {
name: 'PoseKey',
description: 'Unique P+R+S keyframe at cursor',
icon: 'key',
category: 'animation',
condition: () => Animator.open,

click: function() {
let anim = Animator.animation;
if (!anim && Animation.selected) anim = Animation.selected;

if (!anim) {
Blockbench.showQuickMessage("⚠️ No active animation!");
return;
}

let selection_base = [];
if (Group.selected.length > 0) selection_base.push(...Group.selected);

if (Cube.selected.length > 0) {
Cube.selected.forEach(cube => {
if (cube.parent && cube.parent.type === 'group') {
if (!selection_base.includes(cube.parent)) selection_base.push(cube.parent);
}
});
}

if (selection_base.length === 0) {
Blockbench.showQuickMessage("⚠️ Select an object!");
return;
}

let final_targets = getAllDescendants(selection_base);

Undo.initEdit({animations: [anim]});

let currentTime = Timeline.time;

final_targets.forEach(group => {
let uuid = group.uuid;

let bone_animator = anim.animators[uuid];
if (!bone_animator) {
bone_animator = new GeneralAnimator(uuid, anim);
bone_animator.init();
anim.animators[uuid] = bone_animator;
}

['position', 'rotation', 'scale'].forEach(channel => {

let channel_array = bone_animator[channel];
let was_empty = channel_array.length === 0;

bone_animator.getOrMakeKeyframe(channel, currentTime);

if (was_empty && currentTime > 0 && channel_array.length > 1) {
let key_at_zero = channel_array.find(k => k.time === 0);
if (key_at_zero) {
key_at_zero.remove();
}
}
});
});

Undo.finishEdit('PoseKey', {animations: [anim]});
if (Animator.open) Animator.preview();

Blockbench.showQuickMessage(`🔑 Unique keyframe set on ${final_targets.length} groups!`);
}
});
},

onunload() {
if (action_posekey) action_posekey.delete();
}
});
})();