-
Notifications
You must be signed in to change notification settings - Fork 206
Add PoseKey Plugin #847
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?
Add PoseKey Plugin #847
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | |
| 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.', | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'}); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| } | ||
| }); | ||
| })(); | ||
| 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(); | ||
| } | ||
| }); | ||
| })(); |
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 field does not exist