Add-on version: 1.1.0 | Blender: 4.0.0+ | License: GNU GPL v3.0
- Introduction
- Requirements & Installation
- UI Overview
- Getting Started — Core Workflow
- Utilities
- Known Limitations
- Blend Mode Reference
- Developer Reference
- Changelog
- Quick-Start Cheat Sheet
Blender Raster Editor is an open-source Blender add-on that introduces a non-destructive, Photoshop-like layer workflow directly inside the 3D Viewport. Instead of relying on external image editors, you can stack textures, apply blend modes and masks, and composite them in real time — all managed by a smart shader node tree that the add-on builds and maintains automatically behind the scenes.
Key capabilities:
- Non-destructive layer stack with per-layer blend modes and opacity
- 19 blend modes (Multiply, Overlay, Screen, Color Burn, Dodge, and more)
- Per-layer black-and-white masks for non-destructive hiding/revealing of pixel areas
- Video and image-sequence rotoscoping with automatic frame refresh
- One-click Merge Visible bake using Blender's Cycles engine
- Canvas resize utility — adds empty space around existing artwork without any stretching
- Auto-framing orthographic camera setup for clean render output
- Full compatibility with Blender 4.0+ and 5.1+
| Item | Requirement |
|---|---|
| Blender version | 4.0.0 or higher (including 5.x) |
| Python module | numpy (bundled with Blender — no action needed) |
| OS | Windows, macOS, Linux |
| Render engine (for Merge Visible) | Cycles (built-in) |
- Download the repository as a ZIP file (Code → Download ZIP on GitHub).
- Open Blender and go to Edit → Preferences → Add-ons.
- Click Install…, locate the downloaded
.zip, and confirm. - Enable the add-on by ticking the checkbox next to Paint: Blender Raster Editor.
- Press N in the 3D Viewport to open the Sidebar, then select the Paint Layers tab.
Note: The add-on registers three modules in dependency order: Properties → Operators → UI. Unregistration happens in reverse. If Blender reports an error during enable, open the System Console (Window → Toggle System Console on Windows, or launch Blender from a terminal on macOS/Linux) to read the detailed log output. All log messages are prefixed with the module name, e.g.
[blender_raster_editor.engine] DEBUG: ….
All controls live in the Paint Layers sidebar tab (N-panel → Paint Layers). The panel is organised into four sections:
| Section | Purpose |
|---|---|
| Canvas | Create a new canvas plane with a managed material |
| Layer Management | Add layers; each layer row shows its image, controls, blend mode, and opacity |
| Utilities | Apply Opacity, Merge Visible, Resize Canvas, Frame Camera |
| Paint Settings | Quick brush controls — visible only when in Texture Paint mode |
The panel opens by default when the add-on is enabled so that new users immediately see the available controls.
This section walks you through every step of a typical painting session, from creating a blank canvas to outputting a final merged image.
Where: Sidebar → Create Canvas button (top of the Paint Layers panel).
Clicking Create Canvas performs the following automatically:
- Adds a 2 × 2 world-unit plane to the scene, named
Canvas - Creates a new node-based material called
CanvasMaterialand attaches it to the plane - Adds a default
Backgroundlayer to the layer stack - Builds the initial shader node tree, wiring a Principled BSDF to the layer composition group
Working with an existing object: You do not have to use the Create Canvas button. You can select any mesh that already has a node-based material — the add-on attaches its layer data to whichever object is currently active. Just make sure Use Nodes is enabled on the material (Properties → Material → Surface → Use Nodes).
Duplicate name warning: If an object called
Canvasalready exists, Blender will silently rename the new one (e.g.Canvas.001). The add-on will warn you in the viewport header and in the System Console.
Where: Sidebar → Add Layer button.
Each click adds a new layer entry at the top of the visible stack (highest compositing priority). Layers are displayed top-to-bottom in the panel, corresponding to highest-to-lowest in the actual blend order — the same convention used in Photoshop and other raster editors.
After adding a layer you typically want to:
- Give it a meaningful name (click the name field in the layer row and type).
- Assign an image — either click New to generate a blank texture, or Open (folder icon) to load an existing file from disk.
- Set a blend mode and opacity if this is not the base layer.
Tip: The very first layer (index 0, labelled
Backgroundby default) does not display blend mode or opacity controls — it is always fully opaque and acts as the base of the composition. All layers above it blend on top of it.
Each layer is displayed as a box containing several controls. Here is what every element does, reading left to right:
[ ● ] [ 👁 ] [ Layer Name _______ ] [ ▲ ] [ ▼ ] [ ⎘ ] [ ✕ ]
[ Image selector / template_ID ]
[ Mask row (see Section 4.5) ]
[ Blend Mode dropdown ]
[ Opacity ────────────────────────────────────── 1.00 ]
| Control | Icon | Description |
|---|---|---|
| Paint Target | Brush / radio button | Marks this layer (or its mask) as the active paint destination. A filled brush icon means this layer is currently selected for painting. Click it before switching to Texture Paint mode. |
| Visibility | Eye open / Eye closed | Toggles the layer on or off in the composition. Hidden layers are completely excluded from the shader output and from Merge Visible baking. |
| Name field | — | Editable text box. Double-click or click once and type to rename the layer. |
| Move Up ▲ | Triangle up | Moves the layer one position higher in the stack (increases compositing priority). |
| Move Down ▼ | Triangle down | Moves the layer one position lower in the stack (decreases compositing priority). |
| Duplicate ⎘ | Copy icon | Creates an independent copy of the layer directly below the original, with its own node group. Both copies initially reference the same image and mask data-blocks; to get fully independent pixel data, reassign the image on the duplicate after copying. |
| Delete ✕ | X button | Permanently removes the layer from the stack. This action is undoable (Ctrl+Z). |
| Image selector | — | Blender's built-in template_ID widget. Use New to create a blank texture at a chosen resolution, or the folder icon to open an existing file (.png, .jpg, .exr, .mp4, image sequences, etc.). |
| Blend Mode | — | Dropdown with 19 blending algorithms. Only visible on layers above index 0. See Section 7 for full descriptions. |
| Opacity slider | — | Sets per-layer transparency from 0.0 (fully transparent) to 1.0 (fully opaque). Only visible on layers above index 0. If the viewport does not update immediately, click Apply Opacity in the Utilities section. |
Follow these steps every time you want to paint on a specific layer:
- Select the paint target. Click the Brush icon (paint target button) on the desired layer row. The icon fills in to confirm the selection.
- Switch to Texture Paint mode. Press Tab or use the mode dropdown in the viewport header and select Texture Paint.
- Assign an image if needed. If the layer has no image yet, the viewport will be grey. Click New in the image selector on that layer row, choose a resolution (e.g. 2048 × 2048), and confirm. The canvas will become paintable.
- Paint. Use Blender's standard brush tools. The Paint Settings section at the bottom of the panel provides quick access to brush Color, Radius, Strength, and Blend Mode without having to open the Tool shelf.
- Save your work. Blender does not auto-save painted images. Go to Image Editor → Image → Save As (or pack images into the
.blendfile via Image → Pack) to avoid losing paint data.
⚠️ Important: Always confirm the correct layer is selected before painting. The add-on keeps Blender's active image node in sync with the paint target whenever you click the Brush icon, but switching objects, undoing, or manually selecting nodes in the Shader Editor can desync this. If your strokes appear to paint on the wrong layer, click the Brush icon again on the intended layer.
Masks let you non-destructively hide or reveal parts of a layer using a greyscale image:
- White pixels → fully reveal the layer content beneath the mask
- Black pixels → fully hide the layer content
- Grey pixels → partial transparency proportional to the grey value
Click Add Mask on any layer row. The add-on will:
- Create a new 1024 × 1024 white image (fully revealing by default), stored without an alpha channel
- Assign it as the mask for that layer
- Automatically enable the mask and set it as the active paint target
Once a mask exists, the layer row expands to show a mask sub-row below the image selector:
[ ● ] [ 🛡 ] [ Mask image selector ______________ ] [ ✕ ]
| Control | Description |
|---|---|
| Mask Paint Target (brush icon) | Sets the mask as the active paint destination. Click this before painting in greyscale to edit the mask. |
| Mask Enable Toggle (shield icon) | Toggles the mask on/off without deleting it. Useful for quickly comparing masked vs unmasked states. |
| Mask Image Selector | Lets you swap the mask for any other image. You can load an external file as a mask (e.g. an alpha channel exported from another tool). |
| Remove Mask ✕ | Deletes the mask association from the layer. The underlying mask image data-block is not deleted from the .blend file, only unlinked. |
- Click the Brush icon on the mask row (not the main layer row) to target the mask.
- Switch to Texture Paint mode.
- Set your brush colour to black to hide areas, white to restore them, or any grey for partial transparency.
- Paint as normal. The mask effect is visible in real time in the viewport.
Tip: You can use the Erase brush blend mode to paint back to white (fully revealed) quickly.
Blend Mode controls how a layer's pixels are mathematically combined with the layers beneath it. Opacity scales the layer's overall contribution before blending.
Both controls appear on every layer except the background (index 0). The blend mode dropdown is located below the image selector; the opacity slider is directly below it.
Changes to blend mode take effect immediately. Changes to opacity may occasionally lag in EEVEE's viewport due to shader cache behaviour — if you see no change after moving the slider, click Apply Opacity in the Utilities section (see Section 5.1).
For a full description of every available blend mode, see Section 7.
Reordering is done with the ▲ and ▼ buttons on each layer row. Moving a layer up increases its priority in the blend stack (it composites on top of more layers). Moving it down decreases priority.
Duplicating a layer via the ⎘ (copy) icon creates an independent copy inserted directly below the original. The duplicate gets its own internal node group — modifying one layer's node group does not affect the other. Both copies initially reference the same image and mask data-blocks. If you need fully independent pixel data, use the image selector on the duplicate to create or open a new image.
The add-on handles .mp4 video files and image sequences natively, making it well-suited for frame-by-frame rotoscoping work:
- In the image selector on any layer, click the folder icon and load an
.mp4file or the first frame of an image sequence (e.g.frame_0001.png). - The add-on automatically enables
Auto Refreshon the image user and syncsframe_durationso the texture updates as you scrub or play the timeline. - To paint or annotate on top of a video layer, add a new blank layer above it and paint on that layer. The video layer below it remains unmodified.
Limitation: Video and image-sequence layers cannot be resized via the Resize Canvas utility. Resize the source footage externally if needed before loading it.
The Utilities section sits at the bottom of the Paint Layers panel and provides four tools for layer management, baking, and viewport setup.
Button: Utilities → Apply Opacity (file refresh icon)
When to use it: Due to the way EEVEE caches shader node data, moving the Opacity slider on a layer may not visually update the viewport immediately. Clicking Apply Opacity forces the add-on to re-synchronise the entire node tree, flushing any cached values and making all opacity changes visible.
This button is also useful after any operation that modifies layer data programmatically (e.g. via a script) and you want to guarantee the viewport reflects the current state.
Internally, the Opacity slider uses a fast, targeted callback (_opacity_update) that patches only a single Math node rather than rebuilding the whole node tree. Apply Opacity triggers the full rebuild as a manual override when that incremental patch is not enough to satisfy the EEVEE cache. See Section 8.8 for more detail.
Button: Utilities → Merge (or Merge Visible)
What it does: Bakes the entire visible layer stack into a single flat image using Blender's Cycles render engine. The result is placed into a new layer at the top of the stack, and the original visible layers are hidden (not deleted).
Step-by-step:
- Make sure at least 2 layers are visible in the stack. (Merge Visible requires a minimum of 2 visible layers to be meaningful.)
- Click Merge Visible.
- A resolution dialog appears. Enter the desired output resolution (default is 1024 × 1024; maximum is 4096 × 4096).
- Click OK. The add-on will:
- Temporarily switch the render engine to Cycles
- Create a new blank image at the chosen resolution
- Insert a temporary bake target node into the material
- Run a Diffuse bake pass (
bpy.ops.object.bake) - Remove the temporary node (always, even if baking fails)
- Hide all previously visible layers
- Add a Merged Layer at the top of the stack containing the baked result
- Restore the original render engine (always, even if an error occurred)
⚠️ Warning: Baking freezes the Blender interface for several seconds — longer at high resolutions or on complex scenes. Save your.blendfile before using Merge Visible. The undo history (Ctrl+Z) can recover the hidden layers, but the baked image data will be lost if Blender crashes before you save.
Tip: For iteration work, bake at 512 × 512 or 1024 × 1024 to keep bake times short. Only bake at full resolution (2048 × 2048 or 4096 × 4096) for final output.
Button: Utilities → Resize Canvas (fullscreen icon)
What it does: Changes the pixel dimensions of all raster layers on the canvas, adding empty (transparent/black) space around the existing artwork to fill the new size. No stretching occurs — existing pixel data is centred in the new canvas and copied without resampling.
Step-by-step:
- Click Resize Canvas.
- The dialog pre-fills with the current dimensions of the first raster layer it finds. Adjust Width and Height to your target resolution.
- Click OK. The add-on will:
- Read each layer's pixel data into a NumPy array
- Create a new blank canvas at the target size
- Centre the old content and copy it across (with bounds-checking to handle both upscaling and downscaling)
- Replace each layer's image reference with the new resized image
- Resize mask images using a no-alpha channel image, consistent with how masks are originally created
- Adjust the Canvas object's world-space dimensions to maintain the correct aspect ratio (only when all layers resize successfully)
What gets skipped: Video (.mp4) and image-sequence layers cannot be resized this way — they are automatically skipped. The add-on will warn you if any layer could not be resized.
Note on downscaling: If the new canvas is smaller than the original artwork, the content is cropped from the centre outward — not scaled down. Use Blender's Image Editor (Image → Scale Image) before loading your files if you need a proper downscale with resampling.
Button: Utilities → Frame Camera (camera icon)
What it does: Sets up a perfectly aligned orthographic camera that frames the canvas exactly, ready for rendering.
Clicking Frame Camera will:
- Find an existing camera in the scene, or create a new orthographic camera named
Canvas_Cameraif none exists - Position the camera directly above the canvas centre, pointing straight down
- Set
ortho_scaleto match the canvas world-space dimensions (so the render fills the frame with no border) - Set the render resolution to 1920 px on the longest axis, with the shorter axis scaled proportionally to maintain the correct aspect ratio
- Switch the active 3D Viewport to Camera perspective so you immediately see the framed result
Common use cases:
- Setting up the final render after all layers are painted
- Checking what the render will look like at any point during painting
- Re-framing after using Resize Canvas (which changes the canvas aspect ratio)
⚠️ Warning: If the canvas object has a zeroed scale (e.g. you just created it and pressed S then 0), Frame Camera will report an error: "Canvas has zero dimensions — apply scale (Ctrl+A) before framing the camera." Press Ctrl+A → Apply → Scale in the viewport first, then try again.
| Limitation | Details & Workaround |
|---|---|
| Baking performance | Merge Visible uses Cycles and freezes the UI. Use lower resolutions during iteration; merge at full resolution for final output only. |
| Opacity update lag | EEVEE's shader cache may delay opacity changes. Click Apply Opacity to force a refresh. |
| Node tree protection | The managed node frame (LAYER_MANAGER_FRAME) must not be edited manually in the Shader Editor — the add-on will overwrite any manual changes on the next rebuild. Always use the panel UI. |
| Viewport only | Layered edits live in the shader node tree and are visible in the 3D Viewport. To export a standalone image file, always run Merge Visible first, then save the resulting image from the Image Editor. |
| Resize & video layers | Video (.mp4) and image-sequence layers are skipped by Resize Canvas. Resize source files externally if needed. |
| Resize crops, doesn't scale | Downscaling with Resize Canvas crops from the centre rather than scaling down. Use Blender's Image Editor to scale images before loading them. |
| Minimum 2 layers for Merge | Merge Visible requires at least 2 visible layers. If you only have one, duplicate it or add a blank layer before merging. |
| Camera zero-scale guard | Frame Camera will fail if the canvas object's scale is zeroed. Apply scale with Ctrl+A first. |
| Node tree manual edits | Any manual changes made inside the LAYER_MANAGER_FRAME in the Shader Editor will be overwritten the next time any layer property changes, because the add-on rebuilds the frame from scratch on every update. |
| Mode | Description |
|---|---|
| Mix | Standard alpha-compositing blend (default). Blends linearly between the base and blend layers using the layer opacity as the factor. |
| Darken | Compares pixels and keeps the darker value at each channel. Bright areas of the blend layer have no effect. |
| Multiply | Multiplies pixel values together. Always darkens or keeps the same; never lightens. Great for shadows and colour filters. |
| Color Burn | Darkens the base by increasing contrast to reflect the blend colour. Produces strong, saturated darkening. |
| Lighten | Compares pixels and keeps the lighter value at each channel. Dark areas of the blend layer have no effect. |
| Screen | Inverts both layers, multiplies, then inverts the result. Always lightens or keeps the same; never darkens. |
| Color Dodge | Brightens the base layer by decreasing contrast to reflect the blend layer's colour. Produces bright, washed-out highlights. |
| Add | Adds colour values directly. Quickly clips to white (1.0). Useful for glow and light-emission effects. |
| Overlay | Combines Multiply and Screen depending on the base layer brightness: darkens darks and lightens lights, increasing contrast overall. |
| Soft Light | A gentler variant of Overlay. Subtle contrast boost without clipping. Good for dodge/burn-style shading. |
| Linear Light | Combines Linear Burn and Linear Dodge. Very strong contrast effect — use with low opacity. |
| Difference | Subtracts one layer from the other and takes the absolute value. Black where layers are identical; inverted colour where they differ. Useful for alignment checks. |
| Exclusion | Similar to Difference but produces lower contrast. Overlapping areas tend towards grey rather than black. |
| Subtract | Subtracts the blend layer from the base, clamping at black. Useful for darkening specific colour channels. |
| Divide | Divides the base by the blend layer. Brightens the result. White in the blend layer leaves the base unchanged. |
| Hue | Takes the hue from the blend layer and the saturation + value from the base. Changes colour tone without altering brightness. |
| Saturation | Takes the saturation from the blend layer and the hue + value from the base. Useful for desaturating specific areas. |
| Color | Takes hue + saturation from the blend layer and the value from the base. Classic "colorize" effect — applies colour without changing luminosity. |
| Value | Takes the brightness (value) from the blend layer and the hue + saturation from the base. Useful for swapping lightness maps. |
| Module | Responsibility |
|---|---|
__init__.py |
Add-on entry point. Configures the package-level logger, then registers modules in dependency order (Properties → Operators → UI); unregisters in reverse. |
constants.py |
Single source of truth for all magic strings, numeric defaults, node identifiers, and error/info messages. Edit this file to change defaults globally. |
properties.py |
Defines RasterLayerItem (a PropertyGroup) and the three object-level properties (raster_layers, raster_active_index, raster_active_is_mask). Contains _auto_update_tree (fires on most property changes) and _opacity_update (dedicated fast callback for the opacity slider). Also contains _find_owner_object, which resolves the owning scene object from a layer item by identity scan. |
engine.py |
NodeTreeManager class and the top-level rebuild_node_tree() function. All shader node construction, linking, synchronisation, and orphan node group purging live here. |
operators.py |
bpy.types.Operator subclasses for every user action. BaseOperator provides shared _validate_active_object / _validate_active_material helpers and unified reporting helpers. |
ui.py |
VIEW3D_PT_raster_layers panel and all drawing helper methods. Draws layers in reverse index order so the highest-priority layer appears at the top. |
User action in the UI panel
→ Operator.execute()
→ modifies obj.raster_layers (or obj.raster_active_index, etc.)
→ calls rebuild_node_tree(obj)
→ NodeTreeManager.clear_manager_frame() — tears down old frame + purges orphan groups
→ NodeTreeManager.build_composition_chain() — creates group nodes + mix chain
→ NodeTreeManager.set_active_layer_selection() — highlights active node in Shader Editor
→ Blender's viewport updates
Most property changes (image, blend_type, is_visible, use_mask, mask_image) trigger _auto_update_tree, which calls rebuild_node_tree() automatically — no explicit operator call is required.
The opacity property uses the dedicated _opacity_update callback instead. See Section 8.8.
RasterLayerItem is a PropertyGroup stored in obj.raster_layers (a CollectionProperty). Each entry represents one layer.
| Property | Type | Default | Update Callback | Description |
|---|---|---|---|---|
name |
StringProperty |
"Layer" |
— | User-visible layer name. |
image |
PointerProperty(Image) |
None |
_auto_update_tree |
Main texture image. Automatically enables Auto Refresh for movie/sequence sources. |
mask_image |
PointerProperty(Image) |
None |
_auto_update_tree |
Greyscale mask image. White = fully revealed; black = fully hidden. |
use_mask |
BoolProperty |
True |
_auto_update_tree |
Toggles whether the mask is applied in the composition. |
is_visible |
BoolProperty |
True |
_auto_update_tree |
Excludes the layer from the shader output and bake when False. |
blend_type |
EnumProperty |
'MIX' |
_auto_update_tree |
Blending algorithm. 19 options; see Section 7. Not shown for index-0 layer. |
opacity |
FloatProperty |
1.0 |
_opacity_update |
Per-layer opacity, 0.0–1.0. Uses a fast targeted callback instead of full rebuild. |
group_name |
StringProperty |
"" |
— | Internal identifier of the ShaderNodeTree group created for this layer. Must not be edited manually. |
Three additional properties are registered on bpy.types.Object:
| Property | Type | Default | Description |
|---|---|---|---|
raster_layers |
CollectionProperty(RasterLayerItem) |
— | Ordered list of all layers on this object. |
raster_active_index |
IntProperty |
0 |
Index of the layer currently targeted for painting. |
raster_active_is_mask |
BoolProperty |
False |
Whether the active paint target is the mask (True) or the main image (False). |
All configurable values live in constants.py. The most commonly relevant ones:
| Constant | Value | Purpose |
|---|---|---|
NODE_FRAME_NAME |
"LAYER_MANAGER_FRAME" |
Name of the protected frame node inside the material. |
DEFAULT_CANVAS_SIZE |
2.0 |
World-space size of the generated canvas plane (metres). |
DEFAULT_MASK_RESOLUTION |
1024 |
Pixel size of automatically created masks. |
DEFAULT_MERGE_RESOLUTION |
1024 |
Default bake resolution for Merge Visible. |
DEFAULT_RENDER_RESOLUTION |
1920 |
Pixel length of the longest axis when Frame Camera sets render resolution. |
DEFAULT_CAMERA_HEIGHT |
5.0 |
World-space distance above the canvas at which the camera is placed. |
BAKE_ENGINE |
"CYCLES" |
Render engine used during Merge Visible. |
MIN_VISIBLE_LAYERS_FOR_MERGE |
2 |
Minimum visible layers required to run Merge Visible. |
MASK_IMAGE_ALPHA |
False |
Whether mask images are created with an alpha channel (they are not). |
SHADER_TYPE_GROUP |
"ShaderNodeGroup" |
String passed to nodes.new() to create a group node. |
SHADER_NODE_GROUP_TYPE |
"GROUP" |
Value returned by n.type for group nodes — distinct from the above. |
- Subclass
BaseOperatorinoperators.py. - Implement
execute(self, context). Call_validate_active_object()and_validate_active_material()at the start to benefit from shared error handling. - Add the class to
_OPERATOR_CLASSESat the bottom ofoperators.py. - Add a UI button in
ui.pycalling your operator'sbl_idname.
- Add an entry to the
blend_typeEnumPropertyitems list inproperties.py. - Blender's
ShaderNodeMixsupports all standard blend modes natively — no changes toengine.pyare required.
- Define the
bpy.props.*field inRasterLayerIteminproperties.py. - Add
update=_auto_update_treeto the property definition so the node tree rebuilds automatically on change. If the property is a continuous value that may fire rapidly (like a float slider), write a dedicated lightweight callback modelled on_opacity_updateinstead, to avoid per-tick full rebuilds. - Handle the new property in
NodeTreeManager.update_layer_group()inengine.py. - Expose the property in
ui.pyinside_draw_layer_item().
The add-on explicitly handles a breaking change introduced in Blender 5.1 regarding group node strings:
| Context | Correct value | Wrong value |
|---|---|---|
nodes.new(…) — creating a group node |
"ShaderNodeGroup" (SHADER_TYPE_GROUP) |
"GROUP" |
n.type — reading a node's type |
"GROUP" (SHADER_NODE_GROUP_TYPE) |
"ShaderNodeGroup" |
Using the wrong string in either context produces a silent failure in Blender 5.1. The codebase uses distinct named constants for each case (SHADER_TYPE_GROUP vs SHADER_NODE_GROUP_TYPE) to make the distinction explicit.
Similarly, ShaderNodeMix socket names ('A', 'B', 'Factor', 'Result') are accessed by name rather than by numeric index, because socket indices are undocumented and subject to change across Blender versions.
In Blender 5.1+, image_paint.brush became read-only and is managed by the tool system. The Paint Settings section therefore displays the active brush name as a label rather than using template_ID, which would render a misleading red box.
Layer property callbacks (_auto_update_tree, _opacity_update) need to know which scene object owns the layer that changed. Using context.active_object is unreliable in multi-canvas scenes, because the user may have a different object selected while a script mutates a layer on another object.
The add-on resolves the owning object via _find_owner_object(layer_item), which scans bpy.context.scene.objects (current scene only — not bpy.data.objects, which includes all scenes and linked libraries) and compares each layer by Python identity until it finds the owner.
Performance note: this scan is O(objects × layers). In typical scenes with a handful of canvases and a few layers each, this is negligible. If performance becomes a concern for very large scenes, consider caching the owner object name on the layer item and using it as an O(1) fast path with a fallback to the full scan when stale.
The function also guards against bpy.context.scene being None, which can occur in headless/background-render mode.
The opacity property fires _opacity_update instead of the general _auto_update_tree. The reason is performance: a float slider fires its update callback on every redraw tick during a drag — potentially dozens of times per second. Calling rebuild_node_tree() each time would tear down and reconstruct the entire shader graph on each tick, causing noticeable lag on scenes with many layers.
_opacity_update instead calls only NodeTreeManager.update_layer_group(ng, layer), which patches the single Math (Multiply) node's input value in-place. This is sufficient for a pure opacity change and keeps the slider interaction smooth.
The Apply Opacity button in the Utilities panel triggers a full rebuild_node_tree() as a manual override for the rare cases where EEVEE's shader cache still delays the visual result despite the targeted patch.
Opacity slider drag
→ _opacity_update (fires on every tick)
→ NodeTreeManager.update_layer_group() ← patches Math node only
→ fast, no graph rebuild
Apply Opacity button
→ RASTER_OT_sync_layers.execute()
→ rebuild_node_tree() ← full rebuild, flushes cache
Each layer has its own ShaderNodeTree group, referenced by layer.group_name. Understanding when groups are created, reused, and destroyed is important when scripting against the add-on.
Creation: NodeTreeManager.create_or_update_layer_group(layer) is called during build_composition_chain(). If layer.group_name already points to a valid, correctly-structured group, it is reused. If the group is missing, has a stale name, or is missing expected sockets, a new group is created and layer.group_name is updated.
Duplication: RASTER_OT_duplicate_layer explicitly clears new_layer.group_name = "" before the rebuild so that the duplicate is always assigned a new, independent group. Without this, both layers would share a group and mutate each other's state.
Deletion: When NodeTreeManager.clear_manager_frame() tears down the frame, it:
- Snapshots references to all node groups before removing their group nodes (important: users drop to 0 only after removal, so the snapshot must happen first).
- Removes all child nodes.
- Calls
bpy.data.node_groups.remove(ng)on any group whoseuserscount drops to 0, preventing orphan data-block accumulation over a long session.
The group name is saved to a local variable before remove() is called because accessing ng.name after removal raises a ReferenceError in Blender 5.1.
This release consolidates a series of targeted bug fixes across all modules. Changes are grouped by area.
- Attached a
StreamHandlerto the package root logger insideregister()so that log output from all sub-modules is visible in Blender's System Console from the moment the add-on is enabled. A guard prevents duplicate handlers on reload without a full Blender restart.
- Mask toggle fix: Mask factor input links are now always cleared before conditionally re-wiring, preventing the mask factor from getting stuck after toggling
use_maskoff and back on. - Default opacity fix: Both inputs of the
Math (Multiply)opacity node are initialised to1.0when a new layer group is created, so new layers are fully opaque by default instead of transparent. - Base Color link fix: The existing Base Color link is always cleared before attempting to connect the new final output — even when
build_composition_chainreturnsNone— to avoid showing a stale rendered result when all node groups fail. - Orphan group purge: Group node references are snapshotted before their parent nodes are removed, ensuring the
users == 0check is accurate. Groups with no remaining users are purged withbpy.data.node_groups.remove(), preventing data-block accumulation. - Blender 5.1
nodes.new()fix: Group nodes are created using"ShaderNodeGroup"(not"GROUP");n.typechecks use"GROUP". Two separate named constants make the distinction explicit. - Blender 5.1 socket access fix:
ShaderNodeMixinputs/outputs are accessed by name ('A','B','Factor','Result') instead of numeric index. - Node placement counter fix: A dedicated placement counter (rather than the enumerate index) is used when positioning nodes, so gaps do not appear in the Shader Editor when a layer's node group fails to be created.
- Canvas duplicate name warning: If Blender silently renames the new canvas object because
"Canvas"already exists, the user is now warned both in the viewport header and the System Console. - Remove layer active-index fix: The
was_active_indexis captured beforeraster_layers.remove()is called, so theraster_active_is_maskreset correctly targets only the deleted layer (not an unrelated surviving layer at the same index after clamping). - Duplicate layer group fix:
new_layer.group_nameis explicitly cleared on duplication, ensuring the duplicate always receives an independent node group. - Merge Visible render engine restore:
orig_engineis saved before the innertryblock so thefinallyclause can always restore it, even when an early validation error is raised. - Merge Visible bake node cleanup: The temporary bake target node is removed in a
finallyblock, guaranteeing removal on both success and failure. - Merge Visible layer snapshot fix:
visible_layersis computed once before the initialrebuild_node_tree()call, so the same list is used for the minimum-layer check and for hiding originals. - Resize Canvas material validation:
_validate_active_material()is called before resizing begins, so a missing material produces a clear error rather than a silent failure. - Resize Canvas failure tracking: Per-layer resize failures are tracked and reported to the user; the object's world-space dimensions are only adjusted when all layers resized successfully.
- Resize Canvas mask alpha fix: Mask images are resized using
MASK_IMAGE_ALPHA = False(no alpha channel), consistent with how masks are originally created. - Resize Canvas mask guard: The mask resize call is explicitly guarded on
layer.mask_image is not None, making the intent clear rather than relying on the internal early-return of_resize_image_canvas. - Frame Camera zero-scale guard: Returns an error if the canvas object has zero dimensions, preventing an invisible camera viewport.
MASK_IMAGE_ALPHAconstant: Replaces the confusingnot DEFAULT_IMAGE_ALPHAexpression increate_maskand_resize_image_canvas.
- Multi-canvas owner resolution:
_find_owner_object()scansbpy.context.scene.objects(current scene only) instead of usingcontext.active_object, preventing callbacks from operating on the wrong canvas in multi-canvas scenes or when properties are mutated via script. - Scene scope fix: Uses
bpy.context.scene.objectsinstead ofbpy.data.objectsto avoid returning objects from other scenes or linked libraries. - Headless mode guard:
_find_owner_object()returnsNoneearly whenbpy.context.scene is None(headless/background-render mode). - Canvas aspect ratio fix:
_auto_update_treeadjustsobj.dimensions(world-space) rather thanobj.scalewhen auto-fitting the canvas to a newly loaded background image, preventing compounding scale errors on non-unit canvases. - Opacity performance fix:
opacityuses a dedicated_opacity_updatecallback that patches only the Math node in-place, instead of triggering a fullrebuild_node_tree()on every slider tick.
- Panel open by default: Removed
'DEFAULT_CLOSED'from panel options so the panel is immediately visible after enabling the add-on. - Empty layer list hint: A helpful
INFOlabel is shown when no layers exist yet. - Reorder button direction fix:
TRIA_UPnow correctly firesdirection='UP'andTRIA_DOWNfiresdirection='DOWN'. Previously the directions were swapped to compensate for the reversed draw order, making the code misleading. The reversed draw order is now handled exclusively in_draw_layer_list. - Blender 5.1 brush selector fix: Replaced
template_IDfor the active brush (which renders a red box in 5.1 becauseimage_paint.brushis read-only) with a label displaying the brush name, followed by the standard brush property controls.
| Goal | Action |
|---|---|
| Create a new canvas | Sidebar → Create Canvas |
| Add a layer | Sidebar → Add Layer, then assign or create an image |
| Paint on a layer | Click Brush icon on the layer → switch to Texture Paint mode → paint |
| Add a mask to a layer | Click Add Mask on the layer row |
| Paint a mask | Click Brush icon on the mask row → switch to Texture Paint → paint in greyscale |
| Toggle mask on/off | Click the Shield icon on the mask row |
| Remove a mask | Click ✕ on the mask row |
| Change blend mode | Blend Mode dropdown below the layer image selector |
| Change opacity | Opacity slider; click Apply Opacity if the viewport lags |
| Force node tree sync | Utilities → Apply Opacity |
| Reorder layers | ▲ / ▼ buttons on the layer row |
| Duplicate a layer | ⎘ (copy icon) on the layer row |
| Delete a layer | ✕ button on the layer row |
| Hide a layer | Eye icon on the layer row |
| Flatten all layers | Utilities → Merge Visible (requires ≥ 2 visible layers) |
| Change canvas size | Utilities → Resize Canvas → enter new dimensions |
| Set up render camera | Utilities → Frame Camera |
| Load a video layer | Image selector → folder icon → select .mp4 or first frame of sequence |
| Export the final image | Merge Visible → open Image Editor → Image → Save As |
Blender Raster Editor — Licensed under GNU GPL v3.0 — See LICENSE for details.