feat(dashboard): pick and compare the processor

Make again gets a Made by picker, /api/ugc/options lists the processor
choices and default, the settings page has the toolbox_* settings, and
/reprocessproperty takes native or toolbox-blender.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 16:28:36 -05:00
parent 889cc79e94
commit 063361711b
7 changed files with 29 additions and 14 deletions

View File

@@ -503,6 +503,16 @@ namespace {
c.Add(Int(UGC, "embree_gpu_device", "Intel GPU for Embree", "Which Intel GPU the embree-gpu ray tracer uses: 0 is the first Embree supports.", "0", 0, 16, true));
c.Add(Labels(Choice(UGC, "denoise", "Denoise icons", "oidn (when the server was built with Intel Open Image Denoise; else off): a model's icon is drawn from its colors before the occlusion bake, with its occlusion traced per pixel with a few rays and the noise removed by the denoiser, instead of the baked occlusion. The model itself keeps its baked occlusion (a denoiser only works on images).", "off", { "off", "oidn" }), { "Off", "Open Image Denoise" }));
c.Add(Int(UGC, "denoise_samples", "Denoised occlusion rays per pixel", "Occlusion rays traced from each pixel of a denoised icon (before it is scaled down, so 16 times as many per icon pixel); fewer is faster and noisier.", "4", 1, 256));
// LU Toolbox itself in a headless Blender (docs/UgcServer.md, "LU Toolbox in Blender")
c.Add(Labels(Choice(UGC, "processor", "What makes the models", "native: the UGC server's own pipeline. toolbox-blender: LU Toolbox itself in a headless Blender (import, Process Model, Bake Lighting, niftools export), one model at a time and far slower; when Blender, LU Toolbox or niftools can't be found (the settings below), models are made natively and the UGC server logs why. Staff can pick either for one make when making models again.", "native", { "native", "toolbox-blender" }), { "UGC server (native)", "LU Toolbox in Blender" }));
c.Add(Format(Text(UGC, "toolbox_blender", "Blender executable", "The Blender that runs LU Toolbox (3.1 is what LU Toolbox and LU-Toolbox-Standalone are made for). An external program: nothing of it is built into the servers.", ""), eFormat::PATH));
c.Add(Format(Text(UGC, "toolbox_standalone_dir", "LU-Toolbox-Standalone folder", "The folder with lu_batch_driver.py, whose steps the worker runs.", ""), eFormat::PATH));
c.Add(Format(Text(UGC, "toolbox_scripts_dir", "Blender add-ons folder", "A Blender scripts folder whose addons/ has lu_toolbox and io_scene_niftools (empty: the add-ons installed in Blender's own user folder).", ""), eFormat::PATH));
c.Add(Format(Text(UGC, "toolbox_brickdb_dir", "LU Toolbox brick folder", "Made from the client's brickdb.zip and brickprimitives the first time (so LU Toolbox doesn't unpack into the client's res folder). Relative to the server binaries.", "toolbox-brickdb"), eFormat::PATH));
c.Add(Format(Text(UGC, "toolbox_work_dir", "LU Toolbox work folder", "The model being made and Blender's log (blender.log). Relative to the server binaries.", "toolbox-work"), eFormat::PATH));
c.Add(Labels(Choice(UGC, "toolbox_device", "Blender's bake device", "What Cycles bakes on (Process Model's hidden faces and Bake Lighting): the CPU, or an NVIDIA (CUDA, OptiX) or AMD (HIP) GPU; auto: Blender's own preferences.", "cpu", { "cpu", "cuda", "optix", "hip", "auto" }), { "CPU", "CUDA", "OptiX", "HIP", "Auto" }));
c.Add(Int(UGC, "toolbox_threads", "Blender's threads", "CPU threads Blender uses. Its CPU time counts against the CPU limit, which pauses it; worker_nice applies to it.", "4", 1, 256));
c.Add(Unit(Int(UGC, "toolbox_timeout_seconds", "LU Toolbox time limit", "A model taking longer fails and Blender is started again.", "1800", 10, 86400), "seconds"));
c.Add(Bool(UGC, "bake_ao", "Darken hidden corners", "Ambient occlusion baked into the vertex colors.", true));
c.Add(Int(UGC, "ao_samples", "Occlusion rays per vertex", "", "64", 1, 1024));
c.Add(Float(UGC, "ao_distance", "Occlusion distance", "", "5", 0, 1000));

View File

@@ -458,8 +458,8 @@ namespace UgcRoutes {
});
Route(eHTTPMethod::GET, "/api/ugc/options", Perm("properties_view"),
"The processing options (docs/UgcServer.md, \"Processing options\"): the choices ({rays, hsr, denoise}: names), the settings' defaults "
"({rays, hsr, denoise}) and every combination the made models used, with its makes (runs), models and averages per make "
"The processing options (docs/UgcServer.md, \"Processing options\"): the choices ({rays, denoise, processor}: names), the settings' defaults "
"({rays, denoise, processor}) and every combination the made models used, with its makes (runs), models and averages per make "
"(ms, cpuMs, hsrMs, aoMs, iconMs, bricks, trianglesBefore, triangles, removed: the share of triangles removed)",
[](HTTPReply& reply, const HTTPContext& context) {
const auto names = [](const auto& list) {
@@ -477,9 +477,10 @@ namespace UgcRoutes {
{ "triangles", average(summary.triangles) },
{ "removed", summary.trianglesBefore > 0 ? 1.0 - static_cast<double>(summary.triangles) / static_cast<double>(summary.trianglesBefore) : 0.0 } });
}
JsonSuccess(reply, { { "choices", { { "rays", names(UgcProcessOptions::RAYS) }, { "denoise", names(UgcProcessOptions::DENOISE) } } },
JsonSuccess(reply, { { "choices", { { "rays", names(UgcProcessOptions::RAYS) }, { "denoise", names(UgcProcessOptions::DENOISE) },
{ "processor", names(UgcProcessOptions::PROCESSOR) } } },
{ "defaults", { { "rays", UgcSetting("ray_backend").value_or("embree") },
{ "denoise", UgcSetting("denoise").value_or("off") } } },
{ "denoise", UgcSetting("denoise").value_or("off") }, { "processor", UgcSetting("processor").value_or("native") } } },
{ "combinations", combos }, { "canManage", Can(context, "ugc_manage") } });
});
@@ -772,7 +773,7 @@ namespace UgcRoutes {
Route(eHTTPMethod::POST, "/api/ugc/reprocess", Perm("ugc_manage"),
"Have the UGC server make items again. Body: {kind: model|modular, id} for one, {kind: model, property} for every model placed on a property, {kind, failedOnly: true} for the failed ones, {kind} for all; "
"models: options (processing options for this make, e.g. \"embree oidn\": ray backend, denoising; left out: the UGC settings')",
"models: options (processing options for this make, e.g. \"embree oidn\" or \"toolbox-blender\": ray backend, denoising, processor; left out: the UGC settings')",
[](HTTPReply& reply, const HTTPContext& context) {
const auto body = ParseBody(context);
if (!body) return JsonError(reply, eHTTPStatusCode::BAD_REQUEST, "Invalid JSON");

View File

@@ -130,9 +130,9 @@
if (!i.processedAt) return '';
return fmt.unix(i.processedAt) + (costText(i) ? ' \u00b7 ' + costText(i) : '') + (i.madeOptions ? ' \u00b7 with ' + i.madeOptions : '');
}
// The processing options picked for Make again (ray tracer, denoise), as /api/ugc/reprocess takes them
// The processing options picked for Make again (ray tracer, denoise, processor), as /api/ugc/reprocess takes them
function remakeOptions() {
return ['optRays', 'optDenoise'].map(function (x) { return $(x).value; }).filter(Boolean).join(' ');
return ['optRays', 'optDenoise', 'optProcessor'].map(function (x) { return $(x).value; }).filter(Boolean).join(' ');
}
// 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) {
@@ -727,11 +727,13 @@
// ---- 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' };
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']].forEach(function (x) {
[['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 = '<option value="">' + esc(x[2]) + ': settings (' + esc(OPTION_LABELS[d.defaults[x[1]]] || d.defaults[x[1]]) + ')</option>' +
d.choices[x[1]].map(function (name) { return '<option value="' + esc(name) + '">' + esc(x[2]) + ': ' + esc(OPTION_LABELS[name] || name) + '</option>'; }).join('');

View File

@@ -36,7 +36,8 @@
<summary class="card-header py-2 small fw-semibold">Processing options compared</summary>
<div class="card-body py-2 small">
<p class="text-body-secondary mb-2">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) and denoising of icons. The settings pick the defaults; <b>Make again</b> can use others for a make (the options next
NVIDIA GPUs, Embree on Intel GPUs), denoising of icons, and what makes the models (the UGC server itself, or LU Toolbox in Blender:
<code>toolbox-blender</code>, where its hidden faces are Process Model's and its occlusion Bake Lighting's). The settings pick the defaults; <b>Make again</b> can use others for a make (the options next
to it, or <code>/reprocessproperty embree oidn</code> 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.</p>
<div class="table-responsive"><table class="table table-sm align-middle mb-0" id="optionsTable">
@@ -84,6 +85,7 @@
<span class="d-flex gap-1" id="remakeOptions" title="How Make again makes models (the settings' way unless picked here)">
<select class="form-select form-select-sm w-auto" id="optRays" aria-label="Ray tracer for Make again"><option value="">Ray tracer: settings</option></select>
<select class="form-select form-select-sm w-auto" id="optDenoise" aria-label="Denoise for Make again"><option value="">Denoise: settings</option></select>
<select class="form-select form-select-sm w-auto" id="optProcessor" aria-label="What makes the models for Make again"><option value="">Made by: settings</option></select>
</span>
<button type="button" class="btn btn-sm btn-outline-warning" id="retryFailed">Try the failed ones again</button>
<button type="button" class="btn btn-sm btn-outline-danger" id="remakeAll">Make everything again</button>

View File

@@ -497,7 +497,7 @@ void SlashCommandHandler::Startup() {
Command ReprocessPropertyCommand{
.help = "Make this property's models again and reload it",
.info = "The UGC server makes every model placed on the property you are on again, with the current UGC settings or the processing options given (in any order: embree, hiprt or embree-gpu; off or oidn). Once they are made, everyone on the property is sent back into it, so their game loads the new meshes",
.info = "The UGC server makes every model placed on the property you are on again, with the current UGC settings or the processing options given (in any order: embree, hiprt or embree-gpu; off or oidn; native or toolbox-blender). Once they are made, everyone on the property is sent back into it, so their game loads the new meshes",
.aliases = { "reprocessproperty", "reloadpropertymodels" },
.handle = DEVGMCommands::ReprocessProperty,
.requiredLevel = eGameMasterLevel::DEVELOPER

View File

@@ -1990,10 +1990,10 @@ namespace DEVGMCommands {
ChatPackets::SendSystemMessage(sysAddr, u"You aren't on a property.");
return;
}
// Optional processing options for this make: ray backend and denoising, in any order
// Optional processing options for this make: ray backend, denoising and processor, in any order
UgcProcessOptions::Choice choice;
if (!UgcProcessOptions::Parse(args, choice)) {
ChatPackets::SendSystemMessage(sysAddr, u"Usage: /reprocessproperty [embree|hiprt|embree-gpu] [off|oidn] (left out: the UGC settings')");
ChatPackets::SendSystemMessage(sysAddr, u"Usage: /reprocessproperty [embree|hiprt|embree-gpu] [off|oidn] [native|toolbox-blender] (left out: the UGC settings')");
return;
}
const auto options = UgcProcessOptions::ToString(choice);

View File

@@ -137,7 +137,7 @@ These commands are primarily for development and testing. The usage of many of t
|resurrect|`/resurrect`|Resurrects the player.|8|
|setminifig|`/setminifig <body part> <minifig item id>`|Alters your player's minifig. Body part can be one of "Eyebrows", "Eyes", "HairColor", "HairStyle", "Pants", "LeftHand", "Mouth", "RightHand", "Shirt", or "Hands". Changing minifig parts could break the character so this command is limited to GMs.|1|
|testmap|`/testmap <zone> (clone-id) (instance-id) (spawn-point)`|Transfers you to the given zone by id and clone id and then spawns you at the specified spawn point if one was specified. Ignores instance-id for now. Aliases: `/tm`.|1|
|reprocessproperty|`/reprocessproperty [embree\|hiprt\|embree-gpu] [off\|oidn]`|Has the UGC server make every brick built model on the property you are on again, with the current UGC settings or the processing options given, in any order (ray backend, denoising; see docs/UgcServer.md, "Processing options"). When they are all made (at most 15 minutes), everyone on the property is sent back into it, so their game downloads the new meshes. Aliases: `/reloadpropertymodels`.|8|
|reprocessproperty|`/reprocessproperty [embree\|hiprt\|embree-gpu] [off\|oidn] [native\|toolbox-blender]`|Has the UGC server make every brick built model on the property you are on again, with the current UGC settings or the processing options given, in any order (ray backend, denoising, processor; see docs/UgcServer.md, "Processing options"). When they are all made (at most 15 minutes), everyone on the property is sent back into it, so their game downloads the new meshes. Aliases: `/reloadpropertymodels`.|8|
|reportproxphys|`/reportproxphys`|Prints to console the position and radius of proximity sensors.|9|
|spawnphysicsverts|`/spawnphysicsverts`|Spawns a 1x1 brick at all vertices of phantom physics objects|8|
|teleport|`/teleport <x/source player> (y) <z/target player>`|Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z). Any of the coordinates can use the syntax of an exact position (10.0), or a relative position (~+10.0). A ~ means use the current value of that axis as the base value. Addition or subtraction is supported (~+10) (~-10). If source player and target player are players that exist in the world, then the source player will be teleported to target player. Aliases: `/tele`, `/tp`.|6|