add mesh edge selection to allow creating faces
This commit is contained in:
parent
3541c76ccb
commit
4261d2eea9
8 changed files with 92 additions and 38 deletions
|
|
@ -7,6 +7,7 @@
|
|||
// dep: moto.broker
|
||||
// dep: moto.space
|
||||
// dep: mesh.util
|
||||
// dep: mesh.edges
|
||||
// dep: add.three
|
||||
// use: add.array
|
||||
gapp.register("mesh.api", [], (root, exports) => {
|
||||
|
|
@ -24,6 +25,10 @@ let selected = [];
|
|||
let tools = [];
|
||||
|
||||
const selection = {
|
||||
count() {
|
||||
return selected.length;
|
||||
},
|
||||
|
||||
// @returns {MeshObject[]} or all groups if not strict and no selection
|
||||
list(strict = false) {
|
||||
return selected.length || strict ? selected.slice() : groups.slice();
|
||||
|
|
@ -562,11 +567,11 @@ const tool = {
|
|||
return { id: m.id, matrix: m.matrix }
|
||||
}))
|
||||
.then(data => {
|
||||
api.selection.visible(false);
|
||||
let group = api.group.new([new mesh.model({
|
||||
file: `merged`,
|
||||
mesh: data
|
||||
})]).promote();
|
||||
api.selection.visible(false);
|
||||
api.selection.set([group]);
|
||||
api.log.emit('merge complete').unpin();
|
||||
});
|
||||
|
|
@ -582,11 +587,11 @@ const tool = {
|
|||
return { id: m.id, matrix: m.matrix }
|
||||
}))
|
||||
.then(data => {
|
||||
api.selection.visible(false);
|
||||
let group = api.group.new([new mesh.model({
|
||||
file: `union`,
|
||||
mesh: data
|
||||
})]).promote();
|
||||
api.selection.visible(false);
|
||||
api.selection.set([group]);
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
@ -607,11 +612,11 @@ const tool = {
|
|||
return { id: m.id, matrix: m.matrix }
|
||||
}))
|
||||
.then(data => {
|
||||
api.selection.visible(false);
|
||||
let group = api.group.new([new mesh.model({
|
||||
file: `diff`,
|
||||
mesh: data
|
||||
})]).promote();
|
||||
api.selection.visible(false);
|
||||
api.selection.set([group]);
|
||||
})
|
||||
.catch(error => {
|
||||
|
|
@ -857,6 +862,7 @@ const mode = {
|
|||
}
|
||||
$(`mode-${mode}`).classList.add('selected');
|
||||
api.mode.check();
|
||||
mesh.edges.end();
|
||||
},
|
||||
|
||||
get() {
|
||||
|
|
@ -909,6 +915,7 @@ const mode = {
|
|||
selection.clear();
|
||||
}
|
||||
mode.set(modes.edge);
|
||||
mesh.edges.start();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1091,6 +1098,8 @@ const api = exports({
|
|||
});
|
||||
|
||||
const { broker } = gapp;
|
||||
const call = broker.send;
|
||||
|
||||
// publish messages when api functions are called
|
||||
broker.wrapObject(selection, 'selection');
|
||||
broker.wrapObject(model, 'model');
|
||||
|
|
|
|||
|
|
@ -157,20 +157,21 @@ mesh.model = class MeshModel extends mesh.object {
|
|||
// translated into world coordinates (rebuilt from rotation matrix)
|
||||
// defaults to returning a new model in a new group
|
||||
// options to mirror, re-use a group, or update model in-place
|
||||
duplicate(opt = {}) {
|
||||
duplicate(opt = { select: true }) {
|
||||
return worker.model_duplicate({
|
||||
matrix: this.matrix,
|
||||
id: this.id,
|
||||
opt: { mirror: opt.mirror}
|
||||
}).then(data => {
|
||||
if (opt.append) data = [...data, ...opt.append].toFloat32();
|
||||
if (opt.reload) return this.reload(data);
|
||||
let model = new mesh.model({ file: `${this.file}`, mesh: data });
|
||||
let group = opt.group || mesh.api.group.new();
|
||||
group.add(model);
|
||||
group.setSelected();
|
||||
if (opt.mirror) {
|
||||
group.move(0, 0, group.bounds.dim.z);
|
||||
}
|
||||
model.wireframe(this.wireframe());
|
||||
model.normals(this.normals());
|
||||
if (opt.select) group.setSelected();
|
||||
if (opt.mirror) group.move(0, 0, group.bounds.dim.z);
|
||||
return model;
|
||||
});
|
||||
}
|
||||
|
|
@ -209,7 +210,6 @@ mesh.model = class MeshModel extends mesh.object {
|
|||
geo.setIndex(new BufferAttribute(indices, 1));
|
||||
geo = geo.toNonIndexed();
|
||||
}
|
||||
|
||||
let meh = this.mesh = new Mesh(geo, [
|
||||
this.mats.normal,
|
||||
this.mats.face
|
||||
|
|
|
|||
|
|
@ -1,25 +1,32 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
/**
|
||||
* toggle edit/split on Z tool
|
||||
* presents a Z plane on hover
|
||||
* click to split the selected mesh(es)
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.space
|
||||
gapp.register("mesh.split", [], (root, exports) => {
|
||||
|
||||
const { Mesh, MeshPhongMaterial, PlaneGeometry, DoubleSide, Vector3 } = THREE;
|
||||
const { broker } = gapp;
|
||||
const { moto } = root;
|
||||
const { space } = moto;
|
||||
|
||||
// toggle edit/split temporary mode (present plane on hover)
|
||||
let temp_mode;
|
||||
let isActive;
|
||||
|
||||
// split functions
|
||||
let split = {
|
||||
active() {
|
||||
return temp_mode ? true : false;
|
||||
return isActive ? true : false;
|
||||
},
|
||||
|
||||
start() {
|
||||
if (isActive) {
|
||||
return;
|
||||
}
|
||||
let { api, util } = mesh;
|
||||
// highlight button
|
||||
let button = event.target;
|
||||
|
|
@ -54,7 +61,7 @@ let split = {
|
|||
let { dim, mid } = util.bounds(meshes);
|
||||
let { point, face, object } = int;
|
||||
let { x, y, z } = point;
|
||||
|
||||
// set appearance of split plane
|
||||
mat.color.set(0x5555aa);
|
||||
obj.visible = true;
|
||||
if (event.shiftKey) {
|
||||
|
|
@ -62,10 +69,11 @@ let split = {
|
|||
}
|
||||
// y is z in model space for the purposes of a split
|
||||
state.plane = { z: y };
|
||||
// ensure split plane exceeds bounds of split target
|
||||
obj.scale.set(dim.x + 2, dim.y + 2, 1);
|
||||
obj.position.set(mid.x, y, -mid.y);
|
||||
});
|
||||
temp_mode = split;
|
||||
isActive = true;
|
||||
},
|
||||
|
||||
select() {
|
||||
|
|
@ -73,19 +81,22 @@ let split = {
|
|||
let { models, plane } = split.state;
|
||||
log.emit(`splitting ${models.length} model(s) at ${plane.z.round(3)}`).pin();
|
||||
Promise.all(models.map(m => m.split(plane))).then(models => {
|
||||
mesh.api.selection.set(models);
|
||||
mesh.api.selection.set(models.filter(m => m));
|
||||
log.emit('split complete').unpin();
|
||||
split.end();
|
||||
});
|
||||
},
|
||||
|
||||
end() {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
let space = moto.space;
|
||||
let { button, obj } = split.state;
|
||||
button.classList.remove('selected');
|
||||
space.scene.remove(obj);
|
||||
space.mouse.onHover(undefined);
|
||||
temp_mode = split.state = undefined;
|
||||
isActive = split.state = undefined;
|
||||
mesh.api.selection.update();
|
||||
},
|
||||
|
||||
|
|
@ -102,19 +113,6 @@ let split = {
|
|||
}
|
||||
}
|
||||
|
||||
function edit_split(event) {
|
||||
if (temp_mode) {
|
||||
temp_mode.end();
|
||||
} else {
|
||||
split.start();
|
||||
}
|
||||
}
|
||||
|
||||
exports(split);
|
||||
|
||||
// bind functions to topics
|
||||
broker.listeners({
|
||||
edit_split,
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue