stub in subtraction. jscad subtract seems to be broken at the api, though
This commit is contained in:
parent
6e2b93af0f
commit
c7298d2a9f
7 changed files with 113 additions and 47 deletions
|
|
@ -12,6 +12,7 @@ Full docs @ https://docs.grid.space/projects/kiri-moto
|
|||
* `C` substitute some prusa slicer [variables] with KM {variables} on import
|
||||
* `C` fix CAM output order for tool changes an spindle speed updates
|
||||
* `C` add CAM pocket operation using surface selection
|
||||
* `C` fix dogbones on outlines cut by tabs
|
||||
* `-` refactor cnc to use new core slicer engine
|
||||
* `-` improve vertex replacement and widget update matrix tracking
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,10 @@ function restore_space() {
|
|||
let selist = space.select || [];
|
||||
let smodel = api.model.list().filter(m => selist.contains(m.id));
|
||||
let sgroup = api.group.list().filter(m => selist.contains(m.id));
|
||||
api.selection.set([...smodel, ...sgroup]);
|
||||
let tolist = space.tools || [];
|
||||
let tmodel = api.model.list().filter(m => tolist.contains(m.id));
|
||||
let tgroup = api.group.list().filter(m => tolist.contains(m.id));
|
||||
api.selection.set([...smodel, ...sgroup], [...tmodel, ...tgroup]);
|
||||
// restore edit mode
|
||||
api.mode.set(mode);
|
||||
// restore dark mode
|
||||
|
|
@ -350,13 +353,13 @@ function space_init(data) {
|
|||
case 'Backspace':
|
||||
case 'Delete':
|
||||
let mode = api.mode.get();
|
||||
if (mode !== api.modes.object) {
|
||||
if ([api.modes.object, api.modes.tool].contains(mode)) {
|
||||
selection.delete();
|
||||
} else {
|
||||
for (let m of selection.models()) {
|
||||
m.deleteSelections(mode);
|
||||
space.refresh()
|
||||
}
|
||||
} else {
|
||||
selection.delete();
|
||||
}
|
||||
estop(evt);
|
||||
break;
|
||||
|
|
@ -397,19 +400,19 @@ function space_init(data) {
|
|||
|
||||
space.mouse.upSelect((int, event) => {
|
||||
if (event && event.target.nodeName === "CANVAS") {
|
||||
let model = int && int.object.model ? int.object.model : undefined;
|
||||
const model = int && int.object.model ? int.object.model : undefined;
|
||||
if (temp_mode) {
|
||||
return temp_mode.select(model);
|
||||
}
|
||||
if (model) {
|
||||
let group = model.group;
|
||||
let { altKey, ctrlKey, metaKey, shiftKey } = event;
|
||||
const group = model.group;
|
||||
const { altKey, ctrlKey, metaKey, shiftKey } = event;
|
||||
if (metaKey) {
|
||||
// set focus on intersected face
|
||||
let { x, y, z } = int.point;
|
||||
let q = new Quaternion().setFromRotationMatrix(group.object.matrix);
|
||||
const { x, y, z } = int.point;
|
||||
const q = new Quaternion().setFromRotationMatrix(group.object.matrix);
|
||||
// rotate normal using group's matrix
|
||||
let normal = int.face.normal.applyQuaternion(q);
|
||||
const normal = int.face.normal.applyQuaternion(q);
|
||||
// y,z swap due to world rotation for orbit controls
|
||||
api.focus({center: { x, y:-z, z:y }, normal});
|
||||
} else if (ctrlKey) {
|
||||
|
|
@ -417,18 +420,18 @@ function space_init(data) {
|
|||
group.faceDown(int.face.normal);
|
||||
selection.update();
|
||||
} else {
|
||||
let { modes } = api;
|
||||
let { surface } = api.prefs.map;
|
||||
let opt = { radians: 0, radius: surface.radius };
|
||||
switch(api.mode.get()) {
|
||||
const { modes } = api;
|
||||
const { surface } = api.prefs.map;
|
||||
const opt = { radians: 0, radius: surface.radius };
|
||||
const mode = api.mode.get();
|
||||
switch(mode) {
|
||||
case modes.object:
|
||||
selection.toggle(shiftKey ? model : model.group);
|
||||
case modes.tool:
|
||||
selection.toggle(shiftKey ? model : model.group, mode === modes.tool);
|
||||
break;
|
||||
case modes.surface:
|
||||
opt.radians = surface.radians;
|
||||
case modes.face:
|
||||
case modes.line:
|
||||
case modes.vertex:
|
||||
// find faces adjacent to point/line clicked
|
||||
model.find(int,
|
||||
altKey ? { toggle: true } :
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const worker = moto.client.fn
|
|||
const groups = [];
|
||||
|
||||
let selected = [];
|
||||
let tools = [];
|
||||
|
||||
const selection = {
|
||||
// @returns {MeshObject[]} or all groups if not strict and no selection
|
||||
|
|
@ -57,28 +58,32 @@ const selection = {
|
|||
},
|
||||
|
||||
// @param group {MeshObject[]}
|
||||
set(objects) {
|
||||
set(objects, toolist) {
|
||||
selected = objects;
|
||||
tools = toolist || [];
|
||||
util.defer(selection.update);
|
||||
},
|
||||
|
||||
// @param group {MeshObject}
|
||||
add(object) {
|
||||
add(object, tool) {
|
||||
// pendantic code necessary to minimize re-entrant api calls
|
||||
if (object.models) {
|
||||
// if group, remove discrete selected members
|
||||
for (let m of object.models) {
|
||||
if (selected.contains(m)) {
|
||||
selection.remove(m);
|
||||
tools.remove(m);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if model, remove selcted group
|
||||
if (selected.contains(object.group)) {
|
||||
selection.remove(object.group);
|
||||
tools.remove(objec.group);
|
||||
}
|
||||
}
|
||||
selected.addOnce(object);
|
||||
if (tool) tools.addOnce(object);
|
||||
util.defer(selection.update);
|
||||
},
|
||||
|
||||
|
|
@ -92,23 +97,27 @@ const selection = {
|
|||
delete() {
|
||||
for (let s of selection.list()) {
|
||||
selection.remove(s);
|
||||
tools.remove(s);
|
||||
s.showBounds(false);
|
||||
s.remove();
|
||||
}
|
||||
},
|
||||
|
||||
// @param group {MeshObject}
|
||||
toggle(object) {
|
||||
toggle(object, tool) {
|
||||
if (selected.contains(object)) {
|
||||
selection.remove(object);
|
||||
tools.remove(object);
|
||||
} else {
|
||||
selection.add(object);
|
||||
if (tool) tools.addOnce(object);
|
||||
}
|
||||
},
|
||||
|
||||
clear() {
|
||||
for (let s of selection.list()) {
|
||||
selection.remove(s);
|
||||
tools.remove(s);
|
||||
}
|
||||
for (let m of api.model.list()) {
|
||||
m.clearSelections();
|
||||
|
|
@ -135,10 +144,11 @@ const selection = {
|
|||
selected = selected.filter(sel => !mgsel.contains(sel)).filter(v => v);
|
||||
// highlight selected
|
||||
for (let object of selected) {
|
||||
object.select(true);
|
||||
object.select(true, tools.contains(object));
|
||||
}
|
||||
// update saved selection id list
|
||||
prefs.save( prefs.map.space.select = selected.map(s => s.id) );
|
||||
prefs.save( prefs.map.space.tools = tools.map(s => s.id) );
|
||||
// force repaint in case of idle
|
||||
space.update();
|
||||
return selection;
|
||||
|
|
@ -277,6 +287,7 @@ let add = {
|
|||
} })
|
||||
]) ]
|
||||
});
|
||||
api.modal.bound.gencyl.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -366,6 +377,22 @@ const tool = {
|
|||
});
|
||||
},
|
||||
|
||||
subtract(models) {
|
||||
models = fallback(models);
|
||||
api.log.emit(`subtract ${models.length} model(s)`).pin();
|
||||
worker.model_subtract(models.map(m => {
|
||||
return { id: m.id, matrix: m.matrix, tool: m.tool() }
|
||||
}))
|
||||
.then(data => {
|
||||
let group = api.group.new([new mesh.model({
|
||||
file: `subtracted`,
|
||||
mesh: data
|
||||
})]).promote();
|
||||
api.selection.set([group]);
|
||||
api.log.emit('subtract complete').unpin();
|
||||
});
|
||||
},
|
||||
|
||||
duplicate() {
|
||||
for (let m of selection.models()) {
|
||||
m.duplicate();
|
||||
|
|
@ -536,9 +563,8 @@ const tool = {
|
|||
|
||||
const modes = {
|
||||
object: "object",
|
||||
tool: "tool",
|
||||
face: "face",
|
||||
line: "line",
|
||||
vertex: "vertex",
|
||||
surface: "surface"
|
||||
};
|
||||
|
||||
|
|
@ -577,23 +603,22 @@ const mode = {
|
|||
mode.set(modes.object);
|
||||
},
|
||||
|
||||
tool() {
|
||||
if (mode.is([ modes.face, modes.surface ])) {
|
||||
selection.clear();
|
||||
}
|
||||
mode.set(modes.tool);
|
||||
},
|
||||
|
||||
face() {
|
||||
if (mode.is([ modes.object ])) {
|
||||
if (mode.is([ modes.object, modes.tool ])) {
|
||||
selection.clear();
|
||||
}
|
||||
mode.set(modes.face);
|
||||
},
|
||||
|
||||
line() {
|
||||
mode.set(modes.line);
|
||||
},
|
||||
|
||||
vertex() {
|
||||
mode.set(modes.vertex);
|
||||
},
|
||||
|
||||
surface() {
|
||||
if (mode.is([ modes.object ])) {
|
||||
if (mode.is([ modes.object, modes.tool ])) {
|
||||
selection.clear();
|
||||
}
|
||||
mode.set(modes.surface);
|
||||
|
|
|
|||
|
|
@ -342,13 +342,13 @@ function ui_build() {
|
|||
let { file, selection, mode, tool, prefs, add } = api;
|
||||
|
||||
// top/center mode selector
|
||||
h.bind(modes, [
|
||||
h.bind(modes, [ h.div([
|
||||
h.button({ _: 'object', id: "mode-object", onclick() { mode.object() } }),
|
||||
h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }),
|
||||
h.button({ _: 'line', id: "mode-line", onclick() { mode.line() } }),
|
||||
h.button({ _: 'vertex', id: "mode-vertex", onclick() { mode.vertex() } }),
|
||||
h.button({ _: 'tool', id: "mode-tool", onclick() { mode.tool() } }),
|
||||
]), h.div([
|
||||
h.button({ _: 'surface', id: "mode-surface", onclick() { mode.surface() } }),
|
||||
]);
|
||||
h.button({ _: 'face', id: "mode-face", onclick() { mode.face() } }),
|
||||
]) ]);
|
||||
|
||||
// create hotkey/action menu (top/left)
|
||||
h.bind(actions, [
|
||||
|
|
@ -376,9 +376,13 @@ function ui_build() {
|
|||
h.button({ _: 'regroup', onclick: tool.regroup }),
|
||||
h.button({ _: 'merge', onclick: tool.merge }),
|
||||
h.button({ _: 'mirror', onclick: tool.mirror }),
|
||||
h.button({ _: 'union', onclick: tool.union }),
|
||||
h.button({ _: 'split', onclick: call.edit_split }),
|
||||
]),
|
||||
h.div([
|
||||
h.div({ _: "bool", class: "head" }),
|
||||
h.button({ _: 'union', onclick: tool.union }),
|
||||
h.button({ _: 'subtract', onclick: tool.subtract }),
|
||||
]),
|
||||
h.div([
|
||||
h.div({ _: "normal", class: "head" }),
|
||||
h.button({ _: 'invert', onclick: tool.invert }),
|
||||
|
|
|
|||
|
|
@ -40,6 +40,15 @@ let materials = mesh.material = {
|
|||
color: 0x00e000,
|
||||
opacity: 1
|
||||
}),
|
||||
// model selected as tool
|
||||
tool: new MeshPhongMaterial({
|
||||
side: DoubleSide,
|
||||
transparent: true,
|
||||
shininess: 125,
|
||||
specular: 0x202020,
|
||||
color: 0xe00000,
|
||||
opacity: 1
|
||||
}),
|
||||
// face selected (for groups ranges)
|
||||
face: new MeshPhongMaterial({
|
||||
side: DoubleSide,
|
||||
|
|
@ -78,6 +87,7 @@ mesh.model = class MeshModel extends mesh.object {
|
|||
this.mats = {
|
||||
normal: materials.normal.clone(),
|
||||
select: materials.select.clone(),
|
||||
tool: materials.tool.clone(),
|
||||
face: materials.face.clone()
|
||||
};
|
||||
|
||||
|
|
@ -266,14 +276,16 @@ mesh.model = class MeshModel extends mesh.object {
|
|||
}
|
||||
|
||||
// get, set, or toggle selection of model (coloring)
|
||||
select(bool) {
|
||||
select(bool, stool) {
|
||||
const cmat = this.mesh.material[0];
|
||||
const { normal, select, tool } = this.mats;
|
||||
if (bool === undefined) {
|
||||
return this.mesh.material[0] === this.mats.normal;
|
||||
return cmat !== normal;
|
||||
}
|
||||
if (bool.toggle) {
|
||||
return this.select(!this.select());
|
||||
return this.select(!this.select(), cmat === tool);
|
||||
}
|
||||
this.mesh.material[0] = bool ? this.mats.select : this.mats.normal;
|
||||
this.mesh.material[0] = bool ? (stool ? tool : select) : normal;
|
||||
return bool;
|
||||
}
|
||||
|
||||
|
|
@ -282,6 +294,13 @@ mesh.model = class MeshModel extends mesh.object {
|
|||
return this.select();
|
||||
}
|
||||
|
||||
tool(bool) {
|
||||
if (bool === undefined) {
|
||||
return this.mesh.material[0] === this.mats.tool;
|
||||
}
|
||||
return this.select(this.selected(), bool);
|
||||
}
|
||||
|
||||
opacity(ov, opt = {}) {
|
||||
let mat = this.mesh.material;
|
||||
if (ov === undefined) {
|
||||
|
|
|
|||
|
|
@ -150,6 +150,20 @@ let model = {
|
|||
return base.CSG.toPositionArray(union);
|
||||
},
|
||||
|
||||
subtract(recs) {
|
||||
let bases = recs.filter(rec => !rec.tool)
|
||||
.map(rec => translate_encode(rec.id, rec.matrix))
|
||||
.map(a => base.CSG.fromPositionArray(a));
|
||||
let tools = recs.filter(rec => rec.tool)
|
||||
.map(rec => translate_encode(rec.id, rec.matrix))
|
||||
.map(a => base.CSG.fromPositionArray(a));
|
||||
let subs = [];
|
||||
for (let obj of bases) {
|
||||
subs.appendAll(base.CSG.subtract(obj, ...tools));
|
||||
}
|
||||
return base.CSG.toPositionArray(subs);
|
||||
},
|
||||
|
||||
// used to generate a list for split snapping
|
||||
zlist(data) {
|
||||
let { id, matrix, round } = data;
|
||||
|
|
|
|||
|
|
@ -408,6 +408,9 @@ button.selected:hover {
|
|||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
#modes > div {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
|
|
@ -418,10 +421,6 @@ button.selected:hover {
|
|||
gap: 2px;
|
||||
}
|
||||
|
||||
#mode-line, #mode-vertex {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/** slide in/out logging window **/
|
||||
|
||||
#logger {
|
||||
|
|
@ -518,6 +517,7 @@ button.selected:hover {
|
|||
}
|
||||
|
||||
#grouplist .models .square {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue