2
0
mirror of https://github.com/JannisX11/blockbench.git synced 2025-04-06 17:31:09 +08:00

Compare commits

...

72 Commits

Author SHA1 Message Date
JannisX11
b549e4c060 v4.12.4 [ci-build] 2025-03-28 20:45:46 +01:00
JannisX11
6b8c51ca6b Fix Error dragging group to outliner end 2025-03-27 21:31:40 +01:00
JannisX11
91ceb36f42 Add Split RGB Channels into Layers 2025-03-27 21:25:20 +01:00
JannisX11
9624352b2a Generate PBR maps tweaks 2025-03-27 21:24:55 +01:00
JannisX11
ae5378c7d1 Add setting to pick combined color 2025-03-27 20:17:22 +01:00
JannisX11
deb6927d50 Support for editing subsurface scattering in texture sets 2025-03-26 23:07:19 +01:00
JannisX11
46c20d5297 Add new skin variants for cow, pig, chicken, magmacube 2025-03-26 20:54:46 +01:00
JannisX11
72c3725a10 Fix height map blurry when converted to different pbr channel 2025-03-25 00:05:33 +01:00
JannisX11
bb80e4f928 Auto-load texture_sets when opening bedrock model
Improve texture set and pbr map name generation
2025-03-24 23:22:40 +01:00
JannisX11
4e5898bdd3 Fix Face properties selection issue 2025-03-23 14:38:19 +01:00
JannisX11
c15bbd1f77 Fix PBR action conditions
Fix context menu not closing when selecting texture
2025-03-23 13:42:38 +01:00
JannisX11
34dcf0a601 FIx "Keep Multi Texture Occupancy" doesn't work for mirrored UV 2025-03-23 13:20:48 +01:00
JannisX11
c4fd308f73 When generating PBR MER map, combine into existing MER texture as Add layer
Fix MER map not updating when editing layers
2025-03-22 23:34:21 +01:00
JannisX11
acdb41af74 Fix issue with loading animation controllers 2025-03-22 15:11:10 +01:00
JannisX11
c515592598 Fix Multiple images in the same imare project can overwrite each other on save 2025-03-19 23:28:38 +01:00
JannisX11
7c29703b3c Rename bone binding item slot 2025-03-19 23:09:49 +01:00
JannisX11
b0f9bc9dc1 Fix issues with generating MER map from existing texture
Fix MER map display issues
Materials can no longer be created from textures that are already in a material
2025-03-19 20:33:32 +01:00
JannisX11
e36c119082 FIx Open Parent & Adopt Textures still does an action when dismissing dialog 2025-03-16 23:23:03 +01:00
JannisX11
095ed3ef76 Merge branch 'master' into patch 2025-03-16 23:07:48 +01:00
JannisX11
834a97c47e Remove unused field 2025-02-27 23:26:18 +01:00
JannisX11
1289d43eea JSON exporter improvements
Move to new file
Improve performance
Fix  NaN is exported to JSON as NaN
2025-02-22 13:44:11 +01:00
JannisX11
a49f6494a8 Update webapp deploy config 2025-02-19 23:18:44 +01:00
JannisX11
690430ac68 v4.12.3 [ci-build] 2025-02-19 23:03:54 +01:00
JannisX11
717aed5517 Update lang files 2025-02-19 22:57:59 +01:00
JannisX11
73f4fe2cf7 Convert vertex snap amend edit ignore axis into one line 2025-02-19 22:04:22 +01:00
JannisX11
b22df10302 Merge branch 'patch' 2025-02-19 21:55:24 +01:00
JannisX11
8b81761068 Fix default actions cannot be removed from toolbars in some cases 2025-02-16 23:35:32 +01:00
JannisX11
b7d2dba3e8 Add experimental fix for bbmodel file extensions on android 2025-02-16 21:59:12 +01:00
JannisX11
41faa51738 Fix texture not updating when moving cube in per group texture format 2025-02-16 19:01:31 +01:00
JannisX11
a21c60c589 Fix selecting collections with invalid state can lock interface 2025-02-16 18:56:10 +01:00
JannisX11
824000b509 Fix edit session issue with undo selections enabled 2025-02-14 20:22:39 +01:00
JannisX11
43297d58cd Fix edit sessions not snycing correctly 2025-02-14 20:19:07 +01:00
JannisX11
e93ba41176 Fix face directions when extruding edge loop
Fix amend edit not working when extruding edges
2025-02-09 15:06:31 +01:00
JannisX11
65862e5747 Fix graph editor limit value not applying 2025-02-09 14:13:59 +01:00
JannisX11
458a8bf7f2 Change form linked ratio to update ratio when enabling link 2025-02-09 14:13:40 +01:00
JannisX11
c51a420178 Make sure selection is valid after mirror modeling mesh 2025-02-09 14:12:57 +01:00
JannisX11
dd4fbd9bfd Ignroe box UV when determining format version for bedrock geo 2025-02-09 13:40:14 +01:00
JannisX11
71b0bd77ea Fix Mesh wireframes sometimes do not update 2025-02-06 22:59:07 +01:00
JannisX11
2c0d8f11b3 Fix tiny numbers not rounded in minified bbmodel 2025-02-05 00:30:36 +01:00
JannisX11
e7dd11e6d9 Limit height of placeholder variable buttons list 2025-02-04 21:26:19 +01:00
JannisX11
03d640c569 Fix per group texture not being saved in custom formats 2025-02-02 00:16:09 +01:00
JannisX11
7953f13112 Fix dragging multiple groups in outliner can causes recursive structure 2025-02-01 14:52:20 +01:00
JannisX11
d207e00cae Add V3_set support vor three vectors 2025-01-31 21:41:28 +01:00
JannisX11
b9b3a1afa3 Fix material instances in face properties editor not applying to all cubes 2025-01-30 20:32:01 +01:00
JannisX11
65bf903fef Make minimum reference image size scale with zoom level 2025-01-30 20:11:19 +01:00
JannisX11
a26bf1c17f Add creaking skin preset 2025-01-30 01:07:07 +01:00
JannisX11
d766cd1678 Fix issue where undoing a selection of a deleted element would create invalid state
Make tool config API match type
2025-01-30 01:06:48 +01:00
JannisX11
368efc7c82 Update Readme 2025-01-21 20:30:27 +01:00
JannisX11
774ef189e1 Fix missing certificate identifier 2025-01-20 18:59:04 +01:00
JannisX11
0b99ed8787 v4.12.2 [ci-build] 2025-01-20 18:48:34 +01:00
JannisX11
5e51f95352 Update languages 2025-01-20 18:27:16 +01:00
JannisX11
75d5c59a9a Mirror modeling fix 2025-01-20 18:20:47 +01:00
JannisX11
8f0d81c5a3 Bluesky link on start screen
Remove console logs
2025-01-20 17:53:10 +01:00
JannisX11
213fd7fa40 Merge branch 'patch' 2025-01-20 12:57:11 +01:00
JannisX11
4c63e657ae Fix issues with retargeting animations
Fix animation undo issues
2025-01-20 12:56:50 +01:00
JannisX11
fac8535b21
Merge pull request from Nestorboy/fix-java-brush-matrix
Fixed Java brush matrix
2025-01-20 12:19:49 +01:00
JannisX11
545b052dee Improve molang handling and export 2025-01-20 11:53:27 +01:00
JannisX11
09bbf45c5f Revamp how undo tracks selections in regular edits 2025-01-19 17:50:40 +01:00
JannisX11
a11651b3ab Fix move tool not working with multi selected groups in some cases 2025-01-19 17:33:25 +01:00
JannisX11
f438cbb816 Fix select-all not multi-selecting root groups 2025-01-19 17:32:48 +01:00
JannisX11
2a5bd7cf07 Fix e-16 numbers in molang strings 2025-01-18 21:34:07 +01:00
Nestorboy
ee80888e71 Fix java block/item brush matrix
Since we're applying the brush matrix directly, we need to factor in the parent matrices too, since the brush is parented to the scene. By using the parent matrix instead, we make it account for all translations, rotations and scaling.
2025-01-17 01:52:56 +01:00
Nestorboy
97f6382e8a Revert "Fix brush outline offset in java block/item models"
This reverts commit 3039c281d22f0539afe0c1084f33a72a8a0ee847.
2025-01-17 01:47:26 +01:00
JannisX11
89bea015b1 Fix rounding errors being imported into molang fields 2025-01-16 23:29:25 +01:00
JannisX11
f1d3243f0f v4.12.1 [ci-build] 2025-01-16 16:01:59 +01:00
JannisX11
3039c281d2 Fix brush outline offset in java block/item models 2025-01-16 14:30:48 +01:00
JannisX11
54838b65b1 Turned gamepad controls off by default 2025-01-16 14:27:32 +01:00
JannisX11
8208382ccd Fix referencing selected collections fails without project 2025-01-16 14:26:02 +01:00
JannisX11
fc66268c64 Fix drag+dropping unselected groups in outliner moves selection instead
Fix dragging groups and elements at the same time not working
2025-01-16 14:11:57 +01:00
JannisX11
22d3497ce3 Fix invisible texture issue by disabling bleed fix setting 2025-01-16 14:02:42 +01:00
JannisX11
7488b73f9b Add setting to disable gamepad controls 2025-01-16 13:45:06 +01:00
JannisX11
3d88d91bf0 Fix issue with importing JEM models 2025-01-16 13:33:23 +01:00
64 changed files with 3698 additions and 2761 deletions

@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Build
@ -29,10 +29,10 @@ jobs:
npm install
npm run prepublish
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: '.'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

@ -1,9 +1,9 @@
# Blockbench
Blockbench is a free, modern model editor for low-poly and boxy models with pixel art textures.
Blockbench is a free and open source model editor for low-poly models with pixel art textures.
Models can be exported into standardized formats, to be shared, rendered, 3D-printed, or used in game engines. There are also multiple dedicated formats for Minecraft Java and Bedrock Edition with format-specific features.
Blockbench features a modern and intuitive UI, plugin support and innovative features. It is the industry standard for creating custom 3D models for the Minecraft Marketplace.
Blockbench features a modern and beginner friendly interface, but also offers lots of customization and advanced features for experienced 3D artists. Plugins can extend the functionality of the program even further.
Website and download: [blockbench.net](https://www.blockbench.net)
@ -27,6 +27,8 @@ To launch Blockbench from source, you can clone the repository, navigate to the
* Install [NodeJS](https://nodejs.org/en/).
* Then install all dependencies via
`npm install`
* Bundle the code via
`npm run bundle`
* Finally, launch Blockbench using
`npm run dev`
@ -34,7 +36,7 @@ To launch Blockbench from source, you can clone the repository, navigate to the
## Plugins
Blockbench supports Javascript-based plugins. Learn more about creating plugins on [https://www.blockbench.net/wiki/api/index](https://www.blockbench.net/wiki/api/index).
Blockbench supports Javascript-based plugins. Learn more about creating plugins on [https://www.blockbench.net/wiki/docs/plugin](https://www.blockbench.net/wiki/docs/plugin).

@ -1218,6 +1218,10 @@
}
/* Placeholders */
ul#placeholder_buttons {
max-height: 32%;
overflow: auto;
}
#placeholder_buttons li {
padding: 0px 8px;
height: 30px;

@ -98,6 +98,7 @@
<script src="js/util/array_util.js"></script>
<script src="js/util/event_system.js"></script>
<script src="js/util/property.js"></script>
<script src="js/util/json.js"></script>
<script src="js/interface/menu.js"></script>
<script src="js/interface/actions.js"></script>
<script src="js/interface/themes.js"></script>

@ -133,8 +133,8 @@ class Animation extends AnimationItem {
copy.animators = {}
for (var uuid in this.animators) {
let ba = this.animators[uuid]
var kfs = ba.keyframes
if ((kfs && kfs.length) || ba.rotation_global) {
let kfs = ba.keyframes
if ((kfs && kfs.length) || ba.rotation_global || !save) {
let ba_copy = copy.animators[uuid] = {
name: ba.name,
type: ba.type,
@ -160,10 +160,10 @@ class Animation extends AnimationItem {
if (this.length) ani_tag.animation_length = Math.roundTo(this.length, 4);
if (this.override) ani_tag.override_previous_animation = true;
if (this.anim_time_update) ani_tag.anim_time_update = this.anim_time_update.replace(/\n/g, '');
if (this.blend_weight) ani_tag.blend_weight = this.blend_weight.replace(/\n/g, '');
if (this.start_delay) ani_tag.start_delay = this.start_delay.replace(/\n/g, '');
if (this.loop_delay && ani_tag.loop) ani_tag.loop_delay = this.loop_delay.replace(/\n/g, '');
if (this.anim_time_update) ani_tag.anim_time_update = exportMolang(this.anim_time_update);
if (this.blend_weight) ani_tag.blend_weight = exportMolang(this.blend_weight);
if (this.start_delay) ani_tag.start_delay = exportMolang(this.start_delay);
if (this.loop_delay && ani_tag.loop) ani_tag.loop_delay = exportMolang(this.loop_delay);
ani_tag.bones = {};
for (var uuid in this.animators) {
@ -1769,12 +1769,17 @@ BARS.defineActions(function() {
temp_animators[target_uuid] = new animator.constructor(target_uuid, animation);
copyAnimator(temp_animators[target_uuid], target_animator);
}
let tempsave_current_animator = !temp_animators[animator.uuid];
if (tempsave_current_animator) {
temp_animators[animator.uuid] = new animator.constructor(animator.uuid, animation);
copyAnimator(temp_animators[animator.uuid], animator);
}
copyAnimator(target_animator, temp_animators[animator.uuid] ?? animator);
// Reset animator
if (!temp_animators[animator.uuid]) {
temp_animators[animator.uuid] = new animator.constructor(animator.uuid, animation);
copyAnimator(temp_animators[animator.uuid], animator);
if (tempsave_current_animator) {
resetAnimator(animator)
}
}

@ -75,6 +75,7 @@ class AnimationControllerState {
target: '',
condition: ''
};
this.transitions.push(transition);
if (typeof a == 'object' && typeof a.uuid == 'string' && a.uuid.length == 36) {
// Internal
Object.assign(transition, a);
@ -90,11 +91,13 @@ class AnimationControllerState {
setTimeout(() => {
// Delay to after loading controller so that all states can be found
let state_match = this.controller.states.find(state => state !== this && state.name == key);
if (state_match) transition.target = state_match.uuid;
if (state_match) {
let updated_transition = this.transitions.find(t => t.uuid == transition.uuid) ?? transitions;
updated_transition.target = state_match.uuid;
}
}, 0);
}
}
this.transitions.push(transition);
})
}
if (data.particles instanceof Array) {

@ -1470,8 +1470,8 @@ Interface.definePanels(function() {
<ul id="placeholder_buttons">
<li v-for="button in buttons" :key="button.id" :class="{placeholder_slider: button.type == 'slider'}" @click="button.type == 'impulse' && changeButtonValue(button, $event)" :buttontype="button.type">
<i v-if="button.type == 'impulse'" class="material-icons">play_arrow</i>
<input v-if="button.type == 'toggle'" type="checkbox" class="tab_target" :value="button.value == 1" @change="changeButtonValue(button, $event)" :id="'placeholder_button_'+button.id">
<numeric-input v-if="button.type == 'slider'" class="dark_bordered tab_target" :step="button.step" :min="button.min" :max="button.max" v-model="button.value" @input="changeButtonValue(button, $event)" />
<input v-if="button.type == 'toggle'" type="checkbox" :value="button.value == 1" @change="changeButtonValue(button, $event)" :id="'placeholder_button_'+button.id">
<numeric-input v-if="button.type == 'slider'" :step="button.step" :min="button.min" :max="button.max" v-model="button.value" @input="changeButtonValue(button, $event)" />
<label :for="'placeholder_button_'+button.id" @mousedown="slideButton(button, $event)" @touchstart="slideButton(button, $event)">{{ button.id }}</label>
</li>
</ul>

@ -94,11 +94,8 @@ class Keyframe {
data_point = this.data_points[data_point];
if (!data_point || !data_point[axis]) {
return this.transform ? 0 : '';
} else if (!isNaN(data_point[axis])) {
let num = parseFloat(data_point[axis]);
return isNaN(num) ? 0 : num;
} else {
return data_point[axis]
return exportMolang(data_point[axis])
}
}
calc(axis, data_point = 0) {
@ -113,6 +110,7 @@ class Keyframe {
}
set(axis, value, data_point = 0) {
if (data_point) data_point = Math.clamp(data_point, 0, this.data_points.length-1);
if (typeof value == 'number') value = Math.roundTo(value, 10).toString();
if (this.data_points[data_point]) {
if (this.uniform) {
this.data_points[data_point].x = value;

@ -931,7 +931,7 @@ Interface.definePanels(() => {
let padding = 16;
let min_size = 2.4;
let unit_size = Math.clamp(max-min, min_size, 1e4);
let unit_size = Math.clamp(max-min, min_size, Timeline.graph_editor_limit);
this.graph_size = (clientHeight - 2*padding) / unit_size;
let blend = Math.clamp(1 - (max-min) / min_size, 0, 1)
this.graph_offset = clientHeight - padding + (this.graph_size * (min - unit_size/2 * blend ) );

@ -310,7 +310,13 @@ Object.assign(Blockbench, {
saveAs(blob, file_name)
} else {
var blob = new Blob([options.content], {type: "text/plain;charset=utf-8"});
let type = 'text/plain;charset=utf-8';
if (file_name.endsWith('json')) {
type = 'application/json;charset=utf-8';
} else if (file_name.endsWith('bbmodel')) {
type = 'model/vnd.blockbench.bbmodel';
}
var blob = new Blob([options.content], {type});
saveAs(blob, file_name, {autoBOM: true})
}

@ -1534,17 +1534,21 @@ class Toolbar {
}
}
}
/**
* Builds the toolbar from data
* @param {object} data Data used to build the toolbar
* @param {boolean} force If true, customization data will be ignored. Used when resetting toolbar
*/
build(data, force) {
var scope = this;
//Items
this.children.length = 0;
var items = data.children
if (!force && BARS.stored[scope.id] && typeof BARS.stored[scope.id] === 'object') {
items = BARS.stored[scope.id]
if (!force && BARS.stored[this.id] && typeof BARS.stored[this.id] === 'object') {
items = BARS.stored[this.id];
if (data.children) {
// Add new actions to existing toolbars
// Add new actions (newly added via bb update) to existing toolbars
data.children.forEach((key, index) => {
if (typeof key == 'string' && key.length > 1 && !items.includes(key) && !Keybinds.stored[key] && BarItems[key]) {
if (typeof key == 'string' && key.length > 1 && !items.includes(key) && !Keybinds.stored[key] && BARS.stored._known?.includes(key) == false && BarItems[key]) {
// Figure out best index based on item before. Otherwise use index from original array
let prev_index = items.indexOf(data.children[index-1]);
if (prev_index != -1) index = prev_index+1;
@ -1554,7 +1558,7 @@ class Toolbar {
}
}
if (items && items instanceof Array) {
var content = $(scope.node).find('div.content')
var content = $(this.node).find('div.content')
content.children().detach()
for (var itemPosition = 0; itemPosition < items.length; itemPosition++) {
let item = items[itemPosition];
@ -1566,7 +1570,10 @@ class Toolbar {
continue;
}
if (typeof item == 'string') item = BarItems[item]
if (typeof item == 'string') {
BARS.stored._known?.safePush(item);
item = BarItems[item];
}
if (item) {
item.pushToolbar(this);
@ -1581,8 +1588,8 @@ class Toolbar {
}
}
}
$(scope.node).toggleClass('no_wrap', this.no_wrap)
$(scope.node).toggleClass('vertical', this.vertical)
$(this.node).toggleClass('no_wrap', this.no_wrap)
$(this.node).toggleClass('vertical', this.vertical)
if (data.default_place) {
this.toPlace(this.id)
}
@ -1748,7 +1755,10 @@ class Toolbar {
}
})
BARS.stored[this.id] = arr;
if (arr.equals(this.default_children)) {
let identical_to_default = this.default_children.length == arr.length && this.default_children.allAre((item, i) => {
return arr[i] == item || (typeof arr[i] == 'string' && arr[i].startsWith(item));
})
if (identical_to_default) {
delete BARS.stored[this.id];
}
// Temporary fix
@ -1779,7 +1789,9 @@ Toolbar.prototype.menu = new Menu([
])
const BARS = {
stored: {},
stored: {
_known: []
},
editing_bar: undefined,
action_definers: [],
condition: Condition,
@ -2145,6 +2157,9 @@ const BARS = {
stored = JSON.parse(stored)
if (typeof stored === 'object') {
BARS.stored = stored;
if (!BARS.stored._known) {
BARS.stored._known = [];
}
}
}
@ -2266,9 +2281,6 @@ const BARS = {
}
})
}
Blockbench.onUpdateTo('4.4.0-beta.0', () => {
delete BARS.stored.brush;
})
Toolbars.brush = new Toolbar({
id: 'brush',
no_wrap: true,

@ -535,6 +535,8 @@ window.MessageBox = class MessageBox extends Dialog {
super(options.id, options);
this.options = options;
if (!options.buttons) this.buttons = ['dialog.ok'];
this.cancelIndex = Math.min(this.buttons.length-1, this.cancelIndex);
this.confirmIndex = Math.min(this.buttons.length-1, this.confirmIndex);
this.callback = callback;
}
close(button, result, event) {
@ -687,6 +689,7 @@ window.ToolConfig = class ToolConfig extends Dialog {
}
save() {
localStorage.setItem(`tool_config.${this.id}`, JSON.stringify(this.options));
return this;
}
changeOptions(options) {
for (let key in options) {
@ -696,6 +699,7 @@ window.ToolConfig = class ToolConfig extends Dialog {
this.form.setValues(options);
}
this.save();
return this;
}
close(button, event) {
this.save();
@ -712,6 +716,7 @@ window.ToolConfig = class ToolConfig extends Dialog {
this.object.style.top = (anchor_position.top+anchor.offsetHeight) + 'px';
this.object.style.left = Math.clamp(anchor_position.left - 30, 0, window.innerWidth-this.object.clientWidth - (this.title ? 0 : 30)) + 'px';
}
return this;
}
build() {
if (this.object) this.object.remove();

@ -348,8 +348,9 @@ class InputForm extends EventSystem {
linked_ratio_toggle.addEventListener('click', event => {
data.linked_ratio = !data.linked_ratio;
if (data.linked_ratio) {
updateInputs(vector_inputs[0]);
scope.updateValues();
initial_value = vector_inputs.map(v => v.value);
// updateInputs(vector_inputs[0]);
// scope.updateValues();
}
updateState();
})

@ -331,6 +331,7 @@ const MenuBar = {
'adjust_curves',
new MenuSeparator('filters'),
'limit_to_palette',
'split_rgb_into_layers',
'clear_unused_texture_space',
new MenuSeparator('transform'),
'flip_texture_x',

@ -436,7 +436,7 @@ const Settings = {
Canvas.updateShading()
}});
new Setting('antialiasing', {category: 'preview', value: true, requires_restart: true});
new Setting('antialiasing_bleed_fix', {category: 'preview', value: true, requires_restart: true});
new Setting('antialiasing_bleed_fix', {category: 'preview', value: false, requires_restart: true});
new Setting('fov', {category: 'preview', value: 45, type: 'number', min: 1, max: 120, onChange(val) {
Preview.all.forEach(preview => preview.setFOV(val));
}});
@ -490,6 +490,7 @@ const Settings = {
Preview.all.forEach(viewport => viewport.controls.zoomSpeed = value / 100 * 1.5)
}});
new Setting('editor_2d_zoom_speed', {category: 'controls', value: 100, min: 10, max: 1000, type: 'number'});
new Setting('gamepad_controls', {category: 'controls', value: false, name: 'Gamepad Controls', description: 'Use a gamepad or 3D mouse to navigate the viewport'});
new Setting('double_click_switch_tools',{category: 'controls', value: true});
new Setting('canvas_unselect', {category: 'controls', value: false});
new Setting('selection_tolerance', {category: 'controls', value: 10, type: 'number', min: 1, max: 50});
@ -527,6 +528,7 @@ const Settings = {
new Setting('outlines_in_paint_mode', {category: 'paint', value: true});
new Setting('move_with_selection_tool', {category: 'paint', value: true});
new Setting('pick_color_opacity', {category: 'paint', value: false});
new Setting('pick_combined_color', {category: 'paint', value: false});
new Setting('paint_through_transparency', {category: 'paint', value: true});
new Setting('paint_side_restrict', {category: 'paint', value: true});
new Setting('paint_with_stylus_only', {category: 'paint', value: false});
@ -626,9 +628,8 @@ const Settings = {
new Setting('sketchfab_token', {category: 'export', value: '', type: 'password'});
new Setting('credit', {category: 'export', value: 'Made with Blockbench', type: 'text'});
Blockbench.onUpdateTo('4.7.1', () => {
settings.brush_opacity_modifier.set('none');
settings.brush_size_modifier.set('none');
Blockbench.onUpdateTo('4.12.1', () => {
settings.antialiasing_bleed_fix.set(false);
})
},
setupProfiles() {

@ -573,23 +573,39 @@ ModelLoader.loaders = {};
});
documentReady.then(() => {
//Twitter
let twitter_ad;
if (!settings.classroom_mode.value && Blockbench.startup_count < 20 && Blockbench.startup_count % 5 === 4) {
twitter_ad = true;
addStartScreenSection('twitter_link', {
color: '#1da1f2',
//Bluesky
let bsky_ad;
Blockbench.onUpdateTo('4.12.2', () => {
//Bluesky
if (!settings.classroom_mode.value) {
bsky_ad = true;
addStartScreenSection('bluesky_link', {
color: 'rgb(32, 139, 254);',
text_color: '#ffffff',
graphic: {type: 'icon', icon: 'fab.fa-bluesky'},
text: [
{type: 'h3', text: 'Blockbench on Bluesky'},
{text: 'Follow Blockbench on Bluesky for the latest news & cool models from the community! [@blockbench.net](https://bsky.app/profile/blockbench.net)'}
],
last: true
})
}
})
if (!settings.classroom_mode.value && !bsky_ad && Blockbench.startup_count < 20 && Blockbench.startup_count % 5 === 4) {
bsky_ad = true;
addStartScreenSection('bluesky_link', {
color: 'rgb(32, 139, 254);',
text_color: '#ffffff',
graphic: {type: 'icon', icon: 'fab.fa-twitter'},
graphic: {type: 'icon', icon: 'fab.fa-bluesky'},
text: [
{type: 'h2', text: 'Blockbench on Twitter'},
{text: 'Follow Blockbench on Twitter for the latest news as well as cool models from the community! [twitter.com/blockbench](https://twitter.com/blockbench/)'}
{type: 'h3', text: 'Blockbench on Bluesky'},
{text: 'Follow Blockbench on Bluesky for the latest news & cool models from the community! [@blockbench.net](https://bsky.app/profile/blockbench.net)'}
],
last: true
})
}
//Discord
if (!settings.classroom_mode.value && Blockbench.startup_count < 6 && !twitter_ad) {
if (!settings.classroom_mode.value && Blockbench.startup_count < 6 && !bsky_ad) {
addStartScreenSection('discord_link', {
color: '#5865F2',
text_color: '#ffffff',

@ -263,7 +263,7 @@ var codec = new Codec('project', {
if (Animation.all.length) {
model.animations = [];
Animation.all.forEach(a => {
model.animations.push(a.getUndoCopy({bone_names: true, absolute_paths: options.absolute_paths}, true))
model.animations.push(a.getUndoCopy({absolute_paths: options.absolute_paths}, true))
})
}
if (AnimationController.all.length) {
@ -332,15 +332,11 @@ var codec = new Codec('project', {
if (options.raw) {
return model;
} else if (options.compressed) {
var json_string = JSON.stringify(model);
var json_string = compileJSON(model, {small: true});
var compressed = '<lz>'+LZUTF8.compress(json_string, {outputEncoding: 'StorageBinaryString'});
return compressed;
} else {
if (Settings.get('minify_bbmodel') || options.minify) {
return JSON.stringify(model);
} else {
return compileJSON(model);
}
return compileJSON(model, {small: Settings.get('minify_bbmodel') || options.minify});
}
},
parse(model, path) {

@ -96,7 +96,8 @@ window.BedrockEntityManager = class BedrockEntityManager {
}
}
if (valid_textures_list.length == 1) {
new Texture({keep_size: true, render_mode}).fromPath(valid_textures_list[0]).add()
let texture = new Texture({keep_size: true, render_mode}).fromPath(valid_textures_list[0]).add()
if (isApp) loadAdjacentTextureSet(texture);
if (render_mode == 'layered') {
updateLayeredTextures();
}
@ -169,14 +170,15 @@ window.BedrockEntityManager = class BedrockEntityManager {
cancelIndex: 2,
onButton(index) {
dialog.hide();
let textures_to_import = [];
if (index == 1) {
valid_textures_list.forEach(path => {
new Texture({keep_size: true, render_mode}).fromPath(path).add()
})
textures_to_import = valid_textures_list;
} else if (index == 0) {
selected_textures.forEach(path => {
new Texture({keep_size: true, render_mode}).fromPath(path).add()
})
textures_to_import = selected_textures;
}
for (let path of textures_to_import) {
let texture = new Texture({keep_size: true, render_mode}).fromPath(path).add();
if (isApp) loadAdjacentTextureSet(texture);
}
if (render_mode == 'layered') {
updateLayeredTextures();
@ -322,7 +324,8 @@ window.BedrockEntityManager = class BedrockEntityManager {
} else {
function tryItWith(extension) {
if (fs.existsSync(texture_path+'.'+extension)) {
var texture = new Texture({keep_size: true}).fromPath(texture_path+'.'+extension).add()
var texture = new Texture({keep_size: true}).fromPath(texture_path+'.'+extension).add();
loadAdjacentTextureSet(texture);
return true;
}
}
@ -454,6 +457,7 @@ window.BedrockBlockManager = class BedrockBlockManager {
])
if (full_texture_path) {
let texture = new Texture({keep_size: true}).fromPath(full_texture_path).add();
if (isApp) loadAdjacentTextureSet(texture);
if (target == '*') {
texture.use_as_default = true;
@ -1051,6 +1055,7 @@ function getFormatVersion() {
}
}
for (let cube of Cube.all) {
if (cube.box_uv) continue;
for (let fkey in cube.faces) {
if (cube.faces[fkey].rotation) return '1.21.0';
}

@ -24,4 +24,5 @@ new ModelFormat({
animation_mode: true,
animated_textures: true,
locators: true,
pbr: true,
})

@ -1,7 +1,7 @@
(function() {
let codec = new Codec('image', {
name: tl('format.name'),
name: tl('format.image'),
extension: 'png',
remember: true,
load_filter: {

@ -453,7 +453,7 @@ var codec = new Codec('java_block', {
open_with_textures: {text: 'message.child_model_only.open_with_textures', condition: Texture.all.length > 0}
}
}, (result) => {
if (result) {
if (typeof result == 'string') {
let parent = model.parent.replace(/\w+:/, '');
let path_arr = path.split(osfs);
let index = path_arr.length - path_arr.indexOf('models');

@ -254,7 +254,7 @@ var codec = new Codec('optifine_entity', {
let texture = importTexture(b.texture, b.textureSize);
let group = 0;
if (!model._is_jpm) {
let group = new Group({
group = new Group({
name: b.part,
origin: b.translate,
rotation: b.rotate,

File diff suppressed because it is too large Load Diff

@ -487,149 +487,6 @@ const Extruder = {
Undo.finishEdit('Add extruded texture', {elements: selected, outliner: true, textures: [Texture.all[Texture.all.length-1]]})
}
}
//Json
function compileJSON(object, options = {}) {
let indentation = options.indentation;
if (typeof indentation !== 'string') {
switch (settings.json_indentation.value) {
case 'spaces_4': indentation = ' '; break;
case 'spaces_2': indentation = ' '; break;
case 'tabs': default: indentation = '\t'; break;
}
}
function newLine(tabs) {
if (options.small === true) {return '';}
let s = '\n';
for (let i = 0; i < tabs; i++) {
s += indentation;
}
return s;
}
function escape(string) {
return string.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n|\r\n/g, '\\n').replace(/\t/g, '\\t')
}
function handleVar(o, tabs, breaks = true) {
var out = ''
if (typeof o === 'string') {
//String
out += '"' + escape(o) + '"'
} else if (typeof o === 'boolean') {
//Boolean
out += (o ? 'true' : 'false')
} else if (o === null || o === Infinity || o === -Infinity) {
//Null
out += 'null'
} else if (typeof o === 'number') {
//Number
o = (Math.round(o*100000)/100000).toString()
out += o
} else if (o instanceof Array) {
//Array
let has_content = false
let multiline = !!o.find(item => typeof item === 'object');
if (!multiline) {
let length = 0;
o.forEach(item => {
length += typeof item === 'string' ? (item.length+4) : 3;
});
if (length > 140) multiline = true;
}
out += '['
for (var i = 0; i < o.length; i++) {
var compiled = handleVar(o[i], tabs+1)
if (compiled) {
if (has_content) {out += ',' + ((options.small || multiline) ? '' : ' ')}
if (multiline) {out += newLine(tabs)}
out += compiled
has_content = true
}
}
if (multiline) {out += newLine(tabs-1)}
out += ']'
} else if (typeof o === 'object') {
//Object
breaks = 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, breaks)
if (compiled) {
if (has_content) {out += ',' + (breaks || options.small?'':' ')}
if (breaks) {out += newLine(tabs)}
out += '"' + escape(key) + '":' + (options.small === true ? '' : ' ')
out += compiled
has_content = true
}
}
}
if (breaks && has_content) {out += newLine(tabs-1)}
out += '}'
}
return out;
}
let file = handleVar(object, 1);
if ((settings.final_newline.value && options.final_newline != false) || options.final_newline == true) {
file += '\n';
}
return file;
}
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;
if (data.match(/\n\r?[><]{7}/)) {
Blockbench.showMessageBox({
title: 'message.invalid_file.title',
icon: 'fab.fa-git-alt',
message: 'message.invalid_file.merge_conflict'
})
return;
}
let error_part = '';
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`
})
error_part = result.substr(0, result.length-1) + ' <-- HERE';
console.log(error_part);
}
console.error(err)
var length = err.toString().split('at position ')[1]
if (length) {
length = parseInt(length)
var start = limitNumber(length-32, 0, Infinity)
logErrantPart(data, start, 1+length-start)
} else if (err.toString().includes('Unexpected end of JSON input')) {
logErrantPart(data, data.length-16, 10)
}
Blockbench.showMessageBox({
translateKey: 'invalid_file',
icon: 'error',
message: tl('message.invalid_file.message', [err]) + (error_part ? `\n\n\`\`\`\n${error_part}\n\`\`\`` : '')
})
return;
}
}
return data;
}
BARS.defineActions(function() {
@ -719,7 +576,9 @@ BARS.defineActions(function() {
Codecs.project.write(Codecs.project.compile(), Project.save_path);
}
if (Project.export_path && export_codec?.compile) {
export_codec.write(export_codec.compile(), Project.export_path)
if (export_codec.id != 'image') {
export_codec.write(export_codec.compile(), Project.export_path)
}
} else if (export_codec?.export && !Project.save_path) {
if (export_codec.id === 'project' || settings.dialog_save_codec.value == false) {

@ -2208,9 +2208,9 @@ BARS.defineActions(function() {
Mesh.selected.forEach(mesh => {
let original_vertices = mesh.getSelectedVertices().slice();
let selected_edges = mesh.getSelectedEdges(true);
let selected_face_keys = mesh.getSelectedFaces();
let new_vertices;
let new_face_keys = [];
let selected_face_keys = mesh.getSelectedFaces();
if (original_vertices.length && (BarItems.selection_mode.value == 'vertex' || BarItems.selection_mode.value == 'edge')) {
selected_face_keys.empty();
}
@ -2379,13 +2379,13 @@ BARS.defineActions(function() {
if (vertices.length == 2) delete mesh.faces[selected_face_keys[face_index]];
})
// Create Face between extruded edges
// Create Faces for extruded edges
let new_faces = [];
selected_edges.forEach(edge => {
let face, sorted_vertices;
for (let fkey in mesh.faces) {
let face2 = mesh.faces[fkey];
let vertices = face2.getSortedVertices();
let vertices = face2.vertices;
if (vertices.includes(edge[0]) && vertices.includes(edge[1])) {
face = face2;
sorted_vertices = vertices;
@ -2400,6 +2400,9 @@ BARS.defineActions(function() {
let new_face = new MeshFace(mesh, face).extend({
vertices: [a, b, c, d]
});
if (new_face.getAngleTo(face) > 90) {
new_face.invert();
}
let [face_key] = mesh.addFaces(new_face);
new_face_keys.push(face_key);
new_faces.push(new_face);
@ -2453,7 +2456,6 @@ BARS.defineActions(function() {
}},
even_extend: {type: 'checkbox', value: false, label: 'edit.extrude_mesh_selection.even_extend'},
}, form => {
console.log(form)
runEdit(true, form.extend, form.direction_mode, form.even_extend);
})
}

@ -284,7 +284,9 @@ const MirrorModeling = {
} else {
// change
let original_vkey = pre_part_connections.vertices[vkey];
new_face.uv[new_vkey] = original_face.uv[original_vkey].slice();
if (original_face.uv[original_vkey]) {
new_face.uv[new_vkey] = original_face.uv[original_vkey].slice();
}
}
})
new_face.invert();
@ -294,8 +296,14 @@ const MirrorModeling = {
[new_face_key] = mesh.addFaces(new_face);
}
}
}
let selected_vertices = mesh.getSelectedVertices(true);
selected_vertices.replace(selected_vertices.filter(vkey => mesh.vertices[vkey]));
let selected_edges = mesh.getSelectedEdges(true);
selected_edges.replace(selected_edges.filter(edge => edge.allAre(vkey => mesh.vertices[vkey])));
let selected_faces = mesh.getSelectedFaces(true);
selected_faces.replace(selected_faces.filter(fkey => mesh.faces[fkey]));
let {preview_controller} = mesh;
preview_controller.updateGeometry(mesh);
preview_controller.updateFaces(mesh);

@ -335,9 +335,9 @@ const Vertexsnap = {
let mode = BarItems.vertex_snap_mode.get();
function ignoreVectorAxes(vector) {
if (options.ignore_x) vector.x = 0;
if (options.ignore_y) vector.y = 0;
if (options.ignore_z) vector.z = 0;
if (options.ignore_axis?.x) vector.x = 0;
if (options.ignore_axis?.y) vector.y = 0;
if (options.ignore_axis?.z) vector.z = 0;
}
if (Vertexsnap.move_origin) {
@ -497,9 +497,7 @@ const Vertexsnap = {
y: tl('edit.vertex_snap.align.align_axis', 'Y'),
z: tl('edit.vertex_snap.align.align_axis', 'Z'),
}},
ignore_x: {type: 'checkbox', label: tl('edit.vertex_snap.ignore_axis', 'X'), value: false},
ignore_y: {type: 'checkbox', label: tl('edit.vertex_snap.ignore_axis', 'Y'), value: false},
ignore_z: {type: 'checkbox', label: tl('edit.vertex_snap.ignore_axis', 'Z'), value: false},
ignore_axis: {type: 'inline_multi_select', label: tl('edit.vertex_snap.ignore_axis', ''), options: {x: 'X', y: 'Y', z: 'Z'}, value: {x: false, y: false, z: false}},
}, form => {
Vertexsnap.snap(data, form, true);
})

@ -1152,7 +1152,7 @@
}
})
}
_has_groups = Format.bone_rig && Group.first_selected && Group.first_selected.matchesSelection() && Toolbox.selected.transformerMode == 'translate';
_has_groups = Format.bone_rig && Group.first_selected && Toolbox.selected.transformerMode == 'translate';
var rotate_group = Format.bone_rig && Group.first_selected && (Toolbox.selected.transformerMode == 'rotate');
if (Toolbox.selected.id == 'move_tool') {

@ -25,8 +25,7 @@ class Collection {
if (Modes.animate && Animation.selected && !(event?.ctrlOrCmd || Pressing.overrides.ctrl)) {
Timeline.animators.empty();
}
for (let uuid of this.children) {
let node = OutlinerNode.uuids[uuid];
for (let node of this.getChildren()) {
if (Modes.animate && Animation.selected) {
if (node.constructor.animator) {
let animator = Animation.selected.getBoneAnimator(node);
@ -363,7 +362,7 @@ Object.defineProperty(Collection, 'all', {
})
Object.defineProperty(Collection, 'selected', {
get() {
return Project.collections.filter(c => c.selected);
return Project ? Project.collections.filter(c => c.selected) : [];
}
})

@ -399,6 +399,7 @@ class Group extends OutlinerNode {
obj.locked = this.locked;
obj.visibility = this.visibility;
obj.autouv = this.autouv;
obj.selected = Group.multi_selected.includes(this);
}
if (this.rotation.allEqual(0)) {
@ -562,7 +563,7 @@ new Property(Group, 'string', 'bedrock_binding', {condition: {formats: ['bedrock
new Property(Group, 'array', 'cem_animations', {condition: {formats: ['optifine_entity']}});
new Property(Group, 'boolean', 'cem_attach', {condition: {formats: ['optifine_entity']}});
new Property(Group, 'number', 'cem_scale', {condition: {formats: ['optifine_entity']}});
new Property(Group, 'string', 'texture', {condition: {formats: ['optifine_entity']}});
new Property(Group, 'string', 'texture', {condition: {features: ['per_group_texture']}});
//new Property(Group, 'vector2', 'texture_size', {condition: {formats: ['optifine_entity']}});
new Property(Group, 'vector', 'skin_original_origin', {condition: {formats: ['skin']}});
new Property(Group, 'number', 'color');
@ -738,7 +739,7 @@ BARS.defineActions(function() {
showPresetMenu(event) {
new Menu([
{
name: 'Main Hand',
name: 'Item Slot',
icon: 'build',
click: () => {
this.binding = 'q.item_slot_to_bone_name(c.item_slot)';

@ -1136,6 +1136,10 @@ new NodePreviewController(Mesh, {
Mesh.preview_controller.updatePixelGrid(element);
if (Project.view_mode == 'wireframe' && this.fixWireframe) {
this.fixWireframe(element);
}
this.dispatchEvent('update_geometry', {element});
},
updateFaces(element) {
@ -1447,5 +1451,20 @@ new NodePreviewController(Mesh, {
mesh.add(box);
this.dispatchEvent('update_painting_grid', {element});
},
fixWireframe(element) {
let geometry_orig = element.mesh.geometry;
if (!geometry_orig) return;
let geometry_clone = element.mesh.geometry.clone();
element.mesh.geometry = geometry_clone;
geometry_orig.dispose();
}
})
Blockbench.dispatchEvent('change_view_mode', ({view_mode}) => {
if (view_mode == 'wireframe') {
for (let mesh of Mesh.selected) {
Mesh.preview_controller.fixWireframe(mesh);
}
}
});

@ -800,7 +800,8 @@ function parseGroups(array, import_reference, startIndex) {
OutlinerNode.uuids[array[i].uuid].removeFromParent();
delete OutlinerNode.uuids[array[i].uuid];
}
var obj = new Group(array[i], array[i].uuid)
// todo: Update old groups instead of rebuilding all
let obj = new Group(array[i], array[i].uuid)
obj.parent = addGroup
obj.isOpen = !!array[i].isOpen
if (array[i].uuid) {
@ -814,6 +815,9 @@ function parseGroups(array, import_reference, startIndex) {
if (array[i].content && array[i].content.length > 0) {
iterate(array[i].content, obj.children, obj)
}
if (array[i].selected) {
obj.multiSelect();
}
}
i++;
}
@ -844,20 +848,20 @@ function moveOutlinerSelectionTo(item, target, event, order) {
iterate(target)
if (is_parent) return;
}
if (item instanceof OutlinerElement && Outliner.selected.includes( item )) {
if (item instanceof OutlinerNode && item.selected) {
var items = [];
// ensure elements are in displayed order
Outliner.root.forEach(node => {
if (node instanceof Group) {
node.forEachChild(child => {
if (child.selected && child instanceof Group == false) items.push(child);
})
if (child.selected && !child.parent.selected && (target instanceof OutlinerNode == false || !target.isChildOf?.(child))) {
items.push(child);
}
}, null, true);
} else if (node.selected) {
items.push(node);
}
})
} else if (item instanceof Group) {
var items = Group.multi_selected.filter(g => !g.parent.selected);
} else {
var items = [item];
}
@ -882,6 +886,9 @@ function moveOutlinerSelectionTo(item, target, event, order) {
}
} else {
item.preview_controller.updateTransform(item);
if (Format.per_group_texture && item.preview_controller.updateFaces) {
item.preview_controller.updateFaces(item);
}
}
}
}
@ -1131,14 +1138,15 @@ SharedActions.add('select_all', {
Undo.initSelection();
let selectable_elements = Outliner.elements.filter(element => !element.locked);
if (Outliner.selected.length < selectable_elements.length) {
if (Outliner.root.length == 1 && !Outliner.root[0].locked) {
Outliner.root[0].select();
} else {
selectable_elements.forEach(obj => {
obj.selectLow()
})
TickUpdates.selection = true;
for (let node of Outliner.root) {
if (node instanceof Group) {
node.multiSelect();
}
}
selectable_elements.forEach(obj => {
obj.selectLow()
})
TickUpdates.selection = true;
Undo.finishSelection('Select all elements');
} else {
unselectAllElements()

@ -1155,6 +1155,9 @@ class Preview {
let matrix_offset = new THREE.Matrix4().makeTranslation(z_offset.x, z_offset.y, z_offset.z);
brush_matrix.multiplyMatrices(matrix_offset, brush_matrix);
// Since we're setting the brush matrix, we need to multiply in its parents matrix as well in case there are any.
if (Canvas.brush_outline.parent)
brush_matrix.multiplyMatrices(Canvas.brush_outline.parent.matrixWorld.clone().invert(), brush_matrix);
Canvas.brush_outline.matrix = brush_matrix;
}
@ -1939,12 +1942,15 @@ class OrbitGizmo {
window.addEventListener("gamepadconnected", function(event) {
let is_space_mouse = event.gamepad.id.includes('SpaceMouse') || event.gamepad.id.includes('SpaceNavigator') || event.gamepad.id.includes('3Dconnexion');
console.log('Gamepad Connected', event);
let zoom_timer = 0;
let interval = setInterval(() => {
let gamepad = navigator.getGamepads()[event.gamepad.index];
let preview = Preview.selected;
if (!document.hasFocus() || !preview || !gamepad || !gamepad.axes || gamepad.axes.allEqual(0) || gamepad.axes.find(v => isNaN(v)) != undefined) return;
if (settings.gamepad_controls.value == false) return;
if (!document.hasFocus() || !preview || !gamepad || !gamepad.axes || !gamepad.connected || gamepad.axes.allEqual(0) || gamepad.axes.find(v => isNaN(v)) != undefined) return;
if (is_space_mouse) {
let offset = new THREE.Vector3(
@ -2203,6 +2209,7 @@ BARS.defineActions(function() {
material: {name: true, icon: 'pages', condition: () => ((!Toolbox.selected.allowed_view_modes || Toolbox.selected.allowed_view_modes.includes('material')) && TextureGroup.all.find(tg => tg.is_material))},
},
onChange() {
let previous_view_mode = Project.view_mode;
Project.view_mode = this.value;
Canvas.updateViewMode();
if (Modes.id === 'animate') {
@ -2215,6 +2222,9 @@ BARS.defineActions(function() {
if (icon_node) icon_node.replaceWith(icon);
}
})
if (Project.view_mode != previous_view_mode) {
Blockbench.dispatchEvent('change_view_mode', {view_mode: Project.view_mode, previous_view_mode});
}
//Blockbench.showQuickMessage(tl('action.view_mode') + ': ' + tl('action.view_mode.' + this.value));
}
})

@ -349,14 +349,19 @@ class ReferenceImage {
(e2.clientX - e1.clientX) * multiplier,
(e2.clientY - e1.clientY) * multiplier,
];
this.size[0] = Math.max(original_size[0] + offset[0] * sign_x, 48);
let zoom_level = this.getZoomLevel();
let max_size = [
32 / zoom_level,
24 / zoom_level
];
this.size[0] = Math.max(original_size[0] + offset[0] * sign_x, max_size[0]);
this.position[0] = original_position[0] + offset[0] / 2, 0;
if (!e2.ctrlOrCmd && !Pressing.overrides.ctrl) {
offset[1] = sign_y * (this.size[0] / this.aspect_ratio - original_size[1]);
}
this.size[1] = Math.max(original_size[1] + offset[1] * sign_y, 32);
this.size[1] = Math.max(original_size[1] + offset[1] * sign_y, max_size[1]);
this.position[1] = original_position[1] + offset[1] / 2, 0;
if (this.layer !== 'blueprint') {

@ -643,6 +643,43 @@ BARS.defineActions(function() {
Undo.finishEdit('Limit texture to palette')
}
})
new Action('split_rgb_into_layers', {
icon: 'stacked_bar_chart',
category: 'textures',
condition: {modes: ['paint'], selected: {texture: true}},
click() {
let texture = Texture.getDefault();
let original_data = texture.ctx.getImageData(0, 0, texture.canvas.width, texture.canvas.height);
Undo.initEdit({textures: [texture], bitmap: true});
texture.layers_enabled = true;
let i = 0;
for (let color of ['red', 'green', 'blue']) {
data_copy = new ImageData(original_data.data.slice(), original_data.width, original_data.height);
for (let j = 0; j < data_copy.data.length; j += 4) {
if (i != 0) data_copy.data[j+0] = 0;
if (i != 1) data_copy.data[j+1] = 0;
if (i != 2) data_copy.data[j+2] = 0;
}
let layer = new TextureLayer({
name: color,
blend_mode: 'add'
}, texture);
layer.setSize(original_data.width, original_data.height);
layer.ctx.putImageData(data_copy, 0, 0);
texture.layers.unshift(layer);
if (color == 'red') {
layer.select();
}
i++;
}
texture.updateLayerChanges(true);
Undo.finishEdit('Split texture into RGB layers');
updateInterfacePanels();
BARS.updateConditions();
}
})
new Action('clear_unused_texture_space', {
icon: 'cleaning_services',
category: 'textures',

@ -1181,7 +1181,7 @@ const Painter = {
}, {no_undo: true, use_cache: true});
},
colorPicker(texture, x, y, event) {
var ctx = Painter.getCanvas(texture).getContext('2d')
let {ctx} = settings.pick_combined_color.value ? texture : texture.getActiveCanvas();
let color = Painter.getPixelColor(ctx, x, y);
if (settings.pick_color_opacity.value) {
let opacity = Math.floor(color.getAlpha()*256);

@ -348,21 +348,23 @@ const TextureGenerator = {
});
function faceRect(cube, face_key, tex, x, y, face_old_pos_id) {
this.cube = cube;
this.face = cube.faces[face_key];
if (options.rearrange_uv) {
this.width = Math.abs(x) * res_multiple;
this.height = Math.abs(y) * res_multiple;
this.mirror_x = Math.sign(this.face.uv_size[0]);
this.mirror_y = Math.sign(this.face.uv_size[1]);
this.width = ((this.width >= 0.01 && this.width < 1) ? 1 : Math.round(this.width)) / res_multiple;
this.height = ((this.height >= 0.01 && this.height < 1) ? 1 : Math.round(this.height)) / res_multiple;
} else {
this.posx = cube.faces[face_key].uv[0], cube.faces[face_key].uv[0+2];
this.posy = cube.faces[face_key].uv[1], cube.faces[face_key].uv[1+2];
this.width = cube.faces[face_key].uv[0+2] - cube.faces[face_key].uv[0];
this.height = cube.faces[face_key].uv[1+2] - cube.faces[face_key].uv[1];
this.posx = this.face.uv[0], this.face.uv[0+2];
this.posy = this.face.uv[1], this.face.uv[1+2];
this.width = this.face.uv[0+2] - this.face.uv[0];
this.height = this.face.uv[1+2] - this.face.uv[1];
}
this.size = this.width * this.height;
this.face_key = face_key;
this.texture = tex
this.face = cube.faces[face_key];
this.face_old_pos_id = face_old_pos_id;
}
function faceOldPositionIdentifier(face) {
@ -374,7 +376,14 @@ const TextureGenerator = {
vertex_identifiers.sort(sort_collator.compare);
uv_id = vertex_identifiers.join(',');
} else if (face.uv instanceof Array) {
uv_id = face.uv.map(v => Math.roundTo(v, 4)).join(',');
let absolute_uv = face.uv.slice();
for (let i = 0; i < 2; i++) {
if (absolute_uv[i] > absolute_uv[i+2]) {
absolute_uv[i] = absolute_uv[i+2];
absolute_uv[i+2] = face.uv[i];
}
}
uv_id = absolute_uv.map(v => Math.roundTo(v, 4)).join(',');
}
let texture = face.getTexture();
return uv_id + ':' + (texture ? texture.uuid : 'blank');
@ -1408,6 +1417,15 @@ const TextureGenerator = {
uv: flip_rotation ? [pos.y, pos.x] : [pos.x, pos.y]
})
target.face.uv_size = flip_rotation ? [pos.h, pos.w] : [pos.w, pos.h];
if (source != target) {
// Double occupancy mirroring
if (target.mirror_x == -1) {
[target.face.uv[2], target.face.uv[0]] = [target.face.uv[0], target.face.uv[2]];
}
if (target.mirror_y == -1) {
[target.face.uv[3], target.face.uv[1]] = [target.face.uv[1], target.face.uv[3]];
}
}
if (target.face_key == 'up') {
[target.face.uv[2], target.face.uv[0]] = [target.face.uv[0], target.face.uv[2]];
[target.face.uv[3], target.face.uv[1]] = [target.face.uv[1], target.face.uv[3]];

@ -110,6 +110,8 @@ class TextureGroup {
let normal_tex = textures.find(t => t.pbr_channel == 'normal');
let height_tex = textures.find(t => t.pbr_channel == 'height');
let mer_tex = textures.find(t => t.pbr_channel == 'mer');
// Albedo
if (color_tex) {
material.map = color_tex.getOwnMaterial().map;
material.color.set('#ffffff');
@ -120,13 +122,15 @@ class TextureGroup {
material.color.set({r: c[0] / 255, g: c[1] / 255, b: c[2] / 255});
material.opacity = c[4] / 255;
}
// Height
if (normal_tex) {
material.normalMap = normal_tex.getOwnMaterial().map;
material.bumpMap = null;
// Use DirectX normal maps for RenderDragon. Flips the "handedness" of the normal map.
material.normalScale = Project.format.id.includes('bedrock') ? new THREE.Vector2(1, -1) : new THREE.Vector2(1, 1);
} else if (height_tex) {
material.bumpMap = height_tex.getOwnMaterial().map;
material.bumpMap = height_tex.getOwnMaterial().map.clone();
material.bumpScale = 0.4;
material.normalMap = null;
// Bump map scale
@ -139,8 +143,13 @@ class TextureGroup {
material.bumpMap.image = canvas;
material.bumpMap.magFilter = THREE.LinearFilter;
material.bumpMap.needsUpdate = true;
} else {
material.normalMap = null;
material.bumpMap = null;
}
if (mer_tex && mer_tex.img?.naturalWidth) {
// MER
if (mer_tex && mer_tex.img?.naturalWidth && mer_tex.width) {
let image_data = mer_tex.canvas.getContext('2d').getImageData(0, 0, mer_tex.width, mer_tex.height);
const extractEmissiveChannel = () => {
@ -179,21 +188,21 @@ class TextureGroup {
}
function generateMap(source_channel, key) {
let canvas = material[key]?.image ?? document.createElement('canvas');
let canvas = material[key]?.image;
if (!canvas || key == 'emissiveMap') {
canvas = document.createElement('canvas');
}
let ctx = canvas.getContext('2d');
canvas.width = mer_tex.width;
canvas.height = mer_tex.height;
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, mer_tex.width, mer_tex.height);
document.body.append(canvas);
// document.body.append(canvas);
ctx.putImageData(source_channel === 1 ? extractEmissiveChannel() : extractGrayscaleValue(source_channel), 0, 0);
if (!material[key] || true) {
material[key] = new THREE.Texture(canvas, THREE.UVMapping, THREE.RepeatWrapping, THREE.RepeatWrapping, THREE.NearestFilter, THREE.NearestFilter);
material[key].needsUpdate = true;
}
//material.map = material[key];
material[key] = new THREE.Texture(canvas, THREE.UVMapping, THREE.RepeatWrapping, THREE.RepeatWrapping, THREE.NearestFilter, THREE.NearestFilter);
material[key].needsUpdate = true;
}
generateMap(0, 'metalnessMap');
generateMap(1, 'emissiveMap');
@ -318,7 +327,14 @@ class TextureGroupMaterialConfig {
texture_set.color = this.color_value.slice();
}
if (mer_tex) {
texture_set.metalness_emissive_roughness = getTextureName(mer_tex);
let texture_name = getTextureName(mer_tex);
if (this.subsurface_value) {
texture_set.metalness_emissive_roughness_subsurface = texture_name;
} else {
texture_set.metalness_emissive_roughness = texture_name;
}
} else if (this.subsurface_value) {
texture_set.metalness_emissive_roughness_subsurface = [...this.mer_value, this.subsurface_value];
} else if (!this.mer_value.allEqual(0)) {
texture_set.metalness_emissive_roughness = this.mer_value.slice();
}
@ -328,8 +344,12 @@ class TextureGroupMaterialConfig {
texture_set.heightmap = getTextureName(height_tex);
}
let format_version = "1.16.100";
if (texture_set.metalness_emissive_roughness_subsurface) {
format_version = "1.21.30";
}
let file = {
format_version: "1.16.100",
format_version,
"minecraft:texture_set": texture_set
}
return file;
@ -398,7 +418,7 @@ class TextureGroupMaterialConfig {
a: this.color_value[3] / 255
}
},
'mer': '_',
'_mers': '_',
mer: {
type: 'select',
label: 'dialog.material_config.mer',
@ -412,7 +432,21 @@ class TextureGroupMaterialConfig {
min: 0, max: 255, step: 1, force_step: true,
value: this.mer_value.map(v => Math.clamp(v, 0, 255)),
},
'depth': '_',
subsurface: {
type: 'checkbox',
label: 'dialog.material_config.subsurface',
description: 'dialog.material_config.subsurface_enabled.desc',
condition: form => isUUID(form.mer),
value: this.subsurface_value > 0,
},
subsurface_value: {
label: 'dialog.material_config.subsurface',
condition: form => form.mer == 'uniform',
type: 'number',
min: 0, max: 255, step: 1, force_step: true,
value: Math.clamp(this.subsurface_value, 0, 255),
},
'_depth': '_',
depth_type: {
type: 'inline_select',
label: 'dialog.material_config.depth_type',
@ -457,10 +491,12 @@ class TextureGroupMaterialConfig {
for (let texture of textures) {
if (texture.pbr_channel == 'mer') texture.group = '';
}
this.subsurface_value = result.subsurface_value;
} else {
this.mer_value.replace([0, 0, 0]);
let target = textures.find(t => t.uuid == result.mer);
if (target) target.pbr_channel = 'mer';
this.subsurface_value = result.subsurface ? 1 : 0;
}
if (result.depth_type == 'normal') {
@ -485,6 +521,7 @@ class TextureGroupMaterialConfig {
}
new Property(TextureGroupMaterialConfig, 'vector4', 'color_value', {default: [255, 255, 255, 255]});
new Property(TextureGroupMaterialConfig, 'vector', 'mer_value');
new Property(TextureGroupMaterialConfig, 'number', 'subsurface_value');
new Property(TextureGroupMaterialConfig, 'boolean', 'saved', {default: true});
TextureGroupMaterialConfig.prototype.menu = new Menu('texture_group_material_config', [
'generate_pbr_map',
@ -532,7 +569,7 @@ function importTextureSet(file) {
Undo.initEdit({textures: new_textures, texture_groups: new_texture_groups});
if (file.name.endsWith('texture_set.json')) {
let texture_group = new TextureGroup({is_material: true});
texture_group.name = file.name.replace('.texture_set.json', '');
texture_group.name = file.name.replace('.texture_set.json', '.png material');
let content = fs.readFileSync(file.path, {encoding: 'utf-8'});
let content_json = autoParseJSON(content);
@ -543,6 +580,7 @@ function importTextureSet(file) {
normal: 'normal',
heightmap: 'height',
metalness_emissive_roughness: 'mer',
metalness_emissive_roughness_subsurface: 'mer',
};
for (let key in channels) {
let source = content_json['minecraft:texture_set'][key];
@ -558,6 +596,9 @@ function importTextureSet(file) {
new_textures.push(t);
t.group = texture_group.uuid;
})
if (key == 'metalness_emissive_roughness_subsurface') {
texture_group.material_config.subsurface_value = 1;
}
} else {
let color_array = source;
if (typeof source == 'string') {
@ -572,6 +613,9 @@ function importTextureSet(file) {
}
} else if (key == 'metalness_emissive_roughness') {
texture_group.material_config.mer_value.V3_set(color_array);
} else if (key == 'metalness_emissive_roughness_subsurface') {
texture_group.material_config.mer_value.V3_set(color_array);
texture_group.material_config.subsurface_value = Math.clamp(color_array[3] ?? 0, 0, 255);
}
}
}
@ -583,6 +627,14 @@ function importTextureSet(file) {
}
Undo.finishEdit('Import texture set');
}
function loadAdjacentTextureSet(texture) {
let path = texture.path.replace(/\.png$/i, '.texture_set.json');
if (fs.existsSync(path)) {
Blockbench.read([path], {}, (files) => {
importTextureSet(files[0])
})
}
}
SharedActions.add('rename', {
condition: () => Prop.active_panel == 'textures' && TextureGroup.active_menu_group,
@ -615,6 +667,7 @@ BARS.defineActions(function() {
new Action('create_material', {
icon: 'lightbulb_circle',
category: 'textures',
condition: () => (!Texture.selected || !Texture.selected.getGroup()?.is_material) && Format.pbr,
click() {
let texture = Texture.selected;
let texture_group = new TextureGroup({is_material: true});
@ -653,7 +706,7 @@ BARS.defineActions(function() {
let new_data = ctx.getImageData(0, 0, canvas.width, canvas.height);
canvas.style.width = 256 + 'px';
canvas.style.height = 256 + 'px';
let original_image = new CanvasFrame(texture.canvas);
let original_image = new CanvasFrame(texture.canvas, true);
original_image.canvas.style.width = 256 + 'px';
original_image.canvas.style.height = 256 + 'px';
@ -754,6 +807,17 @@ BARS.defineActions(function() {
width: 564,
lines: [preview],
form: {
channel: {
type: 'select',
label: 'PBR Channel',
options: {
//normal: 'menu.texture.pbr_channel.normal',
height: 'menu.texture.pbr_channel.height',
metalness: 'Metalness',
emissive: 'Emissive',
roughness: 'Roughness',
}
},
method: {
type: 'select',
label: 'Source',
@ -767,17 +831,6 @@ BARS.defineActions(function() {
blue: 'Blue',
}
},
channel: {
type: 'select',
label: 'PBR Channel',
options: {
//normal: 'menu.texture.pbr_channel.normal',
height: 'menu.texture.pbr_channel.height',
metalness: 'Metalness',
emissive: 'Emissive',
roughness: 'Roughness',
}
},
in_range: {
type: 'vector',
label: 'Input Range',
@ -798,15 +851,53 @@ BARS.defineActions(function() {
onConfirm(result) {
updateCanvas(result);
let textures = [];
Undo.initEdit({texture_groups: texture_group ? [texture_group] : null, textures});
let pbr_channel = result.channel;
let new_texture = new Texture({
name: texture.name,
pbr_channel,
group: texture_group?.uuid,
}).fromDataURL(canvas.toDataURL()).add(false);
textures.push(new_texture);
let pbr_channel;
switch (result.channel) {
case 'height': pbr_channel = result.channel; break;
default: pbr_channel = 'mer'; break;
}
let existing_channel_texture = texture_group.getTextures().find(tex => tex.pbr_channel == pbr_channel);
if (existing_channel_texture && pbr_channel == 'mer') {
Undo.initEdit({textures: [existing_channel_texture], bitmap: true});
if (!existing_channel_texture.layers_enabled) {
existing_channel_texture.activateLayers(false);
}
let layer = new TextureLayer({
name: result.channel,
blend_mode: 'add'
}, existing_channel_texture);
let image_data = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
layer.setSize(canvas.width, canvas.height);
layer.ctx.putImageData(image_data, 0, 0);
layer.addForEditing();
existing_channel_texture.updateLayerChanges(true);
} else {
Undo.initEdit({texture_groups: texture_group ? [texture_group] : null, textures});
let main_texture = texture_group?.getTextures().find(t => t.pbr_channel == 'color');
let name = main_texture ? main_texture.name : texture.name;
name = name.replace('.', `_${pbr_channel}.`);
let new_texture = new Texture({
name,
pbr_channel,
group: texture_group?.uuid,
}).fromDataURL(canvas.toDataURL()).add(false);
textures.push(new_texture);
if (texture_group.material_config) {
texture_group.material_config.saved = false;
}
}
setTimeout(() => {
texture_group.updateMaterial();
}, 50);
Undo.finishEdit('Create PBR map');
updateSelection();
},
onOpen() {
updateCanvas(this.getFormResult());

@ -1788,6 +1788,9 @@ class Texture {
this.source = this.canvas.toDataURL('image/png', 1);
this.updateImageFromCanvas();
}
if ((this.pbr_channel == 'mer' || this.pbr_channel == 'height') && this.getGroup()?.is_material && BarItems.view_mode.value == 'material') {
this.getGroup().updateMaterial();
}
this.saved = false;
this.syncToOtherProject();
}
@ -1996,6 +1999,7 @@ class Texture {
'adjust_curves',
new MenuSeparator('filters'),
'limit_to_palette',
'split_rgb_into_layers',
'clear_unused_texture_space',
new MenuSeparator('transform'),
'flip_texture_x',
@ -2649,6 +2653,9 @@ Interface.definePanels(function() {
addEventListeners(document, 'mousemove touchmove', move, {passive: false});
addEventListeners(document, 'mouseup touchend', off, {passive: false});
},
closeContextMenu() {
if (Menu.open) Menu.open.hide();
}
},
template: `
@ -2656,7 +2663,7 @@ Interface.definePanels(function() {
v-bind:class="{ selected: texture.selected, multi_selected: texture.multi_selected, particle: texture.particle, use_as_default: texture.use_as_default}"
v-bind:texid="texture.uuid"
class="texture"
@click.stop="texture.select($event)"
@click.stop="closeContextMenu();texture.select($event)"
@mousedown="highlightTexture($event)"
@mouseup="unhighlightTexture($event)"
@dblclick="texture.openMenu($event)"

@ -1277,7 +1277,6 @@ const UVEditor = {
Undo.finishEdit('Toggle cullface')
},
switchTint(event) {
var scope = this;
var val = UVEditor.getReferenceFace().tint === -1 ? 0 : -1;
if (event === 0 || event === false) val = event
@ -4204,28 +4203,42 @@ Interface.definePanels(function() {
},
toggleFaceTint(key, event) {
Undo.initEdit({elements: Cube.selected, uv_only: true})
UVEditor.switchTint(event)
UVEditor.vue.$forceUpdate();
let value = UVEditor.getFirstMappableElement()?.faces[key]?.tint === -1 ? 0 : -1;
UVEditor.forCubes(cube => {
cube.faces[key].tint = value;
})
this.$forceUpdate();
Undo.finishEdit('Toggle face tint')
},
changeFaceTint(key, event) {
Undo.initEdit({elements: Cube.selected, uv_only: true})
UVEditor.setTint(event, parseInt(event.target.value));
Undo.finishEdit('Toggle face tint');
let value = parseInt(event.target.value);
UVEditor.forCubes(cube => {
cube.faces[key].tint = value;
})
Undo.finishEdit('Set face tint');
},
setCullface(key, value) {
Undo.initEdit({elements: Cube.selected, uv_only: true})
UVEditor.forCubes(obj => {
UVEditor.getSelectedFaces(obj).forEach(face => {
obj.faces[face].cullface = value;
})
if (obj.faces[key]) {
obj.faces[key].cullface = value;
}
})
Undo.finishEdit(value ? `Set cullface to ${value}` : 'Disable cullface');
},
startInputMaterialInstance(event) {
Undo.initEdit({elements: Cube.selected, uv_only: true})
},
endInputMaterialInstance(event) {
endInputMaterialInstance(event, fkey) {
let value = this.mappable_elements[0]?.faces[fkey]?.material_name;
if (typeof value == 'string') {
for (let element of this.mappable_elements) {
if (element.faces[fkey]) {
element.faces[fkey].material_name = value;
}
}
}
Undo.finishEdit('Change material instances');
},
showInfoBox(title, text) {
@ -4329,7 +4342,7 @@ Interface.definePanels(function() {
title="${tl('uv_editor.face_properties.material_instance')}"
v-model="mappable_elements[0].faces[key].material_name"
@focus="startInputMaterialInstance($event)"
@focusout="endInputMaterialInstance($event)"
@focusout="endInputMaterialInstance($event, key)"
>
</template>
</li>

@ -15,12 +15,16 @@ class UndoSystem {
}
}
initEdit(aspects, amended = false) {
// todo: selecting all groups, moving, then undoing, unselects all multi-selected groups
if (aspects && aspects.cubes) {
console.warn('Aspect "cubes" is deprecated. Please use "elements" instead.');
aspects.elements = aspects.cubes;
}
this.startChange(amended);
this.current_save = new UndoSystem.save(aspects)
this.current_save = new UndoSystem.save(aspects);
if (aspects.selection) {
this.current_selection_save = new UndoSystem.selectionSave(typeof aspects.selection == 'object' ? typeof aspects.selection : 0);
}
Blockbench.dispatchEvent('init_edit', {aspects, amended, save: this.current_save})
return this.current_save;
}
@ -40,7 +44,18 @@ class UndoSystem {
type: 'edit',
time: Date.now()
}
this.current_save = entry.post
if (aspects.selection && this.current_selection_save) {
let selection_aspects = typeof aspects.selection == 'object' ? aspects.selection : this.current_selection_save.aspects;
let selection_before = this.current_selection_save;
let selection_post = new UndoSystem.selectionSave(selection_aspects);
if (!selection_before.matches(selection_post)) {
entry.selection_before = selection_before;
entry.selection_post = selection_post;
}
}
if (this.history.length > this.index) {
this.history.length = this.index;
}
@ -62,7 +77,7 @@ class UndoSystem {
return entry;
}
initSelection(aspects) {
if (!settings.undo_selections.value || Blockbench.hasFlag('loading_selection_save')) return;
if (!settings.undo_selections.value || Blockbench.hasFlag('loading_selection_save') || Project.EditSession) return;
if (this.current_selection_save) return;
this.current_selection_save = new UndoSystem.selectionSave(aspects);
@ -70,7 +85,7 @@ class UndoSystem {
return this.current_selection_save;
}
finishSelection(message, aspects) {
if (!settings.undo_selections.value || Blockbench.hasFlag('loading_selection_save')) return;
if (!settings.undo_selections.value || Blockbench.hasFlag('loading_selection_save') || Project.EditSession) return;
if (!this.current_selection_save) return;
aspects = aspects || this.current_selection_save.aspects;
@ -89,7 +104,6 @@ class UndoSystem {
type: 'selection',
time: Date.now()
}
this.current_selection_save = entry.selection_post
if (this.history.length > this.index) {
this.history.length = this.index;
}
@ -178,10 +192,14 @@ class UndoSystem {
this.index--;
var entry = this.history[this.index];
if (entry.before) entry.before.load(entry.post);
if (entry.selection_before) entry.selection_before.load(entry.selection_post);
if (Project.EditSession && remote !== true) {
Project.EditSession.sendAll('command', 'undo')
if (entry.before) {
this.loadSave(entry.before, entry.post);
}
if (entry.selection_before instanceof UndoSystem.selectionSave) {
entry.selection_before.load(entry.selection_post);
}
if (Project.EditSession && remote !== true && entry.type != 'selection') {
Project.EditSession.sendAll('command', 'undo');
}
Blockbench.dispatchEvent('undo', {entry})
}
@ -195,15 +213,19 @@ class UndoSystem {
var entry = this.history[this.index]
this.index++;
if (entry.post) entry.post.load(entry.before);
if (entry.selection_post) entry.selection_post.load(entry.selection_before);
if (Project.EditSession && remote !== true) {
Project.EditSession.sendAll('command', 'redo')
if (entry.post) {
this.loadSave(entry.post, entry.before);
}
if (entry.selection_post instanceof UndoSystem.selectionSave) {
entry.selection_post.load(entry.selection_before);
}
if (Project.EditSession && remote !== true && entry.type != 'selection') {
Project.EditSession.sendAll('command', 'redo');
}
Blockbench.dispatchEvent('redo', {entry})
}
remoteEdit(entry) {
this.loadSave(entry.post, entry.before, 'session')
this.loadSave(entry.post, entry.before, 'session');
if (entry.save_history !== false) {
delete this.current_save;
@ -228,18 +250,29 @@ class UndoSystem {
return false;
}
loadSave(save, reference, mode) {
if (save instanceof UndoSystem.save == false) {
save = new UndoSystem.save().fromJSON(save);
}
save.load(reference, mode);
}
}
UndoSystem.save = class {
constructor(aspects) {
if (aspects) {
this.fromState(aspects);
}
}
fromJSON(data) {
Object.assign(this, data);
return this;
}
fromState(aspects) {
var scope = this;
this.aspects = aspects;
this.mode = Modes.selected.id;
if (aspects.selection) {
/*if (aspects.selection) {
this.selection = [];
this.mesh_selection = {};
selected.forEach(obj => {
@ -251,8 +284,7 @@ UndoSystem.save = class {
if (Group.multi_selected.length) {
this.selected_groups = Group.multi_selected.map(g => g.uuid);
}
}
}*/
if (aspects.elements) {
this.elements = {}
@ -367,6 +399,7 @@ UndoSystem.save = class {
}
Blockbench.dispatchEvent('create_undo_save', {save: this, aspects})
return this;
}
load(reference, mode) {
let is_session = mode === 'session';
@ -440,7 +473,7 @@ UndoSystem.save = class {
}
}
if (this.selection && !is_session) {
/*if (this.selection && !is_session) {
selected.length = 0;
Outliner.elements.forEach((obj) => {
if (this.selection.includes(obj.uuid)) {
@ -450,7 +483,7 @@ UndoSystem.save = class {
}
}
})
}
}*/
if (this.groups) {
for (let saved_group of this.groups) {
@ -619,7 +652,7 @@ UndoSystem.save = class {
if (this.animations) {
for (var uuid in this.animations) {
var animation = (reference.animations && reference.animations[uuid]) ? this.getItemByUUID(Animator.animations, uuid) : null;
var animation = (reference.animations && reference.animations[uuid]) ? Undo.getItemByUUID(Animator.animations, uuid) : null;
if (!animation) {
animation = new Animation()
animation.uuid = uuid
@ -631,7 +664,7 @@ UndoSystem.save = class {
}
for (var uuid in reference.animations) {
if (!this.animations[uuid]) {
var animation = this.getItemByUUID(Animator.animations, uuid)
var animation = Undo.getItemByUUID(Animator.animations, uuid)
if (animation) {
animation.remove(false)
}
@ -641,7 +674,7 @@ UndoSystem.save = class {
if (this.animation_controllers) {
for (var uuid in this.animation_controllers) {
var controller = (reference.animation_controllers && reference.animation_controllers[uuid]) ? this.getItemByUUID(AnimationController.all, uuid) : null;
var controller = (reference.animation_controllers && reference.animation_controllers[uuid]) ? Undo.getItemByUUID(AnimationController.all, uuid) : null;
if (!controller) {
controller = new AnimationController();
controller.uuid = uuid;
@ -653,7 +686,7 @@ UndoSystem.save = class {
}
for (var uuid in reference.animation_controllers) {
if (!this.animation_controllers[uuid]) {
var controller = this.getItemByUUID(AnimationController.all, uuid);
var controller = Undo.getItemByUUID(AnimationController.all, uuid);
if (controller) {
controller.remove(false);
}
@ -790,7 +823,7 @@ UndoSystem.selectionSave = class {
if (element instanceof Mesh) {
this.geometry[element.uuid] = {
faces: element.getSelectedFaces().slice(),
edges: element.getSelectedEdges().slice(),
edges: element.getSelectedEdges().map(edge => edge.slice()),
vertices: element.getSelectedVertices().slice(),
}
} else if (element instanceof Cube && !element.box_uv) {
@ -841,7 +874,7 @@ UndoSystem.selectionSave = class {
unselectAllElements();
if (this.elements) {
Outliner.selected.replace(this.elements.map(uuid => OutlinerNode.uuids[uuid]));
Outliner.selected.replace(this.elements.map(uuid => OutlinerNode.uuids[uuid]).filter(element => element instanceof OutlinerElement));
}
if (this.groups) {
for (let uuid of this.groups) {

@ -111,6 +111,7 @@ Object.defineProperty(Array.prototype, "equals", {enumerable: false});
//Array Vector
Array.prototype.V3_set = function(x, y, z) {
if (x instanceof Array) return this.V3_set(...x);
if (x instanceof THREE.Vector3) return this.V3_set(x.x, x.y, x.z);
if (y === undefined && z === undefined) z = y = x;
this[0] = parseFloat(x)||0;
this[1] = parseFloat(y)||0;

156
js/util/json.js Normal file

@ -0,0 +1,156 @@
function compileJSON(object, options = {}) {
let indentation = options.indentation;
if (typeof indentation !== 'string') {
switch (settings.json_indentation.value) {
case 'spaces_4': indentation = ' '; break;
case 'spaces_2': indentation = ' '; break;
case 'tabs': default: indentation = '\t'; break;
}
}
function newLine(tabs) {
if (options.small === true) {return '';}
let s = '\n';
for (let i = 0; i < tabs; i++) {
s += indentation;
}
return s;
}
function escape(string) {
if (string.includes('\\')) {
string = string.replace(/\\/g, '\\\\');
}
if (string.includes('"')) {
string = string.replace(/"/g, '\\"');
}
if (string.includes('\n')) {
string = string.replace(/\n|\r\n/g, '\\n');
}
if (string.includes('\t')) {
string = string.replace(/\t/g, '\\t');
}
return string;
}
function handleVar(o, tabs, breaks = true) {
var out = ''
let type = typeof o;
if (type === 'string') {
//String
out += '"' + escape(o) + '"'
} else if (type === 'boolean') {
//Boolean
out += (o ? 'true' : 'false')
} else if (o === null || o === Infinity || o === -Infinity) {
//Null
out += 'null'
} else if (type === 'number') {
//Number
o = (Math.round(o*100000)/100000).toString()
if (o == 'NaN') o = null
out += o
} else if (o instanceof Array) {
//Array
let has_content = false
let multiline = !!o.find(item => typeof item === 'object');
if (!multiline) {
let length = 0;
o.forEach(item => {
length += typeof item === 'string' ? (item.length+4) : 3;
});
if (length > 140) multiline = true;
}
out += '['
for (var i = 0; i < o.length; i++) {
var compiled = handleVar(o[i], tabs+1)
if (compiled) {
if (has_content) {out += ',' + ((options.small || multiline) ? '' : ' ')}
if (multiline) {out += newLine(tabs)}
out += compiled
has_content = true
}
}
if (multiline) {out += newLine(tabs-1)}
out += ']'
} else if (type === 'object') {
//Object
breaks = 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, breaks)
if (compiled) {
if (has_content) {out += ',' + (breaks || options.small?'':' ')}
if (breaks) {out += newLine(tabs)}
out += '"' + escape(key) + '":' + (options.small === true ? '' : ' ')
out += compiled
has_content = true
}
}
}
if (breaks && has_content) {out += newLine(tabs-1)}
out += '}'
}
return out;
}
let file = handleVar(object, 1);
if ((settings.final_newline.value && options.final_newline != false) || options.final_newline == true) {
file += '\n';
}
return file;
}
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;
if (data.match(/\n\r?[><]{7}/)) {
Blockbench.showMessageBox({
title: 'message.invalid_file.title',
icon: 'fab.fa-git-alt',
message: 'message.invalid_file.merge_conflict'
})
return;
}
let error_part = '';
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`
})
error_part = result.substr(0, result.length-1) + ' <-- HERE';
console.log(error_part);
}
console.error(err)
var length = err.toString().split('at position ')[1]
if (length) {
length = parseInt(length)
var start = limitNumber(length-32, 0, Infinity)
logErrantPart(data, start, 1+length-start)
} else if (err.toString().includes('Unexpected end of JSON input')) {
logErrantPart(data, data.length-16, 10)
}
Blockbench.showMessageBox({
translateKey: 'invalid_file',
icon: 'error',
message: tl('message.invalid_file.message', [err]) + (error_part ? `\n\n\`\`\`\n${error_part}\n\`\`\`` : '')
})
return;
}
}
return data;
}

@ -410,9 +410,9 @@ var Merge = {
},
molang(obj, source, index) {
if (typeof source[index] == 'string') {
obj[index] = source[index];
obj[index] = source[index].replace(/-?\d\.\d+e-\d\d/g, '0');
} else if (typeof source[index] == 'number') {
obj[index] = source[index].toString();
obj[index] = Math.roundTo(source[index], 9).toString();
}
},
boolean(obj, source, index, validate) {
@ -491,6 +491,21 @@ Object.defineProperty(String.prototype, 'hashCode', {
return hash;
}
});
function exportMolang(input) {
if (!input) return 0;
if (typeof input == 'string') {
if (!isNaN(input)) {
let num = parseFloat(input);
return isNaN(num) ? 0 : num;
} else {
return input.replace(/\n/g, '');
}
} else if (typeof input == 'number') {
return input;
} else {
return 0;
}
}
// HTML
function isNodeUnderCursor(node, event) {

@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Längste Achse",
"edit.vertex_snap.align.direction": "Richtung vom Angelpunkt",
"edit.vertex_snap.align.align_axis": "%0-Achse",
"edit.vertex_snap.ignore_axis": "%0-Achse ignorieren",
"edit.vertex_snap.ignore_axis": "Achse ignorieren",
"codec.common.format": "Format",
"codec.image.quality": "Qualität",
"panel.collections": "Sammlungen",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Auswahltoleranz",
"settings.selection_tolerance.desc": "Größe des Bereichs der zur Auswahl eines Eckpunktes oder einer Kante angeklickt werden kann",
"action.delete.keep_vertices": "Kanten/Eckpunkte behalten",
"menu.mesh": "Masche"
"menu.mesh": "Masche",
"dialog.material_config.subsurface": "Volumenstreuung",
"dialog.material_config.subsurface_enabled.desc": "Verwende den Transparenzkanal der MER Map für Volumenstreuung",
"settings.pick_combined_color": "Farbkombination auswählen",
"settings.pick_combined_color.desc": "Mit der Farbpipette die kombinierte Farbe aller Ebenen auswählen, an Stelle von der Farbe der ausgewählten Ebene",
"action.split_rgb_into_layers": "RGB Farbkanäle in Ebenen aufteilen",
"action.split_rgb_into_layers.desc": "Teile die Textur in eine additive Ebene pro RGB Farbkanal auf"
}

@ -521,6 +521,8 @@
"dialog.material_config.color_value": "Color Value",
"dialog.material_config.mer": "Metal-Emissive-Roughness",
"dialog.material_config.mer_value": "MER Value",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"dialog.material_config.depth_type": "Depth Type",
"dialog.edit_texture.preview": "Preview",
@ -949,7 +951,7 @@
"settings.antialiasing": "Anti-aliasing",
"settings.antialiasing.desc": "Toggle anti-aliasing in the preview",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.render_sides": "Render Sides",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
@ -1079,6 +1081,8 @@
"settings.color_wheel.desc": "Use the color wheel as the main color picker",
"settings.pick_color_opacity": "Pick Color Opacity",
"settings.pick_color_opacity.desc": "Pick the color opacity with the Color Picker and set it as brush opacity",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"settings.brush_cursor_2d": "2D Brush Cursor",
"settings.brush_cursor_2d.desc": "Display an outline around the brush in the 2D workspace",
"settings.brush_cursor_3d": "3D Brush Cursor",
@ -1659,6 +1663,8 @@
"action.adjust_curves.desc": "Adjust the brightness curves of the selected texture",
"action.limit_to_palette": "Limit to Palette",
"action.limit_to_palette.desc": "Limits the colors of the texture to those in the currently loaded palette",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel",
"action.clear_unused_texture_space": "Clear Unused Texture Space",
"action.clear_unused_texture_space.desc": "Clear parts of the texture that are not UV-mapped to any elements",
"action.flip_texture_x": "Flip Horizontally",
@ -2278,7 +2284,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"web.download_app": "Download App",

@ -14,7 +14,7 @@
"data.preview": "Previsualización",
"data.toolbar": "Barra de herramientas",
"data.image": "Imagen",
"keys.ctrl": "Control",
"keys.ctrl": "Ctrl",
"keys.shift": "Shift",
"keys.alt": "Alt",
"keys.meta": "CMD",
@ -62,7 +62,7 @@
"message.unsaved_textures.title": "Texturas sin guardar",
"message.unsaved_textures.message": "Tu modelo tiene texturas sin guardar. Asegúrate de guardarlas en la carpeta correcta del paquete de recursos",
"message.model_clipping.title": "Módelo muy grande",
"message.model_clipping.message": "Tu modelo contiene %0 cubos que son más grandes que el límite de 3x3x3 bloques establecido por Minecraft. Este modelo no funcionará en Minecraft.",
"message.model_clipping.message": "Tu modelo contiene %0 cubos que son más grandes que el límite de bloques establecido por Minecraft. Este modelo no funcionará en Minecraft.",
"message.loose_texture.title": "Importar Textura",
"message.loose_texture.message": "La texture importada no forma parte de un Resource Pack. Minecraft sólo puede cargar texturas dentro de una carpeta de textures en un Resource Pack cargado.",
"message.loose_texture.change": "Cambiar directorio",
@ -81,9 +81,9 @@
"message.image_editor.title": "Selecciona un editor de imágenes",
"message.image_editor.file": "Seleccionar archivo...",
"message.image_editor.exe": "Seleccionar el ejecutable de un editor de imágenes",
"message.display_skin.title": "Mostrar Skin",
"message.display_skin.title": "Skin de Minecraft",
"message.display_skin.message": "Selecciona un archivo de una skin de tu ordenador o escribe el nombre de un jugador",
"message.display_skin.upload": "Subir Skin",
"message.display_skin.upload": "Seleccionar Archivo",
"message.display_skin.reset": "Restablecer",
"message.invalid_plugin": "Archivo de plugin inválido, lee la Consola",
"message.load_plugin_app": "¿Quieres permitir a este plugin que haga cambios a tu PC? Carga sólo plugins de personas en las que confíes.",
@ -94,7 +94,7 @@
"dialog.project.title": "Proyecto",
"dialog.project.name": "Nombre de Archivo",
"dialog.project.parent": "Modelo Padre",
"dialog.project.geoname": "Nombre de la Geometría del Mob",
"dialog.project.geoname": "Identificador del Modelo",
"dialog.project.ao": "Oclusión Ambiental",
"dialog.texture.title": "Textura",
"dialog.texture.variable": "Variable",
@ -114,7 +114,7 @@
"dialog.select.title": "Seleccionar",
"dialog.select.group": "En el Grupo Seleccionado",
"dialog.select.name": "El Nombre Contiene",
"dialog.select.random": "Aleatorio",
"dialog.select.random": "Oportunidad Aleatoria (%)",
"dialog.select.select": "Seleccionar",
"dialog.scale.title": "Reescalar Modelo",
"dialog.scale.axis": "Ejes",
@ -160,7 +160,7 @@
"layout.color.light": "Claro",
"layout.color.light.desc": "Texto destacado",
"layout.color.accent_text": "Acento del texto",
"layout.color.accent_text.desc": "Texto en elementos claros o acentuados",
"layout.color.accent_text.desc": "Texto sobre fondos con acento",
"layout.font.main": "Fuente principal",
"layout.font.headline": "Fuente para títulos",
"about.version": "Versión:",
@ -173,28 +173,28 @@
"settings.category.dialogs": "Diálogos",
"settings.category.export": "Exportar",
"settings.language": "Lenguaje",
"settings.language.desc": "Lenguaje de la interfaz. Reinicia Blockbench para aplicar los cambios.",
"settings.language.desc": "Lenguaje de Interfaz",
"settings.backup_interval": "Intervalo de Respaldos",
"settings.backup_interval.desc": "Intervalo de los respaldos automáticos en minutos",
"settings.origin_size": "Marcador de Pivote",
"settings.origin_size.desc": "Tamaño del marcador del punto de pivote",
"settings.control_size": "Tamaño del control de los ejes",
"settings.control_size.desc": "Tamaño de la herramienta de control de los 3 ejes",
"settings.display_skin": "Mostrar Skin",
"settings.display_skin.desc": "Skin usada para la referencia del modelo de jugador",
"settings.control_size": "Transformar el tamaño del artilugio",
"settings.control_size.desc": "Tamaño del artilugio de transformación",
"settings.display_skin": "Skin de Minecraft",
"settings.display_skin.desc": "Skin usada para la referencia del modelo jugador",
"settings.shading": "Sombreado",
"settings.shading.desc": "Activar sombreado",
"settings.shading.desc": "Habilitar sombreado en vista previa",
"settings.texture_fps": "FPS de las Texturas Animadas",
"settings.texture_fps.desc": "Cuadros por segundo para texturas animadas",
"settings.base_grid": "Cuadrícula Pequeña",
"settings.base_grid.desc": "Mostrar cuadrícula y ejes pequeños",
"settings.large_grid": "Cuadrícula Grande",
"settings.large_grid.desc": "Mostrar cuadrícula de 3x3",
"settings.full_grid": "Cuadrícula Muy Grande",
"settings.full_grid.desc": "Mostar cuadrícula precisa de 3x3",
"settings.large_box": "Caja grande",
"settings.large_box.desc": "Mostrar los límites de 3x3x3",
"settings.display_grid": "Modo de visualización",
"settings.large_grid": "Cuadrícula de bloques",
"settings.large_grid.desc": "Mostrar cuadrícula de 16x16 bloques",
"settings.full_grid": "Cuadrícula de bloques precisa",
"settings.full_grid.desc": "Mostrar cuadrícula de bloques con precisión de píxeles",
"settings.large_box": "Caja con límite de tamaño",
"settings.large_box.desc": "Mostrar límites de tamaño",
"settings.display_grid": "Modo de Visualización Cuadrícula",
"settings.display_grid.desc": "Mostrar cuadrícula en el modo de visualización",
"settings.undo_limit": "Límite de deshacer",
"settings.undo_limit.desc": "Número de acciones que puedes deshacer",
@ -209,7 +209,7 @@
"settings.create_rename": "Renombrar Nuevo Elemento",
"settings.create_rename.desc": "Campo de nombre de enfoque al crear nuevo elemento o grupo",
"settings.edit_size": "Resolución de la Cuadrícula",
"settings.edit_size.desc": "Resolución de la cuadrícula a la que se engancha el cubo",
"settings.edit_size.desc": "Resolución de la cuadrícula a la que se ajustan los elementos",
"settings.shift_size": "Resolución de Shift",
"settings.shift_size.desc": "Resolución de la cuadrícula al mantener Shift",
"settings.ctrl_size": "Resolución de Control",
@ -217,9 +217,9 @@
"settings.negative_size": "Tamaño Negativo",
"settings.negative_size.desc": "Permitir a la herramienta de reescalado usar tamaños negativos",
"settings.minifiedout": "Exportación minimizada",
"settings.minifiedout.desc": "Escribir el archivo JSON en una sola línea",
"settings.minifiedout.desc": "Escribir archivos JSON en una línea",
"settings.export_groups": "Exportar Grupos en modelos item/bloque de Java",
"settings.export_groups.desc": "Guardar grupos en modelos de bloques",
"settings.export_groups.desc": "Guardar grupos en archivos JSON de bloques java o modelos de item",
"settings.credit": "Comentario de Créditos",
"settings.credit.desc": "Añadir un comentario de créditos a archivos exportados",
"settings.default_path": "Directorio por defecto de las texturas de Minecraft",
@ -250,13 +250,13 @@
"action.slider_brush_softness": "Suavidad",
"action.slider_brush_softness.desc": "Suavidad del pincel en porcentaje",
"action.uv_slider_pos_x": "Mover Horizontal",
"action.uv_slider_pos_x.desc": "Mover la selección del UV de todos los cubos seleccionados horizontalmente",
"action.uv_slider_pos_x.desc": "Mover horizontalmente todas las caras UV seleccionadas",
"action.uv_slider_pos_y": "Mover Vertical",
"action.uv_slider_pos_y.desc": "Mover la selección del UV de todos los cubos seleccionados verticalmente",
"action.uv_slider_size_x": "Reescalar Horizontal",
"action.uv_slider_size_x.desc": "Reescalar la selección del UV de todos los cubos seleccionados horizontalmente",
"action.uv_slider_size_y": "Reescalar Vertical",
"action.uv_slider_size_y.desc": "Reescalar la selección del UV de todos los cubos seleccionados verticalmente",
"action.uv_slider_pos_y.desc": "Mover verticalmente todas las caras UV seleccionadas",
"action.uv_slider_size_x": "Tamaño Horizontal",
"action.uv_slider_size_x.desc": "Redimensionar horizontalmente todas las caras UV seleccionadas",
"action.uv_slider_size_y": "Tamaño Vertical",
"action.uv_slider_size_y.desc": "Redimensionar verticalmente todas las caras UV seleccionadas",
"action.vertex_snap_mode": "Modo Imán",
"action.vertex_snap_mode.desc": "Seleccionar si el Imán para Vertices mueve los elementos a la posición seleccionada o si los reescala",
"action.move_tool": "Mover",
@ -264,7 +264,7 @@
"action.resize_tool": "Reescalar",
"action.resize_tool.desc": "Herramienta para seleccionar y reescalar elementos",
"action.brush_tool": "Pincel de pintura",
"action.brush_tool.desc": "Herramienta para pintar en texturas bitmap en superficies o en el editor de UV",
"action.brush_tool.desc": "Pincel de color para dibujar sobre texturas",
"action.vertex_snap_tool": "Imán para Vértices",
"action.vertex_snap_tool.desc": "Mover un cubo a otro cubo al conectar 2 vértices",
"action.swap_tools": "Cambiar Herramientas",
@ -275,14 +275,14 @@
"action.open_model.desc": "Abre un archivo de modelo de tu ordenador",
"action.extrude_texture": "Textura Extruida",
"action.extrude_texture.desc": "General un modelo al extender una textura",
"action.export_blockmodel": "Exportar Modelo de Bloque",
"action.export_blockmodel.desc": "Exporta un modelo de bloque o de ítem",
"action.export_blockmodel": "Exportar modelo de bloque/item",
"action.export_blockmodel.desc": "Exportar un modelo de bloque o de item de Minecraft Java Edition",
"action.export_optifine_part": "Exportar Parte de Optifine",
"action.export_optifine_part.desc": "Exportar una sola parte para un modelo de entidad de OptiFine",
"action.export_optifine_full": "Exportar a OptiFine JEM",
"action.export_optifine_full.desc": "Exportar un modelo completo de entidad de OptiFine",
"action.export_obj": "Exportar Modelo OBJ",
"action.export_obj.desc": "Exportar a un modelo Wavefront OBJ para renderizar o motores de juego",
"action.export_obj.desc": "Exportar un modelo Wavefront OBJ para su renderización",
"action.settings_window": "Ajustes...",
"action.settings_window.desc": "Abre la ventana de ajustes de Blockbench",
"action.plugins_window": "Plugins...",
@ -310,33 +310,33 @@
"action.outliner_toggle": "Activar Más Opciones",
"action.outliner_toggle.desc": "Cambia los botones para más opciones en el Esquema",
"action.duplicate": "Duplicar",
"action.duplicate.desc": "Duplica los grupos o cubos seleccionados",
"action.duplicate.desc": "Duplica los elementos o el grupo seleccionados",
"action.delete": "Borrar",
"action.delete.desc": "Borra los grupos o cubos seleccionados",
"action.delete.desc": "Elimina los elementos o el grupo seleccionados",
"action.sort_outliner": "Ordenar Esquema",
"action.sort_outliner.desc": "Ordena el esquema alfabéticamente",
"action.select_window": "Seleccionar...",
"action.select_window.desc": "Busca y selecciona cubos basados en sus propiedades",
"action.select_window.desc": "Busca y seleccionar elementos basados en sus propiedades",
"action.invert_selection": "Invertir Selección",
"action.invert_selection.desc": "Invierte la selección actual de los cubos",
"action.invert_selection.desc": "Invertir la selección actual de elementos",
"action.select_all": "Seleccionar Todo",
"action.select_all.desc": "Selecciona todos los elementos, caras, vértices, o fotogramas",
"action.collapse_groups": "Colapsar Grupos",
"action.collapse_groups.desc": "Colapsa todos los grupos",
"action.collapse_groups.desc": "Contraer todos los grupos del esquema",
"action.scale": "Reescalar...",
"action.scale.desc": "Reescala los cubos seleccionados",
"action.scale.desc": "Escalar los elementos seleccionados",
"action.toggle_visibility": "Cambiar Visibilidad",
"action.toggle_visibility.desc": "Cambia el ajuste de visibilidad de los cubos seleccionados.",
"action.toggle_visibility.desc": "Cambiar la visibilidad de los elementos seleccionados",
"action.toggle_export": "Cambiar Exportación",
"action.toggle_export.desc": "Cambia el ajuste de exportación de los cubos seleccionados",
"action.toggle_export.desc": "Cambia el ajuste de exportación de los elementos seleccionados",
"action.toggle_autouv": "Cambiar Auto UV",
"action.toggle_autouv.desc": "Cambia el ajuste de Auto UV de los cubos seleccionados",
"action.toggle_autouv.desc": "Cambia el ajuste de Auto UV de los elementos seleccionados",
"action.toggle_shade": "Cambiar Sombreado",
"action.toggle_shade.desc": "Cambia el ajuste de sombreado de los cubos seleccionados",
"action.toggle_shade.desc": "Cambia el ajuste de sombreado de los elementos seleccionados",
"action.rename": "Renombrar",
"action.rename.desc": "Cambia el nombre de los cubos seleccionados",
"action.rename.desc": "Cambia el nombre de los elementos seleccionados",
"action.add_display_preset": "Nueva Plantilla",
"action.add_display_preset.desc": "Añade una nueva plantilla de ajustes de visualización",
"action.add_display_preset.desc": "Añadir un nuevo pre-ajuste de visualización",
"action.fullscreen": "Pantalla Completa",
"action.fullscreen.desc": "Cambia el modo de pantalla completa",
"action.zoom_in": "Hacer zoom",
@ -395,8 +395,8 @@
"menu.texture.file": "Archivo",
"menu.texture.refresh": "Refrescar",
"menu.texture.change": "Cambiar Archivo",
"menu.texture.folder": "Abrir en Carpeta",
"menu.texture.edit": "Editar Externamente",
"menu.texture.folder": "Mostrar en el Explorador de archivos",
"menu.texture.edit": "Editar",
"menu.texture.export": "Guardar Como",
"menu.texture.save": "Guardar",
"menu.texture.properties": "Propiedades",
@ -460,7 +460,7 @@
"display.reference.baby_zombie": "Zombie bebé",
"display.reference.armor_stand_small": "Armor Stand Pequeño",
"display.reference.monitor": "Normal",
"display.reference.bow": "Arco",
"display.reference.bow": "Arco Cargado",
"display.reference.block": "Bloque",
"display.reference.frame": "Marco de Ítems",
"display.reference.inventory_nine": "3x3",
@ -483,7 +483,7 @@
"action.change_textures_folder.desc": "Cambia la carpeta que en la que se guardan todas las texturas",
"menu.texture.particle": "Usar para Partículas",
"message.update_notification.title": "No fue posible Instalar la Actualización",
"message.update_notification.message": "Una nueva versión está disponible. ¡Active Actualizaciones Automáticas para actualizar!",
"message.update_notification.message": "Una nueva versión de Blockbench está disponible. ¡Active Actualizaciones Automáticas para actualizar!",
"message.untextured": "La superficie no tiene una textura",
"dialog.toolbar_edit.title": "Personalizar Barra de Herramientas",
"keybindings.reset": "Resetear",
@ -504,7 +504,7 @@
"action.uv_mirror_x.desc": "Invierte el UV de esta cara en el eje X",
"action.uv_mirror_y": "Invertir UV en Y",
"action.uv_mirror_y.desc": "Invierte el UV de esta cara en el eje Y",
"action.uv_transparent": "Cara Transparente",
"action.uv_transparent": "Eliminar cara",
"action.uv_transparent.desc": "Convierte la cara actual en transparente",
"action.uv_reset": "Resetear Cara",
"action.uv_reset.desc": "Resetea la cara actual",
@ -550,7 +550,7 @@
"action.move_right": "Mover Hacia Derecha",
"action.move_right.desc": "Mueve los cubos seleccionados hacia la derecha relativo al ángulo actual de la cámara",
"action.move_forth": "Mover Hacia Delante",
"action.move_forth.desc": "Mueve los cubos seleccionados hacia delante relativo al ángulo actual de la cámara",
"action.move_forth.desc": "Mueva los elementos seleccionados hacia adelante en relación con el ángulo actual de la cámara",
"action.move_back": "Mover Hacia Atrás",
"action.move_back.desc": "Mueve los cubos seleccionados hacia atrás relativo al ángulo actual de la cámara",
"layout.color.wireframe": "Estructura",
@ -579,7 +579,7 @@
"action.delete_keyframes": "Eliminar Fotogramas Clave",
"action.delete_keyframes.desc": "Elimina todos los fotogramas clave seleccionados",
"menu.animation": "Animación",
"menu.animation.loop": "Repetición",
"menu.animation.loop": "Modo de bucle",
"menu.animation.override": "Sobreescribir",
"menu.animation.anim_time_update": "Actualizar Variable",
"message.display_skin_model.title": "Modelo de Skin",
@ -595,9 +595,9 @@
"dialog.create_gif.title": "Grabar GIF",
"dialog.create_gif.length": "Duración",
"dialog.create_gif.fps": "FPS",
"dialog.create_gif.play": "Empezar Animación",
"dialog.create_gif.play": "Reproducir Animación",
"category.animation": "Animación",
"action.record_model_gif": "Grabar GIF",
"action.record_model_gif": "Grabar GIF...",
"action.record_model_gif.desc": "Graba un GIF animado de este modelo desde este ángulo",
"display.mirror": "Invertir",
"data.separator": "Separador",
@ -611,8 +611,8 @@
"mode.paint": "Pintar",
"mode.display": "Mostrar",
"mode.animate": "Animar",
"status_bar.recording_gif": "Grabando GIF",
"status_bar.processing_gif": "Procesando GIF",
"status_bar.recording_gif": "Grabando",
"status_bar.processing_gif": "Procesando",
"settings.backup_retain": "Mantenimiento de Respaldos",
"settings.backup_retain.desc": "Ajustar cuanto tiempo Blockbench mantiene respaldos viejos en días",
"action.rotate_tool": "Rotar",
@ -631,7 +631,7 @@
"menu.preview.perspective.reset": "Resetear Cámara",
"action.fill_mode": "Modo de Llenado",
"action.fill_mode.face": "Cara",
"action.fill_mode.color": "Color",
"action.fill_mode.color": "Colores",
"action.toggle_mirror_uv": "Invertir UV",
"action.toggle_mirror_uv.desc": "Activa el invertido de UV en el eje X de los cubos seleccionados",
"menu.texture.blank": "Aplicar a Caras sin Textura",
@ -673,7 +673,7 @@
"action.element_colors.desc": "Muestra los colores de cubo en el borde",
"texture.error.file": "Archivo no encontrado",
"texture.error.parent": "Archivo de textura proveído por el modelo padre",
"message.recover_backup.title": "Recuperar Modelo",
"message.recover_backup.title": "Recuperar Modelos",
"message.recover_backup.message": "Blockbench fue cerrado sin guardar. ¿Quieres recuperar el modelo?",
"message.invalid_session.title": "Token de Sesión Inválido",
"message.invalid_session.message": "La sesión a la que estas intentando entrar ha expirado o el token proveído es inválido.",
@ -718,7 +718,7 @@
"format.java_block": "Item/Bloque de Java",
"format.java_block.desc": "Modelo de bloque o item para la edición Java.",
"format.bedrock": "Entidad de Bedrock",
"format.bedrock.desc": "Modelo para la edición Bedrock.",
"format.bedrock.desc": "Modelo de Minecraft Bedrock Edition para entidades y objetos acoplables",
"format.bedrock_old": "Modelo Antiguo de Bedrock",
"format.bedrock_old.desc": "Modelo de entidad de las ediciones de Bedrock anteriores a 1.12",
"format.modded_entity": "Entidad de Mod",
@ -762,7 +762,7 @@
"action.remove_blank_faces.desc": "Elimina todas las caras sin textura de la selección",
"web.download_app": "Descargar Aplicación",
"uv_editor.turned": "Mapeado Girado",
"display.reference.crossbow": "Ballesta",
"display.reference.crossbow": "Ballesta Cargada",
"dialog.settings.search_results": "Resultados de Búsqueda",
"settings.animation_snap": "Imán de Animación",
"settings.animation_snap.desc": "Intervalo del imán para fotogramas clave en la línea de tiempo de la animación en pasos por segundo. Esto puede ser modificado para cada animación. El valor por defecto es 24.",
@ -801,9 +801,9 @@
"message.removed_faces": "Eliminadas %0 caras",
"dialog.sketchfab_uploader.draft": "Borrador",
"action.slider_pos": "Mover %0",
"action.slider_pos.desc": "Mover cubos en el eje %0",
"action.slider_pos.desc": "Mover elementos en el eje %0",
"action.slider_size": "Tamaño %0",
"action.slider_size.desc": "Redimensionar cubos en el eje %0",
"action.slider_size.desc": "Redimensionar elementos en el eje %0",
"action.slider_rotation": "Rotar %0",
"action.slider_rotation.desc": "Rotar cubos en el eje %0",
"action.slider_origin": "Pivote %0",
@ -885,7 +885,7 @@
"format.skin.desc": "Editar las skins de entidades y jugadores",
"message.sketchfab.setup_guide": "¿Quieres aprender a preparar modelos en Sketchfab? Lee %0",
"dialog.skin.title": "Crear Skin",
"dialog.skin.model": "Skin",
"dialog.skin.model": "Modelo",
"dialog.skin.texture": "Textura (Opcional)",
"action.toggle_skin_layer": "Cambiar Capa de Skin",
"action.toggle_skin_layer.desc": "Cambia la capa del gorro y de la ropa en el modelo de skin",
@ -1070,7 +1070,7 @@
"settings.motion_trails": "Senderos de movimiento",
"settings.motion_trails.desc": "Mostrar senderos de movimiento en el editor de animación",
"settings.antialiasing": "Anti-aliasing",
"settings.antialiasing.desc": "Modificar el anti-aliassing en la vista previa. Debes reiniciar Blockbench para aplicar los cambios",
"settings.antialiasing.desc": "Alternar el anti-aliasing en la vista previa",
"action.timeline_frame_back": "Avanza un fotograma atras",
"action.timeline_frame_forth": "Avanza un fotograma adelante",
"panel.bone.ik": "Cinemáticas inversas (Experimental)",
@ -1078,7 +1078,7 @@
"settings.particle_tick_rate.desc": "Efectos de partículas en la tasa de tic por segundo. El defecto es 30",
"action.lock_motion_trail": "Bloquear el rastro de movimiento",
"action.lock_motion_trail.desc": "Bloquear el rastro de movimiento en el grupo seleccionado",
"menu.animation_file.unload": "Descargar archivo de animación",
"menu.animation_file.unload": "Descargar archivo",
"data.null_object": "Objeto Nulo",
"status_bar.toggle_sidebar": "Mostrarbarralateral",
"message.load_plugin_failed.title": "Falló al cargar el plugin",
@ -1098,7 +1098,7 @@
"settings.ctrl_shift_size": "Resolución de Control + Shift",
"settings.ctrl_shift_size.desc": "Resolución del mapa mientras mantienes presionado control y shift",
"settings.hardware_acceleration": "Aceleración de Hardware",
"settings.hardware_acceleration.desc": "Subcontrato en tareas de renderizado a la tarjeta gráfica. Reinicia Blockbench para aplicar los cambios",
"settings.hardware_acceleration.desc": "Subcontratar tareas de renderizado a la tarjeta gráfica",
"action.explode_skin_model": "Expandir Modelo de Skin",
"action.explode_skin_model.desc": "Alternar a una vista de explosión que le permite editar caras cubiertas",
"action.export_minecraft_skin": "Exportar Skin de Minecraft",
@ -1175,7 +1175,7 @@
"action.load_keymap.cinema4d.desc": "Mapa de Teclas para usuarios que están familiarizados con los controles de Cinema 4D",
"action.load_keymap.maya.desc": "Mapa de Teclas para usuarios que estan familiarizados con los controles de Autodek Maya",
"action.import_keymap": "Importar Mapa de Teclas",
"action.import_keymap.desc": "Importar atajos de teclado como un archivo .bbkeymap",
"action.import_keymap.desc": "Importar atajos de teclado a un archivo .bbkeymap",
"action.export_keymap": "Exportar Mapa de Teclas",
"action.export_keymap.desc": "Exportar los actuales atajos de teclado como un archivo .bbkeymap",
"action.edit_history": "Editar Historial...",
@ -1229,7 +1229,7 @@
"message.invalid_link": "Link del Modelo Inválido o Expirado",
"message.default_textures.current": "Ruta actual",
"message.update_after_restart": "La actualización será instalada después del próximo reinicio",
"message.copy_paste_tool_viewport": "Este objeto solo puede usarse en el panel UV",
"message.copy_paste_tool_viewport": "Esta herramienta sólo puede utilizarse en el editor 2D",
"dialog.project.shadow_size": "Tamaño de la Sombra",
"dialog.find_replace.target": "Objetivo",
"dialog.find_replace.target.element_names": "Nombres de los Elementos",
@ -1246,22 +1246,22 @@
"dialog.add_primitive.shape.cylinder": "Cilindro",
"dialog.add_primitive.shape.sphere": "Esfera",
"dialog.add_primitive.shape.torus": "Toro",
"dialog.add_primitive.shape.cube": "Cubo",
"dialog.add_primitive.shape.cube": "Cuboide",
"dialog.add_primitive.shape.pyramid": "Pirámide",
"dialog.add_primitive.diameter": "Diámetro",
"dialog.add_primitive.height": "Peso",
"dialog.add_primitive.sides": "Lados",
"dialog.add_primitive.minor_diameter": "Espesor",
"dialog.add_primitive.minor_sides": "Lador Menores",
"dialog.create_texture.combine_polys": "Combinar Caras",
"dialog.create_texture.combine_polys.desc": "Combinar las caras conectadas en una en la sección UV",
"dialog.create_texture.combine_polys": "Combinar islas",
"dialog.create_texture.combine_polys.desc": "Combinar caras en islas UV conectadas",
"dialog.model_stats.meshes": "Mallas",
"dialog.export_private_settings.omit": "Salir",
"layout.select": "Seleccionar",
"layout.options": "Opciones",
"layout.color": "Escama de color",
"layout.documentation": "Documentación",
"layout.color.bright_ui_text": "Interfaz clara",
"layout.color.bright_ui_text": "Texto de interfaz brillante",
"layout.color.bright_ui_text.desc": "Texto en fondos claros",
"layout.name": "Nombre",
"layout.author": "Autor",
@ -1333,8 +1333,8 @@
"action.split_mesh": "Separar mallas",
"action.split_mesh.desc": "Separar las mallas seleccionadas en una nueva malla",
"action.merge_vertices": "Combinar vértices",
"action.merge_vertices.desc": "Combinar los vértices seleccionados en la posición del primer verted seleccionado",
"action.view_mode.normal": "Cara normal",
"action.merge_vertices.desc": "Fusionar los vértices seleccionados en la posición del primer vértice seleccionado",
"action.view_mode.normal": "Orientación de la cara",
"action.snap_uv_to_pixels": "Cambiar UV a píxeles",
"action.snap_uv_to_pixels.desc": "Ajusta los vértices UV seleccionados a la cuadrícula de píxeles",
"menu.file.import.import_open_project": "Importar proyecto abierto",
@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

File diff suppressed because it is too large Load Diff

@ -2238,7 +2238,7 @@
"settings.classroom_mode": "クラスルームモード",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "トーンマッピング",
"settings.tone_mapping.desc": "PBR レンダリングにハイダイナミックレンジを表示します。",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "メッシュ",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -512,7 +512,7 @@
"action.cullface.desc": "선택한 모델의 측면이 덮인 경우 이 면에 대한 렌더링 비활성화",
"action.auto_cullface": "후면 제거 키기",
"action.auto_cullface.desc": "이 표면의 후면 제거를 자체로 설정",
"action.face_tint": "옅은 색",
"action.face_tint": "",
"action.face_tint.desc": "현재 표면에 대한 색조 옵션을 사용 가능으로 설정",
"menu.toolbar.edit": "사용자 정의",
"menu.toolbar.reset": "초기화",
@ -2088,7 +2088,7 @@
"action.slider_color_select_threshold.desc": "How close the color of a neighboring pixel has to be to get selected by color or wand select",
"action.stretch_tool": "Stretch",
"action.stretch_tool.desc": "Tool to select and stretch elements",
"action.knife_tool": "Knife Tool",
"action.knife_tool": "나이프 도구",
"action.knife_tool.desc": "Tool to cut mesh faces into smaller pieces",
"action.duplicate_project": "Duplicate Project",
"action.duplicate_project.desc": "Creates a copy of the open project",
@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -222,7 +222,7 @@
"settings.export_groups.desc": "Salvar grupos em modelos de bloco ou item",
"settings.credit": "Comentário de crédito",
"settings.credit.desc": "Adicionar um comentário de crédito aos arquivos exportados",
"settings.default_path": "Default Minecraft Textures Path",
"settings.default_path": "Caminho Padrão de Texturas do Minecraft",
"settings.default_path.desc": "Pasta da qual o Blockbench carrega texturas padrão",
"settings.image_editor": "Editor de imagem",
"settings.image_editor.desc": "Editor de imagens padrão para editar texturas",
@ -1699,7 +1699,7 @@
"action.slider_color_red": "Color Red",
"action.slider_color_green": "Color Green",
"action.slider_color_blue": "Color Blue",
"action.add_animation_controller": "Add Animation Controller",
"action.add_animation_controller": "Adicionar Controlador de Animação",
"action.add_animation_controller.desc": "Create a blank animation controller",
"action.animation_controller_preview_mode": "Controller Preview",
"action.animation_controller_preview_mode.paused": "Paused",
@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -237,7 +237,7 @@
"category.textures": "Текстуры",
"category.misc": "Разное",
"keybind.preview_select": "Выделить элемент",
"keybind.preview_rotate": "Вращать вид",
"keybind.preview_rotate": "Вращение вида",
"keybind.preview_drag": "Перетащить вид",
"keybind.confirm": "Подтвердить",
"keybind.cancel": "Отменить",
@ -610,12 +610,12 @@
"mode.edit": "Редактирование",
"mode.paint": "Рисование",
"mode.display": "Предпросмотр",
"mode.animate": "Анимировать",
"mode.animate": "Анимация",
"status_bar.recording_gif": "Запись GIF",
"status_bar.processing_gif": "Обработка GIF",
"settings.backup_retain": "Время хранения автосохранений",
"settings.backup_retain.desc": "Установить продолжительность жизни автосохранения в днях",
"action.rotate_tool": "Вращать",
"action.rotate_tool": "Вращение",
"action.rotate_tool.desc": "Выбрать и провернуть элементы",
"action.fill_tool": "Заполнение",
"action.fill_tool.desc": "Инструмент для заполнения граней одним цветом",
@ -666,8 +666,8 @@
"data.origin": "Центр поворота",
"message.sketchfab.success": "Загрузка модели прошла успешно",
"message.sketchfab.error": "Не удалось загрузить модель на Sketchfab",
"settings.outliner_colors": "Цвета Элементов",
"settings.outliner_colors.desc": "Показывать цвета элементов в списке элементов",
"settings.outliner_colors": "Цвета элементов",
"settings.outliner_colors.desc": "Показывать цвета в списке элементов",
"action.upload_sketchfab.desc": "Загрузить модель на Sketchfab",
"action.element_colors": "Цвета элементов",
"action.element_colors.desc": "Показывать цвета элементов в списке элементов",
@ -686,7 +686,7 @@
"dialog.edit_session.title": "Редактирование сессии",
"edit_session.username": "Имя пользователя",
"edit_session.token": "Токен",
"edit_session.about": "Сессии редактирования позволяют совместно работать над моделями через интернет. Создай сессию, скопируй токен и отправь его друзьям, чтобы они могли присоединиться.",
"edit_session.about": "Сессии редактирования позволяют совместно работать над моделями в онлайн-формате. Создай сессию, скопируй токен и отправь его присоединяемому.",
"edit_session.join": "Присоединиться к сессии",
"edit_session.create": "Создать сессию",
"edit_session.quit": "Покинуть сессию",
@ -714,16 +714,16 @@
"mode.start.new": "Создать",
"mode.start.recent": "Недавние",
"format.free": "Общая модель",
"format.free.desc": "Модель без ограничений для Unity и т. п.",
"format.free.desc": "Модель без осевых, размерных и каких-либо иных ограничений для игровых движков (Unity, Godot) и рендеринга",
"format.java_block": "Модель для Minecraft JE",
"format.java_block.desc": "Модель для Minecraft JE.",
"format.bedrock": "Модель Bedrock",
"format.bedrock.desc": "Модель для Bedrock Edition",
"format.java_block.desc": "Модель для блоков и предметов в Minecraft JE",
"format.bedrock": "Модель для Minecraft BE",
"format.bedrock.desc": "Модель для сущностей и экипированных предметов в Minecraft BE",
"format.bedrock_old": "Устаревшая модель Bedrock",
"format.bedrock_old.desc": "Модель Bedrock Edition для версий старее 1.12",
"format.modded_entity": "Сущность для модов",
"format.modded_entity.desc": "Модель модовой сущности для Minecraft JE. Может быть экспортирована как файл класса .java.",
"format.optifine_entity": "Сущность OptiFine",
"format.bedrock_old.desc": "Устаревшая модель для версий игры до 1.12 в Minecraft BE",
"format.modded_entity": "Модовая сущность",
"format.modded_entity.desc": "Модель модовой сущности для Minecraft JE. Может быть экспортирована в виде .java-файла",
"format.optifine_entity": "OptiFine-сущность",
"format.optifine_entity.desc": "Пользовательская модель сущности для OptiFine",
"keys.mouse": "Кнопка Мыши %0",
"message.cleared_blank_faces.title": "Пустые грани",
@ -764,7 +764,7 @@
"uv_editor.turned": "Преоброзование овёрнуто",
"display.reference.crossbow": "Заряженный арбалет",
"dialog.settings.search_results": "Результаты поиска",
"settings.animation_snap": "Привязка Анимаций",
"settings.animation_snap": "Привязка анимаций",
"settings.animation_snap.desc": "Интервал привязки по умолчанию для ключевых кадров в анимационной шкале времени, измеряемый в FPS. Этот параметр можно изменить для каждой анимации. Значение по умолчанию — 24.",
"action.import_optifine_part": "Импортирование части OptiFine-модели",
"action.import_optifine_part.desc": "Импортировать часть модели сущности OptiFine",
@ -924,10 +924,10 @@
"dialog.sketchfab_uploader.animations": "Анимации",
"dialog.settings.theme": "Тема",
"settings.category.interface": "Интерфейс",
"settings.preview_checkerboard": "Предпросмотр шахматной доски",
"settings.preview_checkerboard.desc": "Переключить фон шахматной доски за предварительным просмотром",
"settings.preview_checkerboard": "Шахматная доска в предпросмотре",
"settings.preview_checkerboard.desc": "Переключить отображение шахматной доски на фоне предварительного просмотра",
"settings.uv_checkerboard": "Шахматная доска UV-редактора",
"settings.uv_checkerboard.desc": "Переключить фон шахматной доски за UV редактором",
"settings.uv_checkerboard.desc": "Переключить отображение шахматной доски на фоне UV-редактора",
"category.paint": "Рисование",
"action.fill_mode.color_connected": "Соединённые цвета",
"action.draw_shape_type": "Тип фигуры",
@ -944,8 +944,8 @@
"action.draw_shape_tool.desc": "Инструмент для рисования простых фигур",
"action.export_gltf": "Экспорт gLTF-модели",
"action.export_gltf.desc": "Экспортировать модель и анимации как glTF-файл для использования в других программах",
"action.transform_space": "Место для Трансформации",
"action.transform_space.desc": "Обычное место для трансформации для элементов и костей",
"action.transform_space": "Способ трансформация",
"action.transform_space.desc": "Метод трансформации элементов относительно собственной, родительской или глобальной осей",
"action.transform_space.global": "Глобальный",
"action.transform_space.local": "Локальный",
"action.toggle_camera_projection": "Включить проекцию камеры",
@ -957,7 +957,7 @@
"menu.help.quickstart": "Путеводитель Blockbench",
"menu.help.developer": "Фунции разработчика",
"menu.help.developer.dev_tools": "Открыть инструменты разработчика",
"menu.help.developer.reset_storage": "Перезагрузка фабрики",
"menu.help.developer.reset_storage": "Сброс до заводских настроек",
"menu.help.developer.reset_storage.confirm": "Ты уверен, что хочешь вернуть Blockbench до заводских параметров? Это сбросит все пользовательские настройки, горячие клавиши и плагины.",
"menu.help.developer.cache_reload": "Перезагрузить кэш",
"menu.preview.orthographic": "Ортография",
@ -1036,7 +1036,7 @@
"dialog.animation_import.title": "Выбери анимации для импорта",
"dialog.create_texture.padding": "Отступ",
"settings.fov": "FOV",
"settings.fov.desc": "Поле зрения камеры. Стандартное 45",
"settings.fov.desc": "Поле зрения камеры. Значение по умолчанию — 45°",
"settings.sync_color": "Синхронизовать цвет",
"settings.sync_color.desc": "Синхронизовать цвет между разными окнами Blockbench",
"settings.minify_bbmodel": "Уменьшенные файлы проекта",
@ -1075,7 +1075,7 @@
"action.timeline_frame_forth": "Перейти на кадр вперед",
"panel.bone.ik": "Инверсная кинематика",
"settings.particle_tick_rate": "Частота тактов частиц",
"settings.particle_tick_rate.desc": "Частота для эффектов частиц в тактах в секунду. По умолчанию 30",
"settings.particle_tick_rate.desc": "Частота для эффектов партиклей в тактах в секунду. Значение по умолчанию — 30",
"action.lock_motion_trail": "Блокировка пути движения",
"action.lock_motion_trail.desc": "Заблокировать пути движения для выбраных групп",
"menu.animation_file.unload": "Скачать файл с анимацией",
@ -1330,8 +1330,8 @@
"action.inset_mesh_selection.desc": "Вставь выбранные части мэша",
"action.dissolve_edges": "Растворение краёв",
"action.dissolve_edges.desc": "Растворить выбранные ребра в сетке и объединить грани, разделяемые ими",
"action.split_mesh": "Разделённая сетка",
"action.split_mesh.desc": "Разделить выбранные грани сетки на новую сетку",
"action.split_mesh": "Разделение мэшей",
"action.split_mesh.desc": "Отделить выбранные грани в новый мэш",
"action.merge_vertices": "Объединение вершин",
"action.merge_vertices.desc": "Объединить выбранные вершины в положение первой выбранной вершины",
"action.view_mode.normal": "Ориентация грани",
@ -1401,7 +1401,7 @@
"action.export_collada.desc": "Экспортировать модель и анимации как DAE-файл для использования в других программах",
"action.paint_mode_uv_overlay": "UV-наложение",
"action.paint_mode_uv_overlay.desc": "Отображение UV-карты в виде наложения в режиме рисования",
"action.bake_animation_into_model": "Запекание анимации в модель",
"action.bake_animation_into_model": "Запечь анимации в модель",
"action.bake_animation_into_model.desc": "Запеки отображаемый кадр анимации в модель. Применяется только вращение и положение, масштаб игнорируется.",
"action.keyframe_interpolation.step": "Пошаговый",
"action.set_ik_target": "Установить цель IK",
@ -1430,7 +1430,7 @@
"panel.skin_pose.jumping": "Прыжок",
"panel.skin_pose.aiming": "Прицеливание",
"edit.loop_cut.direction": "Направление",
"data.texture_mesh": "Текстурная сетка",
"data.texture_mesh": "Текстура мэша",
"generic.left": "Левый",
"generic.right": "Правый",
"mode.start.quick_setup": "Быстрая установка",
@ -1466,8 +1466,8 @@
"action.select_seam": "Выбери UV-шов",
"action.select_seam.desc": "Выбери режим шва UV для выбранных краев.",
"action.select_seam.auto": "Авто",
"action.select_seam.join": "Присоединиться",
"action.select_seam.divide": "Разделять",
"action.select_seam.join": "Присоединить",
"action.select_seam.divide": "Разделить",
"action.adjust_brightness_contrast": "Яркость и контрастность...",
"action.adjust_brightness_contrast.desc": "Отрегулировать яркость и контрастность выбранной текстуры",
"action.adjust_saturation_hue": "Насыщенность и оттенок...",
@ -1603,8 +1603,8 @@
"menu.mirror_painting.local.desc": "Включить зеркальное рисование в локальном пространстве для каждого элемента",
"menu.mirror_painting.texture_frames": "Повтор на кадрах анимированных текстур",
"menu.mirror_painting.texture_frames.desc": "Отражай мазки краской в ​​каждом кадре анимированных текстур.",
"format.bedrock_block.info.size_limit": "Размер Общий размер блока ограничен 30 пикселями во всех измерениях. Предел может быть смещен во всех направлениях на 7 пикселей от центра блока.",
"format.bedrock_block.info.textures": "Несколько текстур можно применять к разным кубам в Blockbench, но для правильной работы в игре требуется дополнительная настройка в пакете поведения.",
"format.bedrock_block.info.size_limit": "Общий размер блока ограничен 30 пикселями во всех измерениях. Предел может быть смещён во всех направлениях на 7 пикселей от центра блока.",
"format.bedrock_block.info.textures": "Несколько текстур можно применять к разным кубам в Blockbench, но для их правильной работы требуется дополнительная настройка в пакете поведения.",
"format.image.desc": "Редактировать изображения в редакторе 2D-изображений",
"generic.delete_all": "Удалить всё",
"message.child_model_only.open": "Открытый родитель",
@ -1845,7 +1845,7 @@
"dialog.copy_to_clipboard": "Скопировать в буфер обмена",
"dialog.open_url": "Открыть URL",
"reference_image.image": "Изображение",
"uv_editor.rotate_uv": "Вращать UV",
"uv_editor.rotate_uv": "Вращение UV",
"generic.error": "Ошибка",
"projects.start_screen": "Начальный экран",
"message.invalid_link.message": "Модель %0, которую ты пытаешься загрузить, недействительна или доступ к ней истёк.",
@ -1876,8 +1876,8 @@
"dialog.share_model.too_large_references": "Не удалось загрузить: Слишком большая модель. Попробуй убрать встроенные референсы.",
"settings.stretch_linked": "Связное растяжение",
"settings.stretch_linked.desc": "Растянуть куб во всех направлениях на одно и то же значение",
"settings.grids": "Показать сетки",
"settings.grids.desc": "Показать или скрыть все 3D-сетки",
"settings.grids": "Показать сетку",
"settings.grids.desc": "Показать или скрыть сетку редактирования",
"settings.double_click_switch_tools": "Переключать инструменты двойным щелчком",
"settings.double_click_switch_tools.desc": "Дважды нажать по области просмотра для переключения между инструментами",
"settings.outlines_in_paint_mode": "Контуры выделения в режиме рисования",
@ -1992,7 +1992,7 @@
"action.export_modded_animations.desc": "Экспортировать анимации для модели модовой сущности Minecraft Java Edition",
"action.swap_colors": "Поменять цвета",
"action.swap_colors.desc": "Поменять основной цвет с вторичным",
"action.apply_mesh_rotation": "Запекание вращения",
"action.apply_mesh_rotation": "Запечь вращения",
"action.apply_mesh_rotation.desc": "Применить вращение к геометрии модели, убрав его из свойств элемента",
"action.flip_texture_x.desc": "Отразить текстуру или слой по-горизонтали",
"action.flip_texture_y.desc": "Отразить текстуру или слой по-вертикали",
@ -2009,7 +2009,7 @@
"action.merge_layer_down.desc": "Соединить выбранный слой с лежащим снизу",
"action.advanced_screenshot": "Продвинутый снимок экрана...",
"action.advanced_screenshot.desc": "Сделать снимок модели с расширенными опциями",
"action.bake_ik_animation": "Запекание анимации обратной кинематики",
"action.bake_ik_animation": "Запечь анимации обратной кинематики",
"action.bake_ik_animation.desc": "Запечь вращения, применённые инверсной кинематикой, в выбранную анимацию",
"action.save_animation_preset": "Сохранить шаблон анимации...",
"action.save_animation_preset.desc": "Сохранить выбранные ключевые кадры как шаблон анимации",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "По длиннейшей оси",
"edit.vertex_snap.align.direction": "По направлению от центра поворота",
"edit.vertex_snap.align.align_axis": "По оси %0",
"edit.vertex_snap.ignore_axis": "Игнорирование оси %0",
"edit.vertex_snap.ignore_axis": "Игнорирование оси",
"codec.common.format": "Формат",
"codec.image.quality": "Качество",
"panel.collections": "Коллекции",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Допуск выделения",
"settings.selection_tolerance.desc": "Размер области, по которой можно нажать, чтобы выбрать ребро или вершину",
"action.delete.keep_vertices": "Сохранить рёбра/вершины",
"menu.mesh": "Мэш"
"menu.mesh": "Мэш",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

File diff suppressed because it is too large Load Diff

@ -293,9 +293,9 @@
"action.load_plugin.desc": "Tải một plugin bằng cách nhập tệp nguồn",
"action.reload_plugins": "Reload các plugin",
"action.reload_plugins.desc": "Tải lại toàn bộ Plugin cho Nhà Phát Triển",
"action.undo": "Quay lại",
"action.undo": "Hoàn tác",
"action.undo.desc": "Quay lại thay đổi trước",
"action.redo": "Tiến",
"action.redo": "Làm lại",
"action.redo.desc": "Quay lại thay đổi cuối",
"action.copy": "Sao chép",
"action.copy.desc": "Sao chép những đồ vật, mặt hay những điều chỉnh hiển thị đã được chọn",
@ -833,7 +833,7 @@
"dialog.timelapse.source.interface": "Giao diện",
"dialog.timelapse.source.locked": "Góc khóa",
"dialog.timelapse.destination": "Thư mục đích",
"layout.color.checkerboard": "Bàn cờ",
"layout.color.checkerboard": "Nền caro",
"layout.color.checkerboard.desc": "Nền của canvas và trình chỉnh sửa UV",
"layout.font.code": "Mã phông chữ",
"layout.css": "CSS tùy chỉnh",
@ -924,10 +924,10 @@
"dialog.sketchfab_uploader.animations": "Hoạt ảnh",
"dialog.settings.theme": "Chủ đề",
"settings.category.interface": "Giao diện",
"settings.preview_checkerboard": "Xem trước bàn cờ",
"settings.preview_checkerboard.desc": "Chuyển đổi nền bàn cờ đằng sau bản xem trước",
"settings.uv_checkerboard": "Chỉnh UV bàn cờ",
"settings.uv_checkerboard.desc": "Chuyển đổi nền bàn cờ phía sau trình chỉnh sửa UV",
"settings.preview_checkerboard": "Xem trước nền caro",
"settings.preview_checkerboard.desc": "Chuyển đổi nền caro đằng sau bản xem trước",
"settings.uv_checkerboard": "Chỉnh UV nền caro",
"settings.uv_checkerboard.desc": "Chuyển đổi nền caro phía sau trình chỉnh sửa UV",
"category.paint": "Vẽ",
"action.fill_mode.color_connected": "Kết nối màu sắc",
"action.draw_shape_type": "Loại hình dạng",
@ -1247,10 +1247,10 @@
"dialog.add_primitive.shape.sphere": "Hình cầu",
"dialog.add_primitive.shape.torus": "Hình xuyến",
"dialog.add_primitive.shape.cube": "Hình khối",
"dialog.add_primitive.shape.pyramid": "Hình kim tự tháp",
"dialog.add_primitive.shape.pyramid": "Hình chóp",
"dialog.add_primitive.diameter": "Đường kính",
"dialog.add_primitive.height": "Chiều cao",
"dialog.add_primitive.sides": "Bên",
"dialog.add_primitive.sides": "Số mặt bên",
"dialog.add_primitive.minor_diameter": "Độ dày",
"dialog.add_primitive.minor_sides": "Bên nhỏ",
"dialog.create_texture.combine_polys": "liên kết bề mặt",
@ -1282,13 +1282,13 @@
"action.add_plugin.desc": "Cài đặt plugin từ kiểm soát hành động",
"action.remove_plugin": "Xóa plugin khỏi kiểm soát hành động",
"action.remove_plugin.desc": "Gỡ cài đặt plugin thông qua Kiểm soát hành động",
"action.add_mesh": "Thêm khung lưới",
"action.add_mesh": "Thêm lưới",
"action.add_mesh.desc": "Thêm một khung lưới mới",
"action.add_texture_mesh": "Thêm khung lưới kết cấu",
"action.add_texture_mesh": "Thêm lưới kết cấu",
"action.add_texture_mesh.desc": "Thêm một khung lưới kết cấu mới",
"action.find_replace": "Tìm/Thay thế...",
"action.find_replace.desc": "Tìm và thay thế các phần của tên",
"action.hide_everything_except_selection": "Ẩn mọi thứ trừ lựa chọn",
"action.hide_everything_except_selection": "Ẩn mọi thứ (trừ phần lựa chọn)",
"action.hide_everything_except_selection.desc": "Chuyển đổi chế độ hiển thị cho tất cả các phần tử ngoại trừ phần tử đã chọn",
"action.transform_space.normal": "Bình thường",
"action.selection_mode": "Chế độ lựa chọn",
@ -1336,7 +1336,7 @@
"action.merge_vertices.desc": "Hợp nhất các đỉnh đã chọn vào vị trí của đỉnh được chọn đầu tiên",
"action.view_mode.normal": "Mặt thường",
"action.snap_uv_to_pixels": "Khớp UV với điểm ảnh",
"action.snap_uv_to_pixels.desc": "Gắn các đỉnh UV đã chọn vào lưới đồ thị điểm ảnh",
"action.snap_uv_to_pixels.desc": "Gắn các đỉnh UV đã chọn vào lưới điểm ảnh",
"menu.file.import.import_open_project": "Nhập dự án mở",
"menu.help.unlock_projects": "Mở khóa tất cả các dự án",
"status_bar.selection.faces": "%0 mặt",
@ -2076,7 +2076,7 @@
"dialog.import_obj.mtl": "Tệp MTL",
"dialog.import_obj.scale": "Tỉ lệ",
"layout.thumbnail": "Tùy chỉnh hình thu nhỏ CSS",
"settings.pixel_grid": "Lưới đồ thị điểm ảnh",
"settings.pixel_grid": "Lưới điểm ảnh",
"settings.pixel_grid.desc": "Hiển thị lưới đồ thị kết cấu điểm ảnh trên các phần tử trong chế độ chỉnh sửa",
"settings.image_editor_grid_size": "Kích thước lưới đồ thị của công cụ chỉnh sửa ảnh",
"settings.image_editor_grid_size.desc": "Kích thước lưới đồ thị lớn trong công cụ chỉnh sửa ảnh 2D",
@ -2092,7 +2092,7 @@
"action.knife_tool.desc": "Công cụ để cắt các mặt khung lưới thành các miếng nhỏ hơn",
"action.duplicate_project": "Nhân bản dự án",
"action.duplicate_project.desc": "Tạo một bản sao của dự án",
"action.transform_space.parent": "Phụ huynh",
"action.transform_space.parent": "Gốc",
"action.transform_pivot_space": "Tâm quay thay đổi không gian",
"action.solidify_mesh_selection": "Làm đông đặc các mặt",
"action.solidify_mesh_selection.desc": "Làm đông đặc các mặt được chọn của khung lưới",
@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "最长轴",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "轴 %0",
"edit.vertex_snap.ignore_axis": "忽略 %0 轴",
"edit.vertex_snap.ignore_axis": "忽略 轴",
"codec.common.format": "格式",
"codec.image.quality": "品质",
"panel.collections": "集合",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "网格"
"menu.mesh": "网格",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -2238,7 +2238,7 @@
"settings.classroom_mode": "Classroom Mode",
"settings.classroom_mode.desc": "Restricts functionality such as installing plugins and removes links to social media",
"settings.antialiasing_bleed_fix": "Fix anti-aliasing bleeding",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing",
"settings.antialiasing_bleed_fix.desc": "Fixes texture bleeding when using anti-aliasing. Potentially unsupported on older hardware.",
"settings.tone_mapping": "Tone Mapping",
"settings.tone_mapping.desc": "Approximation method for displaying high dynamic range on a standard screen for PBR rendering.",
"settings.audio_scrubbing": "Timline Scrubbing Audio",
@ -2307,7 +2307,7 @@
"edit.vertex_snap.align.longest": "Longest Axis",
"edit.vertex_snap.align.direction": "Direction from Pivot",
"edit.vertex_snap.align.align_axis": "%0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore %0 Axis",
"edit.vertex_snap.ignore_axis": "Ignore Axis",
"codec.common.format": "Format",
"codec.image.quality": "Quality",
"panel.collections": "Collections",
@ -2320,5 +2320,11 @@
"settings.selection_tolerance": "Selection Tolerance",
"settings.selection_tolerance.desc": "Size of the area that can be clicked to select an edge or vertex",
"action.delete.keep_vertices": "Keep Edges/Vertices",
"menu.mesh": "Mesh"
"menu.mesh": "Mesh",
"dialog.material_config.subsurface": "Subsurface Scattering",
"dialog.material_config.subsurface_enabled.desc": "Use the MER map alpha channel for subsurface scattering",
"settings.pick_combined_color": "Pick Combined Color",
"settings.pick_combined_color.desc": "Pick the combined color of all layers at the respective pixel, instead of the color on the active layer",
"action.split_rgb_into_layers": "Split RGB Channels into Layers",
"action.split_rgb_into_layers.desc": "Split the texture into additive layers, one for each RGB channel"
}

@ -5,11 +5,11 @@ class CanvasFrame {
/**
*
* @param {Number|HTMLCanvasElement|HTMLImageElement} [a] Image source
* @param {Number} [b]
* @param {Number|Boolean} [b]
*/
constructor(a, b) {
if (a && a.nodeName == 'CANVAS') {
if (a.getContext('2d')) {
if (a.getContext('2d') && b !== true) {
this.canvas = a;
} else {
this.createCanvas(a.width, a.height)

4
package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "Blockbench",
"version": "4.12.0",
"version": "4.12.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Blockbench",
"version": "4.12.0",
"version": "4.12.3",
"license": "GPL-3.0-or-later",
"dependencies": {
"@electron/remote": "^2.1.2",

@ -1,7 +1,7 @@
{
"name": "Blockbench",
"description": "Low-poly modeling and animation software",
"version": "4.12.0",
"version": "4.12.4",
"license": "GPL-3.0-or-later",
"author": {
"name": "JannisX11",
@ -140,4 +140,4 @@
"electron-updater": "^6.3.4",
"gifenc": "^1.0.3"
}
}
}