mirror of
https://github.com/JannisX11/blockbench.git
synced 2024-11-21 01:13:37 +08:00
parent
ca9af96513
commit
5f09911074
@ -588,7 +588,7 @@ class Animation extends AnimationItem {
|
||||
if (undo) {
|
||||
Undo.finishEdit('Remove animation', {animations: []})
|
||||
|
||||
if (isApp && remove_from_file && this.path && fs.existsSync(this.path)) {
|
||||
if (isApp && Format.animation_files && remove_from_file && this.path && fs.existsSync(this.path)) {
|
||||
Blockbench.showMessageBox({
|
||||
translateKey: 'delete_animation',
|
||||
icon: 'movie',
|
||||
@ -792,52 +792,6 @@ class Animation extends AnimationItem {
|
||||
{name: 'menu.animation.loop.loop', icon: animation => (animation.loop == 'loop' ? 'radio_button_checked' : 'radio_button_unchecked'), click(animation) {animation.setLoop('loop', true)}},
|
||||
]},
|
||||
new MenuSeparator('manage'),
|
||||
{
|
||||
name: 'menu.animation.export_java',
|
||||
id: 'save',
|
||||
icon: 'save',
|
||||
condition: () => Format.animation_files,
|
||||
click(animation) {
|
||||
|
||||
let form = {};
|
||||
let keys = [];
|
||||
let animations = Animation.all.slice()
|
||||
if (Format.animation_files) animations.sort((a1, a2) => a1.path.hashCode() - a2.path.hashCode())
|
||||
animations.forEach(animation => {
|
||||
let key = animation.name;
|
||||
keys.push(key)
|
||||
form[key.hashCode()] = {label: key, type: 'checkbox', value: true};
|
||||
})
|
||||
let dialog = new Dialog({
|
||||
id: 'animation_export',
|
||||
title: 'dialog.animation_export.title',
|
||||
form,
|
||||
onConfirm(form_result) {
|
||||
dialog.hide();
|
||||
keys = keys.filter(key => form_result[key.hashCode()]);
|
||||
let animations = keys.map(k => Animation.all.find(anim => anim.name == k));
|
||||
let content = Codecs.modded_entity.compileAnimations(animations);
|
||||
Blockbench.export({
|
||||
resource_id: 'modded_animation',
|
||||
type: 'Modded Entity Animation',
|
||||
extensions: ['java'],
|
||||
name: (Project.geometry_name||'model'),
|
||||
content,
|
||||
})
|
||||
}
|
||||
})
|
||||
form.select_all_none = {
|
||||
type: 'buttons',
|
||||
buttons: ['generic.select_all', 'generic.select_none'],
|
||||
click(index) {
|
||||
let values = {};
|
||||
keys.forEach(key => values[key.hashCode()] = (index == 0));
|
||||
dialog.setFormValues(values);
|
||||
}
|
||||
}
|
||||
dialog.show();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'menu.animation.save',
|
||||
id: 'save',
|
||||
@ -2049,6 +2003,7 @@ Interface.definePanels(function() {
|
||||
'add_animation_controller',
|
||||
'load_animation_file',
|
||||
'slider_animation_length',
|
||||
'export_modded_animations',
|
||||
]
|
||||
})
|
||||
],
|
||||
|
@ -181,6 +181,7 @@ const MenuBar = {
|
||||
'export_obj',
|
||||
'export_fbx',
|
||||
'export_collada',
|
||||
'export_modded_animations',
|
||||
'upload_sketchfab',
|
||||
'share_model',
|
||||
]},
|
||||
|
@ -280,6 +280,7 @@ const Templates = {
|
||||
?(has_no_rotation)%(remove_n), PartPose.offset(%(x), %(y), %(z)));`,
|
||||
renderer: `%(bone).render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);`,
|
||||
cube: `.texOffs(%(uv_x), %(uv_y)){?(has_mirror).mirror()}.addBox(%(x), %(y), %(z), %(dx), %(dy), %(dz), new CubeDeformation(%(inflate))){?(has_mirror).mirror(false)}`,
|
||||
animation_template: 'mojang'
|
||||
},
|
||||
|
||||
get(key, version = Project.modded_entity_version) {
|
||||
@ -295,22 +296,46 @@ const Templates = {
|
||||
}
|
||||
}
|
||||
const AnimationTemplates = {
|
||||
'mojang': {
|
||||
name: 'Mojmaps',
|
||||
file:
|
||||
`// Save this class in your mod and generate all required imports
|
||||
|
||||
/**
|
||||
* Made with Blockbench %(bb_version)
|
||||
* Exported for Minecraft version 1.19 or later with Mojang mappings
|
||||
* @author %(author)
|
||||
*/
|
||||
public class %(identifier)Animation {
|
||||
%(animations)
|
||||
}`,
|
||||
animation: `public static final AnimationDefinition %(name) = AnimationDefinition.Builder.withLength(%(length))%(looping)%(channels).build();`,
|
||||
looping: `.looping()`,
|
||||
channel: `.addAnimation("%(name)", new AnimationChannel(%(channel_type), %(keyframes)))`,
|
||||
keyframe_rotation: `new Keyframe(%(time), KeyframeAnimations.degreeVec(%(x), %(y), %(z)), %(interpolation))`,
|
||||
keyframe_position: `new Keyframe(%(time), KeyframeAnimations.posVec(%(x), %(y), %(z)), %(interpolation))`,
|
||||
keyframe_scale: `new Keyframe(%(time), KeyframeAnimations.scaleVec(%(x), %(y), %(z)), %(interpolation))`,
|
||||
channel_types: {
|
||||
rotation: 'AnimationChannel.Targets.ROTATION',
|
||||
position: 'AnimationChannel.Targets.POSITION',
|
||||
scale: 'AnimationChannel.Targets.SCALE',
|
||||
},
|
||||
interpolations: {
|
||||
linear: 'AnimationChannel.Interpolations.LINEAR',
|
||||
catmullrom: 'AnimationChannel.Interpolations.CATMULLROM',
|
||||
},
|
||||
},
|
||||
|
||||
get(key, version = Project.modded_entity_version) {
|
||||
let temp = Templates[version][key];
|
||||
let mapping = Templates.get('animation_template', version);
|
||||
let temp = AnimationTemplates[mapping || 'mojang'][key];
|
||||
if (typeof temp === 'string') temp = temp.replace(/\t\t\t/g, '');
|
||||
return temp;
|
||||
},
|
||||
keepLine(line) {
|
||||
return line.replace(/\?\(\w+\)/, '');
|
||||
},
|
||||
getVariableRegex(name) {
|
||||
return new RegExp(`%\\(${name}\\)`, 'g');
|
||||
}
|
||||
};
|
||||
|
||||
function getIdentifier() {
|
||||
return (Project.geometry_name && Project.geometry_name.replace(/[\s-]+/g, '_')) || Project.name || 'custom_model';
|
||||
return (Project.geometry_name && Project.geometry_name.replace(/[\s-]+/g, '_')) || Project.name || 'CustomModel';
|
||||
}
|
||||
|
||||
function askToSaveProject() {
|
||||
@ -884,14 +909,57 @@ Object.defineProperty(codec, 'remember', {
|
||||
}
|
||||
})
|
||||
|
||||
codec.compileAnimations = function(animations) {
|
||||
codec.compileAnimations = function(animations = Animation.all) {
|
||||
let R = Templates.getVariableRegex;
|
||||
let identifier = getIdentifier();
|
||||
let interpolations = AnimationTemplates.get('interpolations');
|
||||
|
||||
let file = AnimationTemplates.get('file');
|
||||
|
||||
model = model.replace(R('bb_version'), Blockbench.version);
|
||||
model = model.replace(R('identifier'), identifier);
|
||||
model = model.replace(R('identifier_rl'), identifier.toLowerCase().replace(' ', '_'));
|
||||
model = model.replace(R('texture_width'), Project.texture_width);
|
||||
model = model.replace(R('texture_height'), Project.texture_height);
|
||||
file = file.replace(R('bb_version'), Blockbench.version);
|
||||
file = file.replace(R('author'), Settings.get('username') || 'Author');
|
||||
file = file.replace(R('identifier'), identifier);
|
||||
|
||||
let anim_strings = [];
|
||||
animations.forEach(animation => {
|
||||
let anim_string = AnimationTemplates.get('animation');
|
||||
anim_string = anim_string.replace(R('name'), animation.name);
|
||||
anim_string = anim_string.replace(R('length'), F(animation.length));
|
||||
anim_string = anim_string.replace(R('looping'), animation.loop == 'loop' ? AnimationTemplates.get('looping') : '');
|
||||
|
||||
let channel_strings = [];
|
||||
let channel_types = AnimationTemplates.get('channel_types');
|
||||
for (let id in animation.animators) {
|
||||
let animator = animation.animators[id];
|
||||
if (animator instanceof BoneAnimator == false) continue;
|
||||
|
||||
for (let channel_id in channel_types) {
|
||||
if (!(animator[channel_id] && animator[channel_id].length)) continue;
|
||||
let keyframes = animator[channel_id].slice().sort((a, b) => a.time - b.time);
|
||||
let keyframe_strings = keyframes.map(kf => {
|
||||
let kf_string = AnimationTemplates.get('keyframe_'+channel_id);
|
||||
kf_string = kf_string.replace(R('time'), F(kf.time));
|
||||
kf_string = kf_string.replace(R('x'), F(kf.calc('x')));
|
||||
kf_string = kf_string.replace(R('y'), F(kf.calc('y')));
|
||||
kf_string = kf_string.replace(R('z'), F(kf.calc('z')));
|
||||
kf_string = kf_string.replace(R('interpolation'), interpolations[kf.interpolation] || interpolations.catmullrom);
|
||||
return kf_string;
|
||||
})
|
||||
|
||||
let channel_string = AnimationTemplates.get('channel');
|
||||
channel_string = channel_string.replace(R('name'), animator.name);
|
||||
channel_string = channel_string.replace(R('channel_type'), channel_types[channel_id]);
|
||||
channel_string = channel_string.replace(R('keyframes'), '\n\t\t\t' + keyframe_strings.join(',\n\t\t\t') + '\n\t\t');
|
||||
|
||||
channel_strings.push(channel_string);
|
||||
}
|
||||
}
|
||||
|
||||
anim_string = anim_string.replace(R('channels'), '\n\t\t' + channel_strings.join('\n\t\t') + '\n\t\t');
|
||||
|
||||
anim_strings.push(anim_string);
|
||||
})
|
||||
file = file.replace(R('animations'), anim_strings.join('\n\n\t'));
|
||||
return file;
|
||||
}
|
||||
|
||||
var format = new ModelFormat({
|
||||
@ -931,6 +999,50 @@ BARS.defineActions(function() {
|
||||
codec.export()
|
||||
}
|
||||
})
|
||||
new Action('export_modded_animations', {
|
||||
icon: 'free_breakfast',
|
||||
category: 'file',
|
||||
condition: () => Format == format,
|
||||
click() {
|
||||
let form = {};
|
||||
let keys = [];
|
||||
let animations = Animation.all.slice();
|
||||
if (Format.animation_files) animations.sort((a1, a2) => a1.path.hashCode() - a2.path.hashCode());
|
||||
animations.forEach(animation => {
|
||||
let key = animation.name;
|
||||
keys.push(key)
|
||||
form[key.hashCode()] = {label: key, type: 'checkbox', value: true};
|
||||
})
|
||||
let dialog = new Dialog({
|
||||
id: 'animation_export',
|
||||
title: 'dialog.animation_export.title',
|
||||
form,
|
||||
onConfirm(form_result) {
|
||||
dialog.hide();
|
||||
keys = keys.filter(key => form_result[key.hashCode()]);
|
||||
let animations = keys.map(k => Animation.all.find(anim => anim.name == k));
|
||||
let content = Codecs.modded_entity.compileAnimations(animations);
|
||||
Blockbench.export({
|
||||
resource_id: 'modded_animation',
|
||||
type: 'Modded Entity Animation',
|
||||
extensions: ['java'],
|
||||
name: (Project.geometry_name||'model'),
|
||||
content,
|
||||
})
|
||||
}
|
||||
})
|
||||
form.select_all_none = {
|
||||
type: 'buttons',
|
||||
buttons: ['generic.select_all', 'generic.select_none'],
|
||||
click(index) {
|
||||
let values = {};
|
||||
keys.forEach(key => values[key.hashCode()] = (index == 0));
|
||||
dialog.setFormValues(values);
|
||||
}
|
||||
}
|
||||
dialog.show();
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})()
|
@ -1174,6 +1174,8 @@
|
||||
"action.export_entity.desc": "Export the model as an entity model for Minecraft Bedrock Edition",
|
||||
"action.export_class_entity": "Export Java Entity",
|
||||
"action.export_class_entity.desc": "Export the entity model as a Java class",
|
||||
"action.export_modded_animations": "Export Modded Entity Animations",
|
||||
"action.export_modded_animations.desc": "Export animations for a Minecraft Java Edition modded entity model",
|
||||
"action.import_optifine_part": "Import OptiFine Part",
|
||||
"action.import_optifine_part.desc": "Import an entity part model for OptiFine",
|
||||
"action.export_optifine_full": "Export OptiFine JEM",
|
||||
|
Loading…
Reference in New Issue
Block a user