Multi-Mask Combiner (Batch, 5 layers)
Different masks at different stages of generation
- mask_1
- mask_2
- mask_3
- mask_4
- mask_5
- mask_batch
- schedule_graph
- schedule_info
Here's the idea that makes this whole pack interesting, and it's the node the README is most excited about. Normal masking gives you one mask that applies for the entire generation. Temporal masking gives you a schedule: the face controls the first 30% of denoising, the hands take over from 30–60%, the background finishes the job. This node is what turns up to five hand-painted masks into that schedule - a batch of masks where keyframe i represents "which regions are active during time window i."
How it works
Each of the five layers has its own mask_N, strength_N, start_percent_N, and end_percent_N. The node samples the whole generation timeline into num_keyframes snapshots, and for each snapshot it combines only the layers whose time window is active, scaled by their strength. Where layers overlap in time, combine_method decides who wins: max takes the brightest value (the README's recommendation), add sums them, multiply scales, average blends. normalize clamps everything back to 0–1 so downstream nodes don't see values over 1.0.
The fade_in_percent / fade_out_percent inputs are the polish - a fade of 0.05 gives each region a 5% soft ramp in and out instead of a hard on/off. That's what turns a sequence of popping masks into something that doesn't flicker.
Inputs that matter
The only required ones are the batch-level knobs:
num_keyframes- how many mask snapshots to emit (2–200, default 10). The Advanced ControlNet scheduler downstream usually wants a small number like 4.start_percent/end_percent- the overall timeline window, 0 to 1.layer_1_separator/mask_1- masks are optional per layer; a layer with no mask just drops out.
print_schedule is genuinely useful when things go weird - it dumps which masks are active at each keyframe straight to the console.
Outputs
mask_batch- the batch of N masks, wired into the Advanced Curved ControlNet Scheduler'sbatch_masksinput (keyframe i uses mask i).schedule_graph- an IMAGE showing the layer timeline; preview it to see your schedule before generating.schedule_info- a text description of the schedule.
Installing it
Pack install as usual, and this time the dependency matters more than most:
cd ComfyUI/custom_nodes
git clone https://github.com/diffussy69/comfyui-curved_weight_schedule
pip install matplotlib pillow numpy torch scipy
This node feeds Advanced ControlNet keyframes, so the full temporal workflow requires the modified fork of Advanced ControlNet (see the Advanced Curved ControlNet Scheduler article). Without it, the masks go nowhere. Restart, hard-refresh.
Where people get burned
The classic failure: "temporal masks don't work, all keyframes look the same." It's almost always one of two things - every layer's start_percent/end_percent is still at the default 0–1 (so every mask is active the whole time), or you skipped the Redistribute Keyframe Percents node and every keyframe landed at 0%. Set the layer windows to genuinely different ranges, then let the Redistribute node spread them out. The schedule_graph preview exists precisely so you can catch this before spending a generation on it.
Inputs (36)
| Name | Type | Default | Description |
|---|---|---|---|
| num_keyframes | INT | 102–200 | — |
| start_percent | FLOAT | 0.0000–1 | — |
| end_percent | FLOAT | 1.0000–1 | — |
| layer_1_separator | COMBO | ━━━━━━━━━━ LAYER 1 ━━━━━━━━━━ | 1 options: ━━━━━━━━━━ LAYER 1 ━━━━━━━━━━ |
| mask_1 | MASK | — | |
| strength_1 | FLOAT | 1.000–2 | — |
| start_percent_1 | FLOAT | 0.000–1 | — |
| end_percent_1 | FLOAT | 1.000–1 | — |
| layer_2_separatoropt | COMBO | ━━━━━━━━━━ LAYER 2 ━━━━━━━━━━ | 1 options: ━━━━━━━━━━ LAYER 2 ━━━━━━━━━━ |
| mask_2opt | MASK | — | |
| strength_2opt | FLOAT | 1.000–2 | — |
| start_percent_2opt | FLOAT | 0.000–1 | — |
| end_percent_2opt | FLOAT | 1.000–1 | — |
| layer_3_separatoropt | COMBO | ━━━━━━━━━━ LAYER 3 ━━━━━━━━━━ | 1 options: ━━━━━━━━━━ LAYER 3 ━━━━━━━━━━ |
| mask_3opt | MASK | — | |
| strength_3opt | FLOAT | 1.000–2 | — |
| start_percent_3opt | FLOAT | 0.000–1 | — |
| end_percent_3opt | FLOAT | 1.000–1 | — |
| layer_4_separatoropt | COMBO | ━━━━━━━━━━ LAYER 4 ━━━━━━━━━━ | 1 options: ━━━━━━━━━━ LAYER 4 ━━━━━━━━━━ |
| mask_4opt | MASK | — | |
| strength_4opt | FLOAT | 1.000–2 | — |
| start_percent_4opt | FLOAT | 0.000–1 | — |
| end_percent_4opt | FLOAT | 1.000–1 | — |
| layer_5_separatoropt | COMBO | ━━━━━━━━━━ LAYER 5 ━━━━━━━━━━ | 1 options: ━━━━━━━━━━ LAYER 5 ━━━━━━━━━━ |
| mask_5opt | MASK | — | |
| strength_5opt | FLOAT | 1.000–2 | — |
| start_percent_5opt | FLOAT | 0.000–1 | — |
| end_percent_5opt | FLOAT | 1.000–1 | — |
| combine_separatoropt | COMBO | ━━━━━━━━━━ COMBINE ━━━━━━━━━━ | 1 options: ━━━━━━━━━━ COMBINE ━━━━━━━━━━ |
| combine_methodopt | COMBO | max | 4 options: max, add, multiply, average |
| normalizeopt | BOOLEAN | true | — |
| fade_in_percentopt | FLOAT | 0.000–0.5 | — |
| fade_out_percentopt | FLOAT | 0.000–0.5 | — |
| display_separatoropt | COMBO | ━━━━━━━━━━ DISPLAY ━━━━━━━━━━ | 1 options: ━━━━━━━━━━ DISPLAY ━━━━━━━━━━ |
| show_graphopt | BOOLEAN | true | — |
| print_scheduleopt | BOOLEAN | false | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| mask_batch | MASK | — |
| schedule_graph | IMAGE | — |
| schedule_info | STRING | — |