From e9c53c960649fd57dbb1526558ba958dd3c16d69 Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Fri, 27 Sep 2024 00:06:55 -0300 Subject: [PATCH 01/10] prototype texture_repeat; lang; package-lock? # Conflicts: # lang/zh.json # package-lock.json --- js/texturing/textures.js | 12 +++++++++--- lang/cz.json | 1 + lang/de.json | 1 + lang/en.json | 1 + lang/es.json | 1 + lang/fr.json | 1 + lang/it.json | 1 + lang/ja.json | 1 + lang/ko.json | 1 + lang/nl.json | 1 + lang/pl.json | 1 + lang/pt.json | 1 + lang/ru.json | 1 + lang/sv.json | 1 + lang/uk.json | 1 + lang/vi.json | 1 + lang/zh_tw.json | 1 + 17 files changed, 25 insertions(+), 3 deletions(-) diff --git a/js/texturing/textures.js b/js/texturing/textures.js index 2e3923b9..99e10b1d 100644 --- a/js/texturing/textures.js +++ b/js/texturing/textures.js @@ -21,6 +21,7 @@ class Texture { this.height = 0; this.uv_width = Project ? Project.texture_width : 16; this.uv_height = Project ? Project.texture_height : 16; + this.repeating = false; this.currentFrame = 0; this.saved = true; this.layers = []; @@ -56,8 +57,8 @@ class Texture { img.src = 'assets/missing.png' var tex = new THREE.Texture(this.canvas); - tex.magFilter = THREE.NearestFilter - tex.minFilter = THREE.NearestFilter + tex.magFilter = THREE.NearestFilter; + tex.minFilter = THREE.NearestFilter; tex.name = this.name; img.tex = tex; @@ -733,6 +734,8 @@ class Texture { mat.side = this.render_sides == 'auto' ? Canvas.getRenderSide() : (this.render_sides == 'front' ? THREE.FrontSide : THREE.DoubleSide); // Map + mat.map.wrapS = this.repeating ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + mat.map.wrapT = this.repeating ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; mat.map.needsUpdate = true; return this; } @@ -1157,6 +1160,7 @@ class Texture { if (Format.per_texture_uv_size) { form.uv_size = {type: 'vector', label: 'dialog.texture.uv_size', value: [this.uv_width, this.uv_height], dimensions: 2, step: 1, min: 1}; } + form.repeating = {label: 'dialog.texture.repeating', type: 'checkbox', value: this.repeating}; if (Format.texture_mcmeta) { Object.assign(form, { 'texture_mcmeta': '_', @@ -1185,7 +1189,7 @@ class Texture { onConfirm: results => { dialog.hide(); - if (['name', 'variable', 'folder', 'namespace', 'frame_time', 'frame_interpolate', 'frame_order_type', 'frame_order'].find(key => { + if (['name', 'variable', 'folder', 'namespace', 'frame_time', 'frame_interpolate', 'frame_order_type', 'frame_order', 'repeating'].find(key => { return results[key] !== undefined && results[key] !== this[key]; }) == undefined) { return; @@ -1201,6 +1205,7 @@ class Texture { if (results.namespace !== undefined) this.namespace = results.namespace; if (results.render_mode !== undefined) this.render_mode = results.render_mode; if (results.render_sides !== undefined) this.render_sides = results.render_sides; + if (results.repeating !== undefined) this.repeating = results.repeating; if (Format.per_texture_uv_size) { let changed = this.uv_width != results.uv_size[0] || this.uv_height != results.uv_size[1]; @@ -2079,6 +2084,7 @@ class Texture { new Property(Texture, 'string', 'sync_to_project') new Property(Texture, 'enum', 'render_mode', {default: 'default'}) new Property(Texture, 'enum', 'render_sides', {default: 'auto'}) + new Property(Texture, 'boolean', 'repeating') new Property(Texture, 'number', 'frame_time', {default: 1}) new Property(Texture, 'enum', 'frame_order_type', {default: 'loop', values: ['custom', 'loop', 'backwards', 'back_and_forth']}) diff --git a/lang/cz.json b/lang/cz.json index 3b66ebf1..9f3a1732 100644 --- a/lang/cz.json +++ b/lang/cz.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", diff --git a/lang/de.json b/lang/de.json index 2722d5d3..57d30ab5 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Datei laden und alle Änderungen und Ebenen in Blockbench verwerfen", "message.group_required_to_animate": "Diese Art von Element kann nicht animiert werden. Erstelle stattdessen eine Gruppe.", "dialog.texture.uv_size": "UV-Größe", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Kamerablickwinkel", "dialog.advanced_screenshot.resolution": "Auflösung", "dialog.advanced_screenshot.zoom_to_fit": "Passend zoomen", diff --git a/lang/en.json b/lang/en.json index 43e1362e..b38c0239 100644 --- a/lang/en.json +++ b/lang/en.json @@ -458,6 +458,7 @@ "dialog.texture.namespace": "Namespace", "dialog.texture.folder": "Folder", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.texture.frame_time": "Frame Time", "dialog.texture.frame_time.desc": "Set for how long each frame will be visible, in ticks. Each tick is 1/20th of a second.", "dialog.texture.frame_interpolate": "Interpolate", diff --git a/lang/es.json b/lang/es.json index 242c193c..324d5483 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", diff --git a/lang/fr.json b/lang/fr.json index 6a83cb86..1eb44edf 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Résolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoomer pour ajuster", diff --git a/lang/it.json b/lang/it.json index 7903cb10..9840e29d 100644 --- a/lang/it.json +++ b/lang/it.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", diff --git a/lang/ja.json b/lang/ja.json index 6fefdd13..89ed6a9c 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "ファイルをロードし、Blockbench のすべての変更とレイヤーを破棄します", "message.group_required_to_animate": "このタイプのエレメントをアニメーション化することはできません。 グループを作成してアニメーション化します。", "dialog.texture.uv_size": "UV サイズ", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "アングルプリセット", "dialog.advanced_screenshot.resolution": "解像度", "dialog.advanced_screenshot.zoom_to_fit": "フィットズーム", diff --git a/lang/ko.json b/lang/ko.json index 20ca4a00..7c9f0efa 100644 --- a/lang/ko.json +++ b/lang/ko.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", diff --git a/lang/nl.json b/lang/nl.json index b4e32f2b..4c977c91 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", diff --git a/lang/pl.json b/lang/pl.json index 44233603..83557815 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", diff --git a/lang/pt.json b/lang/pt.json index 783ecb55..a4182cab 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -100,6 +100,7 @@ "dialog.texture.variable": "Variável", "dialog.texture.namespace": "Ambiente", "dialog.texture.folder": "Pasta", + "dialog.texture.repeating": "Repetição", "dialog.extrude.title": "Extrudar imagem", "dialog.extrude.mode": "Modo de digitalização", "dialog.extrude.mode.areas": "Áreas", diff --git a/lang/ru.json b/lang/ru.json index f697c151..ca23108b 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Загрузить файл и отменить все изменения и слои в Blockbench", "message.group_required_to_animate": "Вы не можете анимировать этот тип элемента. Создайте группу для анимации.", "dialog.texture.uv_size": "Размер UV", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Предустановка угла", "dialog.advanced_screenshot.resolution": "Разрешение", "dialog.advanced_screenshot.zoom_to_fit": "Масштаб по размеру", diff --git a/lang/sv.json b/lang/sv.json index 4514941e..61621638 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", diff --git a/lang/uk.json b/lang/uk.json index 4a5f2d8d..cadefc87 100644 --- a/lang/uk.json +++ b/lang/uk.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Завантажити файл і скасувати всі зміни та шари в Blockbench", "message.group_required_to_animate": "Цей тип елементів не можна анімувати. Створіть групу, щоб анімувати його.", "dialog.texture.uv_size": "UV-Розмір", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Попередньо заданий кут", "dialog.advanced_screenshot.resolution": "Роздільність", "dialog.advanced_screenshot.zoom_to_fit": "Розмір зображення", diff --git a/lang/vi.json b/lang/vi.json index dab19513..1dc8f3f6 100644 --- a/lang/vi.json +++ b/lang/vi.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Tải tệp và loại bỏ tất cả các thay đổi và lớp trong Blockbench", "message.group_required_to_animate": "Bạn không thể tạo hiệu ứng chuyển động cho phần tử này. Hãy tạo một nhóm để tạo hiệu ứng chuyển động.", "dialog.texture.uv_size": "Kích cỡ UV", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Góc giá trị cài sẵn", "dialog.advanced_screenshot.resolution": "Độ phân giải", "dialog.advanced_screenshot.zoom_to_fit": "Thu phóng tới khi vừa", diff --git a/lang/zh_tw.json b/lang/zh_tw.json index d5a7c30f..6e4f139d 100644 --- a/lang/zh_tw.json +++ b/lang/zh_tw.json @@ -1963,6 +1963,7 @@ "message.texture_refresh_conflict.keep_theirs": "Load file and discard all changes and layers in Blockbench", "message.group_required_to_animate": "You cannot animate this type of element. Create a group to animate it.", "dialog.texture.uv_size": "UV Size", + "dialog.texture.repeating": "Repeat", "dialog.advanced_screenshot.angle_preset": "Angle Preset", "dialog.advanced_screenshot.resolution": "Resolution", "dialog.advanced_screenshot.zoom_to_fit": "Zoom To Fit", From d0bdbc59d3e935f992f6e13302aead68a06343f5 Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Tue, 16 Jul 2024 12:06:12 -0300 Subject: [PATCH 02/10] new option in the toolbar to toggle 'snap to image bounds'; force disabled zoom out limit --- js/texturing/uv.js | 113 +++++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 1c4467a5..34d63484 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -454,7 +454,9 @@ const UVEditor = { //Set setZoom(zoom) { let max_zoom = Math.round((this.vue.texture ? this.vue.texture.height : Project.texture_width) * 32 / UVEditor.width); - zoom = Math.clamp(zoom, UVEditor.height > 800 ? 0.2 : 0.5, Math.clamp(max_zoom, 16, 64)) + // zoom = Math.clamp(zoom, UVEditor.height > 800 ? 0.2 : 0.5, Math.clamp(max_zoom, 16, 64)); + zoom = Math.clamp(zoom, 0.0, Math.clamp(max_zoom, 16, 64)); + console.log(zoom, max_zoom); this.vue.zoom = zoom; Project.uv_viewport.zoom = this.zoom; Vue.nextTick(() => { @@ -2156,6 +2158,12 @@ BARS.defineActions(function() { Undo.finishEdit('Cycle reverse UV') } }) + new Toggle('snap_uv_to_image_bounds', { + icon: 'grid_goldenratio', + category: 'uv', + condition: {modes: ['edit']}, + value: true + }) new Toggle('edit_mode_uv_overlay', { name: 'action.paint_mode_uv_overlay', description: 'action.edit_mode_uv_overlay.desc', @@ -3012,36 +3020,38 @@ Interface.definePanels(function() { event, snap: UVEditor.isBoxUV() ? 1 : undefined, onDrag: (diff_x, diff_y) => { - elements.forEach(element => { - if (element instanceof Mesh) { - UVEditor.getSelectedFaces(element).forEach(key => { - let face = element.faces[key]; - if (!face) return; - face.vertices.forEach(vertex_key => { - diff_x = Math.clamp(diff_x, -face.uv[vertex_key][0], UVEditor.getUVWidth() - face.uv[vertex_key][0]); - diff_y = Math.clamp(diff_y, -face.uv[vertex_key][1], UVEditor.getUVHeight() - face.uv[vertex_key][1]); + // Snap UV to Image Bounds + if (BarItems.snap_uv_to_image_bounds.value) { + elements.forEach(element => { + if (element instanceof Mesh) { + UVEditor.getSelectedFaces(element).forEach(key => { + let face = element.faces[key]; + if (!face) return; + face.vertices.forEach(vertex_key => { + diff_x = Math.clamp(diff_x, -face.uv[vertex_key][0], UVEditor.getUVWidth() - face.uv[vertex_key][0]); + diff_y = Math.clamp(diff_y, -face.uv[vertex_key][1], UVEditor.getUVHeight() - face.uv[vertex_key][1]); + }) }) - }) - } else if (element.box_uv) { - let size = element.size(undefined, Format.box_uv_float_size != true); - let uv_size = [ - size[2] + size[0] + (size[1] ? size[2] : 0) + size[0], - size[2] + size[1], - ] - diff_x = Math.clamp(diff_x, -element.uv_offset[0] - (size[1] ? 0 : size[2]), UVEditor.getUVWidth() - element.uv_offset[0] - uv_size[0]); - diff_y = Math.clamp(diff_y, -element.uv_offset[1] - (size[0] ? 0 : size[2]), UVEditor.getUVHeight() - element.uv_offset[1] - uv_size[1]); - - } else { - UVEditor.getSelectedFaces(element).forEach(key => { - if (element.faces[key] && element instanceof Cube && element.faces[key].texture !== null) { - diff_x = Math.clamp(diff_x, -element.faces[key].uv[0], UVEditor.getUVWidth() - element.faces[key].uv[0]); - diff_y = Math.clamp(diff_y, -element.faces[key].uv[1], UVEditor.getUVHeight() - element.faces[key].uv[1]); - diff_x = Math.clamp(diff_x, -element.faces[key].uv[2], UVEditor.getUVWidth() - element.faces[key].uv[2]); - diff_y = Math.clamp(diff_y, -element.faces[key].uv[3], UVEditor.getUVHeight() - element.faces[key].uv[3]); - } - }) - } - }) + } else if (element.box_uv) { + let size = element.size(undefined, Format.box_uv_float_size != true); + let uv_size = [ + size[2] + size[0] + (size[1] ? size[2] : 0) + size[0], + size[2] + size[1], + ] + diff_x = Math.clamp(diff_x, -element.uv_offset[0] - (size[1] ? 0 : size[2]), UVEditor.getUVWidth() - element.uv_offset[0] - uv_size[0]); + diff_y = Math.clamp(diff_y, -element.uv_offset[1] - (size[0] ? 0 : size[2]), UVEditor.getUVHeight() - element.uv_offset[1] - uv_size[1]); + } else { + UVEditor.getSelectedFaces(element).forEach(key => { + if (element.faces[key] && element instanceof Cube && element.faces[key].texture !== null) { + diff_x = Math.clamp(diff_x, -element.faces[key].uv[0], UVEditor.getUVWidth() - element.faces[key].uv[0]); + diff_y = Math.clamp(diff_y, -element.faces[key].uv[1], UVEditor.getUVHeight() - element.faces[key].uv[1]); + diff_x = Math.clamp(diff_x, -element.faces[key].uv[2], UVEditor.getUVWidth() - element.faces[key].uv[2]); + diff_y = Math.clamp(diff_y, -element.faces[key].uv[3], UVEditor.getUVHeight() - element.faces[key].uv[3]); + } + }) + } + }) + } elements.forEach(element => { if (element instanceof Mesh) { UVEditor.getSelectedFaces(element).forEach(key => { @@ -3107,6 +3117,7 @@ Interface.definePanels(function() { } }) }, + // Drag UV edge from Cube with Per-Face-UV resizeFace(face_key, event, x_side, y_side) { if (event.which == 2 || event.which == 3) return; event.stopPropagation(); @@ -3130,11 +3141,18 @@ Interface.definePanels(function() { onDrag: (x, y) => { elements.forEach(element => { UVEditor.getSelectedFaces(element).forEach(key => { - if (element.faces[key] && element instanceof Cube) { - if (x_side && (x_side == -1) != inverted[element.uuid][key][0]) element.faces[key].uv[0] = Math.clamp(element.faces[key].uv[0] + x, 0, UVEditor.getUVWidth()); - if (y_side && (y_side == -1) != inverted[element.uuid][key][1]) element.faces[key].uv[1] = Math.clamp(element.faces[key].uv[1] + y, 0, UVEditor.getUVHeight()); - if (x_side && (x_side == 1) != inverted[element.uuid][key][0]) element.faces[key].uv[2] = Math.clamp(element.faces[key].uv[2] + x, 0, UVEditor.getUVWidth()); - if (y_side && (y_side == 1) != inverted[element.uuid][key][1]) element.faces[key].uv[3] = Math.clamp(element.faces[key].uv[3] + y, 0, UVEditor.getUVHeight()); + if (BarItems.snap_uv_to_image_bounds.value) { + if (element.faces[key] && element instanceof Cube) { + if (x_side && (x_side == -1) != inverted[element.uuid][key][0]) element.faces[key].uv[0] = Math.clamp(element.faces[key].uv[0] + x, 0, UVEditor.getUVWidth()); + if (y_side && (y_side == -1) != inverted[element.uuid][key][1]) element.faces[key].uv[1] = Math.clamp(element.faces[key].uv[1] + y, 0, UVEditor.getUVHeight()); + if (x_side && (x_side == 1) != inverted[element.uuid][key][0]) element.faces[key].uv[2] = Math.clamp(element.faces[key].uv[2] + x, 0, UVEditor.getUVWidth()); + if (y_side && (y_side == 1) != inverted[element.uuid][key][1]) element.faces[key].uv[3] = Math.clamp(element.faces[key].uv[3] + y, 0, UVEditor.getUVHeight()); + } + } else { + if (x_side && (x_side == -1) != inverted[element.uuid][key][0]) element.faces[key].uv[0] = element.faces[key].uv[0] + x; + if (y_side && (y_side == -1) != inverted[element.uuid][key][1]) element.faces[key].uv[1] = element.faces[key].uv[1] + y; + if (x_side && (x_side == 1) != inverted[element.uuid][key][0]) element.faces[key].uv[2] = element.faces[key].uv[2] + x; + if (y_side && (y_side == 1) != inverted[element.uuid][key][1]) element.faces[key].uv[3] = element.faces[key].uv[3] + y; } }) }) @@ -3229,8 +3247,13 @@ Interface.definePanels(function() { face.uv[vkey][1] = face.old_uv[vkey][1] - face_center[1]; let a = (face.uv[vkey][0] * cos - face.uv[vkey][1] * sin); let b = (face.uv[vkey][0] * sin + face.uv[vkey][1] * cos); - face.uv[vkey][0] = Math.clamp(a + face_center[0], 0, UVEditor.getUVWidth()); - face.uv[vkey][1] = Math.clamp(b + face_center[1], 0, UVEditor.getUVHeight()); + if (BarItems.snap_uv_to_image_bounds.value) { + face.uv[vkey][0] = Math.clamp(a + face_center[0], 0, UVEditor.getUVWidth()); + face.uv[vkey][1] = Math.clamp(b + face_center[1], 0, UVEditor.getUVHeight()); + } else { + face.uv[vkey][0] = a + face_center[0] + face.uv[vkey][1] = b + face_center[1] + } }) }) } @@ -3345,7 +3368,6 @@ Interface.definePanels(function() { } }) }, - dragVertices(element, vertex_key, event) { if (event.which == 2 || event.which == 3) return; @@ -3374,12 +3396,14 @@ Interface.definePanels(function() { UVEditor.getSelectedFaces(element).forEach(key => { let face = element.faces[key]; if (!face) return; - face.vertices.forEach(vertex_key => { - if (vertices.includes(vertex_key)) { - x = Math.clamp(x, -face.uv[vertex_key][0], UVEditor.getUVWidth() - face.uv[vertex_key][0]); - y = Math.clamp(y, -face.uv[vertex_key][1], UVEditor.getUVHeight() - face.uv[vertex_key][1]); - } - }) + if (BarItems.snap_uv_to_image_bounds.value) { + face.vertices.forEach(vertex_key => { + if (vertices.includes(vertex_key)) { + x = Math.clamp(x, -face.uv[vertex_key][0], UVEditor.getUVWidth() - face.uv[vertex_key][0]); + y = Math.clamp(y, -face.uv[vertex_key][1], UVEditor.getUVHeight() - face.uv[vertex_key][1]); + } + }) + } }) }) elements.forEach(element => { @@ -3420,7 +3444,6 @@ Interface.definePanels(function() { } }) }, - toPixels(uv_coord, offset = 0) { return (uv_coord / this.uv_resolution[0] * this.inner_width + offset) + 'px'; }, From 633c1e27df605ab06ef7d9e329adb07f9edf8a6a Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Tue, 16 Jul 2024 12:12:46 -0300 Subject: [PATCH 03/10] Attempt to give the new tool a name, without success --- lang/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/en.json b/lang/en.json index b38c0239..e299157e 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1796,6 +1796,7 @@ "action.uv_cycle_invert.desc": "Reverse the order of UV vertices without changing the UV positions", "action.paint_mode_uv_overlay": "UV Overlay", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", + "action.snap_uv_to_image_bounds": "Snap UV to image bounds", "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.remove_blank_faces": "Remove Blank Faces", "action.remove_blank_faces.desc": "Deletes all untextured faces of the selection", From e440e650a44f51ae52ad5553acef17ed84a51fee Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Tue, 16 Jul 2024 12:33:12 -0300 Subject: [PATCH 04/10] make the 'repeating' property be applied at load --- js/texturing/textures.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/texturing/textures.js b/js/texturing/textures.js index 99e10b1d..15006aaf 100644 --- a/js/texturing/textures.js +++ b/js/texturing/textures.js @@ -215,6 +215,9 @@ class Texture { this.uv_width = scope.width; this.uv_height = scope.display_height; } + + tex.wrapS = this.repeating ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + tex.wrapT = this.repeating ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; if (scope.isDefault) { console.log('Successfully loaded '+scope.name+' from default pack') From f8be82c49d23d3d1179c0e5a6887f02d19dba650 Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Tue, 16 Jul 2024 23:16:03 -0300 Subject: [PATCH 05/10] new tool to unwrap mesh faces --- js/texturing/texture_generator.js | 616 ++++++++++++++++++++++++++++++ js/texturing/uv.js | 8 + 2 files changed, 624 insertions(+) diff --git a/js/texturing/texture_generator.js b/js/texturing/texture_generator.js index ff51d47c..45797250 100644 --- a/js/texturing/texture_generator.js +++ b/js/texturing/texture_generator.js @@ -1530,6 +1530,622 @@ const TextureGenerator = { }) } }, + + unwrapFaceDialog() { + let resolution_presets = { + 16: '16x', + 32: '32x', + 64: '64x', + 128: '128x', + 256: '256x', + 512: '512x', + }; + var dialog = new Dialog({ + id: 'add_bitmap', + title: tl('action.unwrap_mesh'), + width: 480, + form: { + resolution_vec: {label: 'dialog.create_texture.resolution', type: 'vector', dimensions: 2, value: [Project.texture_width, Project.texture_height], min: 1, max: 4096}, + resolution: {label: 'dialog.create_texture.pixel_density', description: 'dialog.create_texture.pixel_density.desc', type: 'select', value: 16, options: resolution_presets}, + combine_polys: {label: 'dialog.create_texture.combine_polys', description: 'dialog.create_texture.combine_polys.desc', type: 'checkbox', value: true, condition: (form) => (Mesh.selected.length)}, + max_edge_angle: {label: 'dialog.create_texture.max_edge_angle', description: 'dialog.create_texture.max_edge_angle.desc', type: 'number', value: 45, condition: (form) => Mesh.selected.length}, + max_island_angle: {label: 'dialog.create_texture.max_island_angle', description: 'dialog.create_texture.max_island_angle.desc', type: 'number', value: 45, condition: (form) => Mesh.selected.length}, + padding: {label: 'dialog.create_texture.padding', description: 'dialog.create_texture.padding.desc', type: 'checkbox', value: false}, + }, + onConfirm(options) { + dialog.hide() + TextureGenerator.unwrapFace(options); + return false; + } + }).show() + }, + unwrapFace(options) { + let res_multiple = options.resolution / 16.0; + console.log(res_multiple) + + let element_list = ((Format.single_texture) ? Outliner.elements : Outliner.selected); + element_list = element_list.filter(el => { + return (el instanceof Mesh && el.visibility); + }); + + Undo.initEdit({ + elements: element_list, + uv_only: true, + bitmap: true, + uv_mode: true + }) + + let face_list = []; + let vec1 = new THREE.Vector3(), + vec2 = new THREE.Vector3(), + vec3 = new THREE.Vector3(), + vec4 = new THREE.Vector3(); + element_list.forEach(mesh => { + let mirror_modeling_duplicate = BarItems.mirror_modeling.value && MirrorModeling.cached_elements[mesh.uuid] && MirrorModeling.cached_elements[mesh.uuid].is_copy; + if (mirror_modeling_duplicate) return; + + let face_groups = []; + for (let fkey in mesh.faces) { + let face = mesh.faces[fkey]; + if (face.vertices.length < 3) continue; + + let face_old_pos_id; + if (BarItems.selection_mode.value !== 'object' && !face.isSelected(fkey)) continue; + face_groups.push({ + type: 'face_group', + mesh, + faces: [face], + keys: [fkey], + face_old_pos_id, + edges: new Map(), + normal: face.getNormal(true), + vertex_uvs: {}, + }) + } + + function getEdgeLength(edge) { + let edge_vertices = edge.map(vkey => mesh.vertices[vkey]); + return Math.sqrt( + Math.pow(edge_vertices[1][0] - edge_vertices[0][0], 2) + + Math.pow(edge_vertices[1][1] - edge_vertices[0][1], 2) + + Math.pow(edge_vertices[1][2] - edge_vertices[0][2], 2) + ) + } + + // Sort straight faces first + function getNormalStraightness(normal) { + let absolute = normal.map(Math.abs); + return ( + (absolute[0] < 0.5 ? absolute[0] : (1-absolute[0]))*1.6 + + (absolute[1] < 0.5 ? absolute[1] : (1-absolute[1])) + + (absolute[2] < 0.5 ? absolute[2] : (1-absolute[2])) + ) + } + face_groups.sort((a, b) => { + return getNormalStraightness(a.normal) - getNormalStraightness(b.normal); + }) + + function projectFace(face, fkey, face_group, connection) { + // Project vertex coords onto plane + let {vertex_uvs} = face_group; + let normal_vec = vec1.fromArray(face.getNormal(true)); + let plane = new THREE.Plane().setFromNormalAndCoplanarPoint( + normal_vec, + vec2.fromArray(mesh.vertices[face.vertices[0]]) + ) + let sorted_vertices = face.getSortedVertices(); + let rot = cameraTargetToRotation([0, 0, 0], normal_vec.toArray()); + let e = new THREE.Euler(Math.degToRad(rot[1] - 90), Math.degToRad(rot[0] + 180), 0); + + let face_vertex_uvs = {}; + + face.vertices.forEach(vkey => { + let coplanar_pos = plane.projectPoint(vec3.fromArray(mesh.vertices[vkey]), vec4); + coplanar_pos.applyEuler(e); + face_vertex_uvs[vkey] = [ + Math.roundTo(coplanar_pos.x, 4), + Math.roundTo(coplanar_pos.z, 4), + ] + }) + + if (connection) { + // Rotate to connect to previous face + let other_face_vertex_uvs = vertex_uvs[connection.fkey]; + let uv_hinge = face_vertex_uvs[connection.edge[0]]; + let uv_latch = face_vertex_uvs[connection.edge[1]]; + let uv_lock = other_face_vertex_uvs[connection.edge[1]]; + + // Join hinge + let offset = uv_hinge.slice().V2_subtract(other_face_vertex_uvs[connection.edge[0]]); + for (let vkey in face_vertex_uvs) { + face_vertex_uvs[vkey].V2_subtract(offset); + } + + // Join latch + uv_hinge = uv_hinge.slice(); + let angle = Math.atan2( + uv_hinge[0] - uv_latch[0], + uv_hinge[1] - uv_latch[1], + ) - Math.atan2( + uv_hinge[0] - uv_lock[0], + uv_hinge[1] - uv_lock[1], + ); + let s = Math.sin(angle); + let c = Math.cos(angle); + for (let vkey in face_vertex_uvs) { + let point = face_vertex_uvs[vkey].slice().V2_subtract(uv_hinge); + face_vertex_uvs[vkey][0] = point[0] * c - point[1] * s; + face_vertex_uvs[vkey][1] = point[0] * s + point[1] * c; + face_vertex_uvs[vkey].V2_add(uv_hinge); + } + + // Check overlap + function isSameUVVertex(point_a, point_b) { + return (Math.epsilon(point_a[0], point_b[0], 0.1) + && Math.epsilon(point_a[1], point_b[1], 0.1)) + } + let i = -1; + for (let other_face of face_group.faces) { + i++; + if (other_face == connection.face) continue; + let other_fkey = face_group.keys[i]; + let sorted_vertices_b = other_face.getSortedVertices(); + let l1 = 0; + for (let vkey_1_a of sorted_vertices) { + let vkey_1_b = sorted_vertices[l1+1] || sorted_vertices[0] + let l2 = 0; + for (let vkey_2_a of sorted_vertices_b) { + let vkey_2_b = sorted_vertices_b[l2+1] || sorted_vertices_b[0]; + + if (intersectLines( + face_vertex_uvs[vkey_1_a], + face_vertex_uvs[vkey_1_b], + face_group.vertex_uvs[other_fkey][vkey_2_a], + face_group.vertex_uvs[other_fkey][vkey_2_b] + )) { + if ( + !isSameUVVertex(face_vertex_uvs[vkey_1_a], face_group.vertex_uvs[other_fkey][vkey_2_a]) && + !isSameUVVertex(face_vertex_uvs[vkey_1_a], face_group.vertex_uvs[other_fkey][vkey_2_b]) && + !isSameUVVertex(face_vertex_uvs[vkey_1_b], face_group.vertex_uvs[other_fkey][vkey_2_a]) && + !isSameUVVertex(face_vertex_uvs[vkey_1_b], face_group.vertex_uvs[other_fkey][vkey_2_b]) + ) { + return false; + } + } + l2++; + } + l1++; + } + } + } + + face_group.vertex_uvs[fkey] = face_vertex_uvs; + return true; + } + + let processed_faces = []; + if (options.combine_polys) { + face_groups.slice().forEach((face_group) => { + if (!face_groups.includes(face_group)) return; + + function growFromFaces(faces) { + let perimeter = {}; + for (let fkey in faces) { + let face = faces[fkey]; + let face_connection_count = 0; + processed_faces.push(face); + let face_normal = face.getNormal(true); + [2, 0, 3, 1].forEach(i => { + if (!face.vertices[i]) return; + let other_face_match = face.getAdjacentFace(i); + let edge = other_face_match && face.vertices.filter(vkey => other_face_match.face.vertices.includes(vkey)); + if (other_face_match && edge.length == 2 && !face_group.faces.includes(other_face_match.face) && !processed_faces.includes(other_face_match.face)) { + let other_face = other_face_match.face; + let other_face_group = face_groups.find(group => group.faces[0] == other_face); + if (!other_face_group) return; + let seam = mesh.getSeam(other_face_match.edge); + if (seam === 'divide') return; + if (seam !== 'join') { + + let angle = face.getAngleTo(other_face); + if (angle > (options.max_edge_angle||36)) return; + + let angle_total = face_group.faces[0].getAngleTo(other_face); + if (angle_total > (options.max_island_angle||45)) return; + + let edge_length = getEdgeLength(other_face_match.edge); + if (edge_length < 2.2 && face_connection_count >= 2) return; + + let other_face_normal = other_face.getNormal(true); + if (Math.abs(other_face_normal[0]) > 0.04 && + Math.epsilon(face_normal[0], -other_face_normal[0], 0.04) && + Math.epsilon(face_normal[1], other_face_normal[1], 0.04) && + Math.epsilon(face_normal[2], other_face_normal[2], 0.04) + ) return; + } + let projection_success = projectFace(other_face, other_face_match.key, face_group, {face, fkey, edge}); + if (!projection_success) return; + + face_group.faces.push(other_face); + face_group.keys.push(other_face_match.key); + face_groups.remove(other_face_group); + perimeter[other_face_match.key] = other_face; + face_connection_count++; + } + }) + } + if (Object.keys(perimeter).length) growFromFaces(perimeter); + } + projectFace(face_group.faces[0], face_group.keys[0], face_group); + growFromFaces({[face_group.keys[0]]: face_group.faces[0]}); + }); + } else { + face_groups.forEach(face_group => { + face_group.faces.forEach((face, i) => { + let fkey = face_group.keys[i]; + projectFace(face, fkey, face_group); + }) + }) + } + + face_groups.forEach(face_group => { + let {vertex_uvs} = face_group; + // Rotate UV to match corners + if (face_group.faces.length == 1) { + let rotation_angles = {}; + let precise_rotation_angle = {}; + face_group.faces.forEach((face, i) => { + let fkey = face_group.keys[i]; + let vertices = face.getSortedVertices(); + vertices.forEach((vkey, i) => { + let vkey2 = vertices[i+1] || vertices[0]; + let edge_length = getEdgeLength([vkey, vkey2]); + let rot = Math.atan2( + vertex_uvs[fkey][vkey2][0] - vertex_uvs[fkey][vkey][0], + vertex_uvs[fkey][vkey2][1] - vertex_uvs[fkey][vkey][1], + ) + let snap = 2; + rot = (Math.radToDeg(rot) + 360) % 90; + let rounded + let last_difference = snap; + for (let rounded_angle in precise_rotation_angle) { + let precise = precise_rotation_angle[rounded_angle]; + if (Math.abs(rot - precise) < last_difference) { + last_difference = Math.abs(rot - precise); + rounded = rounded_angle; + } + } + if (!rounded) rounded = Math.round(rot / snap) * snap; + if (rotation_angles[rounded]) { + rotation_angles[rounded] += edge_length; + } else { + rotation_angles[rounded] = edge_length; + precise_rotation_angle[rounded] = rot; + } + }) + }) + let angles = Object.keys(rotation_angles).map(k => parseInt(k)); + angles.sort((a, b) => { + let diff = rotation_angles[b] - rotation_angles[a]; + if (diff) { + return diff; + } else { + return a < b ? -1 : 1; + } + }) + if (rotation_angles[angles[0]] > 1) { + let angle = Math.degToRad(precise_rotation_angle[angles[0]]); + let s = Math.sin(angle); + let c = Math.cos(angle); + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + let point = vertex_uvs[fkey][vkey].slice(); + vertex_uvs[fkey][vkey][0] = point[0] * c - point[1] * s; + vertex_uvs[fkey][vkey][1] = point[0] * s + point[1] * c; + } + } + } + } + + // Define UV bounding box + let min_x = Infinity; + let min_z = Infinity; + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + min_x = Math.min(min_x, vertex_uvs[fkey][vkey][0]); + min_z = Math.min(min_z, vertex_uvs[fkey][vkey][1]); + } + } + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + vertex_uvs[fkey][vkey][0] -= min_x; + vertex_uvs[fkey][vkey][1] -= min_z; + } + } + + // Round + if (face_group.faces.length == 1 && face_group.faces[0].vertices.length == 4) { + let sorted_vertices = face_group.faces[0].getSortedVertices(); + sorted_vertices.forEach((vkey, vi) => { + let vkey2 = sorted_vertices[vi+1] || sorted_vertices[0]; + let vkey0 = sorted_vertices[vi-1] || sorted_vertices.last(); + let snap = 1; + let vertex_uvs_1 = vertex_uvs[face_group.keys[0]]; + + if (Math.epsilon(vertex_uvs_1[vkey][0], vertex_uvs_1[vkey2][0], 0.001)) { + let min = vertex_uvs_1[vkey][0] > vertex_uvs_1[vkey0][0] ? 1 : 0; + vertex_uvs_1[vkey][0] = vertex_uvs_1[vkey2][0] = Math.round(Math.max(min, vertex_uvs_1[vkey][0] * snap)) / snap; + } + if (Math.epsilon(vertex_uvs_1[vkey][1], vertex_uvs_1[vkey2][1], 0.001)) { + let min = vertex_uvs_1[vkey][1] > vertex_uvs_1[vkey0][1] ? 1 : 0; + vertex_uvs_1[vkey][1] = vertex_uvs_1[vkey2][1] = Math.round(Math.max(min, vertex_uvs_1[vkey][1] * snap)) / snap; + } + }) + } + + + max_x = -Infinity; + max_z = -Infinity; + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + max_x = Math.max(max_x, vertex_uvs[fkey][vkey][0]); + max_z = Math.max(max_z, vertex_uvs[fkey][vkey][1]); + } + } + // Center island if it faces front of back + if (Math.epsilon(face_group.normal[0], 0, 0.08)) { + let offset_x = (Math.ceil(max_x*res_multiple)/res_multiple - max_x) / 2; + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + vertex_uvs[fkey][vkey][0] += offset_x; + } + } + } + // ... or on the side + else if (Math.epsilon(face_group.normal[2], 0, 0.05)) { + let offset_x = (Math.ceil(max_x*res_multiple)/res_multiple - max_x) / 2; + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + vertex_uvs[fkey][vkey][0] += offset_x; + } + } + } + // Or align right if face points to right side of model + else if ((face_group.normal[0] > 0) != (face_group.normal[2] < 0)) { + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + vertex_uvs[fkey][vkey][0] += Math.ceil(max_x*res_multiple)/res_multiple - max_x; + } + } + } + // Align bottom if face points downwards + if (face_group.normal[1] < 0) { + for (let fkey in vertex_uvs) { + for (let vkey in vertex_uvs[fkey]) { + vertex_uvs[fkey][vkey][1] += Math.ceil(max_z*res_multiple)/res_multiple - max_z; + } + } + } + face_group.posx = 0; + face_group.posy = 0; + face_group.vertex_uvs = vertex_uvs; + face_group.width = max_x; + face_group.height = max_z; + face_group.size = max_x * max_z; + + let axis = [0, 1, 2].sort((a, b) => { + return Math.abs(face_group.normal[b]) - Math.abs(face_group.normal[a]) - 0.0001 + })[0] + if (axis == 0 && face_group.normal[0] >= 0) face_group.face_key = 'east'; + if (axis == 0 && face_group.normal[0] <= 0) face_group.face_key = 'west'; + if (axis == 1 && face_group.normal[1] >= 0) face_group.face_key = 'up'; + if (axis == 1 && face_group.normal[1] <= 0) face_group.face_key = 'down'; + if (axis == 2 && face_group.normal[2] >= 0) face_group.face_key = 'south'; + if (axis == 2 && face_group.normal[2] <= 0) face_group.face_key = 'north'; + + + }) + face_list.push(...face_groups); + }) + + if (face_list.length == 0) { + Blockbench.showMessage('message.no_valid_elements', 'center') + return; + } + + function getPolygonOccupationMatrix(vertex_uv_faces, width, height) { + let matrix = {}; + function vSub(a, b) { + return [a[0]-b[0], a[1]-b[1]]; + } + function getSide(a, b) { + let cosine_sign = a[0]*b[1] - a[1]*b[0]; + if (cosine_sign > 0) return 1; + if (cosine_sign < 0) return -1; + } + function pointInsidePolygon(x, y) { + face_uvs: + for (let vertex_uvs of vertex_uv_faces) { + let previous_side; + let i = 0; + vertices: + for (let a of vertex_uvs) { + let b = vertex_uvs[i+1] || vertex_uvs[0]; + let affine_segment = vSub(b, a); + let affine_point = vSub([x, y], a); + let side = getSide(affine_segment, affine_point); + if (!side) continue face_uvs; + if (!previous_side) previous_side = side; + if (side !== previous_side) continue face_uvs; + i++; + } + return true; + } + return false; + } + for (let x = 0; x < (0 + width); x++) { + for (let y =0; y < (0 + height); y++) { + let inside = ( pointInsidePolygon(x+0.00001, y+0.00001) + || pointInsidePolygon(x+0.99999, y+0.00001) + || pointInsidePolygon(x+0.00001, y+0.99999) + || pointInsidePolygon(x+0.99999, y+0.99999)); + if (!inside) { + let px_rect = [[x, y], [x+0.99999, y+0.99999]] + faces: + for (let vertex_uvs of vertex_uv_faces) { + let i = 0; + for (let a of vertex_uvs) { + let b = vertex_uvs[i+1] || vertex_uvs[0]; + if (pointInRectangle(a, ...px_rect)) { + inside = true; break faces; + } + if (lineIntersectsReactangle(a, b, ...px_rect)) { + inside = true; break faces; + } + i++; + } + } + } + if (inside) { + if (!matrix[x]) matrix[x] = {}; + matrix[x][y] = true; + } + } + } + return matrix; + } + face_list.forEach(face_group => { + if (!face_group.mesh) return; + let face_uvs = face_group.faces.map((face, i) => { + return face.getSortedVertices().map(vkey => { + return face_group.vertex_uvs[face_group.keys[i]][vkey]; + }) + }); + face_group.matrix = getPolygonOccupationMatrix(face_uvs, face_group.width, face_group.height); + }) + + face_list.sort(function(a,b) { + return b.size - a.size; + }) + + var fill_map = {}; + function occupy(x, y) { + if (!fill_map[x]) fill_map[x] = {} + fill_map[x][y] = true + } + function check(x, y) { + return fill_map[x] && fill_map[x][y] + } + function forTemplatePixel(tpl, sx, sy, cb) { + let w = tpl.width; + let h = tpl.height; + + if (options.padding) { + w++; h++; + for (var x = 0; x < w; x++) { + if (tpl.matrix && !tpl.matrix[x] && !tpl.matrix[x-1]) continue; + for (var y = 0; y < h; y++) { + if ( + tpl.matrix && + (!tpl.matrix[x] || !tpl.matrix[x][y]) && + (!tpl.matrix[x-1] || !tpl.matrix[x-1][y]) && + (!tpl.matrix[x] || !tpl.matrix[x][y-1]) && + (!tpl.matrix[x-1] || !tpl.matrix[x-1][y-1]) + ) continue; + if (cb(sx+x, sy+y)) return; + } + } + } else { + for (var x = 0; x < w; x++) { + if (tpl.matrix && !tpl.matrix[x]) continue; + for (var y = 0; y < h; y++) { + if (tpl.matrix && !tpl.matrix[x][y]) continue; + if (cb(sx+x, sy+y)) return; + } + } + } + } + function place(tpl, x, y) { + var works = true; + forTemplatePixel(tpl, x, y, (tx, ty) => { + if (check(tx, ty)) { + works = false; + return true; + } + }) + if (works) { + forTemplatePixel(tpl, x, y, occupy) + tpl.posx = x; + tpl.posy = y; + return true; + } + } + face_list.forEach(tpl => { + //Scan for empty spot + for (var line = 0; line < 2e3; line++) { + for (var space = 0; space <= line; space++) { + if (place(tpl, space, line)) return; + if (space == line) continue; + if (place(tpl, line, space)) return; + } + } + }) + + face_list.forEach(function(ftemp) { + function applyUV(source, target) { + target.faces.forEach((face, i) => { + let source_face = source.faces[i]; + let source_fkey = source.keys[i]; + face.vertices.forEach((vkey, j) => { + let source_vkey = vkey; + if (ftemp.copy_to) { + for (let vkey2 of source_face.vertices) { + let vertex_uv_a = source_face.uv[vkey2]; + let vertex_uv_b = face.uv[vkey]; + if (Math.epsilon(vertex_uv_a[0], vertex_uv_b[0], 0.002) && Math.epsilon(vertex_uv_a[1], vertex_uv_b[1], 0.002)) { + source_vkey = vkey2; + break; + } + } + } + if (!face.uv[vkey]) face.uv[vkey] = []; + // Final UV sizes and positions, per face. + let uv_size_multiplier = res_multiple; + let uv_offset_multiplier = res_multiple; + face.uv[vkey][0] = source.vertex_uvs[source_fkey][source_vkey][0] * uv_size_multiplier + (source.posx * uv_offset_multiplier); + face.uv[vkey][1] = source.vertex_uvs[source_fkey][source_vkey][1] * uv_size_multiplier + (source.posy * uv_offset_multiplier); + }) + }) + } + if (ftemp.copy_to) { + for (let ftemp2 of ftemp.copy_to) { + applyUV(ftemp, ftemp2); + } + } + applyUV(ftemp, ftemp); + }) + + // Updates the texture in the object, I think. + element_list.forEach(function(element) { + if (!Format.single_texture) { + element.preview_controller.updateFaces(element); + } + element.preview_controller.updateUV(element); + }) + + Project.texture_width = options.resolution_vec[0]; + Project.texture_height = options.resolution_vec[1]; + + updateSelection() + setTimeout(Canvas.updatePixelGrid, 1); + Undo.finishEdit('Unwrap Mesh', { + bitmap: true, + elements: [...element_list], + uv_only: true, + uv_mode: true + }) + }, + generateColorMapTemplate(options, cb) { var background_color = options.color; diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 34d63484..350b0733 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -1699,6 +1699,14 @@ BARS.defineActions(function() { Undo.finishEdit('Auto UV') } }) + new Action('unwrap_mesh_faces', { + icon: 'view_in_ar', + category: 'uv', + condition: () => Mesh.selected.length, + click() { + TextureGenerator.unwrapFaceDialog() + } + }) new Action('uv_project_from_view', { icon: 'view_in_ar', category: 'uv', From dd724fe4638b7a7a56476da69d7a6aabefee21b4 Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Tue, 16 Jul 2024 23:23:26 -0300 Subject: [PATCH 06/10] name changes and attempt to give the tool/dialog a proper name --- js/texturing/texture_generator.js | 10 +++++----- js/texturing/uv.js | 2 +- lang/en.json | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/js/texturing/texture_generator.js b/js/texturing/texture_generator.js index 45797250..9133c96e 100644 --- a/js/texturing/texture_generator.js +++ b/js/texturing/texture_generator.js @@ -1530,8 +1530,8 @@ const TextureGenerator = { }) } }, - - unwrapFaceDialog() { + + unwrapMeshFacesDialog() { let resolution_presets = { 16: '16x', 32: '32x', @@ -1542,7 +1542,7 @@ const TextureGenerator = { }; var dialog = new Dialog({ id: 'add_bitmap', - title: tl('action.unwrap_mesh'), + title: tl('action.unwrap_mesh_faces'), width: 480, form: { resolution_vec: {label: 'dialog.create_texture.resolution', type: 'vector', dimensions: 2, value: [Project.texture_width, Project.texture_height], min: 1, max: 4096}, @@ -1554,12 +1554,12 @@ const TextureGenerator = { }, onConfirm(options) { dialog.hide() - TextureGenerator.unwrapFace(options); + TextureGenerator.unwrapMeshFaces(options); return false; } }).show() }, - unwrapFace(options) { + unwrapMeshFaces(options) { let res_multiple = options.resolution / 16.0; console.log(res_multiple) diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 350b0733..aefe9db3 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -1704,7 +1704,7 @@ BARS.defineActions(function() { category: 'uv', condition: () => Mesh.selected.length, click() { - TextureGenerator.unwrapFaceDialog() + TextureGenerator.unwrapMeshFacesDialog() } }) new Action('uv_project_from_view', { diff --git a/lang/en.json b/lang/en.json index e299157e..61b39086 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1764,6 +1764,7 @@ "action.uv_rel_auto.desc": "Sets the UV of this face to the position and size of the actual face", "action.uv_project_from_view": "UV Project from View", "action.uv_project_from_view.desc": "Automatically generate UVs for the selected faces from the perspective of the 3D view", + "action.unwrap_mesh_faces": "UV Unwrap Mesh Faces", "action.uv_mirror_x": "UV Mirror X", "action.uv_mirror_x.desc": "Mirrors the UV of this face on the X axis", "action.uv_mirror_y": "UV Mirror Y", From f7530d30471130db3ff42ae552b7211ea8bc0058 Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Wed, 17 Jul 2024 00:20:46 -0300 Subject: [PATCH 07/10] indentation --- js/texturing/textures.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/texturing/textures.js b/js/texturing/textures.js index 15006aaf..8d3bb506 100644 --- a/js/texturing/textures.js +++ b/js/texturing/textures.js @@ -58,7 +58,7 @@ class Texture { var tex = new THREE.Texture(this.canvas); tex.magFilter = THREE.NearestFilter; - tex.minFilter = THREE.NearestFilter; + tex.minFilter = THREE.NearestFilter; tex.name = this.name; img.tex = tex; @@ -738,7 +738,7 @@ class Texture { // Map mat.map.wrapS = this.repeating ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; - mat.map.wrapT = this.repeating ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + mat.map.wrapT = this.repeating ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; mat.map.needsUpdate = true; return this; } From 7ff7965f2c5c2774e7a579cc88ce896c2521220c Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Wed, 17 Jul 2024 19:07:11 -0300 Subject: [PATCH 08/10] small tweaks and attempt to optimize the unwrap speed for bigger objects --- js/texturing/texture_generator.js | 168 +++++++++++++++++------------- js/texturing/uv.js | 1 - lang/en.json | 3 + 3 files changed, 101 insertions(+), 71 deletions(-) diff --git a/js/texturing/texture_generator.js b/js/texturing/texture_generator.js index 9133c96e..073ae59c 100644 --- a/js/texturing/texture_generator.js +++ b/js/texturing/texture_generator.js @@ -1545,8 +1545,8 @@ const TextureGenerator = { title: tl('action.unwrap_mesh_faces'), width: 480, form: { - resolution_vec: {label: 'dialog.create_texture.resolution', type: 'vector', dimensions: 2, value: [Project.texture_width, Project.texture_height], min: 1, max: 4096}, - resolution: {label: 'dialog.create_texture.pixel_density', description: 'dialog.create_texture.pixel_density.desc', type: 'select', value: 16, options: resolution_presets}, + resolution_vec: {label: 'dialog.unwrap_mesh.resolution', description: "dialog.unwrap_mesh.resolution.desc", type: 'vector', dimensions: 2, value: [Project.texture_width, Project.texture_height], min: 1, max: 4096}, + resolution: {label: 'dialog.create_texture.pixel_density', description: 'dialog.unwrap_mesh.pixel_density.desc', type: 'select', value: 16, options: resolution_presets}, combine_polys: {label: 'dialog.create_texture.combine_polys', description: 'dialog.create_texture.combine_polys.desc', type: 'checkbox', value: true, condition: (form) => (Mesh.selected.length)}, max_edge_angle: {label: 'dialog.create_texture.max_edge_angle', description: 'dialog.create_texture.max_edge_angle.desc', type: 'number', value: 45, condition: (form) => Mesh.selected.length}, max_island_angle: {label: 'dialog.create_texture.max_island_angle', description: 'dialog.create_texture.max_island_angle.desc', type: 'number', value: 45, condition: (form) => Mesh.selected.length}, @@ -1561,7 +1561,6 @@ const TextureGenerator = { }, unwrapMeshFaces(options) { let res_multiple = options.resolution / 16.0; - console.log(res_multiple) let element_list = ((Format.single_texture) ? Outliner.elements : Outliner.selected); element_list = element_list.filter(el => { @@ -1575,6 +1574,7 @@ const TextureGenerator = { uv_mode: true }) + // Creates a face_list, containing face_groups let face_list = []; let vec1 = new THREE.Vector3(), vec2 = new THREE.Vector3(), @@ -1723,6 +1723,7 @@ const TextureGenerator = { return true; } + // Combined Islands let processed_faces = []; if (options.combine_polys) { face_groups.slice().forEach((face_group) => { @@ -1806,7 +1807,7 @@ const TextureGenerator = { ) let snap = 2; rot = (Math.radToDeg(rot) + 360) % 90; - let rounded + let rounded; let last_difference = snap; for (let rounded_angle in precise_rotation_angle) { let precise = precise_rotation_angle[rounded_angle]; @@ -1948,70 +1949,87 @@ const TextureGenerator = { face_list.push(...face_groups); }) + // Check if there's faces to work with. if (face_list.length == 0) { Blockbench.showMessage('message.no_valid_elements', 'center') return; } + // Gets the matrix of space occupied for each face. function getPolygonOccupationMatrix(vertex_uv_faces, width, height) { let matrix = {}; + let memo = {}; + function vSub(a, b) { - return [a[0]-b[0], a[1]-b[1]]; + return [a[0] - b[0], a[1] - b[1]]; } + function getSide(a, b) { - let cosine_sign = a[0]*b[1] - a[1]*b[0]; - if (cosine_sign > 0) return 1; - if (cosine_sign < 0) return -1; + return a[0] * b[1] - a[1] * b[0]; } - function pointInsidePolygon(x, y) { - face_uvs: - for (let vertex_uvs of vertex_uv_faces) { - let previous_side; - let i = 0; - vertices: - for (let a of vertex_uvs) { - let b = vertex_uvs[i+1] || vertex_uvs[0]; - let affine_segment = vSub(b, a); - let affine_point = vSub([x, y], a); - let side = getSide(affine_segment, affine_point); - if (!side) continue face_uvs; - if (!previous_side) previous_side = side; - if (side !== previous_side) continue face_uvs; - i++; + + function boundingBox(vertices) { + let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + vertices.forEach(([x, y]) => { + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + }); + return { minX, minY, maxX, maxY }; + } + + function pointInsidePolygon(x, y, vertices) { + let key = `${x},${y}`; + if (memo[key] !== undefined) return memo[key]; + + let previous_side; + for (let i = 0; i < vertices.length; i++) { + let a = vertices[i]; + let b = vertices[(i + 1) % vertices.length]; + let affine_segment = vSub(b, a); + let affine_point = vSub([x, y], a); + let side = getSide(affine_segment, affine_point); + if (side === 0) continue; + if (!previous_side) previous_side = side; + if ((side > 0 && previous_side < 0) || (side < 0 && previous_side > 0)) { + memo[key] = false; + return false; } - return true; + previous_side = side; } - return false; + memo[key] = true; + return true; } - for (let x = 0; x < (0 + width); x++) { - for (let y =0; y < (0 + height); y++) { - let inside = ( pointInsidePolygon(x+0.00001, y+0.00001) - || pointInsidePolygon(x+0.99999, y+0.00001) - || pointInsidePolygon(x+0.00001, y+0.99999) - || pointInsidePolygon(x+0.99999, y+0.99999)); - if (!inside) { - let px_rect = [[x, y], [x+0.99999, y+0.99999]] - faces: - for (let vertex_uvs of vertex_uv_faces) { - let i = 0; - for (let a of vertex_uvs) { - let b = vertex_uvs[i+1] || vertex_uvs[0]; - if (pointInRectangle(a, ...px_rect)) { - inside = true; break faces; - } - if (lineIntersectsReactangle(a, b, ...px_rect)) { - inside = true; break faces; - } - i++; + + for (let x = 0; x < width; x++) { + for (let y = 0; y < height; y++) { + let inside = false; + + for (let vertex_uvs of vertex_uv_faces) { + let { minX, minY, maxX, maxY } = boundingBox(vertex_uvs); + console.log(x, y) + + if (x >= minX && x <= maxX && y >= minY && y <= maxY) { + if ( + pointInsidePolygon(x + 0.00001, y + 0.00001, vertex_uvs) || + pointInsidePolygon(x + 0.99999, y + 0.00001, vertex_uvs) || + pointInsidePolygon(x + 0.00001, y + 0.99999, vertex_uvs) || + pointInsidePolygon(x + 0.99999, y + 0.99999, vertex_uvs) + ) { + inside = true; + break; } } } + if (inside) { if (!matrix[x]) matrix[x] = {}; matrix[x][y] = true; } } } + return matrix; } face_list.forEach(face_group => { @@ -2019,27 +2037,31 @@ const TextureGenerator = { let face_uvs = face_group.faces.map((face, i) => { return face.getSortedVertices().map(vkey => { return face_group.vertex_uvs[face_group.keys[i]][vkey]; - }) + }); }); + console.log(face_group.width, face_group.height) face_group.matrix = getPolygonOccupationMatrix(face_uvs, face_group.width, face_group.height); - }) + }); + // Change the order of the faces. face_list.sort(function(a,b) { return b.size - a.size; }) + // Place Faces into an empty space. + // If the faces are too big, this can be really slow. var fill_map = {}; function occupy(x, y) { if (!fill_map[x]) fill_map[x] = {} fill_map[x][y] = true } - function check(x, y) { + function is_occupied(x, y) { return fill_map[x] && fill_map[x][y] } - function forTemplatePixel(tpl, sx, sy, cb) { + function check_place(tpl, sx, sy) { let w = tpl.width; let h = tpl.height; - + const coord = []; if (options.padding) { w++; h++; for (var x = 0; x < w; x++) { @@ -2052,7 +2074,11 @@ const TextureGenerator = { (!tpl.matrix[x] || !tpl.matrix[x][y-1]) && (!tpl.matrix[x-1] || !tpl.matrix[x-1][y-1]) ) continue; - if (cb(sx+x, sy+y)) return; + if (is_occupied(sx+x, sy+y)) { + return null; + } else { + coord.push([sx+x, sy+y]); + } } } } else { @@ -2060,37 +2086,38 @@ const TextureGenerator = { if (tpl.matrix && !tpl.matrix[x]) continue; for (var y = 0; y < h; y++) { if (tpl.matrix && !tpl.matrix[x][y]) continue; - if (cb(sx+x, sy+y)) return; + if (is_occupied(sx+x, sy+y)) { + return null; + } else { + coord.push([sx+x, sy+y]); + } } } } + return coord; } function place(tpl, x, y) { - var works = true; - forTemplatePixel(tpl, x, y, (tx, ty) => { - if (check(tx, ty)) { - works = false; - return true; - } - }) - if (works) { - forTemplatePixel(tpl, x, y, occupy) + const coordinatesToCheck = check_place(tpl, x, y); + if (coordinatesToCheck) { + coordinatesToCheck.forEach(([tx, ty]) => occupy(tx, ty)); tpl.posx = x; tpl.posy = y; return true; } + return false; } face_list.forEach(tpl => { - //Scan for empty spot - for (var line = 0; line < 2e3; line++) { - for (var space = 0; space <= line; space++) { - if (place(tpl, space, line)) return; + outerLoop: + for (let line = 0; line < 2000; line++) { + for (let space = 0; space <= line; space++) { + if (place(tpl, space, line)) break outerLoop; if (space == line) continue; - if (place(tpl, line, space)) return; + if (place(tpl, line, space)) break outerLoop; } } }) + // Applies the UV, using the .posx and .posy set before. face_list.forEach(function(ftemp) { function applyUV(source, target) { target.faces.forEach((face, i) => { @@ -2110,13 +2137,14 @@ const TextureGenerator = { } if (!face.uv[vkey]) face.uv[vkey] = []; // Final UV sizes and positions, per face. - let uv_size_multiplier = res_multiple; - let uv_offset_multiplier = res_multiple; - face.uv[vkey][0] = source.vertex_uvs[source_fkey][source_vkey][0] * uv_size_multiplier + (source.posx * uv_offset_multiplier); - face.uv[vkey][1] = source.vertex_uvs[source_fkey][source_vkey][1] * uv_size_multiplier + (source.posy * uv_offset_multiplier); + let uv_size_pixel_density_multiplier = res_multiple; + let uv_offset_pixel_density_multiplier = res_multiple; + face.uv[vkey][0] = source.vertex_uvs[source_fkey][source_vkey][0] * uv_size_pixel_density_multiplier + (source.posx * uv_offset_pixel_density_multiplier); + face.uv[vkey][1] = source.vertex_uvs[source_fkey][source_vkey][1] * uv_size_pixel_density_multiplier + (source.posy * uv_offset_pixel_density_multiplier); }) }) } + if (ftemp.copy_to) { for (let ftemp2 of ftemp.copy_to) { applyUV(ftemp, ftemp2); diff --git a/js/texturing/uv.js b/js/texturing/uv.js index aefe9db3..672e6749 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -456,7 +456,6 @@ const UVEditor = { let max_zoom = Math.round((this.vue.texture ? this.vue.texture.height : Project.texture_width) * 32 / UVEditor.width); // zoom = Math.clamp(zoom, UVEditor.height > 800 ? 0.2 : 0.5, Math.clamp(max_zoom, 16, 64)); zoom = Math.clamp(zoom, 0.0, Math.clamp(max_zoom, 16, 64)); - console.log(zoom, max_zoom); this.vue.zoom = zoom; Project.uv_viewport.zoom = this.zoom; Vue.nextTick(() => { diff --git a/lang/en.json b/lang/en.json index 61b39086..3f8d7b40 100644 --- a/lang/en.json +++ b/lang/en.json @@ -657,6 +657,9 @@ "dialog.create_texture.resolution.desc": "The height and width of the texture", "dialog.create_texture.pixel_density": "Pixel Density", "dialog.create_texture.pixel_density.desc": "The pixel density of the texture, in pixels per meter. The size of the texture map may differ depending on the amount and size of elements.", + "dialog.unwrap_mesh.resolution": "Target Resolution", + "dialog.unwrap_mesh.resolution.desc": "The resolution to use to scale the UV", + "dialog.unwrap_mesh.pixel_density.desc": "The pixel density of the texture, in pixels per meter.", "dialog.mirror_painting_texture_center.middle": "Middle", "dialog.mirror_painting_texture_center.custom": "Custom", From bfe956c71a2f30fc7bc60426cbfb071756d87d0a Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Fri, 27 Sep 2024 00:08:25 -0300 Subject: [PATCH 09/10] fix the inconsistency of using UV Overlay The UV Overlay is now renamed to UV Show All, as now it's only function is to show the UV from all objects. (it was its only function before, but it was confusing to use, so I made it more clear what it does). The other UV options are shown normally when clicking with the Right Mouse Button in the UVEditor. The settings option regarding the UV Mode is now an Enum, to properly represent what's happening # Conflicts: # lang/vi.json # lang/zh.json # lang/zh_tw.json --- js/interface/settings.js | 7 ++++++- js/io/project.js | 2 +- js/texturing/uv.js | 15 ++++----------- lang/cz.json | 7 +++---- lang/de.json | 7 +++---- lang/en.json | 7 +++---- lang/es.json | 7 +++---- lang/fr.json | 7 +++---- lang/it.json | 7 +++---- lang/ja.json | 7 +++---- lang/ko.json | 7 +++---- lang/nl.json | 7 +++---- lang/pl.json | 7 +++---- lang/pt.json | 7 +++---- lang/ru.json | 7 +++---- lang/sv.json | 7 +++---- lang/uk.json | 7 +++---- 17 files changed, 53 insertions(+), 69 deletions(-) diff --git a/js/interface/settings.js b/js/interface/settings.js index f7708e7d..d7e048cb 100644 --- a/js/interface/settings.js +++ b/js/interface/settings.js @@ -539,7 +539,12 @@ const Settings = { new Setting('autouv', {category: 'defaults', value: true}); new Setting('inherit_parent_color', {category: 'defaults', value: false}); new Setting('create_rename', {category: 'defaults', value: false}); - new Setting('show_only_selected_uv', {category: 'defaults', value: false}); + new Setting('display_uv', {category: 'defaults', value: 'none', type: 'select', description: '', options: { + 'selected_faces': tl('menu.uv.display_uv.selected_faces'), + 'selected_elements': tl('menu.uv.display_uv.selected_elements'), + }, onChange(value) { + Project.display_uv = UVEditor.vue.display_uv = value; + }}); new Setting('default_path', {category: 'defaults', value: false, type: 'click', condition: isApp, icon: 'burst_mode', click: function() { openDefaultTexturePath() }}); new Setting('animation_snap', {category: 'defaults', value: 24, type: 'number'}); new Setting('uniform_keyframe', {category: 'defaults', value: true}); diff --git a/js/io/project.js b/js/io/project.js index e3ccdb9e..180f771e 100644 --- a/js/io/project.js +++ b/js/io/project.js @@ -30,7 +30,7 @@ class ModelProject { this.mode = 'edit'; this.tool = ''; this.view_mode = 'textured'; - this.display_uv = settings.show_only_selected_uv.value ? 'selected_faces' :'selected_elements'; + this.display_uv = settings.display_uv.value; this.exploded_view = false; this.mirror_modeling_enabled = false; this.previews = {}; diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 672e6749..52bd936a 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -1404,7 +1404,7 @@ const UVEditor = { 'zoom_reset' ]}, {name: 'menu.uv.display_uv', id: 'display_uv', icon: 'visibility', condition: () => (!Format.image_editor), children: () => { - let options = ['selected_faces', 'selected_elements', 'all_elements']; + let options = ['selected_faces', 'selected_elements']; return options.map(option => {return { id: option, name: `menu.uv.display_uv.${option}`, @@ -1412,8 +1412,7 @@ const UVEditor = { condition: !(option == 'selected_faces' && UVEditor.isBoxUV() && !Mesh.selected.length), click() { Project.display_uv = UVEditor.vue.display_uv = option; - if (option == 'selected_faces') settings.show_only_selected_uv.set(true); - if (option == 'selected_elements') settings.show_only_selected_uv.set(false); + settings.display_uv.set(option); Settings.saveLocalStorages(); } }}) @@ -2173,20 +2172,14 @@ BARS.defineActions(function() { }) new Toggle('edit_mode_uv_overlay', { name: 'action.paint_mode_uv_overlay', - description: 'action.edit_mode_uv_overlay.desc', icon: 'stack', category: 'uv', condition: {modes: ['edit']}, onChange(value) { if (value) { Project.display_uv = UVEditor.vue.display_uv = 'all_elements'; - settings.show_only_selected_uv.set(true); } else { - if (settings.show_only_selected_uv.value) { - Project.display_uv = UVEditor.vue.display_uv = 'selected_faces'; - } else { - Project.display_uv = UVEditor.vue.display_uv = 'selected_elements'; - } + Project.display_uv = UVEditor.vue.display_uv = settings.display_uv.value; } } }) @@ -2298,7 +2291,7 @@ Interface.definePanels(function() { uv_resolution: [16, 16], elements: [], all_elements: [], - display_uv: 'selected_elements', + display_uv: settings.display_uv.value, selection_outline: '', face_names: { diff --git a/lang/cz.json b/lang/cz.json index 9f3a1732..35e00b37 100644 --- a/lang/cz.json +++ b/lang/cz.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Přepnout šachovnicové pozadí za náhledem", "settings.uv_checkerboard": "Šachovnice UV Editoru", "settings.uv_checkerboard.desc": "Přepnout šachovnicové pozadí za UV editorem", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Malování", "action.fill_mode.color_connected": "Připojené Barvy", "action.draw_shape_type": "Typ Tvaru", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "Found and merged %0 vertices in %1 locations", "settings.vertex_merge_distance": "Vertex Merge Distance", "settings.vertex_merge_distance.desc": "Distance within which vertices are merged using merging by distance", - "settings.show_only_selected_uv": "Show Only Selected UV", - "settings.show_only_selected_uv.desc": "Only display the selected UV faces in the UV editor by default", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Merge All", "action.merge_vertices.merge_all_in_center": "Merge All in Center", "action.merge_vertices.merge_by_distance": "Merge by Distance", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opens a new Blockbench window", "action.export_collada": "Export Collada Model (dae)", "action.export_collada.desc": "Export model and animations as dae file to use it in other 3D applications", - "action.paint_mode_uv_overlay": "UV Overlay", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/de.json b/lang/de.json index 57d30ab5..3db94ee4 100644 --- a/lang/de.json +++ b/lang/de.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Aktiviert das Schachbrettmuster hinter der 3D-Vorschau", "settings.uv_checkerboard": "UV-Fenster-Schachbrettmuster", "settings.uv_checkerboard.desc": "Aktiviert das Schachbrettmuster hinter dem UV-Fenster", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Malen", "action.fill_mode.color_connected": "Verbundene Farben", "action.draw_shape_type": "Form", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "%0 Eckpunkte wurden gefunden und an %1 Stellen vereint", "settings.vertex_merge_distance": "Abstand für Vereinen von Eckpunkten", "settings.vertex_merge_distance.desc": "Abstand, in dem beieinander liegende Eckpunkte durch die Vereinen-Funktion vereint werden", - "settings.show_only_selected_uv": "Nur ausgewählte UV-Flächen anzeigen", - "settings.show_only_selected_uv.desc": "Zeige standardmäßig nur die aktuell ausgewählten UV-Flächen im UV-Editor an", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Alle vereinen", "action.merge_vertices.merge_all_in_center": "Alle in Mittelpunkt vereinen", "action.merge_vertices.merge_by_distance": "Beieinander liegende vereinen", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Öffnet ein neues Blockbench-Fenster", "action.export_collada": "Collada-Modell (dae) exportieren", "action.export_collada.desc": "Exportiert das Modell als dae-Datei zur Weiterverwendung in anderen 3D-Programmen", - "action.paint_mode_uv_overlay": "UV-Hilfslinien", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Überlagert die UV-Maps der Elemente im Malen-Modus", "action.bake_animation_into_model": "Animationen auf Modell anwenden", "action.bake_animation_into_model.desc": "Wendet die aktuell angezeigte Animation auf das Modell an. Nur Position und Drehung werden verwendet, Größe wird ignoriert", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Neue Zeile am Ende", "settings.final_newline.desc": "Füge einen Zeilenumbruch am Ende von exportierten Dateien an", "action.crop_layer_to_selection": "Ebene auf Auswahl zuschneiden", - "action.edit_mode_uv_overlay.desc": "Überlagert die UV-Maps der Elemente im Bearbeitungsmodus", "action.animation_onion_skin_selective": "Zwiebelschalen nur für Auswahl", "action.animation_onion_skin_selective.desc": "Zeige die Zwiebelschalenvorschau nur für den ausgewählten Teil des Modells an", "menu.image": "Bild", diff --git a/lang/en.json b/lang/en.json index 3f8d7b40..25be2867 100644 --- a/lang/en.json +++ b/lang/en.json @@ -953,6 +953,7 @@ "settings.preview_checkerboard.desc": "Toggle the checkerboard background behind the preview", "settings.uv_checkerboard": "UV Editor Checkerboard", "settings.uv_checkerboard.desc": "Toggle the checkerboard background behind the UV editor", + "settings.display_uv.desc": "What to display in the UV Editor", "settings.timecode_frame_number": "Display Timecodes as Frame Numbers", "settings.timecode_frame_number.desc": "Display fraction timecodes in the timeline as frame number instead of centisecond", "settings.only_selected_bezier_handles": "Show Only Selected Bézier Handles", @@ -1019,8 +1020,7 @@ "settings.inherit_parent_color.desc": "Set the marker color of a newly created element to the color of the parent instead of a random color", "settings.create_rename": "Rename New Element", "settings.create_rename.desc": "Focus name field when creating new element or group", - "settings.show_only_selected_uv": "Show Only Selected UV", - "settings.show_only_selected_uv.desc": "Only display the selected UV faces in the UV editor by default", + "settings.display_uv": "Display UV Mode", "settings.animation_snap": "Animation Snap", "settings.animation_snap.desc": "Default snap interval for keyframes in the animation timeline in steps per second. This can also be changed per animation. The default value is 24.", "settings.uniform_keyframe": "Uniform Scale Keyframes", @@ -1798,10 +1798,9 @@ "action.uv_cycle.desc": "Cycle through the order of UV vertices without changing the UV positions", "action.uv_cycle_invert": "Cycle Invert UV", "action.uv_cycle_invert.desc": "Reverse the order of UV vertices without changing the UV positions", - "action.paint_mode_uv_overlay": "UV Overlay", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.snap_uv_to_image_bounds": "Snap UV to image bounds", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.remove_blank_faces": "Remove Blank Faces", "action.remove_blank_faces.desc": "Deletes all untextured faces of the selection", "action.auto_set_cullfaces": "Set Cullfaces Automatically", diff --git a/lang/es.json b/lang/es.json index 324d5483..9480e985 100644 --- a/lang/es.json +++ b/lang/es.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Cambia el fondo de la cuadrícula detrás de la previsualización", "settings.uv_checkerboard": "Cuadrícula del Editor de UV", "settings.uv_checkerboard.desc": "Cambia el fondo de la cuadrícula detrás del editor de UV", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Pintar", "action.fill_mode.color_connected": "Colores Conectados", "action.draw_shape_type": "Tipo de Forma", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "Se encontraron y combinaron %0 vértices en %1 ubicaciones", "settings.vertex_merge_distance": "Distancia de fusión de vértices", "settings.vertex_merge_distance.desc": "Distancia dentro de la cual se fusionan los vértices mediante la fusión por distancia", - "settings.show_only_selected_uv": "Mostrar solo UV seleccionado", - "settings.show_only_selected_uv.desc": "Mostrar solo las caras UV seleccionadas en el editor UV de forma predeterminada", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Unir todo", "action.merge_vertices.merge_all_in_center": "Unir todo al centro", "action.merge_vertices.merge_by_distance": "Unir distancia", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Abrir una nueva ventana en Blockbench", "action.export_collada": "Exportar modelo Collada (dae)", "action.export_collada.desc": "Exportar modelo y animaciones como archivo dae para usarlo en otras aplicaciones 3D", - "action.paint_mode_uv_overlay": "Cubrir UV", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Muestra el mapa UV como una superposición en el modo de pintura", "action.bake_animation_into_model": "Hornear animación en modelo", "action.bake_animation_into_model.desc": "Hornea la animación mostrada actualmente al modelo. Solo aplica a la rotación y la posición, se ignora la escala.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/fr.json b/lang/fr.json index 1eb44edf..830fb98e 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Affiche/masque le damier d'arrière-plan de l'aperçu", "settings.uv_checkerboard": "Damier de l'éditeur UV", "settings.uv_checkerboard.desc": "Affiche/masque le damier d'arrière-plan de l'éditeur UV", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Peinture", "action.fill_mode.color_connected": "Couleurs connectées", "action.draw_shape_type": "Type d'ombre", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "%0 sommets ont été trouvés et fusionné depuis %1 emplacements", "settings.vertex_merge_distance": "Distance de fusion des sommets", "settings.vertex_merge_distance.desc": "Distance avant laquelle les sommets sont fusionnés en utilisant la fusion par distance", - "settings.show_only_selected_uv": "Afficher UV sélectionnés uniquement", - "settings.show_only_selected_uv.desc": "N'afficher que les faces UV sélectionnées dans l'éditeur UV par défaut", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Tout fusionner", "action.merge_vertices.merge_all_in_center": "Fusionne tout, au centre", "action.merge_vertices.merge_by_distance": "Fusion par distnace", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Ouvre une nouvelle fenêtre Blockbench", "action.export_collada": "Exporter modèle Collada (dae)", "action.export_collada.desc": "Exporte le modèle et les animations dans un fichier dae pour l'utiliser dans d'autres logiciels 3D", - "action.paint_mode_uv_overlay": "Overlay UV", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Affiche la carte UV sous forme d'overlay en mode Dessin", "action.bake_animation_into_model": "Intégrer l'animation au modèle", "action.bake_animation_into_model.desc": "Intègre l'image d'animation actuellement affichée au modèle. Seules la rotation et la position s'appliquent, la mise à l'échelle est ignorée.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/it.json b/lang/it.json index 9840e29d..aa063a74 100644 --- a/lang/it.json +++ b/lang/it.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Alterna lo sfondo a scacchiera dieto l'anteprima", "settings.uv_checkerboard": "Scacchiera Editore UV", "settings.uv_checkerboard.desc": "Alterna lo sfondo a scacchiera dietro l'editore UV", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Dipingi", "action.fill_mode.color_connected": "Colori Connessi", "action.draw_shape_type": "Tipo di forma", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "Found and merged %0 vertices in %1 locations", "settings.vertex_merge_distance": "Vertex Merge Distance", "settings.vertex_merge_distance.desc": "Distance within which vertices are merged using merging by distance", - "settings.show_only_selected_uv": "Show Only Selected UV", - "settings.show_only_selected_uv.desc": "Only display the selected UV faces in the UV editor by default", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Merge All", "action.merge_vertices.merge_all_in_center": "Merge All in Center", "action.merge_vertices.merge_by_distance": "Merge by Distance", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opens a new Blockbench window", "action.export_collada": "Export Collada Model (dae)", "action.export_collada.desc": "Export model and animations as dae file to use it in other 3D applications", - "action.paint_mode_uv_overlay": "UV Overlay", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/ja.json b/lang/ja.json index 89ed6a9c..9ac1cc69 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "チェッカーボードの背景を切り替えます", "settings.uv_checkerboard": "UV エディタ", "settings.uv_checkerboard.desc": "UV エディタの背景を切り替えます", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Paint", "action.fill_mode.color_connected": "コネクトカラー", "action.draw_shape_type": "シャープ", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "%1 で %0 の頂点が見つかりました", "settings.vertex_merge_distance": "バーテクス", "settings.vertex_merge_distance.desc": "頂点がマージされる距離を変更します", - "settings.show_only_selected_uv": "セレクト UV", - "settings.show_only_selected_uv.desc": "選択した UV フェイスのみを UV エディタに表示します", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "全てマージ", "action.merge_vertices.merge_all_in_center": "中央に全てマージ", "action.merge_vertices.merge_by_distance": "距離マージ", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "新しい Blockbench ウィンドウを開きます", "action.export_collada": "Collada Model (dae)", "action.export_collada.desc": "モデルとアニメーションを dae ファイルとして書き出します", - "action.paint_mode_uv_overlay": "UV オーバーレイ", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "ペイントモードで UV マップをオーバーレイとして表示します", "action.bake_animation_into_model": "アニメーションをモデルに焼き付ける", "action.bake_animation_into_model.desc": "現在表示されているアニメーションフレームをモデルにベイク処理します。回転と位置のみを適用し、スケールは無視されます。", @@ -2040,7 +2040,6 @@ "settings.final_newline": "改行を追加", "settings.final_newline.desc": "ファイルの末尾に改行を挿入します", "action.crop_layer_to_selection": "レイヤーを選択範囲にトリミング", - "action.edit_mode_uv_overlay.desc": "編集モードで UV マップをオーバーレイとして表示します", "action.animation_onion_skin_selective": "トグルレイヤースキン", "action.animation_onion_skin_selective.desc": "選択したレイヤースキンのみを表示します", "menu.image": "Image", diff --git a/lang/ko.json b/lang/ko.json index 7c9f0efa..59380890 100644 --- a/lang/ko.json +++ b/lang/ko.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "미리보기 뒤에 있는 체크보드 배경 전환", "settings.uv_checkerboard": "UV 에디터 체커보드", "settings.uv_checkerboard.desc": "UV 에디터 뒤에 있는 체크보드 배경 전환", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "칠하기", "action.fill_mode.color_connected": "연결된 색상들", "action.draw_shape_type": "모양 타입", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "%1 위치에서 %0 정점 발견 및 병합", "settings.vertex_merge_distance": "정점 병합 거리", "settings.vertex_merge_distance.desc": "거리별 병합을 사용하여 정점이 병합되는 거리", - "settings.show_only_selected_uv": "선택된 UV만 표시", - "settings.show_only_selected_uv.desc": "기본적으로 선택한 UV 면만 UV 편집기에 표시", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "모두 명합", "action.merge_vertices.merge_all_in_center": "모두 중앙에 병합", "action.merge_vertices.merge_by_distance": "거리에 따라 병합", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "새로운 Blockbench 창 열기", "action.export_collada": "Collada 모델 (dae) 내보내기", "action.export_collada.desc": "모델 및 애니메이션을 dae 파일로 내보내서 다른 3D 응용 프로그램에서 사용", - "action.paint_mode_uv_overlay": "UV 오버레이", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "페인트 모드에서 UV 맵을 오버레이로 표시", "action.bake_animation_into_model": "애니메이션을 모델로 만들기", "action.bake_animation_into_model.desc": "현재 표시된 애니메이션 프레임을 모델로 만듭니다. 회전 및 위치만 적용하고 크기는 무시됩니다.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/nl.json b/lang/nl.json index 4c977c91..5d29a9c7 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Zet de dambord achtergrond achter de voorvertoning aan/uit", "settings.uv_checkerboard": "UV Bewerker Dambord", "settings.uv_checkerboard.desc": "Zet de dambord achtergrond achter de UV bewerker aan/uit", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Verf", "action.fill_mode.color_connected": "Verbonden Kleuren", "action.draw_shape_type": "Vorm Type", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "%0 vertices in %1 locatie(s) gevonden en samengevoegd", "settings.vertex_merge_distance": "Vertex Samenvoeg Afstand", "settings.vertex_merge_distance.desc": "Afstand waarbinnen vertices worden samengevoegd met behulp van samenvoegen op afstand", - "settings.show_only_selected_uv": "Toon Enkel Geselecteerde UVs", - "settings.show_only_selected_uv.desc": "Toont standaard alleen de geselecteerde UV vlakken in de UV-editor", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Voeg Alles Samen", "action.merge_vertices.merge_all_in_center": "Voeg Alles Samen in Centrum", "action.merge_vertices.merge_by_distance": "Voeg Samen volgens Afstand", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opent een nieuw Blockbench venster", "action.export_collada": "Exporteer Collada Model (dae)", "action.export_collada.desc": "Exporteer modellen en animaties als .dae-bestand voor gebruik in andere 3D-applicaties", - "action.paint_mode_uv_overlay": "UV Overlay", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "De UV-map weergeven als een overlay in verfmodus", "action.bake_animation_into_model": "Bak Animatie in Model", "action.bake_animation_into_model.desc": "Bak het huidig weergegeven animatieframe in het model. Past alleen rotatie en positie toe, schaal wordt genegeerd.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/pl.json b/lang/pl.json index 83557815..d1ad536a 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Przełącz tło szachownicy za podglądem", "settings.uv_checkerboard": "Szachownica edytoru UV", "settings.uv_checkerboard.desc": "Przełącz tło szachownicy za edytorem UV", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Maluj", "action.fill_mode.color_connected": "Połączone kolory", "action.draw_shape_type": "Typ kształtu", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "Znaleziono i połączono %0 wierzchołków w %1 lokalizacjach", "settings.vertex_merge_distance": "Odległość scalania wierzchołków", "settings.vertex_merge_distance.desc": "Odległość, w ramach której wierzchołki są scalane przy użyciu scalania przez odległość", - "settings.show_only_selected_uv": "Pokaż tylko wybrane UV", - "settings.show_only_selected_uv.desc": "Domyślnie wyświetlaj tylko wybrane twarze UV w edytorze UV", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Scal wszystko", "action.merge_vertices.merge_all_in_center": "Scal wszystko w centrum", "action.merge_vertices.merge_by_distance": "Scal według odległości", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Otwiera nowe okno Blockbencha", "action.export_collada": "Eksportuj model Collada (dae)", "action.export_collada.desc": "Eksportuj model i animacje jako plik dae, aby używać ich w innych programach 3D", - "action.paint_mode_uv_overlay": "UV Overlay", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/pt.json b/lang/pt.json index a4182cab..4d128e2a 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -934,6 +934,7 @@ "settings.preview_checkerboard.desc": "Alterne o plano de fundo do tabuleiro xadrez atrás da visualização", "settings.uv_checkerboard": "Editor UV do tabuleiro xadrez", "settings.uv_checkerboard.desc": "Alterne o fundo do tabuleiro xadrez atrás do editor UV", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Pintar", "action.fill_mode.color_connected": "Cores Conectadas", "action.draw_shape_type": "Tipo da Forma", @@ -1351,8 +1352,7 @@ "message.merged_vertices": "Found and merged %0 vertices in %1 locations", "settings.vertex_merge_distance": "Vertex Merge Distance", "settings.vertex_merge_distance.desc": "Distance within which vertices are merged using merging by distance", - "settings.show_only_selected_uv": "Show Only Selected UV", - "settings.show_only_selected_uv.desc": "Only display the selected UV faces in the UV editor by default", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Merge All", "action.merge_vertices.merge_all_in_center": "Merge All in Center", "action.merge_vertices.merge_by_distance": "Merge by Distance", @@ -1406,7 +1406,7 @@ "action.new_window.desc": "abrir nova aba do blockbench", "action.export_collada": "exportar collada modelo (dae)", "action.export_collada.desc": "exportar collada modelo (dae) para usar em outras aplicações", - "action.paint_mode_uv_overlay": "Sobreposição UV", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/ru.json b/lang/ru.json index ca23108b..5256b039 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Переключить фон шахматной доски за предварительным просмотром", "settings.uv_checkerboard": "Шахматная доска UV редактора", "settings.uv_checkerboard.desc": "Переключить фон шахматной доски за UV редактором", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Рисование", "action.fill_mode.color_connected": "Соединённые цвета", "action.draw_shape_type": "Тип фигуры", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "Найдены и объединены %0 вершины в %1 местах", "settings.vertex_merge_distance": "Расстояние слияния вершин", "settings.vertex_merge_distance.desc": "Расстояние, в пределах которого вершины объединяются с помощью объединения по расстоянию", - "settings.show_only_selected_uv": "Показать только выбранные UV", - "settings.show_only_selected_uv.desc": "Отображение только выбранных UV-граней в UV-редакторе по умолчанию.", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Соединить всё", "action.merge_vertices.merge_all_in_center": "Объединить все в центре", "action.merge_vertices.merge_by_distance": "Объединить по расстоянию", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Открывает новое окно Blockbench", "action.export_collada": "Экспортировать Collada модель (dae)", "action.export_collada.desc": "Экспортировать модель или анимацию в dae файл, что бы использовать в других редакторах 3D", - "action.paint_mode_uv_overlay": "UV-наложение", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Отображение UV-карты в виде наложения в режиме рисования", "action.bake_animation_into_model": "Запечь анимацию в модель", "action.bake_animation_into_model.desc": "Запеките текущий отображаемый кадр анимации в модель. Применяется только вращение и положение, масштаб игнорируется.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Конечная новая строка", "settings.final_newline.desc": "Вставить символ новой строки в конце экспортируемых файлов", "action.crop_layer_to_selection": "Обрезать слой по выделению", - "action.edit_mode_uv_overlay.desc": "Отображать UV-карту как наложение в режиме редактирования", "action.animation_onion_skin_selective": "Выборочная Onion Skin", "action.animation_onion_skin_selective.desc": "Отображать Onion Skin только для выбранной части модели", "menu.image": "Изображение", diff --git a/lang/sv.json b/lang/sv.json index 61621638..92b36a3c 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Toggle the checkerboard background behind the preview", "settings.uv_checkerboard": "UV Editor Checkerboard", "settings.uv_checkerboard.desc": "Toggle the checkerboard background behind the UV editor", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Paint", "action.fill_mode.color_connected": "Connected Colors", "action.draw_shape_type": "Shape Type", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "Found and merged %0 vertices in %1 locations", "settings.vertex_merge_distance": "Vertex Merge Distance", "settings.vertex_merge_distance.desc": "Distance within which vertices are merged using merging by distance", - "settings.show_only_selected_uv": "Show Only Selected UV", - "settings.show_only_selected_uv.desc": "Only display the selected UV faces in the UV editor by default", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Merge All", "action.merge_vertices.merge_all_in_center": "Merge All in Center", "action.merge_vertices.merge_by_distance": "Merge by Distance", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opens a new Blockbench window", "action.export_collada": "Export Collada Model (dae)", "action.export_collada.desc": "Export model and animations as dae file to use it in other 3D applications", - "action.paint_mode_uv_overlay": "UV Overlay", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Final Newline", "settings.final_newline.desc": "Insert a newline character at the end of exported files", "action.crop_layer_to_selection": "Crop Layer to Selection", - "action.edit_mode_uv_overlay.desc": "Display the UV map as an overlay in edit mode", "action.animation_onion_skin_selective": "Selective Onion Skin", "action.animation_onion_skin_selective.desc": "Only display onion skin for the selected part of the model", "menu.image": "Image", diff --git a/lang/uk.json b/lang/uk.json index cadefc87..491a4aec 100644 --- a/lang/uk.json +++ b/lang/uk.json @@ -933,6 +933,7 @@ "settings.preview_checkerboard.desc": "Увімкнути/вимкнути тло шахівниці позаду моделі", "settings.uv_checkerboard": "Шахівниця редактора UV", "settings.uv_checkerboard.desc": "Перемикати видимість тла шахівниці за редактором UV", + "settings.display_uv.desc": "What to display in the UV Editor", "category.paint": "Малювати", "action.fill_mode.color_connected": "Поєднані кольори", "action.draw_shape_type": "Тип форми", @@ -1350,8 +1351,7 @@ "message.merged_vertices": "Знайдено та об’єднано %0 вершин у %1 місцях", "settings.vertex_merge_distance": "Відстань об’єднання вершин", "settings.vertex_merge_distance.desc": "Відстань, в межах якої об’єднуються вершини з об’єднанням за відстанню", - "settings.show_only_selected_uv": "Показувати лише вибраний UV", - "settings.show_only_selected_uv.desc": "За стандартом у редакторі UV відображати лише вибрані сторони UV", + "settings.display_uv": "Display UV Mode", "action.merge_vertices.merge_all": "Об’єднати всі", "action.merge_vertices.merge_all_in_center": "Об’єднати всі в центрі", "action.merge_vertices.merge_by_distance": "Об’єднати за відстанню", @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Відкриває нове вікно Blockbench", "action.export_collada": "Експортувати модель Collada (DAE)", "action.export_collada.desc": "Експортувати модель і анімацію як файл DAE, щоб використовувати його в інших 3D-застосунках", - "action.paint_mode_uv_overlay": "Накладання UV", + "action.paint_mode_uv_overlay": "UV Show All", "action.paint_mode_uv_overlay.desc": "Показати мапу UV як накладання в режимі малювання", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", @@ -2040,7 +2040,6 @@ "settings.final_newline": "Останній новий рядок", "settings.final_newline.desc": "Вставляти символ нового рядка в кінці експортованих файлів", "action.crop_layer_to_selection": "Обрізати шар до виділення", - "action.edit_mode_uv_overlay.desc": "Відображення UV-мапи як накладання в режимі редагування", "action.animation_onion_skin_selective": "Виділено Onion Skin", "action.animation_onion_skin_selective.desc": "Відображати тільки onion skin для вибраної частини моделі", "menu.image": "Зображення", From 18d6a7bf91b4eda23854119e344e7c96c5e42974 Mon Sep 17 00:00:00 2001 From: Caio Raphael Date: Fri, 27 Sep 2024 00:08:33 -0300 Subject: [PATCH 10/10] UV Overlay tool rework (part 2), with small revert from 'part 1' # Conflicts: # lang/vi.json # lang/zh.json # lang/zh_tw.json --- js/interface/settings.js | 9 +++++++ js/texturing/uv.js | 52 +++++++++++++++++++++++++++++----------- lang/cz.json | 2 +- lang/de.json | 2 +- lang/en.json | 3 ++- lang/es.json | 2 +- lang/fr.json | 2 +- lang/it.json | 2 +- lang/ja.json | 2 +- lang/ko.json | 2 +- lang/nl.json | 2 +- lang/pl.json | 2 +- lang/pt.json | 2 +- lang/ru.json | 2 +- lang/sv.json | 2 +- lang/uk.json | 2 +- 16 files changed, 62 insertions(+), 28 deletions(-) diff --git a/js/interface/settings.js b/js/interface/settings.js index d7e048cb..9a6e9a61 100644 --- a/js/interface/settings.js +++ b/js/interface/settings.js @@ -542,8 +542,17 @@ const Settings = { new Setting('display_uv', {category: 'defaults', value: 'none', type: 'select', description: '', options: { 'selected_faces': tl('menu.uv.display_uv.selected_faces'), 'selected_elements': tl('menu.uv.display_uv.selected_elements'), + 'all_elements': tl('menu.uv.display_uv.all_elements'), }, onChange(value) { Project.display_uv = UVEditor.vue.display_uv = value; + // Affect other tools. + BarItems.edit_mode_uv_overlay.value = value == 'all_elements'; + BarItems.edit_mode_uv_overlay.updateEnabledState(); + if (BarItems.paint_mode_uv_overlay.value && value == 'selected_faces') { + UVEditor.vue.uv_overlay = false; + BarItems.paint_mode_uv_overlay.value = false; + BarItems.paint_mode_uv_overlay.updateEnabledState(); + } }}); new Setting('default_path', {category: 'defaults', value: false, type: 'click', condition: isApp, icon: 'burst_mode', click: function() { openDefaultTexturePath() }}); new Setting('animation_snap', {category: 'defaults', value: 24, type: 'number'}); diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 52bd936a..4983e545 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -1404,16 +1404,25 @@ const UVEditor = { 'zoom_reset' ]}, {name: 'menu.uv.display_uv', id: 'display_uv', icon: 'visibility', condition: () => (!Format.image_editor), children: () => { - let options = ['selected_faces', 'selected_elements']; + let options = ['selected_faces', 'selected_elements', 'all_elements']; return options.map(option => {return { id: option, name: `menu.uv.display_uv.${option}`, - icon: UVEditor.vue.display_uv == option ? 'far.fa-dot-circle' : 'far.fa-circle', + icon: UVEditor.vue.display_uv == option ? 'far.fa-dot-circle' : 'far.fa-circle', // Affected by current. condition: !(option == 'selected_faces' && UVEditor.isBoxUV() && !Mesh.selected.length), click() { Project.display_uv = UVEditor.vue.display_uv = option; + // Affect settings. settings.display_uv.set(option); Settings.saveLocalStorages(); + // Affect other tools. + BarItems.edit_mode_uv_overlay.value = option == 'all_elements'; + BarItems.edit_mode_uv_overlay.updateEnabledState(); + if (BarItems.paint_mode_uv_overlay.value && option == 'selected_faces') { + UVEditor.vue.uv_overlay = false; + BarItems.paint_mode_uv_overlay.value = false; + BarItems.paint_mode_uv_overlay.updateEnabledState(); + } } }}) }}, @@ -1439,6 +1448,7 @@ const UVEditor = { 'uv_maximize', 'uv_auto', 'uv_rel_auto', + 'unwrap_mesh_faces', 'uv_project_from_view', 'connect_uv_faces', 'merge_uv_vertices', @@ -1698,7 +1708,7 @@ BARS.defineActions(function() { } }) new Action('unwrap_mesh_faces', { - icon: 'view_in_ar', + icon: 'map', category: 'uv', condition: () => Mesh.selected.length, click() { @@ -2171,24 +2181,35 @@ BARS.defineActions(function() { value: true }) new Toggle('edit_mode_uv_overlay', { - name: 'action.paint_mode_uv_overlay', icon: 'stack', category: 'uv', - condition: {modes: ['edit']}, + condition: {modes: ['edit', 'paint']}, onChange(value) { if (value) { Project.display_uv = UVEditor.vue.display_uv = 'all_elements'; + // Affect settings. + settings.display_uv.set('all_elements'); + Settings.saveLocalStorages(); } else { - Project.display_uv = UVEditor.vue.display_uv = settings.display_uv.value; + Project.display_uv = UVEditor.vue.display_uv = 'selected_elements'; + // Affect settings. + settings.display_uv.set('selected_elements'); + Settings.saveLocalStorages(); } } }) new Toggle('paint_mode_uv_overlay', { - icon: 'stack', + icon: 'preview', category: 'uv', condition: {modes: ['paint'], method: () => !Format.image_editor}, onChange(value) { UVEditor.vue.uv_overlay = value; + if (value && UVEditor.vue.display_uv == 'selected_faces') { + Project.display_uv = UVEditor.vue.display_uv = 'selected_elements'; + // Affect settings. + settings.display_uv.set('selected_elements'); + Settings.saveLocalStorages(); + } } }) new Toggle('move_texture_with_uv', { @@ -3455,10 +3476,14 @@ Interface.definePanels(function() { } }, getDisplayedUVElements() { - if (this.mode == 'uv' || this.uv_overlay) { - return (this.display_uv === 'all_elements' || this.mode == 'paint') - ? this.all_mappable_elements - : this.mappable_elements; + if (this.mode == 'uv') { + return (this.display_uv === 'all_elements') + ? this.all_mappable_elements + : this.mappable_elements; + } else if (this.mode == 'paint' && this.uv_overlay) { + return (this.display_uv === 'all_elements') + ? this.all_mappable_elements + : this.mappable_elements; } else { return []; } @@ -4373,11 +4398,11 @@ Interface.definePanels(function() { Toolbars.uv_editor.toPlace() + // Force show tool in the 'locked toolbar'. BarItems.paint_mode_uv_overlay.toElement('#toggle_uv_overlay_anchor'); - BarItems.edit_mode_uv_overlay.toElement('#toggle_edit_uv_overlay_anchor'); - let {slider_bar} = UVEditor.vue.$refs; + let {slider_bar} = UVEditor.vue.$refs; var onBefore = function() { Undo.initEdit({elements: UVEditor.getMappableElements()}) } @@ -4511,5 +4536,4 @@ Interface.definePanels(function() { onAfter }).toElement(slider_bar); - BarItems.edit_mode_uv_overlay.toElement(slider_bar); }) diff --git a/lang/cz.json b/lang/cz.json index 35e00b37..f8329955 100644 --- a/lang/cz.json +++ b/lang/cz.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opens a new Blockbench window", "action.export_collada": "Export Collada Model (dae)", "action.export_collada.desc": "Export model and animations as dae file to use it in other 3D applications", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", diff --git a/lang/de.json b/lang/de.json index 3db94ee4..97e45bf9 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Öffnet ein neues Blockbench-Fenster", "action.export_collada": "Collada-Modell (dae) exportieren", "action.export_collada.desc": "Exportiert das Modell als dae-Datei zur Weiterverwendung in anderen 3D-Programmen", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Überlagert die UV-Maps der Elemente im Malen-Modus", "action.bake_animation_into_model": "Animationen auf Modell anwenden", "action.bake_animation_into_model.desc": "Wendet die aktuell angezeigte Animation auf das Modell an. Nur Position und Drehung werden verwendet, Größe wird ignoriert", diff --git a/lang/en.json b/lang/en.json index 25be2867..fc4cca47 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1798,7 +1798,8 @@ "action.uv_cycle.desc": "Cycle through the order of UV vertices without changing the UV positions", "action.uv_cycle_invert": "Cycle Invert UV", "action.uv_cycle_invert.desc": "Reverse the order of UV vertices without changing the UV positions", - "action.paint_mode_uv_overlay": "UV Show All", + "action.edit_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.snap_uv_to_image_bounds": "Snap UV to image bounds", "action.remove_blank_faces": "Remove Blank Faces", diff --git a/lang/es.json b/lang/es.json index 9480e985..b147f36a 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Abrir una nueva ventana en Blockbench", "action.export_collada": "Exportar modelo Collada (dae)", "action.export_collada.desc": "Exportar modelo y animaciones como archivo dae para usarlo en otras aplicaciones 3D", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Muestra el mapa UV como una superposición en el modo de pintura", "action.bake_animation_into_model": "Hornear animación en modelo", "action.bake_animation_into_model.desc": "Hornea la animación mostrada actualmente al modelo. Solo aplica a la rotación y la posición, se ignora la escala.", diff --git a/lang/fr.json b/lang/fr.json index 830fb98e..e70d754e 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Ouvre une nouvelle fenêtre Blockbench", "action.export_collada": "Exporter modèle Collada (dae)", "action.export_collada.desc": "Exporte le modèle et les animations dans un fichier dae pour l'utiliser dans d'autres logiciels 3D", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Affiche la carte UV sous forme d'overlay en mode Dessin", "action.bake_animation_into_model": "Intégrer l'animation au modèle", "action.bake_animation_into_model.desc": "Intègre l'image d'animation actuellement affichée au modèle. Seules la rotation et la position s'appliquent, la mise à l'échelle est ignorée.", diff --git a/lang/it.json b/lang/it.json index aa063a74..175a16d6 100644 --- a/lang/it.json +++ b/lang/it.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opens a new Blockbench window", "action.export_collada": "Export Collada Model (dae)", "action.export_collada.desc": "Export model and animations as dae file to use it in other 3D applications", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", diff --git a/lang/ja.json b/lang/ja.json index 9ac1cc69..ba70955a 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "新しい Blockbench ウィンドウを開きます", "action.export_collada": "Collada Model (dae)", "action.export_collada.desc": "モデルとアニメーションを dae ファイルとして書き出します", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "ペイントモードで UV マップをオーバーレイとして表示します", "action.bake_animation_into_model": "アニメーションをモデルに焼き付ける", "action.bake_animation_into_model.desc": "現在表示されているアニメーションフレームをモデルにベイク処理します。回転と位置のみを適用し、スケールは無視されます。", diff --git a/lang/ko.json b/lang/ko.json index 59380890..e2e2cf12 100644 --- a/lang/ko.json +++ b/lang/ko.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "새로운 Blockbench 창 열기", "action.export_collada": "Collada 모델 (dae) 내보내기", "action.export_collada.desc": "모델 및 애니메이션을 dae 파일로 내보내서 다른 3D 응용 프로그램에서 사용", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "페인트 모드에서 UV 맵을 오버레이로 표시", "action.bake_animation_into_model": "애니메이션을 모델로 만들기", "action.bake_animation_into_model.desc": "현재 표시된 애니메이션 프레임을 모델로 만듭니다. 회전 및 위치만 적용하고 크기는 무시됩니다.", diff --git a/lang/nl.json b/lang/nl.json index 5d29a9c7..44cf3f50 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opent een nieuw Blockbench venster", "action.export_collada": "Exporteer Collada Model (dae)", "action.export_collada.desc": "Exporteer modellen en animaties als .dae-bestand voor gebruik in andere 3D-applicaties", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "De UV-map weergeven als een overlay in verfmodus", "action.bake_animation_into_model": "Bak Animatie in Model", "action.bake_animation_into_model.desc": "Bak het huidig weergegeven animatieframe in het model. Past alleen rotatie en positie toe, schaal wordt genegeerd.", diff --git a/lang/pl.json b/lang/pl.json index d1ad536a..28980c92 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Otwiera nowe okno Blockbencha", "action.export_collada": "Eksportuj model Collada (dae)", "action.export_collada.desc": "Eksportuj model i animacje jako plik dae, aby używać ich w innych programach 3D", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", diff --git a/lang/pt.json b/lang/pt.json index 4d128e2a..695c248f 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -1406,7 +1406,7 @@ "action.new_window.desc": "abrir nova aba do blockbench", "action.export_collada": "exportar collada modelo (dae)", "action.export_collada.desc": "exportar collada modelo (dae) para usar em outras aplicações", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", diff --git a/lang/ru.json b/lang/ru.json index 5256b039..9c7ce9d7 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Открывает новое окно Blockbench", "action.export_collada": "Экспортировать Collada модель (dae)", "action.export_collada.desc": "Экспортировать модель или анимацию в dae файл, что бы использовать в других редакторах 3D", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Отображение UV-карты в виде наложения в режиме рисования", "action.bake_animation_into_model": "Запечь анимацию в модель", "action.bake_animation_into_model.desc": "Запеките текущий отображаемый кадр анимации в модель. Применяется только вращение и положение, масштаб игнорируется.", diff --git a/lang/sv.json b/lang/sv.json index 92b36a3c..cc7f6a44 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Opens a new Blockbench window", "action.export_collada": "Export Collada Model (dae)", "action.export_collada.desc": "Export model and animations as dae file to use it in other 3D applications", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Display the UV map as an overlay in paint mode", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.", diff --git a/lang/uk.json b/lang/uk.json index 491a4aec..b4fa395c 100644 --- a/lang/uk.json +++ b/lang/uk.json @@ -1405,7 +1405,7 @@ "action.new_window.desc": "Відкриває нове вікно Blockbench", "action.export_collada": "Експортувати модель Collada (DAE)", "action.export_collada.desc": "Експортувати модель і анімацію як файл DAE, щоб використовувати його в інших 3D-застосунках", - "action.paint_mode_uv_overlay": "UV Show All", + "action.paint_mode_uv_overlay": "UV Show in Paint Mode", "action.paint_mode_uv_overlay.desc": "Показати мапу UV як накладання в режимі малювання", "action.bake_animation_into_model": "Bake Animation into Model", "action.bake_animation_into_model.desc": "Bake the currently displayed animation frame into the model. Only applies rotation and position, scale is ignored.",