deep clone + args, rework/cleanup ui binding, preserve groups on poly re-nest
This commit is contained in:
parent
15a0131c6b
commit
18b6b8567a
8 changed files with 238 additions and 222 deletions
|
|
@ -90,11 +90,11 @@ AP.addOnce = function(val) {
|
|||
/**
|
||||
* shallow cloning with clone(arg) call on each new element
|
||||
*/
|
||||
AP.clone = function(arg) {
|
||||
AP.clone = function() {
|
||||
let na = this.slice(),
|
||||
ln = na.length,
|
||||
i = 0;
|
||||
while (i < ln) na[i] = na[i++].clone(arg);
|
||||
while (i < ln) na[i] = na[i++].clone(...arguments);
|
||||
return na;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -913,32 +913,33 @@ class Polygon {
|
|||
* @param {Boolean} deep
|
||||
* @returns {Polygon}
|
||||
*/
|
||||
clone(deep) {
|
||||
clone(deep, fields) {
|
||||
let np = newPolygon().copyZ(this.getZ()),
|
||||
ln = this.length,
|
||||
i = 0;
|
||||
|
||||
while (i < ln) np.push(this.points[i++]);
|
||||
|
||||
if (this.fillang) np.fillang = this.fillang;
|
||||
fields && fields.forEach(field => np[field] = this[field]);
|
||||
this.fillang && (np.fillang = this.fillang);
|
||||
np.depth = this.depth;
|
||||
np.open = this.open;
|
||||
|
||||
if (deep && this.inner) {
|
||||
np.inner = this.inner.clone();
|
||||
np.inner = this.inner.clone(false, fields);
|
||||
}
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
// special shallow for-render-or-read-only cloning
|
||||
cloneZ(z, stop) {
|
||||
cloneZ(z, deep = true) {
|
||||
let p = newPolygon();
|
||||
p.z = z;
|
||||
p.open = this.open;
|
||||
p.points = this.points;
|
||||
if (this.inner) {
|
||||
p.inner = this.inner.map(p => p.cloneZ(z, true));
|
||||
if (deep && this.inner) {
|
||||
p.inner = this.inner.map(p => p.cloneZ(z, false));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -822,7 +822,7 @@ function platformLoadFiles(files, group) {
|
|||
let wid = platformLoadWidget(group, vertices, name);
|
||||
if (api.mode.is_cam()) {
|
||||
// attach raw illustration
|
||||
api.event.emit('cam.parse.gerber', { data: text, mesh: wid.mesh });
|
||||
// api.event.emit('cam.parse.gerber', { data: text, mesh: wid.mesh });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,9 @@ function space_init(data) {
|
|||
case 'KeyW':
|
||||
return api.wireframe();
|
||||
case 'KeyG':
|
||||
return shiftKey ? api.tool.regroup() : api.grid();
|
||||
return shiftKey ?
|
||||
(api.mode.is([ api.modes.sketch ]) ? api.sketch.arrange.group() : api.tool.regroup()) :
|
||||
api.grid();
|
||||
case 'KeyL':
|
||||
return api.log.toggle({ spinner: false });
|
||||
case 'KeyS':
|
||||
|
|
|
|||
|
|
@ -212,6 +212,11 @@ const selection = {
|
|||
return selection;
|
||||
},
|
||||
|
||||
update_defer() {
|
||||
util.defer(selection.update);
|
||||
return selection;
|
||||
},
|
||||
|
||||
drag(opt = {}) {
|
||||
selected.forEach(s => s.drag(opt));
|
||||
return selection;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ const { broker } = gapp;
|
|||
const { mesh, moto } = root;
|
||||
const { api, util } = mesh;
|
||||
const { space } = moto;
|
||||
const { bind, div, label, input, hr } = h;
|
||||
|
||||
const devel = api.isDebug;
|
||||
|
||||
let call = broker.send;
|
||||
|
|
@ -36,7 +38,7 @@ const modal = api.modal = {
|
|||
if (this.info.showing) {
|
||||
throw `modal conflict showing "${title}"`;
|
||||
}
|
||||
let bound = h.bind($('modal'), contents);
|
||||
let bound = bind($('modal'), contents);
|
||||
$('modal_title_text').innerText = title;
|
||||
$('modal_page').style.display = 'flex';
|
||||
$('modal_frame').style.display = 'flex';
|
||||
|
|
@ -99,7 +101,7 @@ const modal = api.modal = {
|
|||
let { title, body } = opt;
|
||||
let contents = [];
|
||||
if (typeof body === 'string') {
|
||||
contents.push(h.div(body))
|
||||
contents.push(div(body))
|
||||
} else if (Array.isArray(body)) {
|
||||
contents.appendAll(body);
|
||||
} else {
|
||||
|
|
@ -181,7 +183,7 @@ const log = api.log = {
|
|||
// re-render and show current log messages
|
||||
render() {
|
||||
let area = $('logtext');
|
||||
h.bind(area, log.data.map(rec => h.div({ _: rec.text })));
|
||||
bind(area, log.data.map(rec => div({ _: rec.text })));
|
||||
area.scrollTop = area.scrollHeight;
|
||||
return log.show();
|
||||
},
|
||||
|
|
@ -196,7 +198,7 @@ gapp.broker.subscribe("mesh.log", msg => {
|
|||
api.welcome = function(version = "unknown") {
|
||||
const { prefs } = api;
|
||||
const { map } = prefs;
|
||||
modal.show('About Mesh:Tool', h.div({ class:"welcome" }, [
|
||||
modal.show('About Mesh:Tool', div({ class:"welcome" }, [
|
||||
h.a({
|
||||
target: "docs",
|
||||
_: "Guide & Documentation",
|
||||
|
|
@ -207,10 +209,10 @@ api.welcome = function(version = "unknown") {
|
|||
_: "Grid.Space",
|
||||
href: "https://grid.space/"
|
||||
}),
|
||||
h.div(`Version: ${version}`),
|
||||
h.hr({ width: "100%" }),
|
||||
h.div({ class: "choice" }, [
|
||||
h.input({
|
||||
div(`Version: ${version}`),
|
||||
hr({ width: "100%" }),
|
||||
div({ class: "choice" }, [
|
||||
input({
|
||||
type: "checkbox",
|
||||
[ map.info.welcome !== false ? 'checked' : 'unchecked' ] : 1,
|
||||
onchange: (ev) => {
|
||||
|
|
@ -218,7 +220,7 @@ api.welcome = function(version = "unknown") {
|
|||
prefs.save();
|
||||
}
|
||||
}),
|
||||
h.div("show at startup")
|
||||
div("show at startup")
|
||||
]),
|
||||
]));
|
||||
};
|
||||
|
|
@ -230,83 +232,83 @@ api.settings = function() {
|
|||
const { surface, normals, space, sketch, wireframe } = prefs.map;
|
||||
const { dark } = space;
|
||||
|
||||
const set1 = h.div([
|
||||
h.label('dark mode'),
|
||||
h.input({ type: "checkbox",
|
||||
const set1 = div([
|
||||
label('dark mode'),
|
||||
input({ type: "checkbox",
|
||||
onchange: ev => call.set_darkmode(ev.target.checked),
|
||||
[ dark ? 'checked' : 'unchecked' ] : 1
|
||||
}),
|
||||
h.label('auto floor'),
|
||||
h.input({ type: "checkbox",
|
||||
label('auto floor'),
|
||||
input({ type: "checkbox",
|
||||
onchange: ev => prefs.save( space.floor = !space.floor ),
|
||||
[ space.floor !== false ? 'checked' : 'unchecked' ] : 1
|
||||
}),
|
||||
h.label('auto center'),
|
||||
h.input({ type: "checkbox",
|
||||
label('auto center'),
|
||||
input({ type: "checkbox",
|
||||
onchange: ev => prefs.save( space.center = !space.center ),
|
||||
[ space.center !== false ? 'checked' : 'unchecked' ] : 1
|
||||
}),
|
||||
]);
|
||||
|
||||
const set2 = h.div([
|
||||
h.label({ class: "header", _: 'wire'}),
|
||||
h.label('opacity'),
|
||||
h.input({ type: "text", size: 5, value: parseFloat(wireframe.opacity),
|
||||
const set2 = div([
|
||||
label({ class: "header", _: 'wire'}),
|
||||
label('opacity'),
|
||||
input({ type: "text", size: 5, value: parseFloat(wireframe.opacity),
|
||||
onchange: ev => call.set_wireframe_opacity(ev.target.value)
|
||||
}),
|
||||
h.label('fog x'),
|
||||
h.input({ type: "text", size: 5, value: parseFloat(wireframe.fog),
|
||||
label('fog x'),
|
||||
input({ type: "text", size: 5, value: parseFloat(wireframe.fog),
|
||||
onchange: ev => call.set_wireframe_fog(ev.target.value)
|
||||
}),
|
||||
]);
|
||||
|
||||
const set3 = h.div([
|
||||
h.label({ class: "header", _: 'normal'}),
|
||||
h.label('length'),
|
||||
h.input({ type: "text", size: 6, value: parseFloat(normals.length),
|
||||
const set3 = div([
|
||||
label({ class: "header", _: 'normal'}),
|
||||
label('length'),
|
||||
input({ type: "text", size: 6, value: parseFloat(normals.length),
|
||||
onchange: ev => call.set_normals_length(ev.target.value)
|
||||
}),
|
||||
h.label('color'),
|
||||
h.input({ type: "text", size: 6,
|
||||
label('color'),
|
||||
input({ type: "text", size: 6,
|
||||
value: util.toHexRGB(dark ? normals.color_dark : normals.color_lite),
|
||||
onchange: ev => call.set_normals_color(util.fromHexRGB(ev.target.value))
|
||||
}),
|
||||
]);
|
||||
|
||||
const set4 = h.div([
|
||||
h.label({ class: "header", _: 'surface'}),
|
||||
h.label('radians'),
|
||||
h.input({ type: "text", size: 5, value: parseFloat(surface.radians),
|
||||
const set4 = div([
|
||||
label({ class: "header", _: 'surface'}),
|
||||
label('radians'),
|
||||
input({ type: "text", size: 5, value: parseFloat(surface.radians),
|
||||
onchange: ev => call.set_surface_radians(ev.target.value)
|
||||
}),
|
||||
h.label('radius'),
|
||||
h.input({ type: "text", size: 5, value: parseFloat(surface.radius || 0.2),
|
||||
label('radius'),
|
||||
input({ type: "text", size: 5, value: parseFloat(surface.radius || 0.2),
|
||||
onchange: ev => call.set_surface_radius(ev.target.value)
|
||||
}),
|
||||
]);
|
||||
|
||||
const set5 = h.div([
|
||||
h.label({ class: "header", _: 'snap'}),
|
||||
h.label('value'),
|
||||
h.input({ type: "text", size: 5, value: parseFloat(space.snap || 1),
|
||||
const set5 = div([
|
||||
label({ class: "header", _: 'snap'}),
|
||||
label('value'),
|
||||
input({ type: "text", size: 5, value: parseFloat(space.snap || 1),
|
||||
onchange: ev => call.set_snap_value(ev.target.value)
|
||||
}),
|
||||
h.label('enabled'),
|
||||
h.input({ type: "checkbox",
|
||||
label('enabled'),
|
||||
input({ type: "checkbox",
|
||||
onchange: ev => prefs.save( space.snapon = !space.snapon ),
|
||||
[ space.snapon === true ? 'checked' : 'unchecked' ] : 1
|
||||
}),
|
||||
]);
|
||||
|
||||
const set6 = h.div([
|
||||
h.label({ class: "header", _: 'duplicate'}),
|
||||
h.label('select'),
|
||||
h.input({ type: "checkbox",
|
||||
const set6 = div([
|
||||
label({ class: "header", _: 'duplicate'}),
|
||||
label('select'),
|
||||
input({ type: "checkbox",
|
||||
onchange: ev => prefs.save( space.dup_sel = !space.dup_sel ),
|
||||
[ space.dup_sel === true ? 'checked' : 'unchecked' ] : 1
|
||||
}),
|
||||
h.label('shift'),
|
||||
h.input({ type: "checkbox",
|
||||
label('shift'),
|
||||
input({ type: "checkbox",
|
||||
onchange: ev => prefs.save( space.dup_shift = !space.dup_shift ),
|
||||
[ space.dup_shift === true ? 'checked' : 'unchecked' ] : 1
|
||||
}),
|
||||
|
|
@ -314,18 +316,18 @@ api.settings = function() {
|
|||
|
||||
let ot = sketch.open_type;
|
||||
let srr = () => { api.sketch.list().forEach(s => s.render()) };
|
||||
const set7 = h.div([
|
||||
h.label({ class: "header", _: 'open poly'}),
|
||||
h.label('auto close'),
|
||||
h.input({ type: "checkbox",
|
||||
const set7 = div([
|
||||
label({ class: "header", _: 'open poly'}),
|
||||
label('auto close'),
|
||||
input({ type: "checkbox",
|
||||
onchange: ev => prefs.save( ((sketch.open_close = !sketch.open_close) || true) && srr() ),
|
||||
[ sketch.open_close === true ? 'checked' : 'unchecked' ] : 1
|
||||
}),
|
||||
h.label('width'),
|
||||
h.input({ type: "text", size: 5, value: parseFloat(sketch.open_width || 1),
|
||||
label('width'),
|
||||
input({ type: "text", size: 5, value: parseFloat(sketch.open_width || 1),
|
||||
onchange: ev => prefs.save( (sketch.open_width = parseFloat(ev.target.value)) && srr() )
|
||||
}),
|
||||
h.label('type'),
|
||||
label('type'),
|
||||
h.select({ type: "text", value: sketch.open_type,
|
||||
onchange: ev => prefs.save( (sketch.open_type = ev.target.value) && srr() )
|
||||
}, [
|
||||
|
|
@ -335,7 +337,7 @@ api.settings = function() {
|
|||
]),
|
||||
]);
|
||||
|
||||
modal.show('settings', h.div({ class: "settings" }, [
|
||||
modal.show('settings', div({ class: "settings" }, [
|
||||
set1, set2, set3, set4, set5, set6, set7
|
||||
] ));
|
||||
}
|
||||
|
|
@ -353,12 +355,12 @@ function facon(name, sub) {
|
|||
function menu_item(text, fn, short) {
|
||||
if (short) {
|
||||
short = Array.isArray(short) ? short : [ short ];
|
||||
short = h.div({ class: "short" }, short.map(s => s.startsWith('bi-') ?
|
||||
bicon(s) : h.div(s)
|
||||
short = div({ class: "short" }, short.map(s => s.startsWith('bi-') ?
|
||||
bicon(s) : div(s)
|
||||
));
|
||||
}
|
||||
return h.div({ onclick: fn }, [
|
||||
h.div({ _: text, class: "grow" }),
|
||||
return div({ onclick: fn }, [
|
||||
div({ _: text, class: "grow" }),
|
||||
short
|
||||
].filter(o => o));
|
||||
}
|
||||
|
|
@ -371,194 +373,195 @@ function ui_build() {
|
|||
let eye_closed = FontAwesome.icon({ prefix: "fas", iconName: "eye-slash" }).html[0];
|
||||
|
||||
// top left drop menus
|
||||
h.bind($('top-left'), [
|
||||
h.div({ class: "menu" }, [
|
||||
h.div('File'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
h.input({
|
||||
bind($('top-left'), [
|
||||
div({ class: "menu" }, [
|
||||
div('File'),
|
||||
div({ class: "menu-items" }, [
|
||||
input({
|
||||
id: "import", type: "file", class: ["hide"], multiple: true,
|
||||
onchange(evt) { broker.send.load_files(evt.target.files) }
|
||||
}),
|
||||
menu_item('Import', file.import, 'I'),
|
||||
menu_item('Export', file.export, 'X'),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Slicer', api.kirimoto),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Close', window.close),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu sketch-on" }, [
|
||||
h.div('Edit'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu sketch-on" }, [
|
||||
div('Edit'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('Add Circle', () => { sketch.selected.one?.add.circle() }),
|
||||
menu_item('Add Rectangle', () => { sketch.selected.one?.add.rectangle() }),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Add Sketch', add.sketch),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Delete', api.selection.delete, 'bi-backspace'),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu sketch-off" }, [
|
||||
h.div('Edit'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu sketch-off" }, [
|
||||
div('Edit'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('Add Cylinder', add.cylinder),
|
||||
menu_item('Add Cube', add.cube),
|
||||
menu_item('Add Gear', add.gear),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Add Sketch', add.sketch),
|
||||
devel ? menu_item('Add Vertices', add.input) : undefined,
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Delete', api.selection.delete, 'bi-backspace'),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu" }, [
|
||||
h.div('View'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu" }, [
|
||||
div('View'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('Bounds', () => { selection.boundsBox({ toggle: true }) }, 'B'),
|
||||
menu_item('Normals', () => { api.normals() }, 'N'),
|
||||
menu_item('Wireframes', () => { api.wireframe() }, 'W'),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Gridlines', () => { api.grid() }, 'G'),
|
||||
menu_item('Messages', () => { api.log.toggle({ spinner: false }) }, 'L'),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Home', space.view.home, 'H'),
|
||||
menu_item('Top', space.view.top, 'T'),
|
||||
menu_item('Reset', space.view.reset, 'Z'),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu" }, [
|
||||
h.div('Mode'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
h.div({ _: 'Sketch', id: "mode-sketch", onclick: mode.sketch }),
|
||||
h.hr(),
|
||||
h.div({ _: 'Object', id: "mode-object", onclick: mode.object }),
|
||||
h.div({ _: 'Tool', id: "mode-tool", onclick: mode.tool }),
|
||||
h.hr(),
|
||||
h.div({ _: 'Surface', id: "mode-surface", onclick: mode.surface }),
|
||||
h.div({ _: 'Face', id: "mode-face", onclick: mode.face }),
|
||||
h.div({ _: 'Edge', id: "mode-edge", onclick: mode.edge }),
|
||||
div({ class: "menu" }, [
|
||||
div('Mode'),
|
||||
div({ class: "menu-items" }, [
|
||||
div({ _: 'Sketch', id: "mode-sketch", onclick: mode.sketch }),
|
||||
hr(),
|
||||
div({ _: 'Object', id: "mode-object", onclick: mode.object }),
|
||||
div({ _: 'Tool', id: "mode-tool", onclick: mode.tool }),
|
||||
hr(),
|
||||
div({ _: 'Surface', id: "mode-surface", onclick: mode.surface }),
|
||||
div({ _: 'Face', id: "mode-face", onclick: mode.face }),
|
||||
div({ _: 'Edge', id: "mode-edge", onclick: mode.edge }),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu sketch-on" }, [
|
||||
h.div('Object'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu sketch-on" }, [
|
||||
div('Object'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('Group', sketch.arrange.group),
|
||||
menu_item('Ungroup', sketch.arrange.ungroup),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Center', sketch.arrange.center),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Flip Horizontal', sketch.arrange.fliph),
|
||||
menu_item('Flip Vertical', sketch.arrange.flipv),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Raise', sketch.arrange.up),
|
||||
menu_item('Lower', sketch.arrange.down),
|
||||
menu_item('To Top', sketch.arrange.top),
|
||||
menu_item('To Bottom', sketch.arrange.bottom),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu sketch-off" }, [
|
||||
h.div('Object'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu sketch-off" }, [
|
||||
div('Object'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('Regroup', tool.regroup),
|
||||
menu_item('Isolate', tool.isolate),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Duplicate', tool.duplicate, ['bi-shift','D']),
|
||||
menu_item('Mirror', tool.mirror, 'M'),
|
||||
menu_item('Merge', tool.merge),
|
||||
menu_item('Split', mesh.split.start, 'S'),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Union', tool.union),
|
||||
menu_item('Subtract', tool.subtract),
|
||||
menu_item('Intersect', tool.intersect),
|
||||
menu_item('Difference', tool.difference),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Copy Circle', api.pattern.circle),
|
||||
menu_item('Copy Grid', api.pattern.grid),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu sketch-off" }, [
|
||||
h.div('Faces'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu sketch-off" }, [
|
||||
div('Faces'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('Flip Normals', tool.invert),
|
||||
menu_item('Analyze', tool.analyze),
|
||||
// h.div({ _: 'Patch', onclick: tool.repair }),
|
||||
// h.div({ _: 'rebuild', onclick: tool.rebuild }),
|
||||
// div({ _: 'Patch', onclick: tool.repair }),
|
||||
// div({ _: 'rebuild', onclick: tool.rebuild }),
|
||||
menu_item('Clean', tool.clean),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu sketch-on" }, [
|
||||
h.div('Shape'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu sketch-on" }, [
|
||||
div('Shape'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('Duplicate', tool.duplicate, ['bi-shift','D']),
|
||||
menu_item('Extrude', sketch.extrude, ['bi-shift','E']),
|
||||
h.hr(),
|
||||
menu_item('Extrude', () => sketch.extrude(), ['bi-shift','E']),
|
||||
hr(),
|
||||
menu_item('Union', sketch.boolean.union),
|
||||
menu_item('Intersect', sketch.boolean.intersect),
|
||||
menu_item('Difference', sketch.boolean.difference),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Nest', sketch.boolean.nest),
|
||||
menu_item('Flatten', sketch.boolean.flatten),
|
||||
menu_item('Even Odd', sketch.boolean.evenodd),
|
||||
])
|
||||
]),
|
||||
h.div({ class: "menu" }, [
|
||||
h.div('Help'),
|
||||
h.div({ class: "menu-items" }, [
|
||||
div({ class: "menu" }, [
|
||||
div('Help'),
|
||||
div({ class: "menu-items" }, [
|
||||
menu_item('About', () => { api.welcome(mesh.version) }),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Documentation', api.help),
|
||||
menu_item('Official Site', api.gridspace),
|
||||
menu_item('Donate', api.donate),
|
||||
h.hr(),
|
||||
hr(),
|
||||
menu_item('Versions', api.version),
|
||||
])
|
||||
]),
|
||||
]);
|
||||
|
||||
// add help buttons
|
||||
h.bind($('top-right'), [
|
||||
h.div({ id: "top-settings", onclick: api.settings }, [
|
||||
h.div({ class: "fas fa-gear" }),
|
||||
h.div('Settings')
|
||||
bind($('top-right'), [
|
||||
div({ id: "top-settings", onclick: api.settings }, [
|
||||
div({ class: "fas fa-gear" }),
|
||||
div('Settings')
|
||||
]),
|
||||
]);
|
||||
|
||||
// modal dialog and page blocker
|
||||
h.bind($('modal_page'), [
|
||||
h.div({ id: 'modal_frame' }, [
|
||||
h.div({ id: 'modal_title'}, [
|
||||
h.div({ id: 'modal_title_text', _: 'title' }),
|
||||
h.div({ id: 'modal_title_close', onclick: modal.cancel }, [
|
||||
bind($('modal_page'), [
|
||||
div({ id: 'modal_frame' }, [
|
||||
div({ id: 'modal_title'}, [
|
||||
div({ id: 'modal_title_text', _: 'title' }),
|
||||
div({ id: 'modal_title_close', onclick: modal.cancel }, [
|
||||
h.i({ class: "far fa-window-close" })
|
||||
])
|
||||
]),
|
||||
h.div({ id: 'modal' }, [
|
||||
h.div({ _: 'this is a modal test' })
|
||||
div({ id: 'modal' }, [
|
||||
div({ _: 'this is a modal test' })
|
||||
])
|
||||
]),
|
||||
h.div({ id: 'spinner', class: 'spin' })
|
||||
div({ id: 'spinner', class: 'spin' })
|
||||
]);
|
||||
|
||||
// create top level app areas
|
||||
let bound = h.bind($('app-body'), [
|
||||
let bound = bind($('app-body'), [
|
||||
// display and action areas
|
||||
h.div({ id: 'tools' }, [
|
||||
h.div({ id: 'sketchtools', class: "tools sketch-on" }),
|
||||
h.div({ id: 'objecttools', class: "tools sketch-off" }),
|
||||
h.div({ id: 'logger', onmouseover() { log.show() } }),
|
||||
div({ id: 'tools' }, [
|
||||
div({ id: 'sketchtools', class: "tools sketch-on" }),
|
||||
div({ id: 'objecttools', class: "tools sketch-off" }),
|
||||
div({ id: 'logger', onmouseover() { log.show() } }),
|
||||
]),
|
||||
h.div({ id: 'grouplist' }),
|
||||
h.div({ id: 'selectlist' }),
|
||||
h.div({ id: 'download', class: "hide" })
|
||||
div({ id: 'grouplist' }),
|
||||
div({ id: 'selectlist' }),
|
||||
div({ id: 'download', class: "hide" })
|
||||
]);
|
||||
|
||||
let { sketchtools, grouplist, selectlist, logger } = bound;
|
||||
|
||||
function tool_item(icon, help, fn) {
|
||||
return h.div({ onclick: fn, class: "tool" }, [ bicon(icon), h.div([ h.label(help) ]) ]);
|
||||
return div({ onclick: fn, class: "tool" }, [ bicon(icon), div([ label(help) ]) ]);
|
||||
}
|
||||
|
||||
// bind sketch chiclets
|
||||
h.bind(sketchtools, h.div([
|
||||
bind(sketchtools, div([
|
||||
tool_item('bi-plus', 'New Sketch', add.sketch),
|
||||
tool_item('bi-circle', 'Add Circle', () => { sketch.selected.one?.add.circle() }),
|
||||
tool_item('bi-square', 'Add Rectangle', () => { sketch.selected.one?.add.rectangle() }),
|
||||
|
|
@ -572,7 +575,7 @@ function ui_build() {
|
|||
]));
|
||||
|
||||
// bind object chiclets
|
||||
h.bind(objecttools, h.div([
|
||||
bind(objecttools, div([
|
||||
tool_item('bi-pencil', 'New Sketch', add.sketch),
|
||||
tool_item('bi-box', 'New Cube', add.cube),
|
||||
tool_item('bi-database', 'New Cylinder', add.cylinder),
|
||||
|
|
@ -584,26 +587,26 @@ function ui_build() {
|
|||
]));
|
||||
|
||||
// create slid in/out logging window
|
||||
h.bind(logger, [ h.div({ id: 'logtext' }) ]);
|
||||
bind(logger, [ div({ id: 'logtext' }) ]);
|
||||
|
||||
// for group/model/box/area/mesh dashboard grids
|
||||
function grid(v1, v2, side = [ "pos", "rot"], top = [ "X", "Y", "Z" ], root) {
|
||||
let v = 'val', l = 'lbl';
|
||||
let [ X, Y, Z ] = top;
|
||||
let [ pos, rot ] = side;
|
||||
return h.div({ class: "grid"}, [
|
||||
h.div({ _: "" }),
|
||||
h.div({ _: X, class: "top noselect", id: root ? [ root, l, X ] : und }),
|
||||
h.div({ _: Y, class: "top noselect", id: root ? [ root, l, Y ] : und }),
|
||||
h.div({ _: Z, class: "top noselect", id: root ? [ root, l, Z ] : und }),
|
||||
h.div({ _: side[0], class: "side noselect" }),
|
||||
h.label({ _: v1[0], id: root ? [ root, v, X, pos ] : und }),
|
||||
h.label({ _: v1[1], id: root ? [ root, v, Y, pos ] : und }),
|
||||
h.label({ _: v1[2], id: root ? [ root, v, Z, pos ] : und }),
|
||||
h.div({ _: side[1], class: "side noselect" }),
|
||||
h.label({ _: v2[0], id: root ? [ root, v, X, rot ] : und }),
|
||||
h.label({ _: v2[1], id: root ? [ root, v, Y, rot ] : und }),
|
||||
h.label({ _: v2[2], id: root ? [ root, v, Z, rot ] : und }),
|
||||
return div({ class: "grid"}, [
|
||||
div({ _: "" }),
|
||||
div({ _: X, class: "top noselect", id: root ? [ root, l, X ] : und }),
|
||||
div({ _: Y, class: "top noselect", id: root ? [ root, l, Y ] : und }),
|
||||
div({ _: Z, class: "top noselect", id: root ? [ root, l, Z ] : und }),
|
||||
div({ _: side[0], class: "side noselect" }),
|
||||
label({ _: v1[0], id: root ? [ root, v, X, pos ] : und }),
|
||||
label({ _: v1[1], id: root ? [ root, v, Y, pos ] : und }),
|
||||
label({ _: v1[2], id: root ? [ root, v, Z, pos ] : und }),
|
||||
div({ _: side[1], class: "side noselect" }),
|
||||
label({ _: v2[0], id: root ? [ root, v, X, rot ] : und }),
|
||||
label({ _: v2[1], id: root ? [ root, v, Y, rot ] : und }),
|
||||
label({ _: v2[2], id: root ? [ root, v, Z, rot ] : und }),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -612,16 +615,16 @@ function ui_build() {
|
|||
let v = 'val', l = 'lbl';
|
||||
let [ X, Y ] = top;
|
||||
let [ pos, rot ] = side;
|
||||
return h.div({ class: "grid grid2"}, [
|
||||
h.div({ _: "" }),
|
||||
h.div({ _: X, class: "top noselect", id: root ? [ root, l, X ] : und }),
|
||||
h.div({ _: Y, class: "top noselect", id: root ? [ root, l, Y ] : und }),
|
||||
h.div({ _: side[0], class: "side noselect" }),
|
||||
h.label({ _: v1[0], id: root ? [ root, v, X, pos ] : und }),
|
||||
h.label({ _: v1[1], id: root ? [ root, v, Y, pos ] : und }),
|
||||
h.div({ _: side[1], class: "side noselect" }),
|
||||
h.label({ _: v2[0], id: root ? [ root, v, X, rot ] : und }),
|
||||
h.label({ _: v2[1], id: root ? [ root, v, Y, rot ] : und }),
|
||||
return div({ class: "grid grid2"}, [
|
||||
div({ _: "" }),
|
||||
div({ _: X, class: "top noselect", id: root ? [ root, l, X ] : und }),
|
||||
div({ _: Y, class: "top noselect", id: root ? [ root, l, Y ] : und }),
|
||||
div({ _: side[0], class: "side noselect" }),
|
||||
label({ _: v1[0], id: root ? [ root, v, X, pos ] : und }),
|
||||
label({ _: v1[1], id: root ? [ root, v, Y, pos ] : und }),
|
||||
div({ _: side[1], class: "side noselect" }),
|
||||
label({ _: v2[0], id: root ? [ root, v, X, rot ] : und }),
|
||||
label({ _: v2[1], id: root ? [ root, v, Y, rot ] : und }),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -629,13 +632,13 @@ function ui_build() {
|
|||
function grid1(vals, side = [ "X", "Y"], top = "center", root, post) {
|
||||
let v = 'val', l = 'lbl';
|
||||
let [ X, Y ] = side;
|
||||
return h.div({ class: "grid grid1"}, [
|
||||
h.div({ _: "" }),
|
||||
h.div({ _: top, class: "top noselect", id: root ? [ root, l ] : und }),
|
||||
h.div({ _: side[0], class: "side noselect" }),
|
||||
h.label({ _: vals[0], id: root ? [ root, v, X, post ] : und }),
|
||||
h.div({ _: side[1], class: "side noselect" }),
|
||||
h.label({ _: vals[1], id: root ? [ root, v, Y, post ] : und }),
|
||||
return div({ class: "grid grid1"}, [
|
||||
div({ _: "" }),
|
||||
div({ _: top, class: "top noselect", id: root ? [ root, l ] : und }),
|
||||
div({ _: side[0], class: "side noselect" }),
|
||||
label({ _: vals[0], id: root ? [ root, v, X, post ] : und }),
|
||||
div({ _: side[1], class: "side noselect" }),
|
||||
label({ _: vals[1], id: root ? [ root, v, Y, post ] : und }),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -649,7 +652,7 @@ function ui_build() {
|
|||
let selHas = selection.contains;
|
||||
// map groups to divs
|
||||
let groups = api.group.list()
|
||||
.map(g => h.div([
|
||||
.map(g => div([
|
||||
h.button({ _: g.name || `group`, title: g.id,
|
||||
class: [ "group", selHas(g) ? 'selected' : undefined ],
|
||||
onclick(e) {
|
||||
|
|
@ -657,7 +660,7 @@ function ui_build() {
|
|||
selection.toggle(g);
|
||||
}
|
||||
}),
|
||||
h.div({ class: "models"},
|
||||
div({ class: "models"},
|
||||
// map models to buttons
|
||||
g.models.map(m => [
|
||||
h.button({ _: m.file || m.id,
|
||||
|
|
@ -691,11 +694,11 @@ function ui_build() {
|
|||
]));
|
||||
let sketches = api.sketch.list();
|
||||
if (sketches.length) {
|
||||
let sg = h.div([
|
||||
let sg = div([
|
||||
h.button({ _: 'sketch',
|
||||
class: [ "group" ],
|
||||
}),
|
||||
sketches.map(sketch => h.div({ class: "models"}, [
|
||||
sketches.map(sketch => div({ class: "models"}, [
|
||||
h.button({ _: sketch.file || sketch.id,
|
||||
class: [ selHas(sketch) ? 'selected' : undefined ],
|
||||
onclick(e) {
|
||||
|
|
@ -725,7 +728,7 @@ function ui_build() {
|
|||
]);
|
||||
groups.push(sg);
|
||||
}
|
||||
h.bind(grouplist, groups);
|
||||
bind(grouplist, groups);
|
||||
}
|
||||
|
||||
// return function bound to field editing clicks
|
||||
|
|
@ -758,8 +761,8 @@ function ui_build() {
|
|||
onclick();
|
||||
}
|
||||
};
|
||||
let { tempval } = api.modal.show(title, h.div({ class: "tempedit" }, [
|
||||
h.input({ id: 'tempval', value, size: 10, onkeydown }),
|
||||
let { tempval } = api.modal.show(title, div({ class: "tempedit" }, [
|
||||
input({ id: 'tempval', value, size: 10, onkeydown }),
|
||||
h.button({ _: 'set', onclick })
|
||||
]));
|
||||
tempval.setSelectionRange(0, tempval.value.length);
|
||||
|
|
@ -780,8 +783,8 @@ function ui_build() {
|
|||
data: `${label}_data`
|
||||
};
|
||||
return [
|
||||
h.button({ id: map.show, class: "side" }, [ h.div(label)] ),
|
||||
h.div({ id: map.data }, [
|
||||
h.button({ id: map.show, class: "side" }, [ div(label)] ),
|
||||
div({ id: map.data }, [
|
||||
h.button({ id: map.hide, _: label, title }),
|
||||
grid
|
||||
])
|
||||
|
|
@ -794,7 +797,7 @@ function ui_build() {
|
|||
let s_mdl = selection.models(true);
|
||||
|
||||
if (!(sketch || s_grp.length)) {
|
||||
return h.bind(selectlist, []);
|
||||
return bind(selectlist, []);
|
||||
}
|
||||
|
||||
// render sketch options
|
||||
|
|
@ -828,8 +831,8 @@ function ui_build() {
|
|||
} else if (item.type === 'circle') {
|
||||
return [
|
||||
sblock('circle', '', grid1(
|
||||
[ item.points, item.rotation || 0 ],
|
||||
[ "points", "rotation" ], "value", 'circle', 'other'
|
||||
[ item.points || sel_sobj.poly.length, item.rotation || 0 ],
|
||||
[ "points", "rotation" ], "Value", 'circle', 'other'
|
||||
)),
|
||||
sblock('circle', '', grid2(
|
||||
util.extract(item.center, map),
|
||||
|
|
@ -839,7 +842,7 @@ function ui_build() {
|
|||
];
|
||||
}
|
||||
});
|
||||
let bound = h.bind(selectlist, [
|
||||
let bound = bind(selectlist, [
|
||||
...si_vars,
|
||||
...sk_vars
|
||||
]);
|
||||
|
|
@ -916,36 +919,36 @@ function ui_build() {
|
|||
|
||||
let t_vert = s_mdl.map(m => m.vertices).reduce((a,v) => a+v);
|
||||
let t_face = s_mdl.map(m => m.faces).reduce((a,v) => a+v);
|
||||
let h_msh = [h.div([
|
||||
let h_msh = [div([
|
||||
h.button({ _: `mesh` }),
|
||||
h.div({ class: ["grid","grid1"]}, [
|
||||
h.div({ _: "" }),
|
||||
h.div({ _: "count", class: "top" }),
|
||||
h.div({ _: "vertex", class: "side" }),
|
||||
h.label({ _: util.comma(t_vert) }),
|
||||
h.div({ _: "face", class: "side" }),
|
||||
h.label({ _: util.comma(t_face) }),
|
||||
div({ class: ["grid","grid1"]}, [
|
||||
div({ _: "" }),
|
||||
div({ _: "count", class: "top" }),
|
||||
div({ _: "vertex", class: "side" }),
|
||||
label({ _: util.comma(t_vert) }),
|
||||
div({ _: "face", class: "side" }),
|
||||
label({ _: util.comma(t_face) }),
|
||||
])
|
||||
])];
|
||||
|
||||
let m_viz = !s_mdl.map(m => !m.visible()).reduce((v,b) => v || b);
|
||||
let h_ops = [h.div([
|
||||
let h_ops = [div([
|
||||
h.button({ _: `ops` }),
|
||||
h.div({ class: ["grid","grid0"]}, [
|
||||
h.div({ class: [ 'square' ],
|
||||
div({ class: ["grid","grid0"]}, [
|
||||
div({ class: [ 'square' ],
|
||||
onclick(e) {
|
||||
api.selection.visible(!m_viz);
|
||||
update_selector();
|
||||
}
|
||||
}, [ h.raw(m_viz ? eye_open : eye_closed) ]),
|
||||
h.div({ class: [ 'square' ],
|
||||
div({ class: [ 'square' ],
|
||||
onclick(e) { api.selection.delete() }
|
||||
}, [ h.raw(trash) ])
|
||||
])
|
||||
])];
|
||||
|
||||
// bind elements to selectlist div
|
||||
let bound = h.bind(selectlist, [
|
||||
let bound = bind(selectlist, [
|
||||
...h_grp,
|
||||
...h_ara,
|
||||
...h_msh,
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ mesh.sketch = class MeshSketch extends mesh.object {
|
|||
},
|
||||
|
||||
items() {
|
||||
return sketch.selection.mesh_items().map(i => c.sketch_item.item);
|
||||
return sketch.selection.mesh_items().map(c => c.sketch_item);
|
||||
},
|
||||
|
||||
count() {
|
||||
|
|
@ -232,10 +232,11 @@ mesh.sketch = class MeshSketch extends mesh.object {
|
|||
});
|
||||
},
|
||||
|
||||
item(item, opt = {}) {
|
||||
item(item) {
|
||||
item.group = item.group || mesh.util.uuid();
|
||||
sketch.items.push(item);
|
||||
sketch.render_defer();
|
||||
api.selection.update_defer();
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
|
@ -277,31 +278,30 @@ mesh.sketch = class MeshSketch extends mesh.object {
|
|||
},
|
||||
|
||||
nest() {
|
||||
let items = sketch.selection.mesh_items();
|
||||
let items = sketch.selection.items();
|
||||
if (items.length < 2) return log('operation requires at least 2 items');
|
||||
let polys = items.map(i => i.sketch_item.poly).clone(true);
|
||||
let polys = items.map(si => si.poly.annotate({group:si.group}).clone(true, ['group']));
|
||||
let union = POLYS.nest(POLYS.flatten(polys, [], true));
|
||||
sketch.selection.delete();
|
||||
for (let poly of union) {
|
||||
sketch.add.polygon({ poly });
|
||||
sketch.add.polygon({ poly, group:poly.group });
|
||||
}
|
||||
},
|
||||
|
||||
evenodd() {
|
||||
let items = sketch.selection.mesh_items();
|
||||
// if (items.length < 2) return log('operation requires at least 2 items');
|
||||
let polys = items.map(i => i.sketch_item.poly);
|
||||
let even = POLYS.nest(POLYS.flatten(polys.clone(true), [], true));
|
||||
let odd = POLYS.nest(POLYS.flatten(even.clone(true), [], true).filter(p => p.depth > 0));
|
||||
let items = sketch.selection.items();
|
||||
let polys = items.map(si => si.poly.annotate({group:si.group}));
|
||||
let even = POLYS.nest(POLYS.flatten(polys.clone(true,['group']), [], true));
|
||||
let odd = POLYS.nest(POLYS.flatten(even.clone(true,['group']), [], true).filter(p => p.depth > 0));
|
||||
sketch.selection.delete();
|
||||
for (let poly of [...even, ...odd]) {
|
||||
sketch.add.polygon({ poly });
|
||||
sketch.add.polygon({ poly, group:poly.group });
|
||||
}
|
||||
},
|
||||
|
||||
flatten() {
|
||||
let items = sketch.selection.mesh_items();
|
||||
let polys = items.map(i => i.sketch_item.poly).clone(true);
|
||||
let items = sketch.selection.items();
|
||||
let polys = items.map(si => si.poly.annotate({group:si.group}).clone(true, ['group']));
|
||||
let flat = POLYS.flatten(polys, [], true);
|
||||
sketch.selection.delete();
|
||||
for (let poly of flat) {
|
||||
|
|
@ -566,6 +566,10 @@ class SketchItem {
|
|||
return "sketch_item";
|
||||
}
|
||||
|
||||
get group() {
|
||||
return this.item.group;
|
||||
}
|
||||
|
||||
get selected() {
|
||||
return this.item.selected;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -699,6 +699,7 @@ button:hover {
|
|||
border-radius: 3px;
|
||||
padding: 3px;
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
gap: 2px 2px;
|
||||
grid: min-content 1fr 1fr / min-content 1fr 1fr 1fr;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue