|
|
@ -1,40 +1,38 @@ |
|
|
|
using System; |
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
|
|
|
|
using System.ComponentModel.DataAnnotations; |
|
|
|
using System.ComponentModel.DataAnnotations; |
|
|
|
using System.Drawing; |
|
|
|
using System.Drawing; |
|
|
|
using StabilityMatrix.Avalonia.Models.Inference; |
|
|
|
using StabilityMatrix.Avalonia.Models.Inference; |
|
|
|
using StabilityMatrix.Avalonia.ViewModels.Inference; |
|
|
|
using StabilityMatrix.Avalonia.ViewModels.Inference; |
|
|
|
using StabilityMatrix.Core.Models.Api.Comfy.Nodes; |
|
|
|
using StabilityMatrix.Core.Models.Api.Comfy.Nodes; |
|
|
|
using StabilityMatrix.Core.Models.Api.Comfy.NodeTypes; |
|
|
|
|
|
|
|
using StabilityMatrix.Core.Services; |
|
|
|
using StabilityMatrix.Core.Services; |
|
|
|
|
|
|
|
|
|
|
|
namespace StabilityMatrix.Avalonia.Extensions; |
|
|
|
namespace StabilityMatrix.Avalonia.Extensions; |
|
|
|
|
|
|
|
|
|
|
|
public static class ComfyNodeBuilderExtensions |
|
|
|
public static class ComfyNodeBuilderExtensions |
|
|
|
{ |
|
|
|
{ |
|
|
|
public static void SetupLatentSource( |
|
|
|
public static void SetupEmptyLatentSource( |
|
|
|
this ComfyNodeBuilder builder, |
|
|
|
this ComfyNodeBuilder builder, |
|
|
|
BatchSizeCardViewModel batchSizeCardViewModel, |
|
|
|
int width, |
|
|
|
SamplerCardViewModel samplerCardViewModel |
|
|
|
int height, |
|
|
|
|
|
|
|
int batchSize = 1, |
|
|
|
|
|
|
|
int? batchIndex = null |
|
|
|
) |
|
|
|
) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var emptyLatent = builder.Nodes.AddNamedNode( |
|
|
|
var emptyLatent = builder.Nodes.AddTypedNode( |
|
|
|
ComfyNodeBuilder.EmptyLatentImage( |
|
|
|
new ComfyNodeBuilder.EmptyLatentImage |
|
|
|
"EmptyLatentImage", |
|
|
|
{ |
|
|
|
batchSizeCardViewModel.BatchSize, |
|
|
|
Name = "EmptyLatentImage", |
|
|
|
samplerCardViewModel.Height, |
|
|
|
BatchSize = batchSize, |
|
|
|
samplerCardViewModel.Width |
|
|
|
Height = height, |
|
|
|
) |
|
|
|
Width = width |
|
|
|
|
|
|
|
} |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
builder.Connections.Primary = emptyLatent.Output; |
|
|
|
builder.Connections.Primary = emptyLatent.Output; |
|
|
|
builder.Connections.PrimarySize = new Size( |
|
|
|
builder.Connections.PrimarySize = new Size(width, height); |
|
|
|
samplerCardViewModel.Width, |
|
|
|
|
|
|
|
samplerCardViewModel.Height |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If batch index is selected, add a LatentFromBatch |
|
|
|
// If batch index is selected, add a LatentFromBatch |
|
|
|
if (batchSizeCardViewModel.IsBatchIndexEnabled) |
|
|
|
if (batchIndex is not null) |
|
|
|
{ |
|
|
|
{ |
|
|
|
builder.Connections.Primary = builder.Nodes |
|
|
|
builder.Connections.Primary = builder.Nodes |
|
|
|
.AddNamedNode( |
|
|
|
.AddNamedNode( |
|
|
@ -42,7 +40,7 @@ public static class ComfyNodeBuilderExtensions |
|
|
|
"LatentFromBatch", |
|
|
|
"LatentFromBatch", |
|
|
|
builder.GetPrimaryAsLatent(), |
|
|
|
builder.GetPrimaryAsLatent(), |
|
|
|
// remote expects a 0-based index, vm is 1-based |
|
|
|
// remote expects a 0-based index, vm is 1-based |
|
|
|
batchSizeCardViewModel.BatchIndex - 1, |
|
|
|
batchIndex.Value - 1, |
|
|
|
1 |
|
|
|
1 |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
@ -50,256 +48,63 @@ public static class ComfyNodeBuilderExtensions |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static void SetupBaseSampler( |
|
|
|
public static void SetupImageLatentSource( |
|
|
|
this ComfyNodeBuilder builder, |
|
|
|
this ComfyNodeBuilder builder, |
|
|
|
SamplerCardViewModel samplerCardViewModel, |
|
|
|
BatchSizeCardViewModel batchSizeCardViewModel, |
|
|
|
PromptCardViewModel promptCardViewModel, |
|
|
|
SamplerCardViewModel samplerCardViewModel |
|
|
|
ModelCardViewModel modelCardViewModel, |
|
|
|
|
|
|
|
IModelIndexService modelIndexService, |
|
|
|
|
|
|
|
Action<ComfyNodeBuilder>? postModelLoad = null |
|
|
|
|
|
|
|
) |
|
|
|
) |
|
|
|
{ |
|
|
|
{ |
|
|
|
/*// Load base checkpoint |
|
|
|
var emptyLatent = builder.Nodes.AddTypedNode( |
|
|
|
var checkpointLoader = builder.Nodes.AddNamedNode( |
|
|
|
new ComfyNodeBuilder.EmptyLatentImage |
|
|
|
ComfyNodeBuilder.CheckpointLoaderSimple( |
|
|
|
|
|
|
|
"CheckpointLoader", |
|
|
|
|
|
|
|
modelCardViewModel.SelectedModel?.FileName |
|
|
|
|
|
|
|
?? throw new NullReferenceException("Model not selected") |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
builder.Connections.BaseModel = checkpointLoader.GetOutput<ModelNodeConnection>(0); |
|
|
|
|
|
|
|
builder.Connections.BaseClip = checkpointLoader.GetOutput<ClipNodeConnection>(1); |
|
|
|
|
|
|
|
builder.Connections.BaseVAE = checkpointLoader.GetOutput<VAENodeConnection>(2); |
|
|
|
|
|
|
|
builder.Connections.PrimaryVAE = builder.Connections.BaseVAE; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Run post model load action |
|
|
|
|
|
|
|
postModelLoad?.Invoke(builder);*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Load prompts |
|
|
|
|
|
|
|
var prompt = promptCardViewModel.GetPrompt(); |
|
|
|
|
|
|
|
prompt.Process(); |
|
|
|
|
|
|
|
var negativePrompt = promptCardViewModel.GetNegativePrompt(); |
|
|
|
|
|
|
|
negativePrompt.Process(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If need to load loras, add a group |
|
|
|
|
|
|
|
if (prompt.ExtraNetworks.Count > 0) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// Convert to local file names |
|
|
|
|
|
|
|
var lorasGroup = builder.Group_LoraLoadMany( |
|
|
|
|
|
|
|
"Loras", |
|
|
|
|
|
|
|
builder.Connections.BaseModel, |
|
|
|
|
|
|
|
builder.Connections.BaseClip, |
|
|
|
|
|
|
|
prompt.GetExtraNetworksAsLocalModels(modelIndexService) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Set as source |
|
|
|
|
|
|
|
builder.Connections.BaseModel = lorasGroup.Output1; |
|
|
|
|
|
|
|
builder.Connections.BaseClip = lorasGroup.Output2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Clips |
|
|
|
|
|
|
|
var positiveClip = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.ClipTextEncode( |
|
|
|
|
|
|
|
"PositiveCLIP", |
|
|
|
|
|
|
|
builder.Connections.BaseClip, |
|
|
|
|
|
|
|
prompt.ProcessedText |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
var negativeClip = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.ClipTextEncode( |
|
|
|
|
|
|
|
"NegativeCLIP", |
|
|
|
|
|
|
|
builder.Connections.BaseClip, |
|
|
|
|
|
|
|
negativePrompt.ProcessedText |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
builder.Connections.BaseConditioning = positiveClip.Output; |
|
|
|
|
|
|
|
builder.Connections.BaseNegativeConditioning = negativeClip.Output; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Apply sampler addons (FreeU / ControlNet) to model and conditioning |
|
|
|
|
|
|
|
var samplerStepArgs = new ModuleApplyStepEventArgs |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
Builder = builder, |
|
|
|
|
|
|
|
Temp = |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
Model = builder.Connections.BaseModel, |
|
|
|
Name = "EmptyLatentImage", |
|
|
|
Conditioning = (positiveClip.Output, negativeClip.Output) |
|
|
|
BatchSize = batchSizeCardViewModel.BatchSize, |
|
|
|
|
|
|
|
Height = samplerCardViewModel.Height, |
|
|
|
|
|
|
|
Width = samplerCardViewModel.Width |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
samplerCardViewModel.ApplyStep(samplerStepArgs); |
|
|
|
|
|
|
|
var model = samplerStepArgs.Temp.Model; |
|
|
|
|
|
|
|
var conditioning = samplerStepArgs.Temp.Conditioning; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Primary latent encoding vae |
|
|
|
|
|
|
|
var vae = |
|
|
|
|
|
|
|
builder.Connections.PrimaryVAE |
|
|
|
|
|
|
|
?? builder.Connections.BaseVAE |
|
|
|
|
|
|
|
?? throw new ValidationException("No Primary or Base VAE"); |
|
|
|
|
|
|
|
var latent = builder.GetPrimaryAsLatent(vae); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add base sampler (without refiner) |
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
|
|
modelCardViewModel |
|
|
|
|
|
|
|
is not { IsRefinerSelectionEnabled: true, SelectedRefiner.IsDefault: false } |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
var sampler = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.KSampler( |
|
|
|
|
|
|
|
"Sampler", |
|
|
|
|
|
|
|
model, |
|
|
|
|
|
|
|
builder.Connections.Seed, |
|
|
|
|
|
|
|
samplerCardViewModel.Steps, |
|
|
|
|
|
|
|
samplerCardViewModel.CfgScale, |
|
|
|
|
|
|
|
samplerCardViewModel.SelectedSampler |
|
|
|
|
|
|
|
?? throw new ValidationException("Sampler not selected"), |
|
|
|
|
|
|
|
samplerCardViewModel.SelectedScheduler |
|
|
|
|
|
|
|
?? throw new ValidationException("Scheduler not selected"), |
|
|
|
|
|
|
|
conditioning.Positive, |
|
|
|
|
|
|
|
conditioning.Negative, |
|
|
|
|
|
|
|
latent, |
|
|
|
|
|
|
|
samplerCardViewModel.DenoiseStrength |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
builder.Connections.Primary = sampler.Output; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Add base sampler (with refiner) |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// Total steps is the sum of the base and refiner steps |
|
|
|
|
|
|
|
var totalSteps = samplerCardViewModel.Steps + samplerCardViewModel.RefinerSteps; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var sampler = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.KSamplerAdvanced( |
|
|
|
|
|
|
|
"Sampler", |
|
|
|
|
|
|
|
model, |
|
|
|
|
|
|
|
true, |
|
|
|
|
|
|
|
builder.Connections.Seed, |
|
|
|
|
|
|
|
totalSteps, |
|
|
|
|
|
|
|
samplerCardViewModel.CfgScale, |
|
|
|
|
|
|
|
samplerCardViewModel.SelectedSampler |
|
|
|
|
|
|
|
?? throw new ValidationException("Sampler not selected"), |
|
|
|
|
|
|
|
samplerCardViewModel.SelectedScheduler |
|
|
|
|
|
|
|
?? throw new ValidationException("Sampler not selected"), |
|
|
|
|
|
|
|
conditioning.Positive, |
|
|
|
|
|
|
|
conditioning.Negative, |
|
|
|
|
|
|
|
latent, |
|
|
|
|
|
|
|
0, |
|
|
|
|
|
|
|
samplerCardViewModel.Steps, |
|
|
|
|
|
|
|
true |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
builder.Connections.Primary = sampler.Output; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static void SetupRefinerSampler( |
|
|
|
|
|
|
|
this ComfyNodeBuilder builder, |
|
|
|
|
|
|
|
SamplerCardViewModel samplerCardViewModel, |
|
|
|
|
|
|
|
PromptCardViewModel promptCardViewModel, |
|
|
|
|
|
|
|
ModelCardViewModel modelCardViewModel, |
|
|
|
|
|
|
|
IModelIndexService modelIndexService, |
|
|
|
|
|
|
|
Action<ComfyNodeBuilder>? postModelLoad = null |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
/*// Load refiner checkpoint |
|
|
|
|
|
|
|
var checkpointLoader = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.CheckpointLoaderSimple( |
|
|
|
|
|
|
|
"Refiner_CheckpointLoader", |
|
|
|
|
|
|
|
modelCardViewModel.SelectedRefiner?.RelativePath |
|
|
|
|
|
|
|
?? throw new NullReferenceException("Model not selected") |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
builder.Connections.RefinerModel = checkpointLoader.GetOutput<ModelNodeConnection>(0); |
|
|
|
builder.Connections.Primary = emptyLatent.Output; |
|
|
|
builder.Connections.RefinerClip = checkpointLoader.GetOutput<ClipNodeConnection>(1); |
|
|
|
builder.Connections.PrimarySize = new Size( |
|
|
|
builder.Connections.RefinerVAE = checkpointLoader.GetOutput<VAENodeConnection>(2); |
|
|
|
samplerCardViewModel.Width, |
|
|
|
builder.Connections.PrimaryVAE = builder.Connections.RefinerVAE; |
|
|
|
samplerCardViewModel.Height |
|
|
|
|
|
|
|
); |
|
|
|
// Run post model load action |
|
|
|
|
|
|
|
postModelLoad?.Invoke(builder);*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Load prompts |
|
|
|
|
|
|
|
var prompt = promptCardViewModel.GetPrompt(); |
|
|
|
|
|
|
|
prompt.Process(); |
|
|
|
|
|
|
|
var negativePrompt = promptCardViewModel.GetNegativePrompt(); |
|
|
|
|
|
|
|
negativePrompt.Process(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If need to load loras, add a group |
|
|
|
// If batch index is selected, add a LatentFromBatch |
|
|
|
if (prompt.ExtraNetworks.Count > 0) |
|
|
|
if (batchSizeCardViewModel.IsBatchIndexEnabled) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// Convert to local file names |
|
|
|
builder.Connections.Primary = builder.Nodes |
|
|
|
var lorasGroup = builder.Group_LoraLoadMany( |
|
|
|
.AddNamedNode( |
|
|
|
"Refiner_Loras", |
|
|
|
ComfyNodeBuilder.LatentFromBatch( |
|
|
|
builder.Connections.RefinerModel, |
|
|
|
"LatentFromBatch", |
|
|
|
builder.Connections.RefinerClip, |
|
|
|
builder.GetPrimaryAsLatent(), |
|
|
|
prompt.GetExtraNetworksAsLocalModels(modelIndexService) |
|
|
|
// remote expects a 0-based index, vm is 1-based |
|
|
|
); |
|
|
|
batchSizeCardViewModel.BatchIndex - 1, |
|
|
|
|
|
|
|
1 |
|
|
|
// Set as source |
|
|
|
) |
|
|
|
builder.Connections.RefinerModel = lorasGroup.Output1; |
|
|
|
) |
|
|
|
builder.Connections.RefinerClip = lorasGroup.Output2; |
|
|
|
.Output; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Clips |
|
|
|
|
|
|
|
var positiveClip = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.ClipTextEncode( |
|
|
|
|
|
|
|
"Refiner_PositiveCLIP", |
|
|
|
|
|
|
|
builder.Connections.RefinerClip, |
|
|
|
|
|
|
|
prompt.ProcessedText |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
var negativeClip = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.ClipTextEncode( |
|
|
|
|
|
|
|
"Refiner_NegativeCLIP", |
|
|
|
|
|
|
|
builder.Connections.RefinerClip, |
|
|
|
|
|
|
|
negativePrompt.ProcessedText |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
builder.Connections.RefinerConditioning = positiveClip.Output; |
|
|
|
|
|
|
|
builder.Connections.RefinerNegativeConditioning = negativeClip.Output; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add refiner sampler |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Total steps is the sum of the base and refiner steps |
|
|
|
|
|
|
|
var totalSteps = samplerCardViewModel.Steps + samplerCardViewModel.RefinerSteps; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var sampler = builder.Nodes.AddNamedNode( |
|
|
|
|
|
|
|
ComfyNodeBuilder.KSamplerAdvanced( |
|
|
|
|
|
|
|
"Refiner_Sampler", |
|
|
|
|
|
|
|
builder.Connections.RefinerModel, |
|
|
|
|
|
|
|
false, |
|
|
|
|
|
|
|
builder.Connections.Seed, |
|
|
|
|
|
|
|
totalSteps, |
|
|
|
|
|
|
|
samplerCardViewModel.CfgScale, |
|
|
|
|
|
|
|
samplerCardViewModel.SelectedSampler |
|
|
|
|
|
|
|
?? throw new ValidationException("Sampler not selected"), |
|
|
|
|
|
|
|
samplerCardViewModel.SelectedScheduler |
|
|
|
|
|
|
|
?? throw new ValidationException("Sampler not selected"), |
|
|
|
|
|
|
|
positiveClip.Output, |
|
|
|
|
|
|
|
negativeClip.Output, |
|
|
|
|
|
|
|
builder.GetPrimaryAsLatent(), |
|
|
|
|
|
|
|
samplerCardViewModel.Steps, |
|
|
|
|
|
|
|
totalSteps, |
|
|
|
|
|
|
|
false |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
builder.Connections.Primary = sampler.Output; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static string SetupOutputImage(this ComfyNodeBuilder builder) |
|
|
|
public static string SetupOutputImage(this ComfyNodeBuilder builder) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var previewImage = builder.Nodes.AddTypedNode( |
|
|
|
if (builder.Connections.Primary is null) |
|
|
|
new ComfyNodeBuilder.PreviewImage |
|
|
|
throw new ArgumentException("No Primary"); |
|
|
|
{ |
|
|
|
|
|
|
|
Name = "SaveImage", |
|
|
|
var image = builder.Connections.Primary.Match( |
|
|
|
Images = builder.GetPrimaryAsImage( |
|
|
|
_ => |
|
|
|
|
|
|
|
builder.GetPrimaryAsImage( |
|
|
|
builder.Connections.PrimaryVAE |
|
|
|
builder.Connections.PrimaryVAE |
|
|
|
?? builder.Connections.RefinerVAE |
|
|
|
?? builder.Connections.RefinerVAE |
|
|
|
?? builder.Connections.BaseVAE |
|
|
|
?? builder.Connections.BaseVAE |
|
|
|
) |
|
|
|
?? throw new ArgumentException("No Primary, Refiner, or Base VAE") |
|
|
|
} |
|
|
|
), |
|
|
|
|
|
|
|
image => image |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var previewImage = builder.Nodes.AddTypedNode( |
|
|
|
|
|
|
|
new ComfyNodeBuilder.PreviewImage { Name = "SaveImage", Images = image } |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
builder.Connections.OutputNodes.Add(previewImage); |
|
|
|
builder.Connections.OutputNodes.Add(previewImage); |
|
|
|