blockbench/js/io/io.js

949 lines
26 KiB
JavaScript
Raw Normal View History

2019-07-18 00:02:07 +08:00
var Format = 0;
const Formats = {};
const ModelMeta = {
save_path: '',
export_path: '',
animation_path: '',
_name: '',
get name() {return this._name},
set name(name) {
this._name = name;
Project.name = this._name;
setProjectTitle(this._name)
},
get saved() {return Prop.project_saved},
set saved(s) {Prop.project_saved = !!s},
}
//Formats
class ModelFormat {
constructor(data) {
Formats[data.id] = this;
this.id = data.id;
2019-08-18 00:26:14 +08:00
this.name = data.name || tl('format.'+this.id);
this.description = data.description || tl('format.'+this.id+'.desc');
2019-07-26 19:33:29 +08:00
this.show_on_start_screen = true;
2019-07-18 00:02:07 +08:00
this.box_uv = false;
this.optional_box_uv = false;
this.single_texture = false;
2020-04-26 02:25:07 +08:00
this.animated_textures = false;
2019-07-18 00:02:07 +08:00
this.bone_rig = false;
2019-12-16 03:04:31 +08:00
this.centered_grid = false;
2019-07-18 00:02:07 +08:00
this.rotate_cubes = false;
this.integer_size = false;
this.locators = false;
this.canvas_limit = false;
this.rotation_limit = false;
2020-01-24 01:53:36 +08:00
this.uv_rotation = false;
2019-07-18 00:02:07 +08:00
this.display_mode = false;
this.animation_mode = false;
this.codec = data.codec;
this.onActivation = data.onActivation;
this.onDeactivation = data.onDeactivation;
Merge.string(this, data, 'icon');
2019-07-26 19:33:29 +08:00
Merge.boolean(this, data, 'show_on_start_screen');
2019-07-18 00:02:07 +08:00
Merge.boolean(this, data, 'box_uv');
Merge.boolean(this, data, 'optional_box_uv');
Merge.boolean(this, data, 'single_texture');
2020-04-26 02:25:07 +08:00
Merge.boolean(this, data, 'animated_textures');
2019-07-18 00:02:07 +08:00
Merge.boolean(this, data, 'bone_rig');
2019-12-16 03:04:31 +08:00
Merge.boolean(this, data, 'centered_grid');
2019-07-18 00:02:07 +08:00
Merge.boolean(this, data, 'rotate_cubes');
Merge.boolean(this, data, 'integer_size');
Merge.boolean(this, data, 'locators');
Merge.boolean(this, data, 'canvas_limit');
Merge.boolean(this, data, 'rotation_limit');
2020-01-24 01:53:36 +08:00
Merge.boolean(this, data, 'uv_rotation');
2019-07-18 00:02:07 +08:00
Merge.boolean(this, data, 'display_mode');
Merge.boolean(this, data, 'animation_mode');
}
select(converting) {
if (Format && typeof Format.onDeactivation == 'function') {
Format.onDeactivation()
}
Format = this;
if (typeof this.onActivation == 'function') {
Format.onActivation()
}
if (!converting || !this.optional_box_uv) {
Project.box_uv = Format.box_uv;
}
buildGrid()
2019-12-16 03:04:31 +08:00
if (Format.centered_grid) {
2019-07-19 23:31:22 +08:00
scene.position.set(0, 0, 0);
} else {
scene.position.set(-8, -8, -8);
}
2019-12-16 03:04:31 +08:00
var center = Format.centered_grid ? 8 : 0;
2019-07-19 23:31:22 +08:00
previews.forEach(preview => {
2020-03-05 03:56:17 +08:00
if (preview.isOrtho && typeof preview.angle == 'number') {
preview.loadAnglePreset(DefaultCameraPresets[preview.angle+1])
2019-09-06 06:16:54 +08:00
}
2019-07-26 19:33:29 +08:00
})
2020-04-26 02:25:07 +08:00
uv_dialog.all_editors.forEach(editor => {
editor.img.style.objectFit = Format.animated_textures ? 'cover' : 'fill';
})
2019-07-26 19:33:29 +08:00
updateSelection()
2019-07-22 05:17:36 +08:00
Modes.vue.$forceUpdate()
2019-07-18 00:02:07 +08:00
Canvas.updateRenderSides()
return this;
}
2019-07-26 19:33:29 +08:00
new() {
if (newProject(this)) {
BarItems.project_window.click();
return true;
}
}
2019-07-18 00:02:07 +08:00
convertTo() {
Undo.history.length = 0;
Undo.index = 0;
ModelMeta.export_path = '';
ModelMeta.animation_path = '';
var old_format = Format
this.select(true)
Modes.options.edit.select()
//Single Texture
if (Format.single_texture && !old_format.single_texture) {
if (textures.length > 1) {
textures.splice(1)
}
if (textures.length) {
var tex = textures[0]
tex.particle = false
if (tex.img.naturalWidth !== tex.img.naturalWidth && tex.error) {
tex.error = false
}
}
}
//Bone Rig
if (!Format.bone_rig && old_format.bone_rig) {
Group.all.forEach(group => {
2020-03-05 03:56:17 +08:00
group.rotation.V3_set(0, 0, 0);
2019-07-18 00:02:07 +08:00
})
}
if (Format.bone_rig && !old_format.bone_rig) {
var loose_stuff = []
Outliner.root.forEach(el => {
if (el instanceof Group == false) {
loose_stuff.push(el)
}
})
if (loose_stuff.length) {
var root_group = new Group().init().addTo()
loose_stuff.forEach(el => {
el.addTo(root_group)
})
}
if (!Project.geometry_name && Project.name) {
Project.geometry_name = Project.name;
}
}
//Rotate Cubes
if (!Format.rotate_cubes && old_format.rotate_cubes) {
Cube.all.forEach(cube => {
2020-03-05 03:56:17 +08:00
cube.rotation.V3_set(0, 0, 0)
2019-07-18 00:02:07 +08:00
})
}
//Locators
if (!Format.locators && old_format.locators) {
Locator.all.slice().forEach(locator => {
locator.remove()
})
}
//Canvas Limit
2019-12-16 03:04:31 +08:00
if (Format.canvas_limit && !old_format.canvas_limit && !settings.deactivate_size_limit.value) {
2019-07-18 00:02:07 +08:00
Cube.all.forEach(function(s, i) {
//Push elements into 3x3 block box
[0, 1, 2].forEach(function(ax) {
2019-08-01 06:01:47 +08:00
var overlap = s.to[ax] + s.inflate - 32
2019-07-18 00:02:07 +08:00
if (overlap > 0) {
//If positive site overlaps
s.from[ax] -= overlap
s.to[ax] -= overlap
2019-08-01 06:01:47 +08:00
if (16 + s.from[ax] - s.inflate < 0) {
s.from[ax] = -16 + s.inflate
2019-07-18 00:02:07 +08:00
}
} else {
2019-08-01 06:01:47 +08:00
overlap = s.from[ax] - s.inflate + 16
2019-07-18 00:02:07 +08:00
if (overlap < 0) {
s.from[ax] -= overlap
s.to[ax] -= overlap
2019-08-01 06:01:47 +08:00
if (s.to[ax] + s.inflate > 32) {
s.to[ax] = 32 - s.inflate
2019-07-18 00:02:07 +08:00
}
}
}
})
})
}
//Rotation Limit
if (Format.rotation_limit && !old_format.rotation_limit && Format.rotate_cubes) {
Cube.all.forEach(cube => {
if (!cube.rotation.allEqual(0)) {
var axis = (cube.rotation_axis && getAxisNumber(cube.rotation_axis)) || 0;
var angle = limitNumber( Math.round(cube.rotation[axis]/22.5)*22.5, -45, 45 );
2020-03-05 03:56:17 +08:00
cube.rotation.V3_set(0, 0, 0)
2019-07-18 00:02:07 +08:00
cube.rotation[axis] = angle;
}
})
}
//Animation Mode
if (!Format.animation_mode && old_format.animation_mode) {
Animator.animations.length = 0;
ModelMeta.animation_path = '';
}
Canvas.updateAllPositions()
Canvas.updateAllBones()
Canvas.updateAllFaces()
updateSelection()
EditSession.initNewModel()
}
2020-05-31 21:54:04 +08:00
delete() {
delete Formats[this.id];
}
2019-07-18 00:02:07 +08:00
}
const Codecs = {};
class Codec {
constructor(id, data) {
if (!data) data = 0;
this.id = id;
Codecs[id] = this;
this.name = data.name || 'Unknown Format';
2020-03-05 03:56:17 +08:00
this.events = {};
2019-07-18 00:02:07 +08:00
Merge.function(this, data, 'load');
Merge.function(this, data, 'compile');
Merge.function(this, data, 'parse');
Merge.function(this, data, 'write');
Merge.function(this, data, 'overwrite');
Merge.function(this, data, 'export');
Merge.function(this, data, 'fileName');
Merge.function(this, data, 'afterSave');
Merge.function(this, data, 'afterDownload');
Merge.string(this, data, 'extension');
Merge.boolean(this, data, 'remember');
this.export_action = data.export_action;
}
//Import
load(model, file, add) {
if (!this.parse) return false;
if (!add) {
newProject(this.format)
}
if (file.path && isApp && this.remember && !file.no_file ) {
var name = pathToName(file.path, true);
ModelMeta.name = pathToName(name, false);
ModelMeta.export_path = file.path;
addRecentProject({
name,
path: file.path,
icon: Format.icon
})
}
this.parse(model, file.path)
}
//parse(model, path)
//Export
compile() {
2020-03-05 03:56:17 +08:00
this.dispatchEvent('compile', {content: ''})
2019-07-18 00:02:07 +08:00
return '';
}
export() {
var scope = this;
Blockbench.export({
2020-04-26 02:25:07 +08:00
resource_id: 'model',
2019-09-06 06:16:54 +08:00
type: scope.name,
extensions: [scope.extension],
name: scope.fileName(),
startpath: scope.startPath(),
content: scope.compile(),
2019-07-18 00:02:07 +08:00
custom_writer: isApp ? (a, b) => scope.write(a, b) : null,
}, path => scope.afterDownload(path))
}
fileName() {
return ModelMeta.name||Project.name||'model';
}
startPath() {
return ModelMeta.export_path;
}
write(content, path) {
var scope = this;
if (fs.existsSync(path) && this.overwrite) {
this.overwrite(content, path, path => scope.afterSave(path))
} else {
Blockbench.writeFile(path, {content}, path => scope.afterSave(path));
}
}
//overwrite(content, path, cb)
afterDownload(path) {
if (this.remember) {
Prop.project_saved = true;
}
Blockbench.showQuickMessage(tl('message.save_file', [path ? pathToName(path, true) : this.fileName()]));
}
afterSave(path) {
var name = pathToName(path, true)
2019-07-26 19:33:29 +08:00
if (Format.codec == this || this.id == 'project') {
2019-07-18 00:02:07 +08:00
if (this.id == 'project') {
ModelMeta.save_path = path;
} else {
ModelMeta.export_path = path;
}
ModelMeta.name = pathToName(path, false);
Prop.project_saved = true;
}
2019-07-26 19:33:29 +08:00
if (this.remember) {
addRecentProject({
name,
path: path,
icon: this.id == 'project' ? 'icon-blockbench_file' : Format.icon
});
}
2019-07-18 00:02:07 +08:00
Blockbench.showQuickMessage(tl('message.save_file', [name]));
}
2020-03-05 03:56:17 +08:00
//Events
dispatchEvent(event_name, data) {
var list = this.events[event_name]
if (!list) return;
for (var i = 0; i < list.length; i++) {
if (typeof list[i] === 'function') {
list[i](data)
}
}
}
on(event_name, cb) {
if (!this.events[event_name]) {
this.events[event_name] = []
}
this.events[event_name].safePush(cb)
}
2019-07-18 00:02:07 +08:00
}
//Import
function setupDragHandlers() {
Blockbench.addDragHandler(
'model',
2019-12-16 03:04:31 +08:00
{extensions: ['json', 'jem', 'jpm', 'java', 'bbmodel']},
2019-07-18 00:02:07 +08:00
function(files) {
loadModelFile(files[0])
}
)
Blockbench.addDragHandler(
'style',
2019-12-16 03:04:31 +08:00
{extensions: ['bbstyle', 'bbtheme']},
2019-07-18 00:02:07 +08:00
function(files) {
2019-12-16 03:04:31 +08:00
CustomTheme.import(files[0]);
2019-07-18 00:02:07 +08:00
}
)
Blockbench.addDragHandler(
'plugin',
{extensions: ['bbplugin', 'js']},
function(files) {
2020-04-26 02:25:07 +08:00
new Plugin().loadFromFile(files[0], true)
2019-07-18 00:02:07 +08:00
}
)
Blockbench.addDragHandler(
'texture',
{extensions: ['png', 'tga'], propagate: true, readtype: 'image'},
function(files, event) {
var texture_li = $(event.target).parents('li.texture')
if (texture_li.length) {
var tex = textures.findInArray('uuid', texture_li.attr('texid'))
if (tex) {
tex.fromFile(files[0])
2020-03-11 05:19:17 +08:00
TickUpdates.selection = true;
2019-07-18 00:02:07 +08:00
return;
}
}
files.forEach(function(f) {
new Texture().fromFile(f).add().fillParticle()
})
}
)
}
function loadModelFile(file) {
2019-07-26 19:33:29 +08:00
if (showSaveDialog()) {
resetProject();
2019-07-18 00:02:07 +08:00
var extension = pathToExtension(file.path);
2019-12-16 03:04:31 +08:00
if (extension == 'java') {
Codecs.modded_entity.load(file.content, file)
} else {
var model = autoParseJSON(file.content);
if (extension == 'bbmodel') {
Codecs.project.load(model, file)
} else if (extension == 'json') {
if (model.parent || model.elements || model.textures) {
Codecs.java_block.load(model, file)
} else if (model.format_version && !compareVersions('1.12.0', model.format_version)) {
Codecs.bedrock.load(model, file)
} else if (
model.format_version ||
Object.keys(model).filter((s) => s.match(/^geometry\./)).length
) {
Codecs.bedrock_old.load(model, file)
}
} else if (extension == 'jem') {
Codecs.optifine_entity.load(model, file)
} else if (extension == 'jpm') {
Codecs.optifine_part.load(model, file)
2019-07-18 00:02:07 +08:00
}
}
EditSession.initNewModel()
}
}
var Extruder = {
drawImage: function(file) {
Extruder.canvas = $('#extrusion_canvas').get(0)
var ctx = extrusion_canvas.getContext('2d')
setProgressBar('extrusion_bar', 0)
$('#scan_tolerance').on('input', function() {
$('#scan_tolerance_label').text($(this).val())
})
showDialog('image_extruder')
Extruder.ext_img = new Image()
Extruder.ext_img.src = isApp ? file.path : file.content
Extruder.image_file = file
Extruder.ext_img.style.imageRendering = 'pixelated'
ctx.imageSmoothingEnabled = false;
Extruder.ext_img.onload = function() {
ctx.clearRect(0, 0, 256, 256);
ctx.drawImage(Extruder.ext_img, 0, 0, 256, 256)
Extruder.width = Extruder.ext_img.naturalWidth
Extruder.height = Extruder.ext_img.naturalHeight
if (Extruder.width > 128) return;
var g = 256 / Extruder.width;
var p = 0
ctx.beginPath();
for (var x = 0; x <= 256; x += g) {
ctx.moveTo(0.5 + x + p, p);
ctx.lineTo(0.5 + x + p, 256 + p);
}
for (var x = 0; x <= 256; x += g) {
ctx.moveTo(p, 0.5 + x + p);
ctx.lineTo(256 + p, 0.5 + x + p);
}
ctx.strokeStyle = "black";
ctx.stroke();
}
//Grid
},
startConversion: function() {
var scan_mode = $('select#scan_mode option:selected').attr('id') /*areas, lines, columns, pixels*/
var isNewProject = elements.length === 0;
var pixel_opacity_tolerance = parseInt($('#scan_tolerance').val())
//Undo
Undo.initEdit({elements: selected, outliner: true, textures: []})
var texture = new Texture().fromFile(Extruder.image_file).add(false).fillParticle()
//var ext_x, ext_y;
var ctx = Painter.getCanvas(texture).getContext('2d')
var c = document.createElement('canvas')
var ctx = c.getContext('2d');
c.width = Extruder.ext_img.naturalWidth;
c.height = Extruder.ext_img.naturalHeight;
ctx.drawImage(Extruder.ext_img, 0, 0)
var image_data = ctx.getImageData(0, 0, c.width, c.height).data
var finished_pixels = {}
var cube_nr = 0;
var cube_name = texture.name.split('.')[0]
selected.empty()
//Scale Index
var scale_i = 1;
if (Extruder.width < Extruder.height) {
Extruder.width = Extruder.height;
}
scale_i = 16 / Extruder.width;
function isOpaquePixel(px_x, px_y) {
var opacity = image_data[(px_x + ctx.canvas.width * px_y) * 4 + 3]
return Math.isBetween(px_x, 0, Extruder.width-1)
&& Math.isBetween(px_y, 0, Extruder.height-1)
&& opacity >= pixel_opacity_tolerance;
}
function finishPixel(x, y) {
if (finished_pixels[x] === undefined) {
finished_pixels[x] = {}
}
finished_pixels[x][y] = true
}
function isPixelFinished(x, y) {
return (finished_pixels[x] !== undefined && finished_pixels[x][y] === true)
}
//Scanning
let ext_y = 0;
while (ext_y < Extruder.height) {
let ext_x = 0;
while (ext_x < Extruder.width) {
if (isPixelFinished(ext_x, ext_y) === false && isOpaquePixel(ext_x, ext_y) === true) {
//Search From New Pixel
var loop = true;
var rect = {x: ext_x, y: ext_y, x2: ext_x, y2: ext_y}
var safety_limit = 5000
//Expanding Loop
while (loop === true && safety_limit) {
var y_check, x_check, canExpandX, canExpandY;
//Expand X
if (scan_mode === 'areas' || scan_mode === 'lines') {
y_check = rect.y
x_check = rect.x2 + 1
canExpandX = true
while (y_check <= rect.y2) {
//Check If Row is Free
if (isOpaquePixel(x_check, y_check) === false || isPixelFinished(x_check, y_check) === true) {
canExpandX = false;
}
y_check += 1
}
if (canExpandX === true) {
rect.x2 += 1
}
} else {
canExpandX = false;
}
//Expand Y
if (scan_mode === 'areas' || scan_mode === 'columns') {
x_check = rect.x
y_check = rect.y2 + 1
canExpandY = true
while (x_check <= rect.x2) {
//Check If Row is Free
if (isOpaquePixel(x_check, y_check) === false || isPixelFinished(x_check, y_check) === true) {
canExpandY = false
}
x_check += 1
}
if (canExpandY === true) {
rect.y2 += 1
}
} else {
canExpandY = false;
}
//Conclusion
if (canExpandX === false && canExpandY === false) {
loop = false;
}
safety_limit--;
}
//Draw Rectangle
var draw_x = rect.x
var draw_y = rect.y
while (draw_y <= rect.y2) {
draw_x = rect.x
while (draw_x <= rect.x2) {
finishPixel(draw_x, draw_y)
draw_x++;
}
draw_y++;
}
var current_cube = new Cube({
name: cube_name+'_'+cube_nr,
autouv: 0,
from: [rect.x*scale_i, 0, rect.y*scale_i],
to: [(rect.x2+1)*scale_i, scale_i, (rect.y2+1)*scale_i],
faces: {
up: {uv:[rect.x*scale_i, rect.y*scale_i, (rect.x2+1)*scale_i, (rect.y2+1)*scale_i], texture: texture},
down: {uv:[rect.x*scale_i, (rect.y2+1)*scale_i, (rect.x2+1)*scale_i, rect.y*scale_i], texture: texture},
north: {uv:[(rect.x2+1)*scale_i, rect.y*scale_i, rect.x*scale_i, (rect.y+1)*scale_i], texture: texture},
south: {uv:[rect.x*scale_i, rect.y2*scale_i, (rect.x2+1)*scale_i, (rect.y2+1)*scale_i], texture: texture},
east: {uv:[rect.x2*scale_i, rect.y*scale_i, (rect.x2+1)*scale_i, (rect.y2+1)*scale_i], texture: texture, rotation: 90},
west: {uv:[rect.x*scale_i, rect.y*scale_i, (rect.x+1)*scale_i, (rect.y2+1)*scale_i], texture: texture, rotation: 270}
}
}).init()
selected.push(current_cube)
cube_nr++;
}
ext_x++;
}
ext_y++;
}
var group = new Group(cube_name).init().addTo()
selected.forEach(function(s) {
s.addTo(group).init()
})
Undo.finishEdit('add extruded texture', {elements: selected, outliner: true, textures: [textures[textures.length-1]]})
hideDialog()
}
}
//Export
function uploadSketchfabModel() {
if (elements.length === 0) {
return;
}
var dialog = new Dialog({
id: 'sketchfab_uploader',
title: 'dialog.sketchfab_uploader.title',
width: 540,
form: {
2020-04-26 02:25:07 +08:00
token: {label: 'dialog.sketchfab_uploader.token', value: settings.sketchfab_token.value, type: 'password'},
about_token: {type: 'info', text: tl('dialog.sketchfab_uploader.about_token', ['[sketchfab.com/settings/password](https://sketchfab.com/settings/password)'])},
2019-07-18 00:02:07 +08:00
name: {label: 'dialog.sketchfab_uploader.name'},
description: {label: 'dialog.sketchfab_uploader.description', type: 'textarea'},
tags: {label: 'dialog.sketchfab_uploader.tags', placeholder: 'Tag1 Tag2'},
2020-03-05 03:56:17 +08:00
animations: {label: 'dialog.sketchfab_uploader.animations', value: true, type: 'checkbox', condition: (Format.animation_mode && Animator.animations.length)},
//color: {type: 'color', label: 'dialog.sketchfab_uploader.color'},
2019-12-16 03:04:31 +08:00
draft: {label: 'dialog.sketchfab_uploader.draft', type: 'checkbox'},
// Category
2019-07-18 00:02:07 +08:00
divider: '_',
private: {label: 'dialog.sketchfab_uploader.private', type: 'checkbox'},
password: {label: 'dialog.sketchfab_uploader.password'},
},
onConfirm: function(formResult) {
if (formResult.token && !formResult.name) {
Blockbench.showQuickMessage('message.sketchfab.name_or_token', 1800)
return;
}
if (!formResult.tags.split(' ').includes('blockbench')) {
formResult.tags += ' blockbench';
}
var data = new FormData()
data.append('token', formResult.token)
data.append('name', formResult.name)
data.append('description', formResult.description)
data.append('tags', formResult.tags)
2019-12-16 03:04:31 +08:00
data.append('isPublished', !formResult.draft)
2020-03-05 03:56:17 +08:00
//data.append('background', JSON.stringify({color: formResult.color.toHexString()}))
2019-07-18 00:02:07 +08:00
data.append('private', formResult.private)
data.append('password', formResult.password)
data.append('source', 'blockbench')
settings.sketchfab_token.value = formResult.token
2020-02-08 02:06:24 +08:00
Codecs.gltf.compile({animations: formResult.animations}, (content) => {
var blob = new Blob([content], {type: "text/plain;charset=utf-8"});
var file = new File([blob], 'model.gltf')
2019-07-18 00:02:07 +08:00
data.append('modelFile', file)
$.ajax({
url: 'https://api.sketchfab.com/v3/models',
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(response) {
Blockbench.showMessageBox({
title: tl('message.sketchfab.success'),
2020-01-24 01:53:36 +08:00
message:
2020-03-05 03:56:17 +08:00
`[${formResult.name} on Sketchfab](https://sketchfab.com/models/${response.uid})`, //\n\n&nbsp;\n\n`+
//tl('message.sketchfab.setup_guide', '[Sketchfab Setup and Common Issues](https://blockbench.net/2020/01/22/sketchfab-setup-and-common-issues/)'),
2019-07-18 00:02:07 +08:00
icon: 'icon-sketchfab',
})
},
error: function(response) {
Blockbench.showQuickMessage('message.sketchfab.error', 1500)
console.error(response);
}
})
})
dialog.hide()
}
})
dialog.show()
}
//Json
function compileJSON(object, options) {
var output = ''
if (typeof options !== 'object') options = {}
function newLine(tabs) {
if (options.small === true) {return '';}
var s = '\n'
for (var i = 0; i < tabs; i++) {
s += '\t'
}
return s;
}
function handleVar(o, tabs) {
var out = ''
if (typeof o === 'string') {
//String
2020-03-05 03:56:17 +08:00
out += '"' + o.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n|\r\n/g, '\\n').replace(/\t/g, '\\t') + '"'
2019-07-18 00:02:07 +08:00
} else if (typeof o === 'boolean') {
//Boolean
out += (o ? 'true' : 'false')
} else if (typeof o === 'number') {
//Number
o = (Math.round(o*100000)/100000).toString()
out += o
} else if (o === null || o === Infinity || o === -Infinity) {
//Null
out += 'null'
2020-03-05 03:56:17 +08:00
} else if (typeof o === 'object' && o instanceof Array) {
2019-07-18 00:02:07 +08:00
//Array
var has_content = false
out += '['
for (var i = 0; i < o.length; i++) {
var compiled = handleVar(o[i], tabs+1)
if (compiled) {
var breaks = typeof o[i] === 'object'
if (has_content) {out += ',' + (breaks || options.small?'':' ')}
if (breaks) {out += newLine(tabs)}
out += compiled
has_content = true
}
}
if (typeof o[o.length-1] === 'object') {out += newLine(tabs-1)}
out += ']'
} else if (typeof o === 'object') {
//Object
var breaks = o.constructor.name !== 'oneLiner';
var has_content = false
out += '{'
for (var key in o) {
if (o.hasOwnProperty(key)) {
var compiled = handleVar(o[key], tabs+1)
if (compiled) {
if (has_content) {out += ',' + (breaks || options.small?'':' ')}
if (breaks) {out += newLine(tabs)}
out += '"' + key + '":' + (options.small === true ? '' : ' ')
out += compiled
has_content = true
}
}
}
if (breaks && has_content) {out += newLine(tabs-1)}
out += '}'
}
return out;
}
return handleVar(object, 1)
}
function autoParseJSON(data, feedback) {
if (data.substr(0, 4) === '<lz>') {
data = LZUTF8.decompress(data.substr(4), {inputEncoding: 'StorageBinaryString'})
}
if (data.charCodeAt(0) === 0xFEFF) {
data = data.substr(1)
}
try {
data = JSON.parse(data)
} catch (err1) {
data = data.replace(/\/\*[^(\*\/)]*\*\/|\/\/.*/g, '')
try {
data = JSON.parse(data)
} catch (err) {
if (feedback === false) return;
function logErrantPart(whole, start, length) {
var line = whole.substr(0, start).match(/\n/gm)
line = line ? line.length+1 : 1
var result = '';
var lines = whole.substr(start, length).split(/\n/gm)
lines.forEach((s, i) => {
result += `#${line+i} ${s}\n`
})
console.log(result.substr(0, result.length-1) + ' <-- HERE')
}
console.error(err)
var length = err.toString().split('at position ')[1]
if (length) {
length = parseInt(length)
var start = limitNumber(length-20, 0, Infinity)
logErrantPart(data, start, 1+length-start)
} else if (err.toString().includes('Unexpected end of JSON input')) {
logErrantPart(data, data.length-10, 10)
}
Blockbench.showMessageBox({
translateKey: 'invalid_file',
icon: 'error',
message: tl('message.invalid_file.message', [err])
})
return;
}
}
return data;
}
BARS.defineActions(function() {
2019-07-19 23:31:22 +08:00
new ModelFormat({
id: 'free',
icon: 'icon-format_free',
rotate_cubes: true,
2019-12-16 03:04:31 +08:00
bone_rig: true,
2020-04-26 02:25:07 +08:00
centered_grid: true,
2019-07-19 23:31:22 +08:00
optional_box_uv: true,
2020-01-24 01:53:36 +08:00
uv_rotation: true,
2020-03-05 03:56:17 +08:00
animation_mode: true,
2019-07-19 23:31:22 +08:00
})
2019-07-18 00:02:07 +08:00
//Import
2019-08-18 00:26:14 +08:00
new Action('open_model', {
2019-07-18 00:02:07 +08:00
icon: 'assessment',
category: 'file',
keybind: new Keybind({key: 79, ctrl: true}),
condition: () => (!EditSession.active || EditSession.hosting),
click: function () {
2020-01-24 01:53:36 +08:00
var startpath;
if (isApp && recent_projects && recent_projects.length) {
startpath = recent_projects[0].path;
if (typeof startpath == 'string') {
startpath = startpath.split(osfs);
startpath.pop();
startpath = startpath.join(osfs);
}
}
2019-07-18 00:02:07 +08:00
Blockbench.import({
2020-04-26 02:25:07 +08:00
resource_id: 'model',
2019-12-16 03:04:31 +08:00
extensions: ['json', 'jem', 'jpm', 'java', 'bbmodel'],
2020-01-24 01:53:36 +08:00
type: 'Model',
startpath
2019-07-18 00:02:07 +08:00
}, function(files) {
2019-09-04 15:37:38 +08:00
loadModelFile(files[0]);
2019-07-18 00:02:07 +08:00
})
}
})
2019-08-18 00:26:14 +08:00
new Action('add_model', {
2019-07-18 00:02:07 +08:00
icon: 'assessment',
category: 'file',
2019-12-16 03:04:31 +08:00
condition: _ => (Format.id == 'java_block'),
2019-07-18 00:02:07 +08:00
click: function () {
Blockbench.import({
2020-04-26 02:25:07 +08:00
resource_id: 'model',
2019-07-18 00:02:07 +08:00
extensions: ['json'],
type: 'JSON Model',
multiple: true,
}, function(files) {
files.forEach(file => {
var model = autoParseJSON(file.content)
Codecs.java_block.parse(model, file.path, true)
})
})
}
})
2019-08-18 00:26:14 +08:00
new Action('extrude_texture', {
2019-07-18 00:02:07 +08:00
icon: 'eject',
category: 'file',
2019-07-19 23:31:22 +08:00
condition: _ => !Project.box_uv,
2019-07-18 00:02:07 +08:00
click: function () {
Blockbench.import({
2020-04-26 02:25:07 +08:00
resource_id: 'texture',
2019-07-18 00:02:07 +08:00
extensions: ['png'],
type: 'PNG Texture',
readtype: 'image'
}, function(files) {
if (files.length) {
showDialog('image_extruder')
Extruder.drawImage(files[0])
}
})
}
})
//Export
2019-08-18 00:26:14 +08:00
new Action('export_over', {
2019-07-18 00:02:07 +08:00
icon: 'save',
category: 'file',
2019-07-19 23:31:22 +08:00
keybind: new Keybind({key: 83, ctrl: true}),
2019-07-18 00:02:07 +08:00
click: function () {
if (isApp) {
saveTextures()
if (Format.codec && Format.codec.compile) {
if (ModelMeta.export_path) {
Format.codec.write(Format.codec.compile(), ModelMeta.export_path)
2019-12-16 03:04:31 +08:00
} else if (ModelMeta.save_path) {
Codecs.project.write(Codecs.project.compile(), ModelMeta.save_path);
2019-07-18 00:02:07 +08:00
} else {
Format.codec.export()
}
}
if (Format.animation_mode) {
if (ModelMeta.animation_path) {
2019-07-22 05:17:36 +08:00
Blockbench.writeFile(ModelMeta.animation_path, {
2019-07-18 00:02:07 +08:00
content: autoStringify(Animator.buildFile())
})
2019-08-18 00:26:14 +08:00
} else if (Animator.animations.length) {
BarItems.export_animation_file.trigger()
2019-07-18 00:02:07 +08:00
}
}
} else {
saveTextures()
2019-12-16 03:04:31 +08:00
if (Format.codec && Format.codec.compile) {
Format.codec.export()
}
2019-07-18 00:02:07 +08:00
}
}
})
2019-07-19 23:31:22 +08:00
if (!isApp) {
2019-08-18 00:26:14 +08:00
new Action('export_asset_archive', {
2019-07-19 23:31:22 +08:00
icon: 'archive',
category: 'file',
condition: _ => Format && Format.codec,
click: function() {
var archive = new JSZip();
var content = Format.codec.compile()
2020-03-05 03:56:17 +08:00
var name = `${Format.codec.fileName()}.${Format.codec.extension}`
archive.file(name, content)
2019-07-19 23:31:22 +08:00
textures.forEach(tex => {
if (tex.mode === 'bitmap') {
archive.file(pathToName(tex.name) + '.png', tex.source.replace('data:image/png;base64,', ''), {base64: true});
}
2019-07-18 00:02:07 +08:00
})
2019-07-19 23:31:22 +08:00
archive.generateAsync({type: 'blob'}).then(content => {
Blockbench.export({
type: 'Zip Archive',
extensions: ['zip'],
name: 'assets',
startpath: ModelMeta.export_path,
content: content,
savetype: 'zip'
})
Prop.project_saved = true;
})
}
})
}
2019-08-18 00:26:14 +08:00
new Action('upload_sketchfab', {
2019-07-18 00:02:07 +08:00
icon: 'icon-sketchfab',
category: 'file',
click: function(ev) {
uploadSketchfabModel()
}
})
})