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
This commit is contained in:
parent
8aa0a48034
commit
2c98c43754
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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',
|
||||
|
@ -1135,7 +1135,7 @@ Interface.definePanels(function() {
|
||||
'<i v-else class="outliner_opener_placeholder"></i>' +
|
||||
//Main
|
||||
'<i :class="node.icon + ((settings.outliner_colors.value && node.color >= 0) ? \' ec_\'+node.color : \'\')" v-on:dblclick.stop="if (node.children && node.children.length) {node.isOpen = !node.isOpen;}"></i>' +
|
||||
'<input type="text" class="cube_name tab_target" v-model="node.name" disabled>' +
|
||||
'<input type="text" class="cube_name tab_target" :class="{locked: node.locked}" v-model="node.name" disabled>' +
|
||||
|
||||
|
||||
`<i v-for="btn in node.buttons"
|
||||
|
@ -389,6 +389,7 @@ class Preview {
|
||||
let index = intersect.faceIndex;
|
||||
for (let key in element.faces) {
|
||||
let {vertices} = element.faces[key];
|
||||
if (vertices.length < 3) continue;
|
||||
|
||||
if (index == 0 || (index == 1 && vertices.length == 4)) {
|
||||
face = key;
|
||||
|
@ -524,6 +524,7 @@
|
||||
this.hoverAxis = null;
|
||||
this.direction = true;
|
||||
this.last_valid_position = new THREE.Vector3();
|
||||
this.rotation_selection = new THREE.Euler();
|
||||
|
||||
this.firstLocation = [0,0,0]
|
||||
|
||||
@ -674,6 +675,11 @@
|
||||
} else {
|
||||
object.getWorldQuaternion(this.rotation)
|
||||
}
|
||||
if (this.rotation_selection) {
|
||||
let q = Reusable.quat1.setFromEuler(this.rotation_selection);
|
||||
this.quaternion.multiply(q);
|
||||
worldRotation.setFromQuaternion(this.quaternion);
|
||||
}
|
||||
|
||||
} else {
|
||||
worldRotation.set(0, 0, 0);
|
||||
@ -818,6 +824,7 @@
|
||||
}
|
||||
this.center = function() {
|
||||
delete Transformer.rotation_ref;
|
||||
Transformer.rotation_selection.set(0, 0, 0);
|
||||
if (Modes.edit || Toolbox.selected.id == 'pivot_tool') {
|
||||
if (Transformer.visible) {
|
||||
var rotation_tool = Toolbox.selected.id === 'rotate_tool' || Toolbox.selected.id === 'pivot_tool'
|
||||
@ -855,11 +862,15 @@
|
||||
|
||||
let space = Transformer.getTransformSpace();
|
||||
//Rotation
|
||||
if (space === 2 || Toolbox.selected.id == 'resize_tool') {
|
||||
if (space >= 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;
|
||||
|
@ -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() {
|
||||
|
||||
<template v-if="element.type == 'mesh'">
|
||||
<div class="mesh_uv_face"
|
||||
v-for="(face, key) in element.faces" :key="key"
|
||||
v-for="(face, key) in filterMeshFaces(element.faces)" :key="key"
|
||||
v-if="face.vertices.length > 2 && face.getTexture() == texture"
|
||||
:class="{selected: selected_faces.includes(key)}"
|
||||
@mousedown.prevent="dragFace(key, $event)"
|
||||
|
@ -590,10 +590,12 @@ function moveElementsInSpace(difference, axis) {
|
||||
let space = Transformer.getTransformSpace()
|
||||
let group = Format.bone_rig && Group.selected && Group.selected.matchesSelection() && Group.selected;
|
||||
var group_m;
|
||||
let quaternion = new THREE.Quaternion();
|
||||
let vector = new THREE.Vector3();
|
||||
|
||||
if (group) {
|
||||
if (space === 0) {
|
||||
group_m = new THREE.Vector3();
|
||||
group_m = vector.set(0, 0, 0);
|
||||
group_m[getAxisLetter(axis)] = difference;
|
||||
|
||||
var rotation = new THREE.Quaternion();
|
||||
@ -626,15 +628,20 @@ function moveElementsInSpace(difference, axis) {
|
||||
|
||||
if (!group_m && el instanceof Mesh && Project.selected_vertices[el.uuid] && Project.selected_vertices[el.uuid].length > 0) {
|
||||
|
||||
let quaternion = new THREE.Quaternion();
|
||||
|
||||
let selection_rotation = space == 3 && el.getSelectionRotation();
|
||||
Project.selected_vertices[el.uuid].forEach(key => {
|
||||
|
||||
if (space == 2) {
|
||||
el.vertices[key][axis] += difference;
|
||||
|
||||
} else if (space == 3) {
|
||||
let m = vector.set(0, 0, 0);
|
||||
m[getAxisLetter(axis)] = difference;
|
||||
m.applyEuler(selection_rotation);
|
||||
el.vertices[key].V3_add(m.x, m.y, m.z);
|
||||
|
||||
} else {
|
||||
let m = new THREE.Vector3();
|
||||
let m = vector.set(0, 0, 0);
|
||||
m[getAxisLetter(axis)] = difference;
|
||||
m.applyQuaternion(el.mesh.getWorldQuaternion(quaternion).invert());
|
||||
el.vertices[key].V3_add(m.x, m.y, m.z);
|
||||
@ -646,7 +653,7 @@ function moveElementsInSpace(difference, axis) {
|
||||
|
||||
if (space == 2 && !group_m) {
|
||||
if (el instanceof Locator) {
|
||||
let m = new THREE.Vector3();
|
||||
let m = vector.set(0, 0, 0);
|
||||
m[getAxisLetter(axis)] = difference;
|
||||
m.applyQuaternion(el.mesh.quaternion);
|
||||
el.from.V3_add(m.x, m.y, m.z);
|
||||
@ -668,7 +675,7 @@ function moveElementsInSpace(difference, axis) {
|
||||
if (group_m) {
|
||||
var m = group_m
|
||||
} else {
|
||||
var m = new THREE.Vector3();
|
||||
var m = vector.set(0, 0, 0);
|
||||
m[getAxisLetter(axis)] = difference;
|
||||
|
||||
let parent = el.parent;
|
||||
|
@ -1026,6 +1026,8 @@
|
||||
"action.invert_face.desc": "Invert the selected faces to make them face the opposite direction",
|
||||
"action.extrude_mesh_selection": "Extrude Selection",
|
||||
"action.extrude_mesh_selection.desc": "Extrude the selected parts of the mesh",
|
||||
"action.inset_mesh_selection": "Inset Selection",
|
||||
"action.inset_mesh_selection.desc": "Inset the selected parts of the mesh",
|
||||
"action.loop_cut": "Loop Cut",
|
||||
"action.loop_cut.desc": "Split the mesh in a loop across the selected line",
|
||||
"action.split_mesh": "Split Mesh",
|
||||
|
Loading…
x
Reference in New Issue
Block a user