start undo/redo
This commit is contained in:
parent
3b24ebe0d9
commit
7f75981689
7 changed files with 225 additions and 87 deletions
|
|
@ -7,27 +7,26 @@ import '../add/class.js';
|
||||||
import '../add/three.js';
|
import '../add/three.js';
|
||||||
import '../mesh/build.js';
|
import '../mesh/build.js';
|
||||||
|
|
||||||
|
import { api } from '../mesh/api.js';
|
||||||
|
import { space } from '../moto/space.js';
|
||||||
|
import { broker } from '../moto/broker.js';
|
||||||
|
import { $, $d, h, estop } from '../moto/webui.js';
|
||||||
import { client as motoClient } from '../moto/client.js';
|
import { client as motoClient } from '../moto/client.js';
|
||||||
import { broker as motoBroker } from '../moto/broker.js';
|
|
||||||
import { space as motoSpace } from '../moto/space.js';
|
|
||||||
import { open as dataOpen } from '../data/index.js';
|
|
||||||
import { api as meshApi } from '../mesh/api.js';
|
|
||||||
import { split as meshSplit } from '../mesh/split.js';
|
import { split as meshSplit } from '../mesh/split.js';
|
||||||
import { model as meshModel, materials } from '../mesh/model.js';
|
import { model as meshModel, materials } from '../mesh/model.js';
|
||||||
import { edges as meshEdges } from '../mesh/edges.js';
|
import { edges as meshEdges } from '../mesh/edges.js';
|
||||||
|
import { open as dataOpen } from '../data/index.js';
|
||||||
import { load as fileLoad } from '../load/file.js';
|
import { load as fileLoad } from '../load/file.js';
|
||||||
import { $, $d, h, estop } from '../moto/webui.js';
|
|
||||||
import { THREE } from '../ext/three.js';
|
import { THREE } from '../ext/three.js';
|
||||||
|
|
||||||
const version = '1.5.7';
|
const version = '1.5.7';
|
||||||
const api = meshApi;
|
const call = broker.send;
|
||||||
const call = motoBroker.send;
|
|
||||||
const dbindex = [ "admin", "space" ];
|
const dbindex = [ "admin", "space" ];
|
||||||
|
|
||||||
const { Quaternion } = THREE;
|
const { Quaternion } = THREE;
|
||||||
|
|
||||||
function log() {
|
function log() {
|
||||||
return meshApi.log.emit(...arguments);
|
return api.log.emit(...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set below. called once the DOM readyState = complete
|
// set below. called once the DOM readyState = complete
|
||||||
|
|
@ -38,21 +37,24 @@ function init() {
|
||||||
ortho = false,
|
ortho = false,
|
||||||
zoomrev = true,
|
zoomrev = true,
|
||||||
zoomspd = 1,
|
zoomspd = 1,
|
||||||
platform = motoSpace.platform,
|
platform = space.platform,
|
||||||
db = meshApi.db = {
|
db = api.db = {
|
||||||
admin: stores.promise('admin'),
|
admin: stores.promise('admin'),
|
||||||
space: stores.promise('space')
|
space: stores.promise('space')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// initialize the API (to avoid circular dependencies)
|
||||||
|
api.init();
|
||||||
|
|
||||||
// mark init time and use count
|
// mark init time and use count
|
||||||
db.admin.put("init", Date.now());
|
db.admin.put("init", Date.now());
|
||||||
db.admin.get("uses").then(v => db.admin.put("uses", (v||0) + 1));
|
db.admin.get("uses").then(v => db.admin.put("uses", (v||0) + 1));
|
||||||
|
|
||||||
// setup default workspace
|
// setup default workspace
|
||||||
motoSpace.setAntiAlias(true);
|
space.setAntiAlias(true);
|
||||||
motoSpace.useDefaultKeys(false);
|
space.useDefaultKeys(false);
|
||||||
motoSpace.init($('container'), delta => { }, ortho);
|
space.init($('container'), delta => { }, ortho);
|
||||||
motoSpace.sky.set({
|
space.sky.set({
|
||||||
grid: false,
|
grid: false,
|
||||||
color: dark ? 0 : 0xffffff
|
color: dark ? 0 : 0xffffff
|
||||||
});
|
});
|
||||||
|
|
@ -71,11 +73,11 @@ function init() {
|
||||||
platform.onMove(() => {
|
platform.onMove(() => {
|
||||||
// save last location and focus
|
// save last location and focus
|
||||||
db.admin.put('camera', {
|
db.admin.put('camera', {
|
||||||
place: motoSpace.view.save(),
|
place: space.view.save(),
|
||||||
focus: motoSpace.view.getFocus()
|
focus: space.view.getFocus()
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
motoSpace.view.setZoom(zoomrev, zoomspd);
|
space.view.setZoom(zoomrev, zoomspd);
|
||||||
|
|
||||||
// reload stored space when worker is ready
|
// reload stored space when worker is ready
|
||||||
motoClient.on('ready', restore_space);
|
motoClient.on('ready', restore_space);
|
||||||
|
|
@ -84,7 +86,7 @@ function init() {
|
||||||
motoClient.start(`/v2/lib/mesh/work.js?${version}`);
|
motoClient.start(`/v2/lib/mesh/work.js?${version}`);
|
||||||
|
|
||||||
// trigger space event binding
|
// trigger space event binding
|
||||||
call.space_init({ space: motoSpace, platform });
|
call.space_init({ space: space, platform });
|
||||||
|
|
||||||
// trigger ui building
|
// trigger ui building
|
||||||
call.ui_build();
|
call.ui_build();
|
||||||
|
|
@ -92,10 +94,8 @@ function init() {
|
||||||
|
|
||||||
// restore space layout and view from previous session
|
// restore space layout and view from previous session
|
||||||
async function restore_space() {
|
async function restore_space() {
|
||||||
const api = meshApi;
|
const db_admin = api.db.admin;
|
||||||
const space = motoSpace;
|
const db_space = api.db.space;
|
||||||
const db_admin = meshApi.db.admin;
|
|
||||||
const db_space = meshApi.db.space;
|
|
||||||
// let mcache = {};
|
// let mcache = {};
|
||||||
await db_admin.get("camera")
|
await db_admin.get("camera")
|
||||||
.then(saved => {
|
.then(saved => {
|
||||||
|
|
@ -180,6 +180,7 @@ async function restore_space() {
|
||||||
if (api.prefs.map.info.welcome !== false) {
|
if (api.prefs.map.info.welcome !== false) {
|
||||||
api.welcome(version);
|
api.welcome(version);
|
||||||
}
|
}
|
||||||
|
broker.publish("app_ready");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +188,7 @@ async function restore_space() {
|
||||||
function space_init(data) {
|
function space_init(data) {
|
||||||
let platcolor = 0x00ff00;
|
let platcolor = 0x00ff00;
|
||||||
let { space, platform } = data;
|
let { space, platform } = data;
|
||||||
let { selection } = meshApi;
|
let { selection } = api;
|
||||||
// add file drop handler
|
// add file drop handler
|
||||||
space.event.addHandlers(self, [
|
space.event.addHandlers(self, [
|
||||||
'drop', (evt) => {
|
'drop', (evt) => {
|
||||||
|
|
@ -242,9 +243,6 @@ function space_init(data) {
|
||||||
return shiftKey ? api.tool.merge() : api.tool.mirror();
|
return shiftKey ? api.tool.merge() : api.tool.mirror();
|
||||||
case 'KeyU':
|
case 'KeyU':
|
||||||
return shiftKey && api.tool.union();
|
return shiftKey && api.tool.union();
|
||||||
case 'KeyA':
|
|
||||||
if (api.mode.is([ api.modes.edge ])) return meshEdges.add();
|
|
||||||
return shiftKey && api.tool.analyze();
|
|
||||||
case 'KeyE':
|
case 'KeyE':
|
||||||
if (api.mode.is([ api.modes.sketch ])) {
|
if (api.mode.is([ api.modes.sketch ])) {
|
||||||
estop(evt);
|
estop(evt);
|
||||||
|
|
@ -272,8 +270,6 @@ function space_init(data) {
|
||||||
return shiftKey ? selection.hide() : space.view.home();
|
return shiftKey ? selection.hide() : space.view.home();
|
||||||
case 'KeyT':
|
case 'KeyT':
|
||||||
return shiftKey ? api.tool.triangulate() : space.view.top();
|
return shiftKey ? api.tool.triangulate() : space.view.top();
|
||||||
case 'KeyZ':
|
|
||||||
return space.view.reset();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'keydown', evt => {
|
'keydown', evt => {
|
||||||
|
|
@ -293,14 +289,31 @@ function space_init(data) {
|
||||||
let rot, floor = api.prefs.map.space.floor !== false;
|
let rot, floor = api.prefs.map.space.floor !== false;
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'KeyA':
|
case 'KeyA':
|
||||||
|
estop(evt);
|
||||||
|
if (api.mode.is([ api.modes.edge ])) {
|
||||||
|
return meshEdges.add();
|
||||||
|
}
|
||||||
if (metaKey || ctrlKey) {
|
if (metaKey || ctrlKey) {
|
||||||
if (!selection.sketch()?.selection.all()) {
|
if (!selection.sketch()?.selection.all()) {
|
||||||
api.mode.object();
|
api.mode.object();
|
||||||
selection.set(api.group.list());
|
selection.set(api.group.list());
|
||||||
}
|
}
|
||||||
estop(evt);
|
} else if (shiftKey) {
|
||||||
|
api.tool.analyze();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'KeyY':
|
||||||
|
estop(evt);
|
||||||
|
if (metaKey || ctrlKey) {
|
||||||
|
return shiftKey ? api.history.undo() : api.history.redo();
|
||||||
|
}
|
||||||
|
case 'KeyZ':
|
||||||
|
estop(evt);
|
||||||
|
if (metaKey || ctrlKey) {
|
||||||
|
return shiftKey ? api.history.redo() : api.history.undo();
|
||||||
|
} else {
|
||||||
|
return space.view.reset();
|
||||||
|
}
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
if (selection.clear()) {
|
if (selection.clear()) {
|
||||||
meshEdges.clear();
|
meshEdges.clear();
|
||||||
|
|
@ -480,7 +493,7 @@ function space_init(data) {
|
||||||
|
|
||||||
function load_files(files) {
|
function load_files(files) {
|
||||||
log(`loading file...`);
|
log(`loading file...`);
|
||||||
let api = meshApi;
|
let api = api;
|
||||||
let has_image = false;
|
let has_image = false;
|
||||||
let has_svg = false;
|
let has_svg = false;
|
||||||
let has_gbr = false;
|
let has_gbr = false;
|
||||||
|
|
@ -499,7 +512,7 @@ function load_files(files) {
|
||||||
return diam ? poly.offset_open(diam / 2, 'round') : null;
|
return diam ? poly.offset_open(diam / 2, 'round') : null;
|
||||||
}).filter(p => p).flat();
|
}).filter(p => p).flat();
|
||||||
for (let set of [ closed, open, circs, rects ]) {
|
for (let set of [ closed, open, circs, rects ]) {
|
||||||
let group = meshApi.util.uuid();
|
let group = api.util.uuid();
|
||||||
for (let poly of set) {
|
for (let poly of set) {
|
||||||
sketch.add.polygon({ poly, group });
|
sketch.add.polygon({ poly, group });
|
||||||
}
|
}
|
||||||
|
|
@ -510,11 +523,11 @@ function load_files(files) {
|
||||||
if (sketch && has_svg) {
|
if (sketch && has_svg) {
|
||||||
fileLoad([...files], { flat: true })
|
fileLoad([...files], { flat: true })
|
||||||
.then(polys => polys.forEach(set => {
|
.then(polys => polys.forEach(set => {
|
||||||
let group = meshApi.util.uuid();
|
let group = api.util.uuid();
|
||||||
set.forEach(poly => sketch.add.polygon({ poly, group }))
|
set.forEach(poly => sketch.add.polygon({ poly, group }))
|
||||||
}))
|
}))
|
||||||
.catch(error => dbug.error(error))
|
.catch(error => dbug.error(error))
|
||||||
.finally(() => meshApi.log.hide());
|
.finally(() => api.log.hide());
|
||||||
} else
|
} else
|
||||||
if (has_svg) {
|
if (has_svg) {
|
||||||
api.modal.dialog({
|
api.modal.dialog({
|
||||||
|
|
@ -589,13 +602,13 @@ function load_files_opt(files, opt) {
|
||||||
return fileLoad([...files], opt)
|
return fileLoad([...files], opt)
|
||||||
.then(data => call.space_load(data))
|
.then(data => call.space_load(data))
|
||||||
.catch(error => log(error).pin({}) && dbug.error(error))
|
.catch(error => log(error).pin({}) && dbug.error(error))
|
||||||
// .finally(() => meshApi.log.hide());
|
// .finally(() => api.log.hide());
|
||||||
}
|
}
|
||||||
|
|
||||||
// add object loader
|
// add object loader
|
||||||
function space_load(data) {
|
function space_load(data) {
|
||||||
if (data && data.length && (data = data.flat()).length)
|
if (data && data.length && (data = data.flat()).length)
|
||||||
meshApi.group.new(data.map(el => new meshModel(el)))
|
api.group.new(data.map(el => new meshModel(el)))
|
||||||
.promote()
|
.promote()
|
||||||
.focus();
|
.focus();
|
||||||
}
|
}
|
||||||
|
|
@ -613,7 +626,7 @@ function key_once_cancel(code) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function store_meta() {
|
function store_meta() {
|
||||||
meshApi.db.admin.put("meta", metaCache);
|
api.db.admin.put("meta", metaCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_meta(id, data) {
|
function update_meta(id, data) {
|
||||||
|
|
@ -641,8 +654,8 @@ function object_destroy(id) {
|
||||||
|
|
||||||
// listen for changes like dark mode toggle
|
// listen for changes like dark mode toggle
|
||||||
function set_darkmode(dark) {
|
function set_darkmode(dark) {
|
||||||
let { prefs, model } = meshApi;
|
let { prefs, model } = api;
|
||||||
let { sky, platform } = motoSpace;
|
let { sky, platform } = space;
|
||||||
prefs.map.space.dark = dark;
|
prefs.map.space.dark = dark;
|
||||||
if (dark) {
|
if (dark) {
|
||||||
materials.wireframe.color.set(0xaaaaaa);
|
materials.wireframe.color.set(0xaaaaaa);
|
||||||
|
|
@ -667,7 +680,7 @@ function set_darkmode(dark) {
|
||||||
colorMinor: 0xeeeeee,
|
colorMinor: 0xeeeeee,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
meshApi.updateFog();
|
api.updateFog();
|
||||||
platform.setSize();
|
platform.setSize();
|
||||||
for (let m of model.list()) {
|
for (let m of model.list()) {
|
||||||
m.normals({ refresh: true });
|
m.normals({ refresh: true });
|
||||||
|
|
@ -676,13 +689,13 @@ function set_darkmode(dark) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_normals_length(length) {
|
function set_normals_length(length) {
|
||||||
let { prefs } = meshApi;
|
let { prefs } = api;
|
||||||
prefs.map.normals.length = length || 1;
|
prefs.map.normals.length = length || 1;
|
||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_normals_color(color) {
|
function set_normals_color(color) {
|
||||||
let { prefs } = meshApi;
|
let { prefs } = api;
|
||||||
let { map } = prefs;
|
let { map } = prefs;
|
||||||
if (map.space.dark) {
|
if (map.space.dark) {
|
||||||
map.normals.color_dark = color || 0;
|
map.normals.color_dark = color || 0;
|
||||||
|
|
@ -693,37 +706,37 @@ function set_normals_color(color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_surface_radians(radians) {
|
function set_surface_radians(radians) {
|
||||||
let { prefs } = meshApi;
|
let { prefs } = api;
|
||||||
prefs.map.surface.radians = parseFloat(radians || 0.1);
|
prefs.map.surface.radians = parseFloat(radians || 0.1);
|
||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_surface_radius(radius) {
|
function set_surface_radius(radius) {
|
||||||
let { prefs } = meshApi;
|
let { prefs } = api;
|
||||||
prefs.map.surface.radius = parseFloat(radius || 0.2);
|
prefs.map.surface.radius = parseFloat(radius || 0.2);
|
||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_wireframe_opacity(opacity) {
|
function set_wireframe_opacity(opacity) {
|
||||||
let { prefs } = meshApi;
|
let { prefs } = api;
|
||||||
prefs.map.wireframe.opacity = parseFloat(opacity || 0.15);
|
prefs.map.wireframe.opacity = parseFloat(opacity || 0.15);
|
||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_wireframe_fog(fogx) {
|
function set_wireframe_fog(fogx) {
|
||||||
let { prefs } = meshApi;
|
let { prefs } = api;
|
||||||
prefs.map.wireframe.fog = parseFloat(fogx || 3);
|
prefs.map.wireframe.fog = parseFloat(fogx || 3);
|
||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_snap_value(snap) {
|
function set_snap_value(snap) {
|
||||||
let { prefs } = meshApi;
|
let { prefs } = api;
|
||||||
prefs.map.space.snap = parseFloat(snap || 1);
|
prefs.map.space.snap = parseFloat(snap || 1);
|
||||||
prefs.save();
|
prefs.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// bind functions to topics
|
// bind functions to topics
|
||||||
motoBroker.listeners({
|
broker.listeners({
|
||||||
key_once,
|
key_once,
|
||||||
key_once_cancel,
|
key_once_cancel,
|
||||||
load_files,
|
load_files,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
import { broker } from '../moto/broker.js';
|
import { broker } from '../moto/broker.js';
|
||||||
|
import { history } from './history.js';
|
||||||
import { client as motoClient } from '../moto/client.js';
|
import { client as motoClient } from '../moto/client.js';
|
||||||
import { space as motoSpace } from '../moto/space.js';
|
import { space as motoSpace } from '../moto/space.js';
|
||||||
import { util as meshUtil } from './util.js';
|
import { util as meshUtil } from './util.js';
|
||||||
|
|
@ -210,14 +211,21 @@ const selection = {
|
||||||
},
|
},
|
||||||
|
|
||||||
drag(opt = {}) {
|
drag(opt = {}) {
|
||||||
|
if (opt.start) {
|
||||||
|
broker.publish('drag_start', selected);
|
||||||
|
} else if (opt.end) {
|
||||||
|
broker.publish('drag_end', selected);
|
||||||
|
}
|
||||||
selected.forEach(s => s.drag(opt));
|
selected.forEach(s => s.drag(opt));
|
||||||
return selection;
|
return selection;
|
||||||
},
|
},
|
||||||
|
|
||||||
move(dx = 0, dy = 0, dz = 0) {
|
move(dx = 0, dy = 0, dz = 0) {
|
||||||
for (let s of [...selection.groups(), ...selection.sketches()]) {
|
let set = [...selection.groups(), ...selection.sketches()];
|
||||||
|
for (let s of set) {
|
||||||
s.move(dx, dy, dz);
|
s.move(dx, dy, dz);
|
||||||
}
|
}
|
||||||
|
broker.publish('move', { set, dx, dy, dz });
|
||||||
return selection;
|
return selection;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -225,6 +233,7 @@ const selection = {
|
||||||
for (let s of selection.models()) {
|
for (let s of selection.models()) {
|
||||||
s.move(dx, dy, dz);
|
s.move(dx, dy, dz);
|
||||||
}
|
}
|
||||||
|
broker.publish('move', { set: selection.models(), dx, dy, dz });
|
||||||
return selection;
|
return selection;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -232,6 +241,7 @@ const selection = {
|
||||||
for (let s of selection.groups()) {
|
for (let s of selection.groups()) {
|
||||||
s.rotate(dx, dy, dz);
|
s.rotate(dx, dy, dz);
|
||||||
}
|
}
|
||||||
|
broker.publish('rotate', { set: selection.models(), dx, dy, dz });
|
||||||
return selection;
|
return selection;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -239,6 +249,7 @@ const selection = {
|
||||||
for (let s of selection.groups()) {
|
for (let s of selection.groups()) {
|
||||||
s.qrotate(q);
|
s.qrotate(q);
|
||||||
}
|
}
|
||||||
|
broker.publish('qrotate', { set: selection.groups(), q });
|
||||||
return selection;
|
return selection;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -247,6 +258,7 @@ const selection = {
|
||||||
let { x, y, z } = s.scale();
|
let { x, y, z } = s.scale();
|
||||||
s.scale(x * dx, y * dy, z * dz);
|
s.scale(x * dx, y * dy, z * dz);
|
||||||
}
|
}
|
||||||
|
broker.publish('scale', { set: selection.groups(), dx, dy, dz });
|
||||||
return selection;
|
return selection;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1466,6 +1478,16 @@ let dbug = self.dbug = self.dbug || {
|
||||||
|
|
||||||
// api is augmented in mesh.build (log, modal, download)
|
// api is augmented in mesh.build (log, modal, download)
|
||||||
const api = {
|
const api = {
|
||||||
|
init() {
|
||||||
|
// publish messages when api functions are called
|
||||||
|
// broker.wrapObject(selection, 'selection');
|
||||||
|
// broker.wrapObject(model, 'model');
|
||||||
|
// broker.wrapObject(group, 'group');
|
||||||
|
|
||||||
|
// optimize db writes by merging updates
|
||||||
|
prefs.save = meshUtil.deferWrap(prefs.save, 100);
|
||||||
|
},
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
window.open("https://docs.grid.space/projects/mesh-tool");
|
window.open("https://docs.grid.space/projects/mesh-tool");
|
||||||
},
|
},
|
||||||
|
|
@ -1496,6 +1518,11 @@ const api = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
history: {
|
||||||
|
undo() { history.undo() },
|
||||||
|
redo() { history.redo() }
|
||||||
|
},
|
||||||
|
|
||||||
// @param object {THREE.Object3D | THREE.Object3D[] | Point}
|
// @param object {THREE.Object3D | THREE.Object3D[] | Point}
|
||||||
focus(object) {
|
focus(object) {
|
||||||
let { center } = object.center ? object : meshUtil.bounds(object);
|
let { center } = object.center ? object : meshUtil.bounds(object);
|
||||||
|
|
@ -1567,30 +1594,6 @@ const api = {
|
||||||
prefs.save( prefs.map.space.norm = norm );
|
prefs.save( prefs.map.space.norm = norm );
|
||||||
},
|
},
|
||||||
|
|
||||||
dbug,
|
|
||||||
|
|
||||||
mode,
|
|
||||||
|
|
||||||
modes,
|
|
||||||
|
|
||||||
selection,
|
|
||||||
|
|
||||||
pattern,
|
|
||||||
|
|
||||||
group,
|
|
||||||
|
|
||||||
model,
|
|
||||||
|
|
||||||
sketch,
|
|
||||||
|
|
||||||
add,
|
|
||||||
|
|
||||||
file,
|
|
||||||
|
|
||||||
tool,
|
|
||||||
|
|
||||||
prefs,
|
|
||||||
|
|
||||||
objects() {
|
objects() {
|
||||||
// return model objects suitable for finding ray intersections
|
// return model objects suitable for finding ray intersections
|
||||||
return [
|
return [
|
||||||
|
|
@ -1599,6 +1602,30 @@ const api = {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
add,
|
||||||
|
|
||||||
|
dbug,
|
||||||
|
|
||||||
|
file,
|
||||||
|
|
||||||
|
group,
|
||||||
|
|
||||||
|
mode,
|
||||||
|
|
||||||
|
modes,
|
||||||
|
|
||||||
|
model,
|
||||||
|
|
||||||
|
pattern,
|
||||||
|
|
||||||
|
prefs,
|
||||||
|
|
||||||
|
selection,
|
||||||
|
|
||||||
|
sketch,
|
||||||
|
|
||||||
|
tool,
|
||||||
|
|
||||||
isDebug: self.debug === true
|
isDebug: self.debug === true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1608,17 +1635,9 @@ function log() {
|
||||||
|
|
||||||
const call = broker.send;
|
const call = broker.send;
|
||||||
|
|
||||||
// publish messages when api functions are called
|
|
||||||
broker.wrapObject(selection, 'selection');
|
|
||||||
broker.wrapObject(model, 'model');
|
|
||||||
broker.wrapObject(group, 'group');
|
|
||||||
|
|
||||||
// optimize db writes by merging updates
|
|
||||||
// prefs.save = meshUtil.deferWrap(prefs.save, 100);
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
sketches,
|
||||||
selection,
|
selection,
|
||||||
groups,
|
groups,
|
||||||
sketches,
|
|
||||||
api
|
api
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { version } from '../moto/license.js';
|
||||||
|
|
||||||
const { div, label, input, hr } = h;
|
const { div, label, input, hr } = h;
|
||||||
const { dbug } = api;
|
const { dbug } = api;
|
||||||
const devel = api.isDebug;
|
const isDebug = api.isDebug;
|
||||||
|
|
||||||
let call = broker.send;
|
let call = broker.send;
|
||||||
let rad = 180 / Math.PI;
|
let rad = 180 / Math.PI;
|
||||||
|
|
@ -135,7 +135,7 @@ const log = api.log = {
|
||||||
text: `${dbug.since()} | ${[...arguments].join(' ')}`,
|
text: `${dbug.since()} | ${[...arguments].join(' ')}`,
|
||||||
time: now
|
time: now
|
||||||
});
|
});
|
||||||
if (api.isDebug) {
|
if (isDebug) {
|
||||||
console.log(...data.peek().objs);
|
console.log(...data.peek().objs);
|
||||||
}
|
}
|
||||||
while (!log.pinned && data.length && (data.length > lines || now - data[0].time > age)) {
|
while (!log.pinned && data.length && (data.length > lines || now - data[0].time > age)) {
|
||||||
|
|
@ -408,6 +408,9 @@ function ui_build() {
|
||||||
div({ class: "menu sketch-on" }, [
|
div({ class: "menu sketch-on" }, [
|
||||||
div('Edit'),
|
div('Edit'),
|
||||||
div({ class: "menu-items" }, [
|
div({ class: "menu-items" }, [
|
||||||
|
menu_item('Undo', api.history.undo, ['bi-command','Z']),
|
||||||
|
menu_item('Redo', api.history.redo, ['bi-command','Y']),
|
||||||
|
hr(),
|
||||||
menu_item('Add Circle', api.add.circle),
|
menu_item('Add Circle', api.add.circle),
|
||||||
menu_item('Add Rectangle', api.add.rectangle),
|
menu_item('Add Rectangle', api.add.rectangle),
|
||||||
hr(),
|
hr(),
|
||||||
|
|
@ -419,13 +422,16 @@ function ui_build() {
|
||||||
div({ class: "menu sketch-off" }, [
|
div({ class: "menu sketch-off" }, [
|
||||||
div('Edit'),
|
div('Edit'),
|
||||||
div({ class: "menu-items" }, [
|
div({ class: "menu-items" }, [
|
||||||
|
menu_item('Undo', api.history.undo, ['bi-command','Z']),
|
||||||
|
menu_item('Redo', api.history.redo, ['bi-command','Y']),
|
||||||
|
hr(),
|
||||||
menu_item('Add Cylinder', add.cylinder),
|
menu_item('Add Cylinder', add.cylinder),
|
||||||
menu_item('Add Cube', add.cube),
|
menu_item('Add Cube', add.cube),
|
||||||
menu_item('Add Gear', add.gear),
|
menu_item('Add Gear', add.gear),
|
||||||
menu_item('Add Threads', add.threads),
|
menu_item('Add Threads', add.threads),
|
||||||
hr(),
|
hr(),
|
||||||
menu_item('Add Sketch', add.sketch),
|
menu_item('Add Sketch', add.sketch),
|
||||||
devel ? menu_item('Add Vertices', add.input) : undefined,
|
isDebug ? menu_item('Add Vertices', add.input) : undefined,
|
||||||
hr(),
|
hr(),
|
||||||
menu_item('Delete', api.selection.delete, 'bi-backspace'),
|
menu_item('Delete', api.selection.delete, 'bi-backspace'),
|
||||||
])
|
])
|
||||||
|
|
@ -773,7 +779,7 @@ function ui_build() {
|
||||||
for (let g of api.selection.groups()) {
|
for (let g of api.selection.groups()) {
|
||||||
set(g, tempval, value);
|
set(g, tempval, value);
|
||||||
if (floor && opt.floor !== false) {
|
if (floor && opt.floor !== false) {
|
||||||
g.floor(mesh.group);
|
g.floor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer_selection();
|
defer_selection();
|
||||||
|
|
|
||||||
92
src2/mesh/history.js
Normal file
92
src2/mesh/history.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
import { broker } from "../moto/broker.js";
|
||||||
|
|
||||||
|
class History {
|
||||||
|
#pos = -1;
|
||||||
|
#stack = [];
|
||||||
|
#tracking = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
broker.subscribe({
|
||||||
|
|
||||||
|
app_ready() {
|
||||||
|
this.#tracking = true
|
||||||
|
},
|
||||||
|
|
||||||
|
drag_start(selected) {
|
||||||
|
this.push('position', selected.map(model => ({
|
||||||
|
model,
|
||||||
|
pos: model.position().toArray()
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
|
||||||
|
drag_end(selected) {
|
||||||
|
this.update(selected.map(model => ({
|
||||||
|
model,
|
||||||
|
pos: model.position().toArray()
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
|
||||||
|
move(data) {
|
||||||
|
console.log('move', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
rotate(data) {
|
||||||
|
console.log('rotate', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
scale(data) {
|
||||||
|
console.log('scale', data);
|
||||||
|
},
|
||||||
|
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
push(type, undo, redo) {
|
||||||
|
this.#stack.length = ++this.#pos;
|
||||||
|
this.#stack.push({
|
||||||
|
type,
|
||||||
|
undo,
|
||||||
|
redo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(redo, undo) {
|
||||||
|
let peek = this.#stack.peek();
|
||||||
|
if (peek) {
|
||||||
|
Object.assign(peek, {
|
||||||
|
redo: redo || peek.redo,
|
||||||
|
undo: undo || peek.undo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
do(type, data) {
|
||||||
|
switch (type) {
|
||||||
|
case 'position':
|
||||||
|
for (let { model, pos } of data) {
|
||||||
|
model.position(...pos);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(`invalid record type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
if (this.#pos >= 0) {
|
||||||
|
const rec = this.#stack[this.#pos--];
|
||||||
|
this.do(rec.type, rec.undo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redo() {
|
||||||
|
if (this.#stack.length && this.#pos >= -1 && this.#pos < this.#stack.length - 1) {
|
||||||
|
const rec = this.#stack[++this.#pos];
|
||||||
|
this.do(rec.type, rec.redo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const history = new History();
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
import { THREE } from '../ext/three.js';
|
import { THREE } from '../ext/three.js';
|
||||||
|
import { api } from './api.js';
|
||||||
import { broker as motoBroker } from '../moto/broker.js';
|
import { broker as motoBroker } from '../moto/broker.js';
|
||||||
import { client as motoClient } from '../moto/client.js';
|
import { client as motoClient } from '../moto/client.js';
|
||||||
import { space as motoSpace } from '../moto/space.js';
|
import { space as motoSpace } from '../moto/space.js';
|
||||||
import { api as meshApi } from './api.js';
|
|
||||||
import { util as meshUtil } from './util.js';
|
import { util as meshUtil } from './util.js';
|
||||||
|
|
||||||
const { Matrix4, Vector3, Box3, Box3Helper, Quaternion } = THREE;
|
const { Matrix4, Vector3, Box3, Box3Helper, Quaternion } = THREE;
|
||||||
|
|
@ -28,7 +28,7 @@ class MeshObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
// meshApi.log.emit(this.id, this.type, ...arguments);
|
// api.log.emit(this.id, this.type, ...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type() {
|
||||||
|
|
@ -49,7 +49,7 @@ class MeshObject {
|
||||||
// update worker state
|
// update worker state
|
||||||
worker.object_destroy({id: this.id});
|
worker.object_destroy({id: this.id});
|
||||||
// update object store
|
// update object store
|
||||||
meshApi.db.space.remove(this.id);
|
api.db.space.remove(this.id);
|
||||||
// main app cache workspace updates
|
// main app cache workspace updates
|
||||||
publish.destroy(this.id);
|
publish.destroy(this.id);
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ class MeshObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
meshApi.focus(this.object);
|
api.focus(this.object);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
class Broker {
|
class Broker {
|
||||||
|
debug = false;
|
||||||
|
topics = {};
|
||||||
|
used = {};
|
||||||
|
send = {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.topics = {};
|
|
||||||
this.used = {};
|
|
||||||
this.send = {};
|
|
||||||
// using the pattern "broker.send(msg)" induces runtime errors
|
// using the pattern "broker.send(msg)" induces runtime errors
|
||||||
// when there is not at least one registered listener for a topic
|
// when there is not at least one registered listener for a topic
|
||||||
// while also allowing for a more natural function call interface
|
// while also allowing for a more natural function call interface
|
||||||
|
|
@ -37,7 +39,10 @@ class Broker {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (typeof topic !== 'string') {
|
if (typeof topic !== 'string') {
|
||||||
console.trace({invalid_topic: topic});
|
for (let [ key, fn ] of Object.entries(topic)) {
|
||||||
|
this.subscribe(key, fn.bind(listener));
|
||||||
|
}
|
||||||
|
// console.trace({invalid_topic: topic});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let topics = this.topics;
|
let topics = this.topics;
|
||||||
|
|
@ -81,6 +86,9 @@ class Broker {
|
||||||
if (topic !== ".topic.publish") {
|
if (topic !== ".topic.publish") {
|
||||||
this.publish(".topic.publish", {topic, message, options});
|
this.publish(".topic.publish", {topic, message, options});
|
||||||
}
|
}
|
||||||
|
if (this.debug && !topic.startsWith(".")) {
|
||||||
|
console.log({ topic, message });
|
||||||
|
}
|
||||||
// store last seen message on a topic
|
// store last seen message on a topic
|
||||||
// acts as a tracker for all used topics
|
// acts as a tracker for all used topics
|
||||||
this.used[topic] = message;
|
this.used[topic] = message;
|
||||||
|
|
|
||||||
|
|
@ -1407,7 +1407,7 @@ let Space = {
|
||||||
const dist = camera.position.distanceTo(viewControl.target);
|
const dist = camera.position.distanceTo(viewControl.target);
|
||||||
raycaster.params.Line.threshold = Math.min(1, dist / 100);
|
raycaster.params.Line.threshold = Math.min(1, dist / 100);
|
||||||
}
|
}
|
||||||
broker.publish("space.view.zoom", camera);
|
// broker.publish("space.view.zoom", camera);
|
||||||
updateLastAction();
|
updateLastAction();
|
||||||
if (slider) slider(val);
|
if (slider) slider(val);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue