Skip to content

feat(usage): report which Blendkit assets are still in the file at every save [User issue investigation] - #2296

Open
PetrDlouhy wants to merge 9 commits into
mainfrom
claude/save-presence
Open

feat(usage): report which Blendkit assets are still in the file at every save [User issue investigation]#2296
PetrDlouhy wants to merge 9 commits into
mainfrom
claude/save-presence

Conversation

@PetrDlouhy

@PetrDlouhy PetrDlouhy commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Why

The save-time usage report ("which Blendkit assets are still in the file") has been dead for years, twice over: get_asset_usages() returned {} on every call (#1013), and client_lib.report_usages() posted to a Client route (/report_usages) that only the retired Python daemon ever had; the Go Client answers 404 and requests.post does not raise, so nothing ever left the machine. Production holds 3 such reports for all of 2026.

The report is the first phase of the search-relevance and redistribution data plan (server side: BlenderKit-server #3727 storage, #3792 anonymous reports, #3805 per-scene locking). Nothing consumes it yet; the server keeps per-scene presence state (first download, first/last seen at a save, instance count, removed-at) for later simulation.

What

  • collect_present_assets(scene, file_wide) counts what a scene actually uses: objects and collection instances linked to it, materials per slot, its world, the scene itself when it is a scene asset; brushes and node groups are file-wide and attributed once, to the active scene. Orphan datablocks do not count (Blender drops them on save).
  • build_save_reports() emits one report per scene that ever touched Blendkit, an empty list included (that is the removal signal). Unchanged presence is re-reported at most hourly (SAVE_REPORT_HEARTBEAT_SECONDS); any change reports on the next save. The memory is in-process, so the first save of every session reports.
  • Transport through the Client's existing /wrappers/nonblocking_request to /api/v1/usage_report/ (auth headers added by the Client). The result task is logged, never a popup; a Client that is not running is one warning and a normal save.
  • utils.get_scene_id(scene=None) guarantees a uuid unique across the file. Blender copies custom properties when creating a scene from another ("New" included), so a second scene inherited the first one's uuid and the server merged their histories. First scene in bpy.data.scenes keeps it; later scenes get a fresh one. This also fixes download attribution in multi-scene files.

Verified

  • End to end on devel.blendkit.com with Blender 5.2 and Client 1.12.13: download, save, delete, save, save-as; a two-scene file (separate uuids and histories); logged out (anonymous scene rows); Client-down warning.
  • 15 unit tests in tests/test_save_presence.py, run under a real bpy 5.0 wheel and in the Blender test matrix: collector counts, file-wide attribution, per-scene reports, uuid de-duplication, dedupe and heartbeat, transport call, silent result handling, background mode.

Not changed

No UI, no prompt. check_unused() is untouched. The proximitySet field is sent empty for compatibility with the server serializer.

🤖 Generated with Claude Code

Second commit: the test runner skipped new test files

The Codecov report on the first push showed tests/test_save_presence.py at 0%: tests/test.py imported a hard-coded module list and the new file was not on it, so CI ran 513 tests, green, without the 15 new ones. The second commit keeps the list only for the modules that must run first (in that order) and appends every other tests/test_*.py automatically; a listed module that does not exist is fatal. CI now runs 528 tests; patch coverage 96% (the only misses are the runner script itself, which coverage cannot measure, plus one line in the test module).

Side finding while looking for the report: Codecov had processed nothing for this repo since the rename to Blendkit on 2026-08-21 (uploads were accepted and dropped). A GET on the Codecov v2 API with the new name resynced the repo; the workflows for this PR and for the latest main were rerun afterwards, so the base is current again.

Third and fourth commits: review + endpoint

  • 410c0cdb (agajdosi's review): the usage-report result has its own download.handle_usage_report_task, routed from timer.handle_task; the generic non-blocking handler is generic again.
  • b478ccd8: reports post to /api/v1/scene_save_reports/ (Blendkit-server#3831 gives them their own endpoint instead of a report_type branch in the usage endpoint); reportType is gone from the payload. Requires Blendkit-server#3831 on production before this is released.

Fifth commit: the Client's own route (agajdosi, round two)

The report no longer rides wrappers/nonblocking_request at all. The add-on posts {"report": ...} to the Client's dedicated /report_usages route, which forwards it to /api/v1/scene_save_reports/ in the background and, like /report_event, creates no Task; the URL-sniffing handler and the timer routing are gone. Depends on the bk_client route (branch claude/usage-report-route, PR to follow) being released in the v1.12 series. A Client without it answers 404, which scene_save only logs.

Sixth commit: renders

render_complete (once per render job) sends the same presence report for the rendered scene with "event": "render"; save reports carry "event": "save". Renders are the strongest evidence of use and the only one for files that are rendered but never saved (test renders of HDRs and materials), and they cannot be reconstructed later. Deduplicated apart from saves. Server side: Blendkit-server#3835 (event column + render presence columns).

Seventh commit: opt-out + the Client's own task type

  • "Send usage data to improve Blendkit" preference (default on), per @Tweekazoid's point that machine-side data needs an opt-out shared by every host. The choice is stored in Blendkit-Client's shared settings (usage_data_opt_out, feat: /report_usages route and the usage_data_opt_out shared setting [User issue investigation] bk_client#52) and enforced there; this add-on exposes it, pushes changes to /settings/set, mirrors the Client's settings broadcast back into the preference (a new settings task handler), and skips collecting entirely when off.
  • Transport per @agajdosi: the report is a report_usages task of its own, routed by timer.handle_task like every other task type; nothing rides the generic wrapper any more.
  • Performance, measured in Blender 5.2: 20,000 objects / 8,000 distinct assets collect in 100 ms (688 KB). Server cap raised to 10,000 with one-query asset resolution in Blendkit-server#3843.

Release order: bk_client#52 → Blendkit-server#3843 → this.

@PetrDlouhy PetrDlouhy self-assigned this Sep 9, 2026
…ery save

The save-time usage report has been dead for years, twice over:

- get_asset_usages() returned {} on every call (issue #1013), so the
  save_pre handler never sent anything.
- Even when it had data, client_lib.report_usages() posted to the Client's
  /report_usages route, which only the retired Python daemon ever had. The Go
  Client answers 404 and requests.post does not raise, so the report vanished
  silently. Production holds 3 "save" reports for all of 2026.

This repairs both and gives the report the semantics the server side
(BlenderKit-server #3727, #3792, #3805) stores as per-scene presence state:

- collect_present_assets(scene) counts the Blendkit assets a scene actually
  uses: objects and collection instances linked to it, materials in their
  slots (one per slot), its world, the scene itself when it is a scene
  asset; brushes and node groups are file-wide and attributed once, to the
  active scene. Orphan datablocks do not count - Blender drops them on save.
- build_save_reports() emits one report per scene that ever touched Blendkit.
  A scene with a uuid is reported even when empty: "nothing left" is the
  removal the server needs to see. Unchanged presence is re-reported at most
  hourly (SAVE_REPORT_HEARTBEAT_SECONDS); any change reports on the next
  save; the memory is in-process, so the first save of a session always
  reports.
- The report goes through the Client's generic nonblocking forwarder to
  /api/v1/usage_report/, which adds the auth headers; the result task is
  logged, never shown as a popup (handle_nonblocking_request_task), and a
  Client that is not running is a warning, never a failed save.
- utils.get_scene_id(scene=None) now guarantees a uuid unique across the
  file. Blender copies custom properties when a scene is created from
  another one ("New" included), so a second scene inherited the first one's
  uuid and the server merged both histories. The first scene in
  bpy.data.scenes keeps a shared uuid; later scenes holding it get a fresh
  one. This also fixes download attribution in multi-scene files.

Verified end to end on devel.blendkit.com with Blender 5.2 and Client
1.12.13: download / save / delete / save / save-as, a two-scene file, logged
out (anonymous scene rows), and the Client-down warning path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.65649% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 34.10%. Comparing base (d32be04) to head (8f1412d).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
tests/test.py 0.00% 9 Missing ⚠️
download.py 94.11% 5 Missing ⚠️
client_lib.py 83.33% 3 Missing ⚠️
utils.py 86.66% 2 Missing ⚠️
persistent_preferences.py 0.00% 1 Missing ⚠️
tests/test_save_presence.py 99.59% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2296      +/-   ##
==========================================
+ Coverage   33.14%   34.10%   +0.96%     
==========================================
  Files          86       87       +1     
  Lines       27187    27527     +340     
==========================================
+ Hits         9010     9388     +378     
+ Misses      18177    18139      -38     
Flag Coverage Δ
python 34.10% <94.65%> (+0.96%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
__init__.py 0.00% <ø> (ø)
datas.py 100.00% <100.00%> (ø)
tests/test_init.py 90.90% <ø> (ø)
tests/test_timer.py 97.94% <100.00%> (+0.13%) ⬆️
timer.py 43.56% <100.00%> (+0.96%) ⬆️
persistent_preferences.py 50.52% <0.00%> (-0.54%) ⬇️
tests/test_save_presence.py 99.59% <99.59%> (ø)
utils.py 28.66% <86.66%> (+0.75%) ⬆️
client_lib.py 59.69% <83.33%> (+1.47%) ⬆️
download.py 20.76% <94.11%> (+6.09%) ⬆️
... and 1 more

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

… list

tests/test.py imported a hard-coded list of test modules. A new test file
that was not added to the list never ran in CI while the workflow stayed
green - exactly what happened to tests/test_save_presence.py in this PR:
CI ran 513 tests, the Codecov report showed the file at 0%, and nothing
else complained.

Keep the explicit list only for what it is actually needed for - the
modules that must run first, in that order, because the client tests
depend on the state earlier modules leave behind - and append every
other tests/test_*.py of the installed package after them. A listed
module that does not exist is fatal, so the list cannot rot either.
Discovery stays import-by-qualified-name (not TestLoader.discover) for
the extension-mode relative-import reason documented above it.

Blender 5.2.0 locally: 528 tests from 25 modules (was 513 from 24).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread utils.py Outdated
PetrDlouhy and others added 2 commits September 10, 2026 08:33
Review (agajdosi): the usage-report result was special-cased inside the
generic utils.handle_nonblocking_request_task, which put feature logic
and report parsing into a shared utility.

The generic handler is back to what it was. The report result now has
its own download.handle_usage_report_task (logged, never a popup), and
timer.handle_task routes it there like every other task type. The
Client's forwarder returns every request as "wrappers/nonblocking_request"
and has no usage-report route of its own, so the routing key is the
request URL (download.is_usage_report_task); a dedicated Client route
would remove even that, and is a Client change, not an add-on one.

Blender 5.2.0 locally: 530 tests (two new dispatch tests in test_timer).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The server gave the save-time presence report its own endpoint
(Blendkit-server#3831) instead of telling it apart from usage reports by
the report_type in the body. Post there; the endpoint is the type, so
reportType is gone from the payload. The result task is still recognised
by its URL, since the Client's forwarder has no route of its own for it.

Requires Blendkit-server#3831 on production before release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@agajdosi agajdosi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mergeable antipattern.

Comment thread client_lib.py Outdated
PetrDlouhy and others added 2 commits September 10, 2026 11:22
Review (agajdosi): riding the general-purpose non-blocking wrapper for a
frequent, specialised report is an anti-pattern however the result task
is routed afterwards. The Client gets a dedicated /report_usages route
(bk_client, claude/usage-report-route) that forwards the report to
/api/v1/scene_save_reports/ with the standard headers and, like
/report_event, creates no Task at all.

The add-on posts {"report": ...} there and stops sniffing task URLs: the
task-type routing in timer.py, download.handle_usage_report_task and
is_usage_report_task are gone. A Client without the route answers 404,
which scene_save only logs, so nothing breaks before the Client release.

Requires the bk_client route released in the v1.12 series (the add-on
pins the minor and resolves the patch at build time).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A save report only sees files that get saved: an HDR dropped into the
world for a test render or a material tried once is used and never
saved, so on save data alone those asset types look unused. A finished
render job is the strongest evidence of use and cannot be reconstructed
later, so it is reported from the first release.

render_complete (once per render job, not per frame) sends the same
presence report for the rendered scene with event "render"; save
reports now carry event "save". Saves and renders are deduplicated
apart, so a render right after an identical save still reports, and a
repeat render within the hour does not. Server side:
Blendkit-server#3835.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PetrDlouhy and others added 3 commits September 10, 2026 17:36
…ient

Michal (#add-on, 2026-09-10): data observed on the user's machine needs
an opt-out, in one place every host shares. The choice lives in the
Client's shared settings (bk_client#52, `usage_data_opt_out`), which the
Client enforces at its /report_usages route; this add-on only exposes
and mirrors it.

- Preference "Send usage data to improve Blendkit" (default on). Changing
  it posts the opt-out to /settings/set and saves the preferences; a
  Client that is not running is logged. Persisted with the other
  preferences.
- The Client broadcasts its settings with every report; a new "settings"
  task handler mirrors the opt-out into the preference (changed from
  Maya, seen in Blender) without echoing it back.
- Save and render handlers return before collecting anything when the
  preference is off.
- Transport per agajdosi's review: the report is now a "report_usages"
  task of its own, routed by timer.handle_task to
  download.handle_usage_report_task (logged, never a popup).

Requires bk_client#52 released in the v1.12 series before this ships.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

3 participants