From 6d202aafaad421e0c58648abf17bdb83b72b9dd5 Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 24 Oct 2025 23:56:06 -0400 Subject: [PATCH] add mesh script tool --- src/main/mesh.js | 27 +++++++++++++++++++++++++-- src/mesh/api.js | 10 +++++++--- src/mesh/build.js | 7 ++++++- src/mesh/script.js | 31 +++++++++++++++++++++++++++++++ web/mesh/index.css | 24 ++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 src/mesh/script.js diff --git a/src/main/mesh.js b/src/main/mesh.js index aaae2bee..37306d62 100644 --- a/src/main/mesh.js +++ b/src/main/mesh.js @@ -224,7 +224,11 @@ function space_init(data) { if (evt.key === '?') { return api.welcome(version); } - let { shiftKey, metaKey, ctrlKey, code } = evt; + let { shiftKey, metaKey, ctrlKey, code, target } = evt; + if (target.nodeName === 'TEXTAREA') { + api.script.changed(); + return; + } switch (code) { case 'Digit1': return api.mode.sketch(); @@ -286,7 +290,26 @@ function space_init(data) { } }, 'keydown', evt => { - let { shiftKey, metaKey, ctrlKey, code } = evt; + let { shiftKey, metaKey, ctrlKey, code, target } = evt; + if (target.nodeName === 'TEXTAREA') { + if (code === 'Tab') { + estop(evt); + const start = target.selectionStart; + const end = target.selectionEnd; + const val = target.value; + const upto = val.slice(0, start); + const col = start - upto.lastIndexOf('\n') - 1; + const tab = ' '.substring(col % 4); + target.value = val.slice(0, start) + tab + val.slice(end); + target.selectionStart = target.selectionEnd = start + tab.length; + } + if (code === 'Enter' && metaKey) { + api.script.execute(); + } else { + api.script.changed(); + } + return; + } let once = keyOnce[code]; if (once) { delete keyOnce[code]; diff --git a/src/mesh/api.js b/src/mesh/api.js index baf7fe78..4567d6fd 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -11,6 +11,7 @@ import { edges as meshEdges } from './edges.js'; import { group as meshGroup } from './group.js'; import { model as meshModel } from './model.js'; import { sketch as meshSketch } from './sketch.js'; +import { script } from './script.js'; import { newPolygon } from '../geo/polygon.js'; import { THREE } from '../ext/three.js'; import { $, $C, h } from '../moto/webui.js'; @@ -771,11 +772,12 @@ let add = { }, cube() { + const [ x,y,z ] = [ ...arguments ]; const box = new THREE.BoxGeometry(1,1,1).toNonIndexed(); const vert = box.attributes.position.array; const nmdl = new meshModel({ file: "box", mesh: vert }); const ngrp = api.group.new([ nmdl ]); - ngrp.scale(10, 10, 10).floor(); + ngrp.scale(x ?? 10, y ?? 10, z ?? 10).floor(); selection.set([ nmdl ]); return ngrp; }, @@ -1515,8 +1517,8 @@ const api = { }, clear() { - for (let group of group.list()) { - group.remove(group); + for (let grp of group.list()) { + group.remove(grp); } }, @@ -1645,6 +1647,8 @@ const api = { prefs, + script, + selection, sketch, diff --git a/src/mesh/build.js b/src/mesh/build.js index d4ea9918..3259f49c 100644 --- a/src/mesh/build.js +++ b/src/mesh/build.js @@ -383,7 +383,7 @@ function menu_item(text, fn, short, id) { // create html elements function ui_build() { - let { bind, div, input, hr, label } = h; + let { bind, div, input, hr, label, textarea } = h; let { file, selection, mode, tool, sketch, prefs, add } = api; let trash = FontAwesome.icon({ prefix: "fas", iconName: "trash" }).html[0]; let eye_open = FontAwesome.icon({ prefix: "fas", iconName: "eye" }).html[0]; @@ -402,6 +402,7 @@ function ui_build() { menu_item('Export', file.export, 'X'), hr(), menu_item('Slicer', api.kirimoto), + menu_item('Script', api.script.toggle), hr(), menu_item('Close', window.close), ]) @@ -579,6 +580,10 @@ function ui_build() { div({ id: 'objecttools', class: "tools sketch-off" }), div({ id: 'logger', onmouseover() { log.show() } }), ]), + div({ id: 'script', class: "hide" }, [ + div({ id: 'script-header', _: "script" }), + textarea({ id: 'script-editor', rows: 20, cols: 60 }), + ]), div({ id: 'grouplist' }), div({ id: 'selectlist' }), div({ id: 'download', class: "hide" }) diff --git a/src/mesh/script.js b/src/mesh/script.js new file mode 100644 index 00000000..98085992 --- /dev/null +++ b/src/mesh/script.js @@ -0,0 +1,31 @@ +/** Copyright Stewart Allen -- All Rights Reserved */ + +import { api } from '../mesh/api.js'; +import { $, h, estop } from '../moto/webui.js'; + +export const script = { + visible: false, + changed() { + localStorage.script = $('script-editor').value; + }, + hide() { + $('script').classList.add('hide'); + script.visible = false; + }, + show() { + $('script').classList.remove('hide'); + $('script-editor').value = localStorage.script ?? ''; + script.visible = true; + }, + toggle() { + if (script.visible) { + script.hide(); + } else { + script.show(); + } + }, + execute() { + let cmd = localStorage.script || `console.log('no script')`; + if (cmd) eval(`{ ${cmd} }`); + } +}; diff --git a/web/mesh/index.css b/web/mesh/index.css index 0c3dc2b4..b69a1ca4 100644 --- a/web/mesh/index.css +++ b/web/mesh/index.css @@ -629,6 +629,30 @@ button:hover { display: grid; } +#script { + margin-left: 5px; + flex-direction: column; + justify-content: end; + color: white; +} + +#script-header { + padding: 3px 5px 3px 5px; + background-color: #333; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-top: 1px solid #999; + border-left: 1px solid #999; + border-right: 1px solid #999; + flex-direction: row; +} + +#script-editor { + color: white; + background-color: #222; + outline: none; +} + /** group/model buttons top/right of workspace **/ #grouplist {