Skip to content

Develop - #663

Merged
Weisl merged 11 commits into
mainfrom
develop
Aug 4, 2026
Merged

Develop#663
Weisl merged 11 commits into
mainfrom
develop

Conversation

@Weisl

@Weisl Weisl commented Aug 4, 2026

Copy link
Copy Markdown
Owner

No description provided.

Weisl added 11 commits August 3, 2026 14:24
… Blender

Popen(...).wait() blocked Blender's main thread for as long as CoACD took,
with zero progress feedback and no way to cancel - on real-world
non-manifold meshes that's multiple minutes even with default settings,
which reads as a complete freeze. The CLI (both the main decomposition
pass and the per-hull decimate pass) is now driven from bpy.app.timers,
polled a step at a time, with a HUD status while it runs and Escape
actually killing the subprocess.
…defaults

A synchronously-frozen UI was an accidental guard against a user
triggering Auto Convex (BETA) twice concurrently - now that it stays
responsive during a run, nothing stopped a second invocation from racing
the first on identically-named temp files. Adds a module-level lock
(invoke() refuses a second run while one is active) plus unique per-run
temp filenames as defense in depth, and makes sure the lock always gets
released even if a job errors out unexpectedly.

Also changes CoACD defaults: max hulls 16 (was -1/unlimited) and hull
vertex cap 32 with decimation on by default (was 256, off) - unlimited
hulls at full resolution is what made the original freeze so easy to hit.
"BETA" didn't communicate the actual tradeoff users need to know before
choosing it over V-HACD: CoACD produces tighter, more precise hulls by
searching for better cuts, but is significantly slower - the operator was
never actually unstable, just slow and previously freeze-prone (#660).
Renamed the operator label, panel/button text, and tooltips throughout to
say so directly instead of "BETA", and updated the description text to
call out the speed cost explicitly.
…iewport-less async step

bpy.app.timers callbacks have no guaranteed context - bpy.context.space_data
is None (or belongs to whatever editor the mouse happens to be over)
unless the pointer is currently over this operator's own viewport, which
is unlikely once a run takes minutes. postprocess_colliders() ->
primitive_postprocessing() -> set_viewport_drawing() needs
context.space_data.shading, so this crashed with AttributeError as soon as
the mouse wasn't over the 3D viewport when the last subprocess finished -
and since postprocess_colliders() processes hulls one at a time, every
hull already handled before the crash kept its correct transform/parent/
modifiers, while every hull after it was left with its raw import-time
transform and never parented. That's what looked like "some hulls get the
right rotation/scale, others don't".

Fixed by capturing a stable window/area/region in execute() and running
every async completion step inside context.temp_override() with them,
instead of trusting ambient bpy.context.
16 was already a big improvement over unlimited (-1), but Max Hulls is an
output-count constraint rather than a search-quality one - tightening it
further trims worst-case runtime without degrading individual cuts, unlike
loosening threshold/MCTS params which would erode the precision this
backend exists for.
Add [permissions].network declaration to match. Neither was previously
declared/checked despite this addon already making a real network
call on every register().
…s for speed

Progress: pipe the subprocess's stdout through a background reader thread
into a queue instead of letting it inherit the console - CoACD already
prints phase headers and per-candidate percentages, they just weren't
being read. _drain_progress_queue() parses them non-blockingly each poll
so the HUD shows e.g. "DECOMPOSITION (MCTS) 87.5%" instead of just an
elapsed-time counter, which is indistinguishable from being stuck on a
multi-minute run.

Defaults: measured four isolated runs of the same real (complex,
non-manifold) asset at current settings. MCTS Depth 3->2 and Nodes 20->10
cut compute time ~38% (389s -> 239s) with no quality loss - concavity
came out slightly better, not worse, because at 8 hulls the decomposition
is already hull-count-constrained rather than search-constrained, so
trimming the search doesn't cost precision that was being achieved
anyway. Hausdorff Sampling Resolution only bought ~11% on its own; set to
1000, the lowest value the property's own range allows (tested at 200 via
direct CLI, which is below the declared min and wasn't adopted here).
Combined: 222s, a ~43% reduction from baseline with no measured quality
regression.
…d status overlay while either backend runs

The per-row settings HUD (D for decimate, S for shrink/inflate, etc.) kept
drawing and looking interactive while a CoACD job was running, even though
every one of those hotkeys is inert during a run (modal() swallows them -
there's nothing to adjust until the job produces colliders). Replaced it
with a dedicated, centered, warning-styled overlay
(draw_async_job_overlay() in add_bounding_primitive.py) that fully
replaces the settings HUD while an async job is active, and disappears
back to the normal interactive HUD the moment it finishes - no separate
step needed, since draw_viewport_overlay() just checks
self._async_process each redraw.

Extracted the reusable pieces both backends need into
OBJECT_OT_add_bounding_object: _launch_async_process() (Popen with stdout
piped through a daemon reader thread into a queue - splitting on '\r' as
well as '\n', since V-HACD's progress lines are '\r'-delimited like a
terminal progress bar, not '\n'-delimited like CoACD's) and
_drain_async_progress() (drains that queue each poll via a
_parse_progress_line() hook each operator overrides for its own CLI's
output format). CoACD's own _launch_coacd_process/_drain_progress_queue
were folded into these; its state renamed to the generic
_async_process/_async_start_time so the shared overlay can findit via
getattr() regardless of which backend is running.

V-HACD itself gets the full #660 treatmeant: was still calling
Popen.wait() synchronously (freezing Blender exactly like CoACD used to),
now runs through the same bpy.app.timers poll loop, module-level
run-in-progress lock, cancel handling, and context.temp_override() for
its completion step. Simpler than CoACD's conversion since V-HACD has no
per-hull decimate sub-stage - one subprocess per collider group, straight
through to postprocessing.

Verified: full test suite (11/11) against a real Blender instance: V-HACD
command construction and its file-discovery logic (scans data_path for
new .obj files, deliberately excluding per-hull numbered variants in
favor of the combined decomp.obj) against the actual bundled executable;
transform correctness (rotated + non-uniformly-scaled source object) by
replicating postprocess_colliders()'s exact matrix_world/apply_transform
logic and comparing the resulting world-space hull bounds against the
source geometry; both backends' progress-line regexes against their real
captured stdout output. Modal-operator dispatch itself (invoke() ->
execute() -> timer-driven poll loop) couldn't be exercised in
--background scripting - Blender skips invoke() and calls execute()
directly in that mode - so that specific wiring relies on it being the
same shared code path already exercised live for CoACD earlier in this
work.
@Weisl
Weisl merged commit a6a5e21 into main Aug 4, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant