@@ -20,6 +20,8 @@ import vibe.core.core : vibeYield = yield;
2020import std.stdio : writefln;
2121import nijigenerate.api.mcp.task : ngMcpEnqueueAction, ngRunInMainThread; // scheduling helpers
2222import nijigenerate.utils.crashdump : installNativeCrashDumpThreadHandler;
23+ import nijigenerate.widgets.notification : NotificationPopup;
24+ import bindbc.imgui : ImGuiIO, ImVec2, igText, igProgressBar, igSameLine;
2325
2426version (CMD_LOG) private void cmdLog(T... )(T args) { writefln(args); }
2527else private void cmdLog(T... )(T args) {}
@@ -66,6 +68,52 @@ private class AutoMeshApplyResult : CommandResult {
6668 }
6769}
6870
71+ private class AutoMeshProgressState {
72+ private Mutex lock;
73+ size_t total;
74+ private size_t done_;
75+ private string currentName_;
76+ private bool canceled_;
77+
78+ this (size_t total) {
79+ lock = new Mutex ();
80+ this .total = total;
81+ }
82+
83+ void beginTarget (string name) {
84+ synchronized (lock) {
85+ currentName_ = name;
86+ }
87+ }
88+
89+ void completeTarget () {
90+ synchronized (lock) {
91+ if (done_ < total) ++ done_;
92+ }
93+ }
94+
95+ void requestCancel () {
96+ synchronized (lock) {
97+ canceled_ = true ;
98+ }
99+ }
100+
101+ bool canceled () {
102+ synchronized (lock) {
103+ return canceled_;
104+ }
105+ }
106+
107+ void snapshot (out size_t done, out size_t totalOut, out string currentName, out bool canceledOut) {
108+ synchronized (lock) {
109+ done = done_;
110+ totalOut = total;
111+ currentName = currentName_;
112+ canceledOut = canceled_;
113+ }
114+ }
115+ }
116+
69117// Compile-time presence check for initializer
70118static if (__traits(compiles, { void _ct_probe(){ ngInitCommands! (AutoMeshKey)(); } })) {
71119 // pragma(msg, "[CT] automesh.dynamic: ngInitCommands!(AutoMeshKey) present");
@@ -147,17 +195,44 @@ template ApplyAutoMeshPT(alias PT)
147195 }
148196
149197 auto asyncResult = new AutoMeshApplyResult();
198+ auto progress = new AutoMeshProgressState(targets.length);
199+ string procName = chosen.displayName();
200+ ulong popupId = NotificationPopup.instance().popup((ImGuiIO* io) {
201+ import nijigenerate.widgets : incButtonColored;
202+ import std.string : toStringz;
203+
204+ size_t done, total;
205+ string currentName;
206+ bool canceled;
207+ progress.snapshot(done, total, currentName, canceled);
208+ float ratio = total > 0 ? cast (float )done / cast (float )total : 0 ;
209+ string title = " AutoMesh: " ~ procName ~ (currentName.length ? (" - " ~ currentName) : " " );
210+ igText(title.toStringz);
211+ igProgressBar(ratio, ImVec2 (320 , 0 ));
212+ igSameLine(0 , 8 );
213+ if (canceled) {
214+ igText(" Canceling..." );
215+ } else if (incButtonColored(" Cancel" , ImVec2(96 , 24 ))) {
216+ progress.requestCancel();
217+ }
218+ }, - 1 );
219+
150220 Thread th = new Thread ({
151221 installNativeCrashDumpThreadHandler();
152222 IncMesh[uint ] results;
153223 string workerError;
154224 size_t processed = 0 ;
155225 bool cb (Deformable d, IncMesh mesh) {
226+ if (mesh is null ) {
227+ progress.beginTarget(d.name);
228+ return ! progress.canceled();
229+ }
156230 if (mesh ! is null ) {
157231 results[d.uuid] = mesh;
158232 ++ processed;
233+ progress.completeTarget();
159234 }
160- return true ;
235+ return ! progress.canceled() ;
161236 }
162237 void work () {
163238 Deformable[] bgTargets = targets;
@@ -175,11 +250,15 @@ template ApplyAutoMeshPT(alias PT)
175250
176251 if (workerError.length) {
177252 writefln(" [AutoMesh] worker failed: %s" , workerError);
178- asyncResult.complete(CommandResult(false , workerError));
253+ ngMcpEnqueueAction({
254+ NotificationPopup.instance().close(popupId);
255+ asyncResult.complete(CommandResult(false , workerError));
256+ });
179257 return ;
180258 }
181259
182260 ngMcpEnqueueAction({
261+ scope (exit) NotificationPopup.instance().close(popupId);
183262 size_t applied = 0 ;
184263 foreach (t; targets) {
185264 if (auto pm = t.uuid in results) {
@@ -194,7 +273,9 @@ template ApplyAutoMeshPT(alias PT)
194273 }
195274 }
196275
197- if (applied == 0 ) {
276+ if (progress.canceled()) {
277+ asyncResult.complete(CommandResult(false , " AutoMesh canceled" ));
278+ } else if (applied == 0 ) {
198279 asyncResult.complete(CommandResult(false , " AutoMesh generated no applicable meshes" ));
199280 } else {
200281 asyncResult.complete(CommandResult(true ));
0 commit comments