From 9bcc132dde1941bf94e5ae8f54aabaa1de474565 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Wed, 30 Sep 2026 21:27:57 -0500 Subject: [PATCH] =?UTF-8?q?fix(dashboard):=20UGC=20page:=20Make=20again=20?= =?UTF-8?q?with=E2=80=A6=20replaces=20the=20option=20pickers=20that=20look?= =?UTF-8?q?ed=20like=20settings;=20a=20model's=20makes=20listed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- dDashboardServer/static/js/ugc.js | 103 +++++++++++++++++++++----- dDashboardServer/templates/ugc.jinja2 | 14 ++-- docs/UgcServer.md | 7 +- 3 files changed, 93 insertions(+), 31 deletions(-) diff --git a/dDashboardServer/static/js/ugc.js b/dDashboardServer/static/js/ugc.js index 3aae92034..8a76f2a39 100644 --- a/dDashboardServer/static/js/ugc.js +++ b/dDashboardServer/static/js/ugc.js @@ -128,11 +128,47 @@ // When it was made and what that cost (the cost is only known for makes since it was recorded), and with what function madeText(i) { if (!i.processedAt) return ''; - return fmt.unix(i.processedAt) + (costText(i) ? ' \u00b7 ' + costText(i) : '') + (i.madeOptions ? ' \u00b7 with ' + i.madeOptions : ''); + return fmt.unix(i.processedAt) + (costText(i) ? ' \u00b7 ' + costText(i) : '') + (i.madeOptions ? ' \u00b7 with ' + optionsText(i.madeOptions) : ''); } - // The processing options picked for Make again (ray tracer, denoise, processor), as /api/ugc/reprocess takes them - function remakeOptions() { - return ['optRays', 'optDenoise', 'optProcessor'].map(function (x) { return $(x).value; }).filter(Boolean).join(' '); + + // ---- processing options (docs/UgcServer.md, "Processing options"): the settings pick what every make uses; "Make again with…" another for one make ---- + + var OPTION_LABELS = { embree: 'Embree (CPU)', hiprt: 'HIPRT (GPU)', 'embree-gpu': 'Embree (Intel GPU)', off: 'no denoise', oidn: 'Open Image Denoise', + native: 'UGC server', 'toolbox-blender': 'LU Toolbox (Blender)' }; + var OPTION_GROUPS = [['rays', 'Ray tracer'], ['denoise', 'Denoise icons'], ['processor', 'Made by']]; + var options = null; // /api/ugc/options: choices and the settings' defaults + // Stored options ("embree off", "hiprt oidn toolbox-blender") in words; no processor word: the UGC server's own + function optionsText(text) { + var words = String(text || '').split(/[ ,]+/).filter(function (w) { return w && w !== 'toolbox' && w !== 'fast'; }); + if (!words.length) return ''; + words = words.map(function (w) { return w === 'builtin' ? 'embree' : w; }); + if (words.indexOf('toolbox-blender') < 0 && words.indexOf('native') < 0) words.push('native'); + return words.map(function (w) { return OPTION_LABELS[w] || w; }).join(', '); + } + // A "Make again with…" dropdown: a form with each choice preset to the settings' default, for one make only + function withMenu(target, label) { + if (!options || !canManage) return ''; + var selects = OPTION_GROUPS.filter(function (g) { return options.choices[g[0]]; }).map(function (g) { + return ''; + }).join(''); + return ''; + } + function withOptions(form) { + var chosen = [].map.call(form.querySelectorAll('[data-option]'), function (x) { return x.value; }); + var defaults = OPTION_GROUPS.map(function (g) { return options.defaults[g[0]]; }); + // The same as the settings: no options, so the make follows the settings + return chosen.join(' ') === defaults.join(' ') ? '' : chosen.join(' '); + } + function showOptions() { + var model = list.kind === 'model'; + $('madeWith').innerHTML = options && model ? 'New makes: ' + esc(OPTION_GROUPS.map(function (g) { return OPTION_LABELS[options.defaults[g[0]]] || options.defaults[g[0]]; }).join(', ')) + + (window.DASH && DASH.can('settings') ? ' (change)' : '') : ''; + $('remakeAllWith').innerHTML = model ? withMenu('all', 'Make everything again with…') : ''; } // A model's file as the player named it: its name with the upload's extension (".lxfml"), else the upload's name function fileName(i) { @@ -183,8 +219,8 @@ }) ].concat(madeColumns(), [ column('Options', function (i) { - return '' + esc(i.madeOptions || '') + '' + - (i.processOptions ? '
next: ' + esc(i.processOptions) + '
' : ''); + return '' + esc(optionsText(i.madeOptions)) + '' + + (i.processOptions ? '
next: ' + esc(optionsText(i.processOptions)) + '
' : ''); }), column('Saved', function (i) { if (!i.trianglesBefore) return ''; @@ -264,7 +300,7 @@ b.textContent = (b.dataset.kind === 'modular' ? 'Cars and rockets' : 'Models') + (count === null || count === undefined ? '' : ' (' + Number(count).toLocaleString() + ')'); }); $('manageButtons').classList.toggle('d-none', !canManage); - $('remakeOptions').classList.toggle('d-none', list.kind !== 'model'); + showOptions(); $('cacheCard').classList.toggle('d-none', !canManage); $('counts').innerHTML = countCard('Models', d.counts.model) + countCard('Cars and rockets (builds)', d.counts.modular) + (d.totals ? totalsCard('Models', d.totals.model) + totalsCard('Cars and rockets', d.totals.modular) : ''); @@ -375,7 +411,6 @@ function remake(body, question) { if (question && !confirm(question)) return; body.kind = list.kind; - if (list.kind === 'model' && remakeOptions()) body.options = remakeOptions(); api.post('/api/ugc/reprocess', body).then(function (d) { if (!d.success) { toast(d.error || 'Failed', 'danger'); return; } toast(d.message, 'success'); @@ -385,6 +420,22 @@ // ---- the open item ---- + // Every make of a model with what made it, to compare the options on one model + function loadRuns(id) { + api.get('/api/ugc/' + encodeURIComponent(id) + '/runs').then(function (d) { + var box = $('runsBox'); + if (!box || !current || current.id !== id || !d.success) return; + var ms = function (v) { return v ? esc(duration(v)) : ''; }; + box.innerHTML = '
Makes
' + (d.runs.length ? '
' + + '' + + d.runs.map(function (r) { + return ''; + }).join('') + '
MadeWithTookCPUHidden facesOcclusionIconTriangles
' + esc(fmt.unix(r.madeAt)) + '' + esc(optionsText(r.options)) + '' + ms(r.ms) + '' + ms(r.cpuMs) + + '' + ms(r.hsrMs) + '' + ms(r.aoMs) + '' + ms(r.iconMs) + '' + + esc(r.triangles.toLocaleString()) + '
' : '
No make recorded (made before makes were recorded).
'); + }); + } + function statsTable(now, before) { if (!now) return '
No stats for this model (made before the UGC server wrote them).
'; var lods = now.lods || [], old = before && before.lods || []; @@ -468,7 +519,7 @@ } if (publicUrl && current.fileId) links += 'Open on the UGC server'; if (canManage && current.fileId) { - links += '
' + + links += '
' + (modular ? '' : withMenu(current.fileId, 'Make again with…')) + '' + '
'; } @@ -491,7 +542,8 @@ if (!current || current.id !== id) return; current.stats = s[0]; current.statsLoaded = true; - $('statsBox').innerHTML = statsTable(s[0], s[1]); + $('statsBox').innerHTML = statsTable(s[0], s[1]) + '
'; + loadRuns(id); var lods = (s[0] && s[0].lods) || [{ lod: 0 }]; $('lodSelect').innerHTML = lods.map(function (l, n) { return ''; }).join(''); $('versionSelect').querySelector('[value=previous]').disabled = !s[1]; @@ -629,6 +681,22 @@ $('listCard').addEventListener('click', onItemClick); $('gallery').addEventListener('click', onItemClick); $('gallery').addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onItemClick(e); } }); + document.addEventListener('submit', function (e) { + var form = e.target.closest('[data-remake-with]'); + if (!form) return; + e.preventDefault(); + var body = form.dataset.remakeWith === 'all' ? {} : { id: form.dataset.remakeWith }, picked = withOptions(form); + if (picked) body.options = picked; + var toggle = form.parentNode.querySelector('[data-bs-toggle]'); + if (toggle && window.bootstrap) bootstrap.Dropdown.getOrCreateInstance(toggle).hide(); + remake(body, form.dataset.remakeWith === 'all' ? 'Make every model again' + (picked ? ' with ' + optionsText(picked) : '') + '? This can take a long time.' : null); + }); + $('madeWith').addEventListener('click', function (e) { + if (!e.target.closest('[data-goto-settings]')) return; + e.preventDefault(); + var box = document.querySelector('[data-ugc-settings="UGC models"]'); + if (box) box.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }); $('previewLinks').addEventListener('click', function (e) { var button = e.target.closest('[data-remake-open]'); if (button) remake({ id: button.dataset.remakeOpen }); @@ -726,21 +794,14 @@ // ---- processing options: Make again's choices and the comparison of what made the models ---- - var OPTION_LABELS = { embree: 'Embree (CPU)', hiprt: 'HIPRT (GPU)', 'embree-gpu': 'Embree (Intel GPU)', off: 'Off', oidn: 'Open Image Denoise', - native: 'UGC server', 'toolbox-blender': 'LU Toolbox (Blender)' }; function loadOptions() { return api.get('/api/ugc/options').then(function (d) { if (!d.success) throw new Error(d.error || 'Failed'); - [['optRays', 'rays', 'Ray tracer'], ['optDenoise', 'denoise', 'Denoise'], ['optProcessor', 'processor', 'Made by']].forEach(function (x) { - if (!d.choices[x[1]]) return; - var select = $(x[0]), chosen = select.value; - select.innerHTML = '' + - d.choices[x[1]].map(function (name) { return ''; }).join(''); - select.value = chosen; - }); + options = { choices: d.choices, defaults: d.defaults }; + showOptions(); var ms = function (v) { return v ? esc(duration(v)) : ''; }; $('optionsTable').querySelector('tbody').innerHTML = d.combinations.length ? d.combinations.map(function (c) { - return '' + esc(c.options) + '' + esc(c.runs.toLocaleString()) + '' + esc(c.models.toLocaleString()) + '' + ms(c.ms) + + return '' + esc(optionsText(c.options)) + '' + esc(c.runs.toLocaleString()) + '' + esc(c.models.toLocaleString()) + '' + ms(c.ms) + '' + ms(c.cpuMs) + '' + ms(c.hsrMs) + '' + ms(c.aoMs) + '' + ms(c.iconMs) + '' + esc(c.bricks) + '' + esc((c.removed * 100).toFixed(1)) + '%'; }).join('') : 'No model made since the options were recorded.'; @@ -752,6 +813,8 @@ $('optionsCard').addEventListener('toggle', function () { if (this.open) loadOptions(); }); if (window.Live) Live.on('ugc', load); + // A changed ray tracer, denoise or processor setting changes what new makes use + if (window.Live) Live.on('settings', loadOptions); var linked = readUrl(); writeUrl(false, linked); diff --git a/dDashboardServer/templates/ugc.jinja2 b/dDashboardServer/templates/ugc.jinja2 index 6a3c6d4ac..2cb6119b3 100644 --- a/dDashboardServer/templates/ugc.jinja2 +++ b/dDashboardServer/templates/ugc.jinja2 @@ -37,9 +37,10 @@

The ways of making models to compare: the ray tracer of the occlusion (Embree on the CPU, HIPRT on AMD or NVIDIA GPUs, Embree on Intel GPUs), denoising of icons, and what makes the models (the UGC server itself, or LU Toolbox in Blender: - toolbox-blender, where its hidden faces are Process Model's and its occlusion Bake Lighting's). The settings pick the defaults; Make again can use others for a make (the options next - to it, or /reprocessproperty embree oidn in game). - Every make is counted under what made it: the averages per make below, each model's in the List view's Options column.

+ toolbox-blender, where its hidden faces are Process Model's and its occlusion Bake Lighting's). The settings (How models are made above) pick what every make uses; Make again with… uses others for that one make only + (or /reprocessproperty embree oidn in game). + Every make is recorded with what made it: the averages per make below, each model's in the List view's Options column, and every make of a + model in its window under Makes.

OptionsMakesModelsTookCPUHidden facesOcclusionIconBricksTriangles removed
Loading…
@@ -82,13 +83,10 @@
- - - - - + +
diff --git a/docs/UgcServer.md b/docs/UgcServer.md index fdbe4f235..c25a67223 100644 --- a/docs/UgcServer.md +++ b/docs/UgcServer.md @@ -242,7 +242,7 @@ took 17 to 100 times as long (for example 45 s instead of 2.6 s for a 140-brick ### Processing options Ways of making models to try and compare, none replacing another. The settings pick the defaults; staff can make -models again with others (the UGC page's options next to **Make again**, `/api/ugc/reprocess` with `options`, +models again with others for that make only (the UGC page's **Make again with…** on a model or on everything, `/api/ugc/reprocess` with `options`, `/reprocessproperty [embree|hiprt|embree-gpu] [off|oidn] [native|toolbox-blender]` in game, `UgcServer --make-model [options]`). The choices are written as their names in any order (`UgcProcessOptions` in `dCommon/UgcKeys.h`); one left out is the setting's. Options stored by earlier versions still read: `builtin` (the UGC @@ -950,8 +950,9 @@ name; assemblies the same (most modules for most bricks, no triangles or saved) search, filters, sort, page and view are kept in the address, so Back and Forward and shared links work. The search is `UgcLookupSql` (the same on MySQL and SQLite, `IUgcLookup::ListUgc`); assemblies are grouped from the builds (`UgcAssemblies`). Buttons make one item, the failed ones or everything again (these only reset the -columns; the UGC server picks the rows up), for models with the processing options picked next to them (see -"Processing options"). +columns; the UGC server picks the rows up); for models, **Make again with…** picks other processing options for that +make only, and the toolbar says what new makes use (see "Processing options"). A model's window lists every make of it +(`ugc_process_runs`, `/api/ugc/:id/runs`) with what made it and its times, to compare the options on one model. An assembly opens with its modules, the icon editor and **References**: the builds that use it (`GET /api/ugc/assembly/builds?modules=&q=&page=`), with owner character and account, state and where each is (placed on a