diff --git a/web/i18n/ar.js b/web/i18n/ar.js index f6b4f93..db85ecb 100644 --- a/web/i18n/ar.js +++ b/web/i18n/ar.js @@ -311,6 +311,8 @@ export default { "Restore all": "استعادة الكل", "Highlight small overhangs": "إبراز البروزات المعلّقة الصغيرة", "Clear all": "مسح الكل", + "Remove model": "إزالة النموذج", + "Take this model off the plate, to import another without reloading the page.": "إزالة هذا النموذج من اللوحة لاستيراد نموذج آخر دون إعادة تحميل الصفحة.", "Remove selected": "إزالة المحدد", // Manual Draw hint diff --git a/web/i18n/de.js b/web/i18n/de.js index 54dff65..bca426a 100644 --- a/web/i18n/de.js +++ b/web/i18n/de.js @@ -324,6 +324,8 @@ export default { "Restore all": "Alle wiederherstellen", "Highlight small overhangs": "Kleine Überhänge hervorheben", "Clear all": "Alle löschen", + "Remove model": "Modell entfernen", + "Take this model off the plate, to import another without reloading the page.": "Dieses Modell von der Platte nehmen, um ein anderes ohne Neuladen der Seite zu importieren.", "Remove selected": "Ausgewählte entfernen", // Manual Draw hint diff --git a/web/i18n/nl.js b/web/i18n/nl.js index fa5299d..3b86c0a 100644 --- a/web/i18n/nl.js +++ b/web/i18n/nl.js @@ -312,6 +312,8 @@ export default { "Restore all": "Alles herstellen", "Highlight small overhangs": "Kleine overhangen markeren", "Clear all": "Alles wissen", + "Remove model": "Model verwijderen", + "Take this model off the plate, to import another without reloading the page.": "Haal dit model van de plaat om een ander te importeren zonder de pagina te herladen.", "Remove selected": "Selectie verwijderen", // Manual Draw hint diff --git a/web/i18n/pt.js b/web/i18n/pt.js index 1fd902d..1693f31 100644 --- a/web/i18n/pt.js +++ b/web/i18n/pt.js @@ -296,6 +296,8 @@ export default { "Restore all": "Restaurar todas", "Highlight small overhangs": "Destacar balanços pequenos", "Clear all": "Limpar tudo", + "Remove model": "Remover modelo", + "Take this model off the plate, to import another without reloading the page.": "Tirar este modelo da mesa, para importar outro sem recarregar a página.", "Remove selected": "Remover selecionado", "Click two points across an overhang — straight onto the red faces — to lay a breakaway wall along that line. Click an upright side once to stand a sway brace against it. Esc or right-click cancels.": "Clique em dois pontos atravessando um balanço — direto nas faces vermelhas — para criar uma parede destacável ao longo dessa linha. Clique uma vez num lado vertical para apoiar uma escora lateral nele. Esc ou botão direito cancela.", "off": "desligado", diff --git a/web/i18n/zh.js b/web/i18n/zh.js index e51aee6..2b10bb6 100644 --- a/web/i18n/zh.js +++ b/web/i18n/zh.js @@ -327,6 +327,8 @@ export default { "Restore all": "全部恢复", "Highlight small overhangs": "高亮细小悬垂", "Clear all": "全部清除", + "Remove model": "移除模型", + "Take this model off the plate, to import another without reloading the page.": "将此模型从打印板上移除,无需重新加载页面即可导入另一个模型。", "Remove selected": "移除所选", // Manual Draw hint diff --git a/web/index.html b/web/index.html index 9be7f50..972e8ad 100644 --- a/web/index.html +++ b/web/index.html @@ -204,6 +204,8 @@
Bed pad
—
Analysis
—
+ diff --git a/web/style.css b/web/style.css index b0957df..dcb697a 100644 --- a/web/style.css +++ b/web/style.css @@ -347,6 +347,7 @@ html, body { fin/pad time is appended, and that one-line growth used to nudge everything below the panel down when fins were added. Always two lines high = no shift. */ #stats #s-time { display: block; min-height: 2.6em; line-height: 1.3; } +#stats #remove-part { margin-top: 10px; } #stats hr { border: 0; border-top: 1px solid var(--line); diff --git a/web/ui/io.js b/web/ui/io.js index 89e1e11..1e87abf 100644 --- a/web/ui/io.js +++ b/web/ui/io.js @@ -63,6 +63,14 @@ export function renderImportNote() { /** The part was rescaled (ui/scale.js): the supports a saved session brought back * are gone with it, so the note no longer says they were restored. */ +/** The part was taken off the plate (part.js clearPart): its import note goes too. */ +export function forgetImport() { + importNote = ''; + lastImportMeta = null; + pendingSession = null; + el('s-import-note').textContent = ''; +} + export function forgetSessionNote() { if (!lastImportMeta?.session) return; lastImportMeta.session = null; diff --git a/web/ui/part.js b/web/ui/part.js index a20f77f..91c1c47 100644 --- a/web/ui/part.js +++ b/web/ui/part.js @@ -7,14 +7,14 @@ import { t, tn } from './i18n.js'; import * as THREE from 'three'; import { buildTopology, analyze, DEFAULT_THRESHOLD, MIN_REGION_AREA } from '../overhangs.js'; import { el } from './dom.js'; -import { scene, controls, frame, clip, MODEL } from './scene.js'; +import { scene, controls, frame, clip, MODEL, requestRender } from './scene.js'; import { setSection, followPart } from './section.js'; import { showToolbar } from './toolbar.js'; import { removeMode, cancelRemove, resetRemovals } from './remove.js'; import { resetHistory } from './history.js'; -import { importNote, renderImportNote } from './io.js'; +import { importNote, renderImportNote, forgetImport } from './io.js'; import { currentVolume } from './volume.js'; -import { resetLoad, updateLayerView, updateLoadReadout, syncLoadUI } from './strength.js'; +import { resetLoad, updateLayerView, hideLayerView, updateLoadReadout, syncLoadUI } from './strength.js'; import { hideSuggestions } from './suggest.js'; import { setDrawnWalls, setDrawMsg, markPrintTrisDirty, clearPreview, syncDrawControls, @@ -109,7 +109,21 @@ export function setPart(geometry, filename, { quat } = {}) { gizmo.attach(part); showToolbar(true); - // A new part starts with no hand-drawn walls and a fresh print-space cache. + resetPartState(); + + const size = shade(); + frame(size); + resetScale(); + return size; +} + +/** + * What a part's tools hold that means nothing on another part (or on none): the + * drawn walls, removed fins, armed modes, load arrow, Suggest list, section cut and + * undo history. Shared by setPart and clearPart. + */ +function resetPartState() { + // A new part (or none) starts with no hand-drawn walls and a fresh print-space cache. setDrawnWalls([]); // Per-fin removals are keyed by a content signature that can coincidentally // match a different model's fins, so they must NOT carry across parts -- clear @@ -124,7 +138,7 @@ export function setPart(geometry, filename, { quat } = {}) { clearPreview(); syncAugmentUI(); // "+ Add" is off now: the button and the Draw hint say so syncDrawControls(); - // A new part starts with no load direction either, and no Suggest ranking: the + // A new part (or none) starts with no load direction either, and no Suggest ranking: the // list and verdict were the OLD part's poses. resetLoad(); hideSuggestions(); @@ -136,11 +150,37 @@ export function setPart(geometry, filename, { quat } = {}) { // Undo history does not carry across parts. resetHistory(); +} - const size = shade(); - frame(size); +/** + * Take the part off the plate (the stats card's Remove model): back to the empty + * page a fresh tab shows, so the next model needs no reload. The fin settings stay, + * as they do across a load. + */ +export function clearPart() { + if (!part) return; + gizmo.detach(); + part.remove(hoverFace); + hoverFace.visible = false; + scene.remove(part); + part.geometry.dispose(); + part = null; + topology = null; + lastResult = lastShadeRes = null; + flatRegions = null; + painted = paintedAttr = null; + lastSize = null; + partName = ''; + resetPartState(); + refreshFins(); // no lastResult now: drops the fins, pad and readout resetScale(); - return size; + forgetImport(); + hideLayerView(); + showToolbar(false); + el('stats').hidden = true; + el('status').hidden = true; + el('drop').classList.remove('hidden'); + requestRender(); } /** @@ -431,4 +471,6 @@ export function syncPartLanguage() { renderIslandText(); } +el('remove-part').addEventListener('click', clearPart); + window.addEventListener('languagechange', syncPartLanguage); diff --git a/web/ui/strength.js b/web/ui/strength.js index de77c47..632d195 100644 --- a/web/ui/strength.js +++ b/web/ui/strength.js @@ -108,6 +108,11 @@ export function updateLayerView(size) { layerViz.visible = layersOn && !!part; } +/** No part on the plate: no layer frames either. */ +export function hideLayerView() { + layerViz.visible = false; +} + // ---------------------------------------------------------------- load arrow UI /** diff --git a/web/ui/toolbar.js b/web/ui/toolbar.js index 9e5be87..d499995 100644 --- a/web/ui/toolbar.js +++ b/web/ui/toolbar.js @@ -18,8 +18,8 @@ * 3D view leaves it open (Lay a face flat and Section are used there); Esc folds it * and hands focus back to its badge. `onFold` runs when a popover folds, for a mode * armed from inside it that must not stay armed out of sight (Lay a face flat). - * The bar shows once a part is loaded (showToolbar, from part.js); nothing unloads - * a part, so it is never hidden again. + * The bar shows while a part is loaded (showToolbar, from part.js) and hides again + * when Remove model takes it off, folding any open popover. * * It sits bottom-left, at the window's edge -- or just right of the stats card when * a short window has scrolled the card down beside it. @@ -112,5 +112,6 @@ addEventListener('keydown', (e) => { /** Show the bar (a part is loaded) or hide it. */ export function showToolbar(v) { el('viewbar').hidden = !v; + if (!v && openId) { openId = null; render(); } place(); }