From 2c98c43754c988c4e2a4a9642afc982069ec5297 Mon Sep 17 00:00:00 2001 From: JannisX11 Date: Thu, 16 Sep 2021 16:52:21 +0200 Subject: [PATCH] Add mesh inset action Implement normal transform space Turn name of locked elements gray in outliner Fix face selection issue Improve performance by hiding unselected faces on large meshes Fix issue where imported bbmodels would switch to generic model --- css/panels.css | 3 + js/io/formats/bbmodel.js | 4 +- js/io/project.js | 2 +- js/outliner/mesh.js | 121 ++++++++++++++++++++++++++++++++++++-- js/outliner/outliner.js | 2 +- js/preview/preview.js | 1 + js/preview/transformer.js | 13 +++- js/texturing/uv.js | 16 ++++- js/transform.js | 19 ++++-- lang/en.json | 2 + 10 files changed, 166 insertions(+), 17 deletions(-) diff --git a/css/panels.css b/css/panels.css index 38cf9d2..f638d84 100644 --- a/css/panels.css +++ b/css/panels.css @@ -286,6 +286,9 @@ .outliner_object > input.renaming { pointer-events: auto; } + .outliner_object input.cube_name.locked { + color: var(--color-subtle_text); + } i.outliner_toggle { font-size: 15px; } diff --git a/js/io/formats/bbmodel.js b/js/io/formats/bbmodel.js index a0c7bdc..6e2406c 100644 --- a/js/io/formats/bbmodel.js +++ b/js/io/formats/bbmodel.js @@ -61,8 +61,8 @@ var codec = new Codec('project', { }, load(model, file, add) { - - newProject(Formats[model.meta.type] || Formats.free); + console.log(model) + newProject(Formats[model.meta.model_format] || Formats.free); var name = pathToName(file.path, true); if (file.path && isApp && !file.no_file ) { Project.save_path = file.path; diff --git a/js/io/project.js b/js/io/project.js index d43dbd2..f3725b8 100644 --- a/js/io/project.js +++ b/js/io/project.js @@ -308,7 +308,7 @@ class ModelProject { delete ProjectData[this.uuid]; Project = 0; - if (last_selected !== this) { + if (last_selected && last_selected !== this) { last_selected.select(); } else if (ModelProject.all.length) { ModelProject.all[Math.clamp(index, 0, ModelProject.all.length-1)].select(); diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index 8618a8e..2e0ef4f 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -277,6 +277,16 @@ class Mesh extends OutlinerElement { } return faces; } + getSelectionRotation() { + let [face] = this.getSelectedFaces().map(fkey => this.faces[fkey]); + if (face) { + let normal = face.getNormal(true) + + var y = Math.atan2(normal[0], normal[2]); + var x = Math.atan2(normal[1], Math.sqrt(Math.pow(normal[0], 2) + Math.pow(normal[2], 2))); + return new THREE.Euler(-x, y, 0, 'YXZ'); + } + } transferOrigin(origin, update = true) { if (!this.mesh) return; var q = new THREE.Quaternion().copy(this.mesh.quaternion); @@ -423,6 +433,7 @@ class Mesh extends OutlinerElement { Mesh.prototype.needsUniqueName = false; Mesh.prototype.menu = new Menu([ 'extrude_mesh_selection', + 'inset_mesh_selection', 'loop_cut', 'create_face', 'invert_face', @@ -708,12 +719,13 @@ new NodePreviewController(Mesh, { let mesh = element.mesh; let white = new THREE.Color(0xffffff); + let selected_vertices = element.getSelectedVertices(); if (BarItems.selection_mode.value == 'vertex') { let colors = []; for (let key in element.vertices) { let color; - if (Project.selected_vertices[element.uuid] && Project.selected_vertices[element.uuid].includes(key)) { + if (selected_vertices.includes(key)) { color = white; } else { color = gizmo_colors.grid; @@ -729,7 +741,7 @@ new NodePreviewController(Mesh, { let color; if (!Modes.edit || BarItems.selection_mode.value == 'object') { color = gizmo_colors.outline; - } else if (Project.selected_vertices[element.uuid] && Project.selected_vertices[element.uuid].includes(key)) { + } else if (selected_vertices.includes(key)) { color = white; } else { color = gizmo_colors.grid; @@ -1133,7 +1145,7 @@ BARS.defineActions(function() { } }) Undo.finishEdit('Create mesh face') - Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}}) + Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true}) } }) new Action('convert_to_mesh', { @@ -1196,7 +1208,7 @@ BARS.defineActions(function() { new Action('invert_face', { icon: 'flip_to_back', category: 'edit', - keybind: new Keybind({key: 'i', shift: true}), + keybind: new Keybind({key: 'i', shift: true, ctrl: true}), condition: () => (Modes.edit && Format.meshes && Mesh.selected[0] && Mesh.selected[0].getSelectedFaces().length), click() { Undo.initEdit({elements: Mesh.selected}); @@ -1336,6 +1348,107 @@ BARS.defineActions(function() { Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true}) } }) + new Action('inset_mesh_selection', { + icon: 'fa-compress-arrows-alt', + category: 'edit', + keybind: new Keybind({key: 'i', shift: true}), + condition: () => (Modes.edit && Format.meshes && Mesh.selected[0] && Mesh.selected[0].getSelectedVertices().length), + click() { + Undo.initEdit({elements: Mesh.selected}); + Mesh.selected.forEach(mesh => { + let original_vertices = Project.selected_vertices[mesh.uuid].slice(); + let new_vertices; + let selected_faces = []; + let selected_face_keys = []; + for (let key in mesh.faces) { + let face = mesh.faces[key]; + if (face.isSelected()) { + selected_faces.push(face); + selected_face_keys.push(key); + } + } + + new_vertices = mesh.addVertices(...original_vertices.map(vkey => { + let vector = mesh.vertices[vkey].slice(); + affected_faces = selected_faces.filter(face => { + return face.vertices.includes(vkey) + }) + let inset = [0, 0, 0]; + if (affected_faces.length == 3 || affected_faces.length == 1) { + affected_faces.sort((a, b) => { + let ax = 0; + a.vertices.forEach(vkey => { + ax += affected_faces.filter(face => face.vertices.includes(vkey)).length; + }) + let bx = 0; + b.vertices.forEach(vkey => { + bx += affected_faces.filter(face => face.vertices.includes(vkey)).length; + }) + return bx - ax; + }) + affected_faces[0].vertices.forEach(vkey2 => { + inset.V3_add(mesh.vertices[vkey2]); + }) + inset.V3_divide(affected_faces[0].vertices.length); + vector.V3_add(inset).V3_divide(2); + } + if (affected_faces.length == 2) { + let vkey2 = affected_faces[0].vertices.find(_vkey => _vkey != vkey && affected_faces[1].vertices.includes(_vkey)); + + inset.V3_set(mesh.vertices[vkey2]).V3_multiply(1/4); + vector.V3_multiply(3/4); + vector.V3_add(inset); + } + + return vector; + })) + Project.selected_vertices[mesh.uuid].replace(new_vertices); + + // Move Faces + selected_faces.forEach(face => { + face.vertices.forEach((key, index) => { + face.vertices[index] = new_vertices[original_vertices.indexOf(key)]; + let uv = face.uv[key]; + delete face.uv[key]; + face.uv[face.vertices[index]] = uv; + }) + }) + + // Create extra quads on sides + let remaining_vertices = new_vertices.slice(); + selected_faces.forEach((face, face_index) => { + let vertices = face.getSortedVertices(); + vertices.forEach((a, i) => { + let b = vertices[i+1] || vertices[0]; + if (vertices.length == 2 && i) return; // Only create one quad when extruding line + if (selected_faces.find(f => f != face && f.vertices.includes(a) && f.vertices.includes(b))) return; + + let new_face = new MeshFace(mesh, mesh.faces[selected_face_keys[face_index]]).extend({ + vertices: [ + b, + a, + original_vertices[new_vertices.indexOf(a)], + original_vertices[new_vertices.indexOf(b)], + ] + }); + mesh.addFaces(new_face); + remaining_vertices.remove(a); + remaining_vertices.remove(b); + }) + + if (vertices.length == 2) delete mesh.faces[selected_face_keys[face_index]]; + }) + + remaining_vertices.forEach(a => { + let b = original_vertices[new_vertices.indexOf(a)]; + delete mesh.vertices[b]; + }) + + }) + Undo.finishEdit('Extrude mesh selection') + Canvas.updateView({elements: Mesh.selected, element_aspects: {geometry: true, uv: true, faces: true}, selection: true}) + } + }) new Action('loop_cut', { icon: 'carpenter', category: 'edit', diff --git a/js/outliner/outliner.js b/js/outliner/outliner.js index cb055fc..8b70b41 100644 --- a/js/outliner/outliner.js +++ b/js/outliner/outliner.js @@ -1135,7 +1135,7 @@ Interface.definePanels(function() { '' + //Main '' + - '' + + '' + `= 2 || Toolbox.selected.id == 'resize_tool') { Transformer.rotation_ref = Group.selected ? Group.selected.mesh : (selected[0] && selected[0].mesh); if (Toolbox.selected.id == 'rotate_tool' && Group.selected) { Transformer.rotation_ref = Group.selected.mesh; } + if (space === 3 && Mesh.selected[0]) { + let rotation = Mesh.selected[0].getSelectionRotation(); + if (rotation) Transformer.rotation_selection.copy(rotation); + } } else if (space instanceof Group) { Transformer.rotation_ref = space.mesh; diff --git a/js/texturing/uv.js b/js/texturing/uv.js index 661d7dc..3cd4ad2 100644 --- a/js/texturing/uv.js +++ b/js/texturing/uv.js @@ -2064,6 +2064,18 @@ Interface.definePanels(function() { }) return max - min; }, + filterMeshFaces(faces) { + let keys = Object.keys(faces); + if (keys.length > 800) { + let result = {}; + this.selected_faces.forEach(key => { + if (faces[key]) result[key] = faces[key]; + }) + return result; + } else { + return faces; + } + }, getBrushOutlineStyle() { if (Toolbox.selected.brushTool) { var pixel_size = this.inner_width / (this.texture ? this.texture.width : Project.texture_width); @@ -2107,7 +2119,7 @@ Interface.definePanels(function() { @mouseleave="if (mode == 'paint') mouse_coords.x = -1" class="checkerboard_target" ref="viewport" - v-show="!hidden" + v-if="!hidden" :style="{width: (width+8) + 'px', height: (height+8) + 'px', overflowX: (zoom > 1) ? 'scroll' : 'hidden', overflowY: (inner_height > height) ? 'scroll' : 'hidden'}" > @@ -2157,7 +2169,7 @@ Interface.definePanels(function() {