From 063361711b8aaf47b5059555a5f3090cbc0d793b Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 29 Sep 2026 16:28:36 -0500 Subject: [PATCH] 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 --- dDashboardServer/routes/SettingsCatalog.cpp | 10 ++++++++++ dDashboardServer/routes/UgcRoutes.cpp | 11 ++++++----- dDashboardServer/static/js/ugc.js | 10 ++++++---- dDashboardServer/templates/ugc.jinja2 | 4 +++- dGame/dUtilities/SlashCommandHandler.cpp | 2 +- dGame/dUtilities/SlashCommands/DEVGMCommands.cpp | 4 ++-- docs/Commands.md | 2 +- 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/dDashboardServer/routes/SettingsCatalog.cpp b/dDashboardServer/routes/SettingsCatalog.cpp index 7e0ee10f2..5577aee2a 100644 --- a/dDashboardServer/routes/SettingsCatalog.cpp +++ b/dDashboardServer/routes/SettingsCatalog.cpp @@ -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)); diff --git a/dDashboardServer/routes/UgcRoutes.cpp b/dDashboardServer/routes/UgcRoutes.cpp index d4042a5f9..ca1319af8 100644 --- a/dDashboardServer/routes/UgcRoutes.cpp +++ b/dDashboardServer/routes/UgcRoutes.cpp @@ -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(summary.triangles) / static_cast(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"); diff --git a/dDashboardServer/static/js/ugc.js b/dDashboardServer/static/js/ugc.js index c22db3c13..d6f5af1c9 100644 --- a/dDashboardServer/static/js/ugc.js +++ b/dDashboardServer/static/js/ugc.js @@ -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 = '' + d.choices[x[1]].map(function (name) { return ''; }).join(''); diff --git a/dDashboardServer/templates/ugc.jinja2 b/dDashboardServer/templates/ugc.jinja2 index 99eedee14..6a3c6d4ac 100644 --- a/dDashboardServer/templates/ugc.jinja2 +++ b/dDashboardServer/templates/ugc.jinja2 @@ -36,7 +36,8 @@ Processing options compared

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; Make again 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: + 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.

@@ -84,6 +85,7 @@ + diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 32e4d8132..6520998de 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -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 diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 1f36d15c1..605642482 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -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); diff --git a/docs/Commands.md b/docs/Commands.md index 0b634af1a..dc2a0d5a0 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -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 `|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 (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 (y) `|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|