From 0346393f7c80691e3d2bfe6ef46621fe85727531 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Mon, 29 Jul 2024 17:37:27 -0400 Subject: [PATCH] refactor menu to use helpers, add hot key display --- src/mesh/api.js | 179 +++++++++++++++++++++------------------------ src/mesh/build.js | 141 +++++++++++++++++++---------------- src/mesh/split.js | 8 +- web/mesh/index.css | 26 ++++--- 4 files changed, 180 insertions(+), 174 deletions(-) diff --git a/src/mesh/api.js b/src/mesh/api.js index 7a146556..adc21f15 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -297,7 +297,7 @@ const pattern = { * @param {int} count number of entities * @param {float[]} center [x,y,z] */ - circle(count, center) { + circle() { async function doit(count = 3, center = [0,0], zdelta = 0) { api.modal.hide(); let models = selection.models(); @@ -319,33 +319,29 @@ const pattern = { await api.tool.regroup(modnu); } } - if (!count) { - api.modal.dialog({ - title: "circle pattern", - body: [ h.div({ class: "addgear" }, [ - h.label('total count'), - h.input({ value: 3, size: 5, id: "_count" }), - h.label('center x,y'), - h.input({ value: "0,0", size: 5, id: "_center" }), - // h.label('z delta'), - // h.input({ value: "0", size: 5, id: "_zdelta" }), - h.button({ _: "create", onclick() { - let { _count, _center, _zdelta } = api.modal.bound; - doit( - parseInt(_count.value), - _center.value.split(',').map(v => parseFloat(v)), - // parseFloat(_zdelta.value) - ); - } }) - ]) ] - }); - api.modal.bound._count.focus(); - } else { - doit(count, origin); - } + api.modal.dialog({ + title: "circle pattern", + body: [ h.div({ class: "addgear" }, [ + h.label('total count'), + h.input({ value: 3, size: 5, id: "_count" }), + h.label('center x,y'), + h.input({ value: "0,0", size: 5, id: "_center" }), + // h.label('z delta'), + // h.input({ value: "0", size: 5, id: "_zdelta" }), + h.button({ _: "create", onclick() { + let { _count, _center, _zdelta } = api.modal.bound; + doit( + parseInt(_count.value), + _center.value.split(',').map(v => parseFloat(v)), + // parseFloat(_zdelta.value) + ); + } }) + ]) ] + }); + api.modal.bound._count.focus(); }, - grid(x, y, xs, ys) { + grid() { async function doit(x = 3, y = 3, xs = 10, ys = 10) { api.modal.hide(); let models = selection.models(); @@ -364,32 +360,28 @@ const pattern = { await api.tool.regroup(modnu); } } - if (!(x && y)) { - api.modal.dialog({ - title: "grid pattern", - body: [ h.div({ class: "addgear" }, [ - h.label('x count'), - h.input({ value: 3, size: 5, id: "_x" }), - h.label('y count'), - h.input({ value: 3, size: 5, id: "_y" }), - h.label('x spacing'), - h.input({ value: 20, size: 5, id: "_xs" }), - h.label('y spacing'), - h.input({ value: 20, size: 5, id: "_ys" }), - h.button({ _: "create", onclick() { - doit( - parseInt(api.modal.bound._x.value) || 3, - parseInt(api.modal.bound._y.value) || 3, - parseFloat(api.modal.bound._xs.value) || 3, - parseFloat(api.modal.bound._ys.value) || 3, - ); - } }) - ]) ] - }); - api.modal.bound._x.focus(); - } else { - doit(x, y, xs, ys); - } + api.modal.dialog({ + title: "grid pattern", + body: [ h.div({ class: "addgear" }, [ + h.label('x count'), + h.input({ value: 3, size: 5, id: "_x" }), + h.label('y count'), + h.input({ value: 3, size: 5, id: "_y" }), + h.label('x spacing'), + h.input({ value: 20, size: 5, id: "_xs" }), + h.label('y spacing'), + h.input({ value: 20, size: 5, id: "_ys" }), + h.button({ _: "create", onclick() { + doit( + parseInt(api.modal.bound._x.value) || 3, + parseInt(api.modal.bound._y.value) || 3, + parseFloat(api.modal.bound._xs.value) || 3, + parseFloat(api.modal.bound._ys.value) || 3, + ); + } }) + ]) ] + }); + api.modal.bound._x.focus(); } }; @@ -706,7 +698,7 @@ let add = { api.modal.dialog({ title: "vertices", body: [ h.div({ class: "addact" }, [ - h.textarea({ value: '', size: 5, id: "genvrt" }), + h.textarea({ value: '', cols:30, rows:4, id: "genvrt" }), h.button({ _: "create", onclick() { const vert = (api.modal.bound.genvrt.value) .split(',').map(v => parseFloat(v)).toFloat32(); @@ -725,10 +717,11 @@ let add = { const nmdl = new mesh.model({ file: "box", mesh: vert }); const ngrp = group.new([ nmdl ]); ngrp.scale(10, 10, 10).floor(); + selection.set([ nmdl ]); return ngrp; }, - cylinder(opt = {}) { + cylinder() { function gencyl(opt = {}) { let { diameter, height, facets, chamfer, bore } = opt; api.modal.hide(); @@ -743,43 +736,40 @@ let add = { const vert = cyl.extrude(height, { chamfer }).toFloat32(); const nmdl = new mesh.model({ file: "cylinder", mesh: vert }); const ngrp = group.new([ nmdl ]); + selection.set([ nmdl ]); ngrp.floor(); } } - if (opt.facets && opt.diameter && opt.height) { - return gencyl(opt.diameter, opt.height, opt.facets); - } else { - api.modal.dialog({ - title: "cylinder", - body: [ h.div({ class: "addgear" }, [ - h.label('diameter'), - h.input({ value: opt.diameter || 20, size: 5, id: "_diameter" }), - h.label('height'), - h.input({ value: opt.height || 10, size: 5, id: "_height" }), - h.hr(), - h.label('bore'), - h.input({ value: opt.facets || 0, size: 5, id: "_bore" }), - h.label('facets'), - h.input({ value: opt.facets || 0, size: 5, id: "_facets" }), - h.label('chamfer'), - h.input({ value: opt.chamfer || 0, size: 5, id: "_chamfer" }), - h.button({ _: "create", onclick() { - const { _diameter, _facets, _height, _chamfer, _bore } = api.modal.bound; - gencyl({ - diameter: parseFloat(_diameter.value), - height: parseFloat(_height.value), - facets: parseInt(_facets.value), - bore: parseFloat(_bore.value), - chamfer: parseFloat(_chamfer.value), - }); - } }) - ]) ] - }); - api.modal.bound._diameter.focus(); - } + api.modal.dialog({ + title: "cylinder", + body: [ h.div({ class: "addgear" }, [ + h.label('diameter'), + h.input({ value: 20, size: 5, id: "_diameter" }), + h.label('height'), + h.input({ value: 10, size: 5, id: "_height" }), + h.hr(), + h.label('bore'), + h.input({ value: 0, size: 5, id: "_bore" }), + h.label('facets'), + h.input({ value: 0, size: 5, id: "_facets" }), + h.label('chamfer'), + h.input({ value: 0, size: 5, id: "_chamfer" }), + h.button({ _: "create", onclick() { + const { _diameter, _facets, _height, _chamfer, _bore } = api.modal.bound; + gencyl({ + diameter: parseFloat(_diameter.value), + height: parseFloat(_height.value), + facets: parseInt(_facets.value), + bore: parseFloat(_bore.value), + chamfer: parseFloat(_chamfer.value), + }); + } }) + ]) ] + }); + api.modal.bound._diameter.focus(); }, - gear(opt = {}) { + gear() { function gengear(bound) { const { _teeth, _module, _angle, _twist, _shaft, _offset, _height, _chamfer } = bound; api.modal.hide(); @@ -796,6 +786,7 @@ let add = { }).then(gear => { const nmdl = new mesh.model({ file: "gear", mesh: gear.toFloat32() }); const ngrp = group.new([ nmdl ]); + selection.set([ nmdl ]); ngrp.floor(); Object.assign(last, params); api.prefs.save(); @@ -806,25 +797,25 @@ let add = { title: "gear generator", body: [ h.div({ class: "addgear" }, [ h.label('number of teeth'), - h.input({ value: opt.teeth || last.teeth || 20, size: 5, id: "_teeth" }), + h.input({ value: last.teeth || 20, size: 5, id: "_teeth" }), h.label('shaft diameter'), - h.input({ value: opt.shaft || last.shaft || 5, size: 5, id: "_shaft" }), + h.input({ value: last.shaft || 5, size: 5, id: "_shaft" }), h.label('z height'), - h.input({ value: opt.height || last.height || 15, size: 5, id: "_height" }), + h.input({ value: last.height || 15, size: 5, id: "_height" }), h.label({ _:'chamfer', title:"negative values apply chamfer to the bottom only" }), - h.input({ value: opt.chamfer || last.chamfer || 0, size: 5, id: "_chamfer" }), + h.input({ value: last.chamfer || 0, size: 5, id: "_chamfer" }), h.hr(), h.code("all other settings must"), h.code("match for gears to mesh"), h.hr(), h.label('module (diam / teeth)'), - h.input({ value: opt.module || last.module || 3, size: 5, id: "_module" }), + h.input({ value: last.module || 3, size: 5, id: "_module" }), h.label('pressure angle'), - h.input({ value: opt.angle || last.angle || 20, size: 5, id: "_angle" }), + h.input({ value: last.angle || 20, size: 5, id: "_angle" }), h.label('twist angle (helix)'), - h.input({ value: opt.twist || last.twist || 0, size: 5, id: "_twist" }), + h.input({ value: last.twist || 0, size: 5, id: "_twist" }), h.label('tooth offset (play)'), - h.input({ value: opt.offset || last.offset || 0.1, size: 5, id: "_offset" }), + h.input({ value: last.offset || 0.1, size: 5, id: "_offset" }), h.button({ _: "create", onclick() { gengear(api.modal.bound) } }) ]) ] }); diff --git a/src/mesh/build.js b/src/mesh/build.js index 5a578fc6..0eba73f5 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -8,8 +8,9 @@ gapp.register("mesh.build", [], (root, exports) => { const { broker } = gapp; -const { mesh } = root; +const { mesh, moto } = root; const { api, util } = mesh; +const { space } = moto; const devel = api.isDebug; let call = broker.send; @@ -339,6 +340,20 @@ api.settings = function() { ] )); } +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-') ? + h.i({ class: ["bi", s ]}) : + h.div(s) + )); + } + return h.div({ onclick: fn }, [ + h.div({ _: text, class: "grow" }), + short + ].filter(o => o)); +} + // create html elements function ui_build() { let { file, selection, mode, tool, sketch, prefs, add } = api; @@ -351,52 +366,54 @@ function ui_build() { h.div({ class: "menu" }, [ h.div('File'), h.div({ class: "menu-items" }, [ - h.div({ _: 'Import', onclick: file.import }, [ - h.input({ - id: "import", type: "file", class: ["hide"], multiple: true, - onchange(evt) { broker.send.load_files(evt.target.files) } - }) - ]), - h.div({ _: 'Export', onclick: file.export }), + h.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(), - h.div({ _: "Slicer", onclick: api.kirimoto }), + menu_item('Slicer', api.kirimoto), h.hr(), - h.div({ _: 'Close', onclick: window.close }), + menu_item('Close', window.close), ]) ]), h.div({ class: "menu sketch-on" }, [ h.div('Edit'), h.div({ class: "menu-items" }, [ - h.div({ _: 'Add Circle', onclick() { sketch.selected.one?.add.circle() } }), - h.div({ _: 'Add Rectangle', onclick() { sketch.selected.one?.add.rectangle() } }), + menu_item('Add Circle', () => { sketch.selected.one?.add.circle() }), + menu_item('Add Rectangle', () => { sketch.selected.one?.add.rectangle() }), h.hr(), - h.div({ _: 'Add Sketch', onclick: add.sketch }), + menu_item('Add Sketch', add.sketch), h.hr(), - h.div({ _: 'Delete', onclick: api.selection.delete }), + menu_item('Delete', api.selection.delete, 'bi-backspace'), ]) ]), h.div({ class: "menu sketch-off" }, [ h.div('Edit'), h.div({ class: "menu-items" }, [ - h.div({ _: 'Add Cylinder', onclick() { add.cylinder() } }), - h.div({ _: 'Add Cube', onclick() { add.cube() } }), - h.div({ _: 'Add Gear', onclick() { add.gear() } }), + menu_item('Add Cylinder', add.cylinder), + menu_item('Add Cube', add.cube), + menu_item('Add Gear', add.gear), h.hr(), - h.div({ _: 'Add Sketch', onclick: add.sketch }), - devel ? h.div({ _: 'Add Vertices', onclick: add.input }) : undefined, + menu_item('Add Sketch', add.sketch), + devel ? menu_item('Add Vertices', add.input) : undefined, h.hr(), - h.div({ _: 'Delete', onclick: api.selection.delete }), + menu_item('Delete', api.selection.delete, 'bi-backspace'), ]) ]), h.div({ class: "menu" }, [ h.div('View'), h.div({ class: "menu-items" }, [ - h.div({ _: 'Bounds', onclick() { selection.boundsBox({ toggle: true }) } }), - h.div({ _: 'Normals', onclick() { api.normals() } }), - h.div({ _: 'Wireframes', onclick() { api.wireframe() } }), + menu_item('Bounds', () => { selection.boundsBox({ toggle: true }) }, 'B'), + menu_item('Normals', () => { api.normals() }, 'N'), + menu_item('Wireframes', () => { api.wireframe() }, 'W'), h.hr(), - h.div({ _: 'Gridlines', onclick() { api.grid() } }), - h.div({ _: 'Messages', onclick() { api.log.toggle({ spinner: false }) } }), + menu_item('Gridlines', () => { api.grid() }, 'G'), + menu_item('Messages', () => { api.log.toggle({ spinner: false }) }, 'L'), + h.hr(), + menu_item('Home', space.view.home, 'H'), + menu_item('Top', space.view.top, 'T'), ]) ]), h.div({ class: "menu" }, [ @@ -415,75 +432,75 @@ function ui_build() { h.div({ class: "menu sketch-on" }, [ h.div('Object'), h.div({ class: "menu-items" }, [ - h.div({ _: 'Group', onclick: sketch.arrange.group }), - h.div({ _: 'Ungroup', onclick: sketch.arrange.ungroup }), + menu_item('Group', sketch.arrange.group), + menu_item('Ungroup', sketch.arrange.ungroup), h.hr(), - h.div({ _: 'Center', onclick: sketch.arrange.center }), + menu_item('Center', sketch.arrange.center), h.hr(), - h.div({ _: 'Flip Horizontal', onclick: sketch.arrange.fliph }), - h.div({ _: 'Flip Vertical', onclick: sketch.arrange.flipv }), + menu_item('Flip Horizontal', sketch.arrange.fliph), + menu_item('Flip Vertical', sketch.arrange.flipv), h.hr(), - h.div({ _: 'Raise', onclick: sketch.arrange.up }), - h.div({ _: 'Lower', onclick: sketch.arrange.down }), - h.div({ _: 'Raise to Top', onclick: sketch.arrange.top }), - h.div({ _: 'Lower to Bottom', onclick: sketch.arrange.bottom }), + 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" }, [ - h.div({ _: 'Regroup', onclick: tool.regroup }), - h.div({ _: 'Isolate', onclick: tool.isolate }), + menu_item('Regroup', tool.regroup), + menu_item('Isolate', tool.isolate), h.hr(), - h.div({ _: 'Duplicate', onclick: tool.duplicate }), - h.div({ _: 'Mirror', onclick: tool.mirror }), - h.div({ _: 'Merge', onclick: tool.merge }), - h.div({ _: 'Split', onclick: call.edit_split }), + 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(), - h.div({ _: 'Union', onclick: tool.union }), - h.div({ _: 'Subtract', onclick: tool.subtract }), - h.div({ _: 'Intersect', onclick: tool.intersect }), - h.div({ _: 'Difference', onclick: tool.difference }), + menu_item('Union', tool.union), + menu_item('Subtract', tool.subtract), + menu_item('Intersect', tool.intersect), + menu_item('Difference', tool.difference), h.hr(), - h.div({ _: 'Copy Circle', onclick: api.pattern.circle }), - h.div({ _: 'Copy Grid', onclick: api.pattern.grid }), + 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" }, [ - h.div({ _: 'Flip Normals', onclick: tool.invert }), - h.div({ _: 'Analyze', onclick: tool.analyze }), + menu_item('Flip Normals', tool.invert), + menu_item('Analyze', tool.analyze), // h.div({ _: 'Patch', onclick: tool.repair }), // h.div({ _: 'rebuild', onclick: tool.rebuild }), - h.div({ _: 'Clean', onclick: tool.clean }), + menu_item('Clean', tool.clean), ]) ]), h.div({ class: "menu sketch-on" }, [ h.div('Shape'), h.div({ class: "menu-items" }, [ - h.div({ _: 'Duplicate', onclick: api.tool.duplicate }), - h.div({ _: 'Extrude', onclick: sketch.extrude }), + menu_item('Duplicate', tool.duplicate, ['bi-shift','D']), + menu_item('Extrude', sketch.extrude, ['bi-shift','E']), h.hr(), - h.div({ _: 'Union', onclick: sketch.boolean.union }), - h.div({ _: 'Intersect', onclick: sketch.boolean.intersect }), - h.div({ _: 'Difference', onclick: sketch.boolean.difference }), + menu_item('Union', sketch.boolean.union), + menu_item('Intersect', sketch.boolean.intersect), + menu_item('Difference', sketch.boolean.difference), h.hr(), - h.div({ _: 'Nest', onclick: sketch.boolean.nest }), - h.div({ _: 'Flatten', onclick: sketch.boolean.flatten }), - h.div({ _: 'Even Odd', onclick: sketch.boolean.evenodd }), + 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" }, [ - h.div({ _: 'About', onclick() { api.welcome(mesh.version) } }), + menu_item('About', () => { api.welcome(mesh.version) }), h.hr(), - h.div({ _: 'Documentation', onclick: api.help }), - h.div({ _: 'Official Site', onclick: api.gridspace }), - h.div({ _: "Donate", onclick: api.donate }), + menu_item('Documentation', api.help), + menu_item('Official Site', api.gridspace), + menu_item('Donate', api.donate), h.hr(), - h.div({ _: 'Versions', onclick: api.version }), + menu_item('Versions', api.version), ]) ]), ]); diff --git a/src/mesh/split.js b/src/mesh/split.js index 642d524a..5b8e3f1d 100644 --- a/src/mesh/split.js +++ b/src/mesh/split.js @@ -28,9 +28,6 @@ let split = { return; } let { api, util } = mesh; - // highlight button - let button = event.target; - button.classList.add('selected'); // create split plane visual let geo, mat, obj = new Mesh( geo = new PlaneGeometry(1,1), @@ -45,7 +42,7 @@ let split = { // hide until first hover obj.visible = false; // enable temp mode - let state = split.state = { button, obj }; + let state = split.state = { obj }; let models = state.models = api.selection.models(); let meshes = models.map(m => m.mesh); // for split and lay flat modes @@ -92,8 +89,7 @@ let split = { return; } let space = moto.space; - let { button, obj } = split.state; - button.classList.remove('selected'); + let { obj } = split.state; space.scene.remove(obj); space.mouse.onHover(undefined); isActive = split.state = undefined; diff --git a/web/mesh/index.css b/web/mesh/index.css index f1e58484..8267c5ee 100644 --- a/web/mesh/index.css +++ b/web/mesh/index.css @@ -173,11 +173,11 @@ button:hover { padding: 0 !important; } -.menu { +.menu, #top-right > div { cursor: default; align-self: stretch; align-items: center; - padding: 5px 12px 5px 12px; + padding: 6px 12px 6px 12px; border-radius: 6px; white-space: nowrap; user-select: none; @@ -214,10 +214,19 @@ button:hover { } .menu-items > div { + gap: 15px; border-radius: 6px; padding: 4px 8px 4px 8px; } +.menu-items .grow { + flex-grow: 1; +} + +.menu-items .short { + gap: 1px; +} + #top-mid { flex-grow: 1; } @@ -230,13 +239,6 @@ button:hover { #top-right > div { gap: 8px; - align-self: stretch; - align-items: center; - padding: 5px 12px 5px 12px; - border-radius: 6px; - cursor: default; - user-select: none; - white-space: nowrap; } /** specific modal dialogs */ @@ -357,11 +359,11 @@ button:hover { /** welcome dialog **/ .welcome { + font-size: smaller; flex-direction: column; align-items: center; padding: 5px; gap: 5px; - font-size: smaller; } .welcome a { @@ -469,9 +471,9 @@ button:hover { background-color: rgba(0,255,0,0.8); } -.dark .head { +/* .dark .head { color: #000 !important; -} +} */ /** slide in/out logging window **/