mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-01-18 15:26:19 +08:00
Add Solid Color Template generator
Add menu to import project directly from open tab Add "Snap UV To Pixels" action Fill tool element mode support for meshes (Closes #1065)
This commit is contained in:
parent
4c9d9acd5d
commit
2948152fe2
@ -378,7 +378,7 @@
|
||||
@mouseleave="mouseLeave(project, $event)"
|
||||
>
|
||||
<label class="project_tab_session_badge" v-if="project.EditSession"><i class="material-icons">group</i>{{ project.EditSession.client_count }}</label>
|
||||
<label>{{ project.name || project.geometry_name || project.format.name }}</label>
|
||||
<label>{{ project.getDisplayName() }}</label>
|
||||
<div class="project_tab_close_button" :class="{unsaved: !project.saved}" :title="close_tab_label" @click="project.close()">
|
||||
<i class="material-icons close_icon">clear</i>
|
||||
<i class="material-icons unsaved_icon" v-if="!project.saved">fiber_manual_record</i>
|
||||
|
@ -2358,7 +2358,7 @@ const BARS = {
|
||||
project.geometry_name.toLowerCase().includes(search_input)
|
||||
) {
|
||||
list.push({
|
||||
name: project.name || project.geometry_name || project.format.name,
|
||||
name: project.getDisplayName(),
|
||||
icon: project.format.icon,
|
||||
description: project.path,
|
||||
keybind_label: Modes.options[project.mode].name,
|
||||
|
@ -539,6 +539,31 @@ const MenuBar = {
|
||||
'close_project',
|
||||
'_',
|
||||
{name: 'menu.file.import', id: 'import', icon: 'insert_drive_file', children: [
|
||||
{
|
||||
id: 'import_open_project',
|
||||
name: 'menu.file.import.import_open_project',
|
||||
icon: 'input',
|
||||
condition: () => Project && ModelProject.all.length > 1,
|
||||
children() {
|
||||
let projects = [];
|
||||
ModelProject.all.forEach(project => {
|
||||
if (project == Project) return;
|
||||
projects.push({
|
||||
name: project.getDisplayName(),
|
||||
icon: project.format.icon,
|
||||
description: project.path,
|
||||
click() {
|
||||
let current_project = Project;
|
||||
project.select();
|
||||
let bbmodel = Codecs.project.compile();
|
||||
current_project.select();
|
||||
Codecs.project.merge(JSON.parse(bbmodel));
|
||||
}
|
||||
})
|
||||
})
|
||||
return projects;
|
||||
}
|
||||
},
|
||||
'import_project',
|
||||
'import_java_block_model',
|
||||
'import_optifine_part',
|
||||
|
@ -95,6 +95,9 @@ class ModelProject {
|
||||
get nodes_3d() {
|
||||
return ProjectData[this.uuid].nodes_3d;
|
||||
}
|
||||
getDisplayName() {
|
||||
return this.name || this.geometry_name || this.format.name;
|
||||
}
|
||||
reset() {
|
||||
return;
|
||||
//if (isApp) updateRecentProjectThumbnail();
|
||||
@ -466,6 +469,34 @@ function setStartScreen(state) {
|
||||
}
|
||||
|
||||
onVueSetup(() => {
|
||||
const new_tab = {
|
||||
name: tl('projects.new_tab'),
|
||||
saved: true,
|
||||
selected: true,
|
||||
uuid: guid(),
|
||||
visible: true,
|
||||
is_new_tab: true,
|
||||
getDisplayName() {return this.name},
|
||||
close: () => {
|
||||
if (ModelProject.all.length) {
|
||||
Interface.tab_bar.new_tab.visible = false;
|
||||
let project = ModelProject.all.find(project => project.uuid == Interface.tab_bar.last_opened_project) ||
|
||||
ModelProject.all.last();
|
||||
if (project) project.select();
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
select() {
|
||||
if (Project) {
|
||||
Project.unselect()
|
||||
}
|
||||
Project = 0;
|
||||
Interface.tab_bar.new_tab.selected = true;
|
||||
setProjectTitle(tl('projects.new_tab'));
|
||||
},
|
||||
openSettings() {}
|
||||
}
|
||||
Interface.tab_bar = new Vue({
|
||||
el: '#tab_bar',
|
||||
data: {
|
||||
@ -475,33 +506,7 @@ onVueSetup(() => {
|
||||
close_tab_label: tl('projects.close_tab'),
|
||||
search_tabs_label: tl('generic.search'),
|
||||
last_opened_project: '',
|
||||
new_tab: {
|
||||
name: tl('projects.new_tab'),
|
||||
saved: true,
|
||||
selected: true,
|
||||
uuid: guid(),
|
||||
visible: true,
|
||||
is_new_tab: true,
|
||||
close: () => {
|
||||
if (ModelProject.all.length) {
|
||||
Interface.tab_bar.new_tab.visible = false;
|
||||
let project = ModelProject.all.find(project => project.uuid == Interface.tab_bar.last_opened_project) ||
|
||||
ModelProject.all.last();
|
||||
if (project) project.select();
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
},
|
||||
select() {
|
||||
if (Project) {
|
||||
Project.unselect()
|
||||
}
|
||||
Project = 0;
|
||||
Interface.tab_bar.new_tab.selected = true;
|
||||
setProjectTitle(tl('projects.new_tab'));
|
||||
},
|
||||
openSettings() {}
|
||||
}
|
||||
new_tab
|
||||
},
|
||||
computed: {
|
||||
tabs() {
|
||||
|
@ -370,7 +370,7 @@ const Painter = {
|
||||
var element = Painter.current.element;
|
||||
let {rect, uvFactorX, uvFactorY, w, h} = area;
|
||||
|
||||
if (Painter.erase_mode && (fill_mode === 'cube' || fill_mode === 'face')) {
|
||||
if (Painter.erase_mode && (fill_mode === 'element' || fill_mode === 'face')) {
|
||||
ctx.globalAlpha = b_opacity;
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.globalCompositeOperation = 'destination-out';
|
||||
@ -378,7 +378,7 @@ const Painter = {
|
||||
ctx.fillStyle = tinycolor(ColorPanel.get()).setAlpha(b_opacity).toRgbString();
|
||||
}
|
||||
|
||||
if (element instanceof Cube && fill_mode === 'cube') {
|
||||
if (element instanceof Cube && fill_mode === 'element') {
|
||||
for (var face in element.faces) {
|
||||
var tag = element.faces[face]
|
||||
ctx.beginPath();
|
||||
@ -394,6 +394,32 @@ const Painter = {
|
||||
}
|
||||
}
|
||||
|
||||
} else if (element instanceof Mesh && fill_mode === 'element') {
|
||||
for (var fkey in element.faces) {
|
||||
var face = element.faces[fkey];
|
||||
if (face.vertices.length <= 2 || face.getTexture() !== texture) continue;
|
||||
ctx.beginPath();
|
||||
|
||||
let min_x = Project.texture_width;
|
||||
let min_y = Project.texture_height;
|
||||
let max_x = 0;
|
||||
let max_y = 0;
|
||||
face.vertices.forEach(vkey => {
|
||||
if (!face.uv[vkey]) return;
|
||||
min_x = Math.min(min_x, face.uv[vkey][0]);
|
||||
min_y = Math.min(min_y, face.uv[vkey][1]);
|
||||
max_x = Math.max(max_x, face.uv[vkey][0]);
|
||||
max_y = Math.max(max_y, face.uv[vkey][1]);
|
||||
})
|
||||
ctx.rect(
|
||||
Math.floor(min_x) * uvFactorX,
|
||||
Math.floor(min_y) * uvFactorY,
|
||||
(Math.ceil(max_x) - Math.floor(min_x)) * uvFactorX,
|
||||
(Math.ceil(max_y) - Math.floor(min_y)) * uvFactorY,
|
||||
)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
} else if (fill_mode === 'face') {
|
||||
ctx.fill()
|
||||
} else {
|
||||
@ -1138,7 +1164,7 @@ BARS.defineActions(function() {
|
||||
condition: () => Toolbox && Toolbox.selected.id === 'fill_tool',
|
||||
options: {
|
||||
face: true,
|
||||
cube: true,
|
||||
element: true,
|
||||
color_connected: true,
|
||||
color: true,
|
||||
}
|
||||
|
@ -13,6 +13,13 @@ const TextureGenerator = {
|
||||
south: {c1: '#f8dd72', c2: '#FFF899', place: t => {return {x: t.posx+t.z+t.x+t.z,y: t.posy+t.z, w: t.x, h: t.y}}},
|
||||
},
|
||||
addBitmapDialog() {
|
||||
let type_options = {
|
||||
blank: 'dialog.create_texture.type.blank',
|
||||
template: 'dialog.create_texture.type.template',
|
||||
}
|
||||
if (!Project.box_uv) {
|
||||
type_options.color_map = 'dialog.create_texture.type.color_map';
|
||||
}
|
||||
var dialog = new Dialog({
|
||||
id: 'add_bitmap',
|
||||
title: tl('action.create_texture'),
|
||||
@ -22,7 +29,7 @@ const TextureGenerator = {
|
||||
folder: {label: 'dialog.create_texture.folder', condition: Format.id == 'java_block'},
|
||||
|
||||
color: {label: 'data.color', type: 'color', colorpicker: TextureGenerator.background_color},
|
||||
resolution: {label: 'dialog.create_texture.resolution', description: 'dialog.create_texture.resolution.desc', type: 'select', value: 16, condition: (form) => (form.template), options: {
|
||||
resolution: {label: 'dialog.create_texture.resolution', description: 'dialog.create_texture.resolution.desc', type: 'select', value: 16, condition: (form) => (form.type == 'template'), options: {
|
||||
16: '16',
|
||||
32: '32',
|
||||
64: '64',
|
||||
@ -30,28 +37,28 @@ const TextureGenerator = {
|
||||
256: '256',
|
||||
512: '512',
|
||||
}},
|
||||
resolution_vec: {label: 'dialog.create_texture.resolution', type: 'vector', condition: (form) => (!form.template), dimensions: 2, value: [16, 16], min: 16, max: 2048},
|
||||
resolution_vec: {label: 'dialog.create_texture.resolution', type: 'vector', condition: (form) => (form.type == 'blank'), dimensions: 2, value: [16, 16], min: 16, max: 2048},
|
||||
|
||||
section2: "_",
|
||||
template: {label: 'dialog.create_texture.template', type: 'checkbox', condition: Cube.all.length || Mesh.all.length},
|
||||
type: {label: 'dialog.create_texture.type', type: 'select', condition: Cube.all.length || Mesh.all.length, options: type_options},
|
||||
|
||||
rearrange_uv:{label: 'dialog.create_texture.rearrange_uv', description: 'dialog.create_texture.rearrange_uv.desc', type: 'checkbox', value: true, condition: (form) => (form.template)},
|
||||
compress: {label: 'dialog.create_texture.compress', description: 'dialog.create_texture.compress.desc', type: 'checkbox', value: true, condition: (form) => (form.template && Project.box_uv && form.rearrange_uv)},
|
||||
power: {label: 'dialog.create_texture.power', description: 'dialog.create_texture.power.desc', type: 'checkbox', value: true, condition: (form) => (form.template && form.rearrange_uv)},
|
||||
double_use: {label: 'dialog.create_texture.double_use', description: 'dialog.create_texture.double_use.desc', type: 'checkbox', value: true, condition: (form) => (form.template && Project.box_uv && form.rearrange_uv)},
|
||||
combine_polys: {label: 'dialog.create_texture.combine_polys', description: 'dialog.create_texture.combine_polys.desc', type: 'checkbox', value: true, condition: (form) => (form.template && form.rearrange_uv && Mesh.selected.length)},
|
||||
box_uv: {label: 'dialog.project.uv_mode.box_uv', type: 'checkbox', value: false, condition: (form) => (form.template && !Project.box_uv)},
|
||||
padding: {label: 'dialog.create_texture.padding', description: 'dialog.create_texture.padding.desc', type: 'checkbox', value: false, condition: (form) => (form.template && form.rearrange_uv)},
|
||||
rearrange_uv:{label: 'dialog.create_texture.rearrange_uv', description: 'dialog.create_texture.rearrange_uv.desc', type: 'checkbox', value: true, condition: (form) => (form.type == 'template')},
|
||||
compress: {label: 'dialog.create_texture.compress', description: 'dialog.create_texture.compress.desc', type: 'checkbox', value: true, condition: (form) => (form.type == 'template' && Project.box_uv && form.rearrange_uv)},
|
||||
power: {label: 'dialog.create_texture.power', description: 'dialog.create_texture.power.desc', type: 'checkbox', value: true, condition: (form) => (form.type !== 'blank' && (form.rearrange_uv || form.type == 'color_map'))},
|
||||
double_use: {label: 'dialog.create_texture.double_use', description: 'dialog.create_texture.double_use.desc', type: 'checkbox', value: true, condition: (form) => (form.type == 'template' && Project.box_uv && form.rearrange_uv)},
|
||||
combine_polys: {label: 'dialog.create_texture.combine_polys', description: 'dialog.create_texture.combine_polys.desc', type: 'checkbox', value: true, condition: (form) => (form.type == 'template' && form.rearrange_uv && Mesh.selected.length)},
|
||||
box_uv: {label: 'dialog.project.uv_mode.box_uv', type: 'checkbox', value: false, condition: (form) => (form.type == 'template' && !Project.box_uv)},
|
||||
padding: {label: 'dialog.create_texture.padding', description: 'dialog.create_texture.padding.desc', type: 'checkbox', value: false, condition: (form) => (form.type == 'template' && form.rearrange_uv)},
|
||||
|
||||
},
|
||||
onFormChange(form) {
|
||||
if (form.template && TextureGenerator.background_color.get().toHex8() === 'ffffffff') {
|
||||
if (form.type == 'template' && TextureGenerator.background_color.get().toHex8() === 'ffffffff') {
|
||||
TextureGenerator.background_color.set('#00000000')
|
||||
}
|
||||
},
|
||||
onConfirm: function(results) {
|
||||
results.particle = 'auto';
|
||||
if (!results.template) {
|
||||
if (results.type == 'blank') {
|
||||
results.resolution = results.resolution_vec;
|
||||
}
|
||||
dialog.hide()
|
||||
@ -91,12 +98,12 @@ const TextureGenerator = {
|
||||
if (typeof after === 'function') {
|
||||
after(texture)
|
||||
}
|
||||
if (!options.template) {
|
||||
if (options.type == 'blank') {
|
||||
Undo.finishEdit('Create blank texture', {textures: [texture], selected_texture: true, bitmap: true})
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
if (options.template === true) {
|
||||
if (options.type == 'template') {
|
||||
if (Project.box_uv || options.box_uv) {
|
||||
if (Mesh.selected[0]) {
|
||||
Blockbench.showQuickMessage('message.box_uv_for_meshes', 1600);
|
||||
@ -105,6 +112,8 @@ const TextureGenerator = {
|
||||
} else {
|
||||
TextureGenerator.generateFaceTemplate(options, makeTexture);
|
||||
}
|
||||
} else if (options.type == 'color_map') {
|
||||
TextureGenerator.generateColorMapTemplate(options, makeTexture);
|
||||
} else {
|
||||
Undo.initEdit({textures: [], selected_texture: true})
|
||||
TextureGenerator.generateBlank(options.resolution[1], options.resolution[0], options.color, makeTexture)
|
||||
@ -995,6 +1004,125 @@ const TextureGenerator = {
|
||||
uv_mode: true
|
||||
})
|
||||
},
|
||||
generateColorMapTemplate(options, cb) {
|
||||
|
||||
var background_color = options.color;
|
||||
var texture = options.texture;
|
||||
var new_resolution = [];
|
||||
|
||||
var face_list = [];
|
||||
var element_list = (Format.single_texture ? Outliner.elements : Outliner.selected).filter(el => {
|
||||
return (el instanceof Cube || el instanceof Mesh) && el.visibility;
|
||||
});
|
||||
|
||||
Undo.initEdit({
|
||||
textures: [],
|
||||
elements: element_list,
|
||||
uv_only: true,
|
||||
selected_texture: true,
|
||||
uv_mode: true
|
||||
})
|
||||
|
||||
element_list.forEach(element => {
|
||||
for (let fkey in element.faces) {
|
||||
let face = element.faces[fkey];
|
||||
if (element instanceof Mesh && face.vertices.length <= 2) continue;
|
||||
if (element instanceof Cube && face.texture === null) continue;
|
||||
face_list.push({element, fkey, face});
|
||||
}
|
||||
})
|
||||
|
||||
if (face_list.length == 0) {
|
||||
Blockbench.showMessage('No valid cubes', 'center')
|
||||
return;
|
||||
}
|
||||
|
||||
var max_size = Math.ceil(Math.sqrt(face_list.length));
|
||||
if (options.power) {
|
||||
max_size = Math.getNextPower(max_size, 16);
|
||||
} else {
|
||||
max_size = Math.ceil(max_size/16)*16;
|
||||
}
|
||||
new_resolution = [max_size, max_size];
|
||||
|
||||
if (background_color.getAlpha() != 0) {
|
||||
background_color = background_color.toRgbString()
|
||||
}
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = new_resolution[0];
|
||||
canvas.height = new_resolution[1];
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = typeof background_color == 'string' ? background_color : 'white';
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
let texture_ctxs = {};
|
||||
|
||||
//Drawing
|
||||
face_list.forEach(({face, fkey}, i) => {
|
||||
let x = i % max_size;
|
||||
let y = Math.floor(i / max_size);
|
||||
|
||||
let texture;
|
||||
if (!Format.single_texture) {
|
||||
if (face.texture !== undefined && face.texture !== null) {
|
||||
texture = face.getTexture()
|
||||
}
|
||||
} else {
|
||||
texture = Texture.getDefault();
|
||||
}
|
||||
if (texture && texture.img) {
|
||||
if (!texture_ctxs[texture.uuid]) {
|
||||
texture_ctxs[texture.uuid] = new CanvasFrame(texture.img).ctx;
|
||||
}
|
||||
let color = Painter.getPixelColor(
|
||||
texture_ctxs[texture.uuid],
|
||||
Math.floor((face instanceof CubeFace ? face.uv : face.uv[face.vertices[0]])[0] / Project.texture_width * texture.width),
|
||||
Math.floor((face instanceof CubeFace ? face.uv : face.uv[face.vertices[0]])[1] / Project.texture_height * texture.height),
|
||||
);
|
||||
ctx.fillStyle = color ? color.toHexString() : 'white';
|
||||
} else {
|
||||
ctx.fillStyle = typeof background_color == 'string' ? background_color : 'white';
|
||||
}
|
||||
|
||||
ctx.fillRect(x, y, 1, 1);
|
||||
|
||||
if (face instanceof CubeFace) {
|
||||
face.uv = [x+0.25, y+0.25, x+0.75, y+0.75];
|
||||
} else if (face instanceof MeshFace) {
|
||||
let vertices = face.getSortedVertices();
|
||||
face.uv[vertices[0]] = [x+0.75, y+0.25];
|
||||
face.uv[vertices[1]] = [x+0.25, y+0.25];
|
||||
face.uv[vertices[2]] = [x+0.25, y+0.75];
|
||||
if (vertices[3]) face.uv[vertices[3]] = [x+0.75, y+0.75];
|
||||
console.log(vertices, face.uv)
|
||||
}
|
||||
})
|
||||
var dataUrl = canvas.toDataURL()
|
||||
var texture = cb(dataUrl)
|
||||
|
||||
TextureGenerator.changeProjectResolution(new_resolution[0], new_resolution[1]);
|
||||
|
||||
if (texture) {
|
||||
face_list.forEach(({face, fkey}, i) => {
|
||||
face.texture = texture.uuid;
|
||||
})
|
||||
element_list.forEach(function(element) {
|
||||
element.preview_controller.updateFaces(element);
|
||||
element.preview_controller.updateUV(element);
|
||||
if (typeof element.autouv !== 'undefined') {
|
||||
element.autouv = 0;
|
||||
}
|
||||
})
|
||||
}
|
||||
updateSelection()
|
||||
Undo.finishEdit('Create template', {
|
||||
textures: [texture],
|
||||
bitmap: true,
|
||||
elements: element_list,
|
||||
selected_texture: true,
|
||||
uv_only: true,
|
||||
uv_mode: true
|
||||
})
|
||||
},
|
||||
//Misc
|
||||
changeProjectResolution(width, height) {
|
||||
let factor_x = width / Project.texture_width;
|
||||
|
@ -1214,6 +1214,7 @@ const UVEditor = {
|
||||
'uv_maximize',
|
||||
'uv_auto',
|
||||
'uv_rel_auto',
|
||||
'snap_uv_to_pixels',
|
||||
{icon: 'rotate_90_degrees_ccw', condition: () => Format.uv_rotation, name: 'menu.uv.mapping.rotation', children: function() {
|
||||
var off = 'radio_button_unchecked'
|
||||
var on = 'radio_button_checked'
|
||||
@ -1516,6 +1517,38 @@ BARS.defineActions(function() {
|
||||
Undo.finishEdit('Set face tint')
|
||||
}
|
||||
})
|
||||
new Action('snap_uv_to_pixels', {
|
||||
icon: 'grid_goldenratio',
|
||||
category: 'uv',
|
||||
condition: () => UVEditor.getMappableElements().length,
|
||||
click: function (event) {
|
||||
let elements = UVEditor.getMappableElements();
|
||||
Undo.initEdit({elements, uv_only: true})
|
||||
elements.forEach(element => {
|
||||
let selected_vertices = element instanceof Mesh && element.getSelectedVertices();
|
||||
UVEditor.selected_faces.forEach(fkey => {
|
||||
if (!element.faces[fkey]) return;
|
||||
let face = element.faces[fkey];
|
||||
if (element instanceof Mesh) {
|
||||
face.vertices.forEach(vkey => {
|
||||
if ((!selected_vertices.length || selected_vertices.includes(vkey)) && face.uv[vkey]) {
|
||||
face.uv[vkey][0] = Math.clamp(Math.round(face.uv[vkey][0]), 0, Project.texture_width);
|
||||
face.uv[vkey][1] = Math.clamp(Math.round(face.uv[vkey][1]), 0, Project.texture_height);
|
||||
}
|
||||
})
|
||||
} else if (element instanceof Cube) {
|
||||
face.uv[0] = Math.clamp(Math.round(face.uv[0]), 0, Project.texture_width);
|
||||
face.uv[1] = Math.clamp(Math.round(face.uv[1]), 0, Project.texture_height);
|
||||
face.uv[2] = Math.clamp(Math.round(face.uv[2]), 0, Project.texture_width);
|
||||
face.uv[3] = Math.clamp(Math.round(face.uv[3]), 0, Project.texture_height);
|
||||
}
|
||||
})
|
||||
element.preview_controller.updateUV(element);
|
||||
})
|
||||
UVEditor.loadData();
|
||||
Undo.finishEdit('Set automatic cullface')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
new Toggle('toggle_uv_overlay', {
|
||||
|
File diff suppressed because one or more lines are too long
10
lang/en.json
10
lang/en.json
@ -386,7 +386,10 @@
|
||||
"dialog.animation_export.title": "Select Animations to Export",
|
||||
|
||||
"dialog.create_texture.folder": "Folder",
|
||||
"dialog.create_texture.template": "Template",
|
||||
"dialog.create_texture.type": "Type",
|
||||
"dialog.create_texture.type.blank": "Blank",
|
||||
"dialog.create_texture.type.template": "Texture Template",
|
||||
"dialog.create_texture.type.color_map": "Solid Color Template",
|
||||
"dialog.create_texture.rearrange_uv": "Rearrange UV",
|
||||
"dialog.create_texture.rearrange_uv.desc": "Create a new UV arrangement to give every face its own spot on the texture",
|
||||
"dialog.create_texture.compress": "Compress Template",
|
||||
@ -732,7 +735,7 @@
|
||||
"action.fill_mode.face": "Face",
|
||||
"action.fill_mode.color_connected": "Connected Colors",
|
||||
"action.fill_mode.color": "Colors",
|
||||
"action.fill_mode.cube": "Cube",
|
||||
"action.fill_mode.element": "Element",
|
||||
"action.draw_shape_type": "Shape Type",
|
||||
"action.draw_shape_type.rectangle": "Rectangle",
|
||||
"action.draw_shape_type.rectangle_h": "Rectangle (Hollow)",
|
||||
@ -1156,6 +1159,8 @@
|
||||
"action.slider_face_tint.desc": "Set the tint index of the current face. -1 means unset.",
|
||||
"action.toggle_uv_overlay": "Toggle UV Overlay",
|
||||
"action.toggle_uv_overlay.desc": "When enabled, displays all UV mapping overlays above the texture.",
|
||||
"action.snap_uv_to_pixels": "Snap UV to Pixels",
|
||||
"action.snap_uv_to_pixels.desc": "Snaps the selected UV vertices to the pixel grid",
|
||||
"action.remove_blank_faces": "Remove Blank Faces",
|
||||
"action.remove_blank_faces.desc": "Deletes all untextured faces of the selection",
|
||||
|
||||
@ -1252,6 +1257,7 @@
|
||||
"menu.file.recent": "Recent",
|
||||
"menu.file.recent.clear": "Clear Recent Files",
|
||||
"menu.file.import": "Import",
|
||||
"menu.file.import.import_open_project": "Import Open Project",
|
||||
"menu.file.export": "Export",
|
||||
"menu.file.preferences": "Preferences",
|
||||
"menu.transform.rotate": "Rotate",
|
||||
|
Loading…
Reference in New Issue
Block a user