Skip to content

Commit

Permalink
Add Merge Vertices tool
Browse files Browse the repository at this point in the history
Fix issue with vertex snapping on cubes
Fix skin models opening two tabs
Fix issue with closing projects
Fix #1048 Rescaling with face tool selection creates flickering
Fix ##1051 Arrow keys do not work with meshes (and other non-cubes)
Fix issues with null objects
Fix area select in UV editor selecting faces twice
Finish implementing theme borders option
  • Loading branch information
JannisX11 committed Sep 17, 2021
1 parent ee5fdb2 commit fb8d8ba
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 114 deletions.
52 changes: 52 additions & 0 deletions css/general.css
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,55 @@
flex-shrink: 0;
}

/* Theme Borders */
body.theme_borders .contextMenu,
body.theme_borders dialog,
body.theme_borders .dialog_close_button,
body.theme_borders #start_screen content,
body.theme_borders #quick_message_box,
body.theme_borders action_selector > #action_selector_list
{
border: 1px solid var(--color-border);
}
body.theme_borders #start_screen section {
border-bottom: 1px solid var(--color-border);
}
body.theme_borders .panel {
margin-top: -1px;
border-top: 01px solid var(--color-border);
}
body.theme_borders #right_bar {
border-left: 1px solid var(--color-border);
}
body.theme_borders #left_bar {
border-right: 1px solid var(--color-border);
}
body.theme_borders .preview .preview_menu {
right: 0;
}
body.theme_borders .dialog_sidebar {
border-right: 1px solid var(--color-border);
}
body.theme_borders .dialog_handle {
border-bottom: 1px solid var(--color-border);
}
body.theme_borders .dialog_close_button {
right: -1px;
top: -1px;
height: 31px;
}
body.theme_borders li.animation_file {
border-top: 1px solid var(--color-border);
}
body.theme_borders #main_toolbar, body.theme_borders #tab_bar {
border-bottom: 1px solid var(--color-border);
}
body.theme_borders #status_bar {
border-top: 1px solid var(--color-border);
}
body.theme_borders .contextMenu li.menu_separator {
background-color: var(--color-border);
height: 1px;
padding: 0;
opacity: 1;
}
20 changes: 11 additions & 9 deletions js/blockbench.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,17 @@ function updateSelection(options = {}) {
} else if ((!selected.includes(obj) || obj.locked) && obj.selected) {
obj.unselect()
}
if (Project.selected_vertices[obj.uuid]) {
Project.selected_vertices[obj.uuid].forEachReverse(vkey => {
if (vkey in obj.vertices == false) {
Project.selected_vertices[obj.uuid].remove(vkey);
}
})
}
if (Project.selected_vertices[obj.uuid] && (Project.selected_vertices[obj.uuid].length == 0 || !obj.selected)) {
delete Project.selected_vertices[obj.uuid];
if (obj instanceof Mesh) {
if (Project.selected_vertices[obj.uuid]) {
Project.selected_vertices[obj.uuid].forEachReverse(vkey => {
if (vkey in obj.vertices == false) {
Project.selected_vertices[obj.uuid].remove(vkey);
}
})
}
if (Project.selected_vertices[obj.uuid] && (Project.selected_vertices[obj.uuid].length == 0 || !obj.selected)) {
delete Project.selected_vertices[obj.uuid];
}
}
})
if (Group.selected && Group.selected.locked) Group.selected.unselect()
Expand Down
20 changes: 18 additions & 2 deletions js/interface/themes.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ const CustomTheme = {
CustomTheme.updateSettings();
saveChanges();
},
'data.borders'() {
CustomTheme.updateSettings();
saveChanges();
},
'data.css'() {
CustomTheme.updateSettings();
saveChanges();
Expand Down Expand Up @@ -254,8 +258,12 @@ const CustomTheme = {
<input @input="customizeTheme($event)" style="font-family: var(--font-headline)" type="text" class="half dark_bordered" id="layout_font_headline" v-model="data.headline_font">
</div>
<div class="dialog_bar">
<label class="name_space_left" for="layout_font_cpde">${tl('layout.font.code')}</label>
<input @input="customizeTheme($event)" style="font-family: var(--font-code)" type="text" class="half dark_bordered" id="layout_font_cpde" v-model="data.code_font">
<label class="name_space_left" for="layout_font_code">${tl('layout.font.code')}</label>
<input @input="customizeTheme($event)" style="font-family: var(--font-code)" type="text" class="half dark_bordered" id="layout_font_code" v-model="data.code_font">
</div>
<div class="dialog_bar">
<label class="name_space_left" for="layout_borders">${tl('layout.borders')}</label>
<input @input="customizeTheme($event)" type="checkbox" id="layout_borders" v-model="data.borders">
</div>
</div>
Expand Down Expand Up @@ -361,10 +369,18 @@ const CustomTheme = {
document.body.style.setProperty('--font-custom-main', CustomTheme.data.main_font);
document.body.style.setProperty('--font-custom-headline', CustomTheme.data.headline_font);
document.body.style.setProperty('--font-custom-code', CustomTheme.data.code_font);
document.body.classList.toggle('theme_borders', !!CustomTheme.data.borders);
$('style#theme_css').text(CustomTheme.data.css);
},
loadTheme(theme) {
var app = CustomTheme.data;
app.id = '';
app.name = '';
app.author = '';
app.main_font = '';
app.headline_font = '';
app.code_font = '';
app.borders = false;
Merge.string(app, theme, 'id')
Merge.string(app, theme, 'name')
Merge.string(app, theme, 'author')
Expand Down
6 changes: 2 additions & 4 deletions js/io/formats/skin.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,8 @@ const format = new ModelFormat({
}
})
format.new = function() {
if (newProject(this)) {
skin_dialog.show();
return true;
}
skin_dialog.show();
return true;
}

function generateTemplate(width = 64, height = 64, cubes, name = 'name', eyes, layer_template) {
Expand Down
12 changes: 8 additions & 4 deletions js/io/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,14 @@ class ModelProject {
}
async close(force) {
let last_selected = Project;
this.select();
await new Promise(resolve => setTimeout(resolve, 50));
try {
this.select();
} catch (err) {
console.error(err);
}

function saveWarning() {
async function saveWarning() {
await new Promise(resolve => setTimeout(resolve, 4));
if (Project.saved) {
return true;
} else {
Expand All @@ -295,7 +299,7 @@ class ModelProject {
}
}

if (force || saveWarning()) {
if (force || await saveWarning()) {
if (isApp) await updateRecentProjectThumbnail();

Blockbench.dispatchEvent('close_project');
Expand Down
42 changes: 42 additions & 0 deletions js/outliner/mesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ class Mesh extends OutlinerElement {
'loop_cut',
'create_face',
'invert_face',
'merge_vertices',
'_',
'split_mesh',
'merge_meshes',
Expand Down Expand Up @@ -622,6 +623,7 @@ new NodePreviewController(Mesh, {

mesh.vertex_points.geometry.computeBoundingSphere();
mesh.outline.geometry.computeBoundingSphere();
updateCubeHighlights()
},
updateFaces(element) {
let {mesh} = element;
Expand Down Expand Up @@ -1593,6 +1595,46 @@ 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 && Format.meshes && 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;
let first_vertex = selected_vertices[0];
selected_vertices.forEach(vkey => {
if (vkey == first_vertex) return;
for (let fkey in mesh.faces) {
let face = mesh.faces[fkey];
let index = face.vertices.indexOf(vkey);
if (index === -1) continue;

if (face.vertices.includes(first_vertex)) {
face.vertices.remove(vkey);
delete face.uv[vkey];
if (face.vertices.length < 2) {
delete mesh.faces[fkey];
}
} else {
let uv = face.uv[vkey];
face.vertices.splice(index, 1, first_vertex);
face.uv[first_vertex] = uv;
delete face.uv[vkey];
}
}
delete mesh.vertices[vkey];
})
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_meshes', {
icon: 'upload',
category: 'edit',
Expand Down
5 changes: 5 additions & 0 deletions js/outliner/null_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class NullObject extends OutlinerElement {
this.extend(data);
}
}
get origin() {
return this.from;
}
extend(object) {
for (var key in NullObject.properties) {
NullObject.properties[key].merge(this, object)
Expand Down Expand Up @@ -103,6 +106,8 @@ class NullObject extends OutlinerElement {

OutlinerElement.registerType(NullObject, 'null_object');

new NodePreviewController(NullObject)

BARS.defineActions(function() {
new Action('add_null_object', {
icon: 'far.fa-circle',
Expand Down
4 changes: 2 additions & 2 deletions js/preview/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -752,8 +752,8 @@ class Preview {

}

let face_test = start_face.getAdjacentFace(0);
let index = (face_test && face_test.face.isSelected()) ? 1 : 0;
let face_test = start_face.getAdjacentFace(1);
let index = (face_test && face_test.face.isSelected()) ? 2 : 1;
selectFace(start_face, index);

if (!(event.ctrlOrCmd || Pressing.overrides.ctrl || event.shiftKey || Pressing.overrides.shift)) {
Expand Down
93 changes: 11 additions & 82 deletions js/texturing/uv.js
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,15 @@ const UVEditor = {
this.vue.selected_faces.empty();
}
},
moveSelection(offset, event) {
Undo.initEdit({elements: UVEditor.getMappableElements()})
let step = canvasGridSize(event.shiftKey || Pressing.overrides.shift, event.ctrlOrCmd || Pressing.overrides.ctrl) / UVEditor.grid;
UVEditor.slidePos((old_val) => {
let sign = offset[offset[0] ? 0 : 1];
return old_val + step * sign;
}, offset[0] ? 0 : 1);
Undo.finishEdit('Move UV')
},
disableAutoUV() {
this.forCubes(obj => {
obj.autouv = 0
Expand Down Expand Up @@ -1678,7 +1687,7 @@ Interface.definePanels(function() {
for (let fkey in element.faces) {
let face_rect = getRectangle(...element.faces[fkey].uv);
if (doRectanglesOverlap(rect, face_rect)) {
scope.selected_faces.push(fkey);
scope.selected_faces.safePush(fkey);
}
}
} else if (element instanceof Cube) {
Expand All @@ -1693,94 +1702,14 @@ Interface.definePanels(function() {
i++;
let vkey2 = vertices[i] || vertices[0];
if (lineIntersectsReactangle(face.uv[vkey], face.uv[vkey2], [rect.ax, rect.ay], [rect.bx, rect.by])) {
scope.selected_faces.push(fkey);
scope.selected_faces.safePush(fkey);
break;
}
}
}
}
}
})




/*
<template v-if="mode == 'uv'" v-for="element in (showing_overlays ? all_mappable_elements : mappable_elements)" :key="element.uuid">
<template v-if="element.type == 'cube' && !box_uv">
<div class="cube_uv_face"
v-for="(face, key) in element.faces" :key="key"
v-if="face.getTexture() == texture || texture == 0"
:title="face_names[key]"
:class="{selected: selected_faces.includes(key), unselected: showing_overlays && !mappable_elements.includes(element)}"
@mousedown.prevent="dragFace(key, $event)"
:style="{
left: toPixels(Math.min(face.uv[0], face.uv[2]), -1),
top: toPixels(Math.min(face.uv[1], face.uv[3]), -1),
'--width': toPixels(Math.abs(face.uv_size[0]), 2),
'--height': toPixels(Math.abs(face.uv_size[1]), 2),
}"
>
<template v-if="selected_faces.includes(key) && !(showing_overlays && !mappable_elements.includes(element))">
{{ face_names[key] || '' }}
<div class="uv_resize_side horizontal" @mousedown="resizeFace(key, $event, 0, -1)" style="width: var(--width)"></div>
<div class="uv_resize_side horizontal" @mousedown="resizeFace(key, $event, 0, 1)" style="top: var(--height); width: var(--width)"></div>
<div class="uv_resize_side vertical" @mousedown="resizeFace(key, $event, -1, 0)" style="height: var(--height)"></div>
<div class="uv_resize_side vertical" @mousedown="resizeFace(key, $event, 1, 0)" style="left: var(--width); height: var(--height)"></div>
<div class="uv_resize_corner uv_c_nw" @mousedown="resizeFace(key, $event, -1, -1)" style="left: 0; top: 0"></div>
<div class="uv_resize_corner uv_c_ne" @mousedown="resizeFace(key, $event, 1, -1)" style="left: var(--width); top: 0"></div>
<div class="uv_resize_corner uv_c_sw" @mousedown="resizeFace(key, $event, -1, 1)" style="left: 0; top: var(--height)"></div>
<div class="uv_resize_corner uv_c_se" @mousedown="resizeFace(key, $event, 1, 1)" style="left: var(--width); top: var(--height)"></div>
</template>
</div>
</template>
<div v-else-if="element.type == 'cube'" class="cube_box_uv"
@mousedown.prevent="dragFace(null, $event)"
@click.prevent="selectCube(element, $event)"
:class="{unselected: showing_overlays && !mappable_elements.includes(element)}"
:style="{left: toPixels(element.uv_offset[0]), top: toPixels(element.uv_offset[1])}"
>
<div class="uv_fill" :style="{left: '-1px', top: toPixels(element.size(2, true), -1), width: toPixels(element.size(2, true)*2 + element.size(0, true)*2, 2), height: toPixels(element.size(1, true), 2)}" />
<div class="uv_fill" :style="{left: toPixels(element.size(2, true), -1), top: '-1px', width: toPixels(element.size(0, true)*2, 2), height: toPixels(element.size(2, true), 2), borderBottom: 'none'}" />
<div :style="{left: toPixels(element.size(2, true), -1), top: '-1px', width: toPixels(element.size(0, true), 2), height: toPixels(element.size(2, true) + element.size(1, true), 2)}" />
<div :style="{left: toPixels(element.size(2, true)*2 + element.size(0, true), -1), top: toPixels(element.size(2, true), -1), width: toPixels(element.size(0, true), 2), height: toPixels(element.size(1, true), 2)}" />
</div>
<template v-if="element.type == 'mesh'">
<div class="mesh_uv_face"
v-for="(face, key) in element.faces" :key="key"
v-if="face.vertices.length > 2 && face.getTexture() == texture"
:class="{selected: selected_faces.includes(key)}"
@mousedown.prevent="dragFace(key, $event)"
:style="{
left: toPixels(getMeshFaceCorner(face, 0), -1),
top: toPixels(getMeshFaceCorner(face, 1), -1),
width: toPixels(getMeshFaceWidth(face, 0), 2),
height: toPixels(getMeshFaceWidth(face, 1), 2),
}"
>
<svg>
<polygon :points="getMeshFaceOutline(face)" />
</svg>
<template v-if="selected_faces.includes(key)">
<div class="uv_mesh_vertex" v-for="key in face.vertices"
:class="{selected: selected_vertices[element.uuid] && selected_vertices[element.uuid].includes(key)}"
@mousedown.prevent.stop="dragVertices(element, key, $event)"
:style="{left: toPixels( face.uv[key][0] - getMeshFaceCorner(face, 0) ), top: toPixels( face.uv[key][1] - getMeshFaceCorner(face, 1) )}"
></div>
</template>
</div>
</template>
</template>*/






}
function stop() {
removeEventListeners(document, 'mousemove touchmove', drag);
Expand Down
Loading

0 comments on commit fb8d8ba

Please sign in to comment.