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:
JannisX11 2021-10-14 21:36:57 +02:00
parent e6ce9eac47
commit 8e977958ec
13 changed files with 190 additions and 12103 deletions

View File

@ -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",

View File

@ -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> &amp; <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> &amp; <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>,

View File

@ -629,7 +629,6 @@ const MenuBar = {
'create_face',
'invert_face',
'merge_vertices',
'merge_vertices_by_distance',
'dissolve_edges',
'split_mesh',
'merge_meshes',

View File

@ -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,});

View File

@ -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',

View File

@ -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
}

View File

@ -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();

View File

@ -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]);
})
})
})

View File

@ -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

View File

@ -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,

View File

@ -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

File diff suppressed because one or more lines are too long