mirror of
https://github.com/JannisX11/blockbench.git
synced 2024-11-27 04:21:46 +08:00
Fix fontawesome link
Improve vertex merging with new options Fix #1094 Remove blank faces breaks rendering Implement #1081 Feedback on merge verticies by distance
This commit is contained in:
parent
e6ce9eac47
commit
8e977958ec
@ -4,10 +4,10 @@
|
||||
"text_color": "var(--color-bright_ui_text)",
|
||||
"graphic": {
|
||||
"type": "image",
|
||||
"source": "./content/splash_art.png?39",
|
||||
"source": "./content/splash_art.png?40",
|
||||
"width": 1000,
|
||||
"aspect_ratio": "21/10",
|
||||
"description": "Splash Art Placeholder by [DerBauersSohn](https://twitter.com/DerBauersSohn__)"
|
||||
"description": "Splash Art by [Wackyblocks](https://twitter.com/wackyblocks)"
|
||||
},
|
||||
"layout": "vertical",
|
||||
"text": [
|
||||
@ -23,7 +23,7 @@
|
||||
{
|
||||
"image": "./content/poly_mesh.png",
|
||||
"title": "Poly Mesh Editing!",
|
||||
"text": "Blockbench now supports poly mesh editing, which let's you create any shapes in generic models."
|
||||
"text": "Blockbench now supports poly mesh editing, which let's you create and modify any type of shape in generic models."
|
||||
},
|
||||
{
|
||||
"image": "./content/theme_browser.png",
|
||||
|
@ -305,7 +305,7 @@ BARS.defineActions(() => {
|
||||
<p><b>${tl('about.website')}</b> <a class="open-in-browser" href="https://blockbench.net">blockbench.net</a></p>
|
||||
<p><b>${tl('about.repository')}</b> <a class="open-in-browser" href="https://github.com/JannisX11/blockbench">github.com/JannisX11/blockbench</a></p>
|
||||
<p>${tl('about.vertex_snap')}</p>
|
||||
<p><b>${tl('about.icons')}</b> <a href="https://material.io/icons/" class="open-in-browser">material.io/icons</a> & <a href="https://fontawesome.io/icons/" class="open-in-browser">fontawesome</a></p>
|
||||
<p><b>${tl('about.icons')}</b> <a href="https://material.io/icons/" class="open-in-browser">material.io/icons</a> & <a href="https://fontawesome.com/icons/" class="open-in-browser">fontawesome</a></p>
|
||||
<p><b>${tl('about.libraries')}</b>
|
||||
<a class="open-in-browser" href="https://electronjs.org">Electron</a>,
|
||||
<a class="open-in-browser" href="https://vuejs.org">Vue</a>,
|
||||
|
@ -629,7 +629,6 @@ const MenuBar = {
|
||||
'create_face',
|
||||
'invert_face',
|
||||
'merge_vertices',
|
||||
'merge_vertices_by_distance',
|
||||
'dissolve_edges',
|
||||
'split_mesh',
|
||||
'merge_meshes',
|
||||
|
@ -237,6 +237,7 @@ const Settings = {
|
||||
updateCubeHighlights();
|
||||
}});
|
||||
new Setting('deactivate_size_limit',{category: 'edit', value: false});
|
||||
new Setting('vertex_merge_distance',{category: 'edit', value: 0.1, step: 0.01, type: 'number'});
|
||||
|
||||
//Grid
|
||||
new Setting('base_grid', {category: 'grid', value: true,});
|
||||
|
@ -1890,17 +1890,40 @@ BARS.defineActions(function() {
|
||||
Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
|
||||
}
|
||||
})
|
||||
new Action('merge_vertices', {
|
||||
icon: 'close_fullscreen',
|
||||
category: 'edit',
|
||||
keybind: new Keybind({key: 'm', shift: true}),
|
||||
condition: {modes: ['edit'], features: ['meshes'], method: () => (Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length > 1)},
|
||||
click() {
|
||||
Undo.initEdit({elements: Mesh.selected});
|
||||
Mesh.selected.forEach(mesh => {
|
||||
let selected_vertices = mesh.getSelectedVertices();
|
||||
if (selected_vertices.length < 2) return;
|
||||
function mergeVertices(by_distance, in_center) {
|
||||
let found = 0, result = 0;
|
||||
Undo.initEdit({elements: Mesh.selected});
|
||||
Mesh.selected.forEach(mesh => {
|
||||
let selected_vertices = mesh.getSelectedVertices();
|
||||
if (selected_vertices.length < 2) return;
|
||||
|
||||
if (!by_distance) {
|
||||
let first_vertex = selected_vertices[0];
|
||||
if (in_center) {
|
||||
let center = [0, 0, 0];
|
||||
selected_vertices.forEach(vkey => {
|
||||
center.V3_add(mesh.vertices[vkey]);
|
||||
})
|
||||
center.V3_divide(selected_vertices.length);
|
||||
mesh.vertices[first_vertex].V3_set(center);
|
||||
|
||||
for (let fkey in mesh.faces) {
|
||||
let face = mesh.faces[fkey];
|
||||
let matches = selected_vertices.filter(vkey => face.vertices.includes(vkey));
|
||||
if (matches.length < 2) continue;
|
||||
let center = [0, 0];
|
||||
matches.forEach(vkey => {
|
||||
center[0] += face.uv[vkey][0];
|
||||
center[1] += face.uv[vkey][1];
|
||||
})
|
||||
center[0] /= matches.length;
|
||||
center[1] /= matches.length;
|
||||
matches.forEach(vkey => {
|
||||
face.uv[vkey][0] = center[0];
|
||||
face.uv[vkey][1] = center[1];
|
||||
})
|
||||
}
|
||||
}
|
||||
selected_vertices.forEach(vkey => {
|
||||
if (vkey == first_vertex) return;
|
||||
for (let fkey in mesh.faces) {
|
||||
@ -1925,18 +1948,8 @@ BARS.defineActions(function() {
|
||||
})
|
||||
selected_vertices.splice(1, selected_vertices.length);
|
||||
|
||||
})
|
||||
Undo.finishEdit('Merge vertices')
|
||||
Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
|
||||
}
|
||||
})
|
||||
new Action('merge_vertices_by_distance', {
|
||||
icon: 'close_fullscreen',
|
||||
category: 'edit',
|
||||
condition: {modes: ['edit'], features: ['meshes'], method: () => (Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length > 1)},
|
||||
click() {
|
||||
Undo.initEdit({elements: Mesh.selected});
|
||||
Mesh.selected.forEach(mesh => {
|
||||
} else {
|
||||
|
||||
let selected_vertices = mesh.getSelectedVertices().slice();
|
||||
if (selected_vertices.length < 2) return;
|
||||
let groups = {};
|
||||
@ -1948,7 +1961,7 @@ BARS.defineActions(function() {
|
||||
let vkey2 = selected_vertices[j];
|
||||
let vector1 = mesh.vertices[vkey1];
|
||||
let vector2 = mesh.vertices[vkey2];
|
||||
if (Math.sqrt(Math.pow(vector2[0] - vector1[0], 2) + Math.pow(vector2[1] - vector1[1], 2) + Math.pow(vector2[2] - vector1[2], 2)) < 0.1) {
|
||||
if (Math.sqrt(Math.pow(vector2[0] - vector1[0], 2) + Math.pow(vector2[1] - vector1[1], 2) + Math.pow(vector2[2] - vector1[2], 2)) < settings.vertex_merge_distance.value) {
|
||||
if (!groups[vkey1]) groups[vkey1] = [];
|
||||
groups[vkey1].push(vkey2);
|
||||
}
|
||||
@ -1964,7 +1977,34 @@ BARS.defineActions(function() {
|
||||
|
||||
let current_selected_vertices = mesh.getSelectedVertices();
|
||||
for (let first_vertex in groups) {
|
||||
groups[first_vertex].forEach(vkey => {
|
||||
let group = groups[first_vertex];
|
||||
if (in_center) {
|
||||
let group_all = [first_vertex, ...group];
|
||||
let center = [0, 0, 0];
|
||||
group_all.forEach(vkey => {
|
||||
center.V3_add(mesh.vertices[vkey]);
|
||||
})
|
||||
center.V3_divide(group_all.length);
|
||||
mesh.vertices[first_vertex].V3_set(center);
|
||||
|
||||
for (let fkey in mesh.faces) {
|
||||
let face = mesh.faces[fkey];
|
||||
let matches = group_all.filter(vkey => face.vertices.includes(vkey));
|
||||
if (matches.length < 2) continue;
|
||||
let center = [0, 0];
|
||||
matches.forEach(vkey => {
|
||||
center[0] += face.uv[vkey][0];
|
||||
center[1] += face.uv[vkey][1];
|
||||
})
|
||||
center[0] /= matches.length;
|
||||
center[1] /= matches.length;
|
||||
matches.forEach(vkey => {
|
||||
face.uv[vkey][0] = center[0];
|
||||
face.uv[vkey][1] = center[1];
|
||||
})
|
||||
}
|
||||
}
|
||||
group.forEach(vkey => {
|
||||
for (let fkey in mesh.faces) {
|
||||
let face = mesh.faces[fkey];
|
||||
let index = face.vertices.indexOf(vkey);
|
||||
@ -1983,14 +2023,55 @@ BARS.defineActions(function() {
|
||||
delete face.uv[vkey];
|
||||
}
|
||||
}
|
||||
found++;
|
||||
delete mesh.vertices[vkey];
|
||||
current_selected_vertices.remove(vkey);
|
||||
})
|
||||
found++;
|
||||
result++;
|
||||
}
|
||||
})
|
||||
Undo.finishEdit('Merge vertices by distance')
|
||||
Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
|
||||
}
|
||||
})
|
||||
Undo.finishEdit('Merge vertices')
|
||||
Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true})
|
||||
if (by_distance) {
|
||||
Blockbench.showQuickMessage(tl('message.merged_vertices', [found, result]), 2000);
|
||||
}
|
||||
}
|
||||
new Action('merge_vertices', {
|
||||
icon: 'close_fullscreen',
|
||||
category: 'edit',
|
||||
keybind: new Keybind({key: 'm', shift: true}),
|
||||
condition: {modes: ['edit'], features: ['meshes'], method: () => (Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length > 1)},
|
||||
click() {
|
||||
new Menu(this.children).open('mouse');
|
||||
},
|
||||
children: [
|
||||
{
|
||||
id: 'merge_all',
|
||||
name: 'action.merge_vertices.merge_all',
|
||||
icon: 'north_east',
|
||||
click() {mergeVertices(false, false);}
|
||||
},
|
||||
{
|
||||
id: 'merge_all_in_center',
|
||||
name: 'action.merge_vertices.merge_all_in_center',
|
||||
icon: 'close_fullscreen',
|
||||
click() {mergeVertices(false, true);}
|
||||
},
|
||||
{
|
||||
id: 'merge_by_distance',
|
||||
name: 'action.merge_vertices.merge_by_distance',
|
||||
icon: 'expand_less',
|
||||
click() {mergeVertices(true, false);}
|
||||
},
|
||||
{
|
||||
id: 'merge_by_distance_in_center',
|
||||
name: 'action.merge_vertices.merge_by_distance_in_center',
|
||||
icon: 'unfold_less',
|
||||
click() {mergeVertices(true, true);}
|
||||
}
|
||||
]
|
||||
})
|
||||
new Action('merge_meshes', {
|
||||
icon: 'upload',
|
||||
|
@ -605,7 +605,7 @@ const Canvas = {
|
||||
var side = Canvas.getRenderSide();
|
||||
ModelProject.all.forEach(project => {
|
||||
project.textures.forEach(function(t) {
|
||||
var mat = Project.materials[t.uuid]
|
||||
var mat = project.materials[t.uuid]
|
||||
if (mat) {
|
||||
mat.side = side
|
||||
}
|
||||
|
@ -153,7 +153,6 @@ class Texture {
|
||||
var size_control = {};
|
||||
|
||||
this.img.onload = function() {
|
||||
if (!this.src || Texture.all.indexOf(scope) == -1) return;
|
||||
this.tex.needsUpdate = true;
|
||||
let dimensions_changed = scope.width !== img.naturalWidth || scope.height !== img.naturalHeight;
|
||||
scope.width = img.naturalWidth;
|
||||
@ -163,59 +162,64 @@ class Texture {
|
||||
console.log('Successfully loaded '+scope.name+' from default pack')
|
||||
}
|
||||
|
||||
//Width / Animation
|
||||
if (img.naturalWidth !== img.naturalHeight && Format.id == 'java_block') {
|
||||
BARS.updateConditions()
|
||||
}
|
||||
let project = Texture.all.includes(scope) ? Project : ModelProject.all.find(project => project.textures.includes(scope));
|
||||
if(!project) return;
|
||||
project.whenNextOpen(() => {
|
||||
|
||||
if (Project.box_uv && Format.single_texture && !scope.error) {
|
||||
|
||||
if (!scope.keep_size) {
|
||||
let pw = Project.texture_width;
|
||||
let ph = Project.texture_height;
|
||||
let nw = img.naturalWidth;
|
||||
let nh = img.naturalHeight;
|
||||
|
||||
//texture is unlike project
|
||||
var unlike = (pw != nw || ph != nh);
|
||||
//Resolution of this texture has changed
|
||||
var changed = size_control.old_width && (size_control.old_width != nw || size_control.old_height != nh);
|
||||
//Resolution could be a multiple of project size
|
||||
var multi = (
|
||||
(pw%nw == 0 || nw%pw == 0) &&
|
||||
(ph%nh == 0 || nh%ph == 0)
|
||||
)
|
||||
|
||||
if (unlike && changed && !multi) {
|
||||
Blockbench.showMessageBox({
|
||||
translateKey: 'update_res',
|
||||
icon: 'photo_size_select_small',
|
||||
buttons: [tl('message.update_res.update'), tl('dialog.cancel')],
|
||||
confirm: 0,
|
||||
cancel: 1
|
||||
}, function(result) {
|
||||
if (result === 0) {
|
||||
setProjectResolution(img.naturalWidth, img.naturalHeight)
|
||||
if (selected.length) {
|
||||
UVEditor.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
//Width / Animation
|
||||
if (img.naturalWidth !== img.naturalHeight && Format.id == 'java_block') {
|
||||
BARS.updateConditions()
|
||||
}
|
||||
delete scope.keep_size;
|
||||
size_control.old_width = img.naturalWidth
|
||||
size_control.old_height = img.naturalHeight
|
||||
}
|
||||
|
||||
if (dimensions_changed) {
|
||||
TextureAnimator.updateButton()
|
||||
Canvas.updateAllFaces(scope)
|
||||
}
|
||||
if (typeof scope.load_callback === 'function') {
|
||||
scope.load_callback(scope);
|
||||
delete scope.load_callback;
|
||||
}
|
||||
if (Project.box_uv && Format.single_texture && !scope.error) {
|
||||
|
||||
if (!scope.keep_size) {
|
||||
let pw = Project.texture_width;
|
||||
let ph = Project.texture_height;
|
||||
let nw = img.naturalWidth;
|
||||
let nh = img.naturalHeight;
|
||||
|
||||
//texture is unlike project
|
||||
var unlike = (pw != nw || ph != nh);
|
||||
//Resolution of this texture has changed
|
||||
var changed = size_control.old_width && (size_control.old_width != nw || size_control.old_height != nh);
|
||||
//Resolution could be a multiple of project size
|
||||
var multi = (
|
||||
(pw%nw == 0 || nw%pw == 0) &&
|
||||
(ph%nh == 0 || nh%ph == 0)
|
||||
)
|
||||
|
||||
if (unlike && changed && !multi) {
|
||||
Blockbench.showMessageBox({
|
||||
translateKey: 'update_res',
|
||||
icon: 'photo_size_select_small',
|
||||
buttons: [tl('message.update_res.update'), tl('dialog.cancel')],
|
||||
confirm: 0,
|
||||
cancel: 1
|
||||
}, function(result) {
|
||||
if (result === 0) {
|
||||
setProjectResolution(img.naturalWidth, img.naturalHeight)
|
||||
if (selected.length) {
|
||||
UVEditor.loadData()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
delete scope.keep_size;
|
||||
size_control.old_width = img.naturalWidth
|
||||
size_control.old_height = img.naturalHeight
|
||||
}
|
||||
|
||||
if (dimensions_changed) {
|
||||
TextureAnimator.updateButton()
|
||||
Canvas.updateAllFaces(scope)
|
||||
}
|
||||
if (typeof scope.load_callback === 'function') {
|
||||
scope.load_callback(scope);
|
||||
delete scope.load_callback;
|
||||
}
|
||||
})
|
||||
}
|
||||
this.img.onerror = function(error) {
|
||||
if (isApp &&
|
||||
@ -529,7 +533,7 @@ class Texture {
|
||||
if (Texture.all.includes(scope)) {
|
||||
scope.reloadTexture();
|
||||
} else {
|
||||
let project = ModelProject.find(project => project.textures.includes(scope));
|
||||
let project = ModelProject.all.find(project => project.textures.includes(scope));
|
||||
if (project) {
|
||||
project.whenNextOpen(() => {
|
||||
scope.reloadTexture();
|
||||
|
@ -608,7 +608,6 @@ const UVEditor = {
|
||||
if (!face.uv[vkey]) return;
|
||||
face.uv[vkey][axis] = (face.uv[vkey][axis] - start) * multiplier + start;
|
||||
if (isNaN(face.uv[vkey][axis])) face.uv[vkey][axis] = start;
|
||||
console.log(face.uv[vkey][axis]);
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1718,15 +1718,20 @@ BARS.defineActions(function() {
|
||||
unselectAll()
|
||||
arr.forEach(element => {
|
||||
var clear_count = 0;
|
||||
var original_face_count = Object.keys(element.faces).length
|
||||
for (var face in element.faces) {
|
||||
var face_tag = element.faces[face];
|
||||
if (face_tag.texture == false) {
|
||||
face_tag.texture = null
|
||||
if (element instanceof Cube) {
|
||||
face_tag.texture = null;
|
||||
} else {
|
||||
delete element.faces[face];
|
||||
}
|
||||
clear_count++;
|
||||
cleared_total++;
|
||||
}
|
||||
}
|
||||
if (clear_count == 6) {
|
||||
if (clear_count == original_face_count) {
|
||||
empty_elements.push(element);
|
||||
}
|
||||
})
|
||||
@ -1744,6 +1749,7 @@ BARS.defineActions(function() {
|
||||
empty_elements.forEach(element => {
|
||||
if (r == 0) {
|
||||
element.remove();
|
||||
elements.remove(element)
|
||||
} else {
|
||||
for (var face in element.faces) {
|
||||
element.faces[face].texture = false;
|
||||
@ -1751,11 +1757,11 @@ BARS.defineActions(function() {
|
||||
}
|
||||
})
|
||||
updateSelection();
|
||||
Canvas.updateAllFaces();
|
||||
Canvas.updateView({elements, element_aspects: {geometry: true, faces: true, uv: true}})
|
||||
Undo.finishEdit('Remove blank faces');
|
||||
})
|
||||
} else {
|
||||
Canvas.updateAllFaces();
|
||||
Canvas.updateView({elements, element_aspects: {geometry: true, faces: true, uv: true}})
|
||||
Undo.finishEdit('Remove blank faces');
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -75,7 +75,6 @@
|
||||
"loop_cut": {"key": 82, "ctrl": true},
|
||||
"dissolve_edges": {"key": 88, "ctrl": true},
|
||||
"merge_vertices": {"key": 77, "shift": true},
|
||||
"merge_vertices_by_distance": null,
|
||||
"merge_meshes": {"key": 77},
|
||||
"split_mesh": null,
|
||||
"import_obj": null,
|
||||
|
10
lang/en.json
10
lang/en.json
@ -268,6 +268,8 @@
|
||||
"message.small_face_dimensions.message": "The selection contains faces that are smaller than 1 unit in one direction. The Box UV mapping system considers any faces below that threshold as 0 pixels wide. The texture on those faces therefore may not work correctly.",
|
||||
"message.small_face_dimensions.face_uv": "The current format supports per-face UV maps which can handle small face dimensions. Go to \"File\" > \"Project...\" and change \"UV Mode\" to \"Per-face UV\".",
|
||||
|
||||
"message.merged_vertices": "Found and merged %0 vertices in %1 locations",
|
||||
|
||||
"message.import_palette.replace_palette": "Replace old palette",
|
||||
"message.import_palette.threshold": "Merge Threshold",
|
||||
|
||||
@ -635,6 +637,8 @@
|
||||
"settings.highlight_cubes.desc": "Highlight cubes when you hover over them or select them",
|
||||
"settings.deactivate_size_limit": "Deactivate Size Limit",
|
||||
"settings.deactivate_size_limit.desc": "Deactivate the size limit for specific model formats. WARNING: This can cause invalid models.",
|
||||
"settings.vertex_merge_distance": "Vertex Merge Distance",
|
||||
"settings.vertex_merge_distance.desc": "Distance within which vertices are merged using merging by distance",
|
||||
|
||||
"settings.autouv": "Auto UV",
|
||||
"settings.autouv.desc": "Enable Auto UV by default",
|
||||
@ -1048,8 +1052,10 @@
|
||||
"action.split_mesh.desc": "Split the selected faces of the mesh into a new mesh",
|
||||
"action.merge_vertices": "Merge Vertices",
|
||||
"action.merge_vertices.desc": "Merge the selected vertices into the position of the first selected verted",
|
||||
"action.merge_vertices_by_distance": "Merge Vertices by Distance",
|
||||
"action.merge_vertices_by_distance.desc": "Merge vertices that are close in proximity to each other",
|
||||
"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",
|
||||
"action.merge_vertices.merge_by_distance_in_center": "Merge by Distance in Center",
|
||||
"action.merge_meshes": "Merge Meshes",
|
||||
"action.merge_meshes.desc": "Merge multiple meshes into one",
|
||||
|
||||
|
12010
lib/vue.min.js
vendored
12010
lib/vue.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user