…'s objects
sync_render_visibility wrote hide_render with
bpy.data.objects.foreach_set(), which writes the DNA directly and skips
the RNA update. Nothing tags the depsgraph, so the evaluated visibility
stays a render behind: after switching drawings, bpy.ops.render.render()
draws the previous drawing's object set with the new drawing's camera.
For a roof plan rendered after a floor plan, the underlay came out as the
floor plan seen from the roof camera.
The camera was always correct and every python-visible flag read back
correct, because visible_get() and hide_render read the DNA we had just
written - only the evaluated copy was stale. That is why clearing
use_persistent_data, calling view_layer.update(), evaluated_depsgraph_get(),
frame_set() or toggling the render engine made no difference, and why
rendering the same drawing twice reproduced the same wrong image.
Assign hide_render through RNA instead, which tags each object. The
equality check keeps it cheap by only writing objects that actually
change: on a 3041 object model this is 0.65s for the first drawing
(2982 changes) and 0.06s for the next (248 changes).
Verified on both bpy.ops.bim.create_drawing and print_all batches by
aligning the two underlay PNGs on camera position at equal px/m and
diffing: the roof underlay went from 0.99% of pixels differing from the
floor plan underlay (ie. the same image) to 67%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes #8603.
The bug
A drawing created after a different drawing was created earlier in the session gets an underlay showing the previous drawing's objects, rendered with the current drawing's camera. A ROOF plan rendered after a 1ST FLOOR plan comes out as the floor plan seen from the roof camera — the roof is simply absent, so you see down to the slab. The first drawing of a session is always correct.
Root cause
Blender.sync_render_visibility wrote hide_render with bpy.data.objects.foreach_set(...). foreach_set writes the DNA directly and skips the RNA update, so nothing tags the depsgraph — the evaluated visibility stays a render behind and bpy.ops.render.render() draws the previous drawing's object set. The camera is unaffected, since camera changes go through normal RNA assignment.
This is why it resisted diagnosis: visible_get(), obj.hide_render and context.visible_objects all read the DNA we had just written, so every python readback reports correct state. Instrumenting right up to the render.render() call showed the correct camera and the correct 276-object renderable set — and it still rendered the other drawing.
It also explains why the previously attempted workarounds all failed (use_persistent_data = False, view_layer.update(), evaluated_depsgraph_get(), frame_set(), warm-up renders, toggling the render engine): they flush or rebuild, but nothing re-tags objects that were never marked dirty.
The fix
Assign hide_render through RNA so each object is tagged, with an equality check so only objects that actually change are written.
Cost on a 3041-object model: 0.65s for the first drawing (2982 changes), 0.06s for the next (248 changes). Note the visible_get() scan still runs over every object, exactly as before — that part is unchanged.
Verification
Tested on a real model (Highland Haven), on both bpy.ops.bim.create_drawing and SHIFT+click print_all batches.
Both underlays render at the same 246 px/m, so aligning them on camera position and pixel-diffing is a direct measure of "is this the other drawing's image":
Controlled A/B: the renderable fingerprint going into the render was byte-identical between the failing and passing runs (n=276 md5=c819482e421d), same camera, same clip band. The only variable changed was foreach_set → RNA assignment, and the output flipped from wrong to correct.
Second commit
Unrelated bug noticed in the same code path: CreateDrawing saves should_use_underlay_cache into a local that is never read again — and does it inside the loop, so each drawing overwrites it. Any batch print left the user's underlay cache setting disabled until they toggled it back by hand. Hoisted the save/disable out of the loop and restored the value alongside the existing active-drawing restore.
Note on the previous diagnosis
The original issue attributed this to bpy.ops.render.render() carrying state across calls under EEVEE-Next and suggested an upstream Blender report. That was wrong — see #8603 (comment). In particular, re-running a single drawing does not fix it (the rerun produces a pixel-identical wrong image), and the visible-object sets were never identical. Draft PR #8900 restructures print_all into a modal operator on that basis; it does not address this, since the failure reproduces across two separate operator invocations with a full event loop in between.
🤖 Generated with Claude Code