RioShiina commited on
Commit
08a8f61
·
verified ·
1 Parent(s): 478e753

Add IP-Adapter, Style & Reference Latent injectors

Browse files
chain_injectors/flux1_ipadapter_injector.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def inject(assembler, chain_definition, chain_items):
2
+ if not chain_items:
3
+ return
4
+
5
+ ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
6
+ if ksampler_name not in assembler.node_map:
7
+ print(f"Warning: KSampler node '{ksampler_name}' not found for Flux1 IPAdapter chain. Skipping.")
8
+ return
9
+
10
+ ksampler_id = assembler.node_map[ksampler_name]
11
+
12
+ if 'model' not in assembler.workflow[ksampler_id]['inputs']:
13
+ print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping Flux1 IPAdapter chain.")
14
+ return
15
+
16
+ current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
17
+
18
+ for item_data in chain_items:
19
+ image_loader_id = assembler._get_unique_id()
20
+ image_loader_node = assembler._get_node_template("LoadImage")
21
+ image_loader_node['inputs']['image'] = item_data['image']
22
+ assembler.workflow[image_loader_id] = image_loader_node
23
+
24
+ ipadapter_loader_id = assembler._get_unique_id()
25
+ ipadapter_loader_node = assembler._get_node_template("IPAdapterFluxLoader")
26
+ ipadapter_loader_node['inputs']['ipadapter'] = "ip-adapter.bin"
27
+ ipadapter_loader_node['inputs']['clip_vision'] = "google/siglip-so400m-patch14-384"
28
+ ipadapter_loader_node['inputs']['provider'] = "cuda"
29
+ assembler.workflow[ipadapter_loader_id] = ipadapter_loader_node
30
+
31
+ apply_ipa_id = assembler._get_unique_id()
32
+ apply_ipa_node = assembler._get_node_template("ApplyIPAdapterFlux")
33
+
34
+ apply_ipa_node['inputs']['weight'] = item_data['weight']
35
+ apply_ipa_node['inputs']['start_percent'] = item_data.get('start_percent', 0.0)
36
+ apply_ipa_node['inputs']['end_percent'] = item_data.get('end_percent', 0.6)
37
+
38
+ apply_ipa_node['inputs']['model'] = current_model_connection
39
+ apply_ipa_node['inputs']['ipadapter_flux'] = [ipadapter_loader_id, 0]
40
+ apply_ipa_node['inputs']['image'] = [image_loader_id, 0]
41
+
42
+ assembler.workflow[apply_ipa_id] = apply_ipa_node
43
+ current_model_connection = [apply_ipa_id, 0]
44
+
45
+ assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
46
+ print(f"Flux1 IPAdapter injector applied. KSampler model input re-routed through {len(chain_items)} IPAdapter(s).")
chain_injectors/ipadapter_injector.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def inject(assembler, chain_definition, chain_items):
2
+ if not chain_items:
3
+ return
4
+
5
+ final_settings = {}
6
+ if chain_items and isinstance(chain_items[-1], dict) and chain_items[-1].get('is_final_settings'):
7
+ final_settings = chain_items.pop()
8
+
9
+ if not chain_items:
10
+ return
11
+
12
+ end_node_name = chain_definition.get('end')
13
+ if not end_node_name or end_node_name not in assembler.node_map:
14
+ print(f"Warning: Target node '{end_node_name}' for IPAdapter chain not found. Skipping chain injection.")
15
+ return
16
+
17
+ end_node_id = assembler.node_map[end_node_name]
18
+
19
+ if 'model' not in assembler.workflow[end_node_id]['inputs']:
20
+ print(f"Warning: Target node '{end_node_name}' is missing 'model' input. Skipping IPAdapter chain.")
21
+ return
22
+
23
+ current_model_connection = assembler.workflow[end_node_id]['inputs']['model']
24
+
25
+ model_type = final_settings.get('model_type', 'sdxl')
26
+ megapixels = 1.05 if model_type == 'sdxl' else 0.39
27
+
28
+ pos_embed_outputs = []
29
+ neg_embed_outputs = []
30
+
31
+ for i, item_data in enumerate(chain_items):
32
+ loader_type = 'FaceID' if 'FACEID' in item_data.get('preset', '') else 'Unified'
33
+
34
+ loader_template_name = "IPAdapterUnifiedLoader"
35
+ if loader_type == 'FaceID':
36
+ loader_template_name = "IPAdapterUnifiedLoaderFaceID"
37
+
38
+ image_loader_id = assembler._get_unique_id()
39
+ image_loader_node = assembler._get_node_template("LoadImage")
40
+ image_loader_node['inputs']['image'] = item_data['image']
41
+ assembler.workflow[image_loader_id] = image_loader_node
42
+
43
+ image_scaler_id = assembler._get_unique_id()
44
+ image_scaler_node = assembler._get_node_template("ImageScaleToTotalPixels")
45
+ image_scaler_node['inputs']['image'] = [image_loader_id, 0]
46
+ image_scaler_node['inputs']['megapixels'] = megapixels
47
+ image_scaler_node['inputs']['upscale_method'] = "lanczos"
48
+ assembler.workflow[image_scaler_id] = image_scaler_node
49
+
50
+ ipadapter_loader_id = assembler._get_unique_id()
51
+ ipadapter_loader_node = assembler._get_node_template(loader_template_name)
52
+ ipadapter_loader_node['inputs']['model'] = current_model_connection
53
+ ipadapter_loader_node['inputs']['preset'] = item_data['preset']
54
+ if loader_type == 'FaceID':
55
+ ipadapter_loader_node['inputs']['lora_strength'] = item_data.get('lora_strength', 0.6)
56
+ assembler.workflow[ipadapter_loader_id] = ipadapter_loader_node
57
+
58
+ encoder_id = assembler._get_unique_id()
59
+ encoder_node = assembler._get_node_template("IPAdapterEncoder")
60
+ encoder_node['inputs']['weight'] = item_data['weight']
61
+ encoder_node['inputs']['ipadapter'] = [ipadapter_loader_id, 1]
62
+ encoder_node['inputs']['image'] = [image_scaler_id, 0]
63
+ assembler.workflow[encoder_id] = encoder_node
64
+
65
+ pos_embed_outputs.append([encoder_id, 0])
66
+ neg_embed_outputs.append([encoder_id, 1])
67
+
68
+ pos_combiner_id = assembler._get_unique_id()
69
+ pos_combiner_node = assembler._get_node_template("IPAdapterCombineEmbeds")
70
+ pos_combiner_node['inputs']['method'] = final_settings.get('final_combine_method', 'concat')
71
+ for i, conn in enumerate(pos_embed_outputs):
72
+ pos_combiner_node['inputs'][f'embed{i+1}'] = conn
73
+ assembler.workflow[pos_combiner_id] = pos_combiner_node
74
+
75
+ neg_combiner_id = assembler._get_unique_id()
76
+ neg_combiner_node = assembler._get_node_template("IPAdapterCombineEmbeds")
77
+ neg_combiner_node['inputs']['method'] = final_settings.get('final_combine_method', 'concat')
78
+ for i, conn in enumerate(neg_embed_outputs):
79
+ neg_combiner_node['inputs'][f'embed{i+1}'] = conn
80
+ assembler.workflow[neg_combiner_id] = neg_combiner_node
81
+
82
+ final_loader_type = 'FaceID' if 'FACEID' in final_settings.get('final_preset', '') else 'Unified'
83
+ final_loader_template_name = "IPAdapterUnifiedLoader"
84
+ if final_loader_type == 'FaceID':
85
+ final_loader_template_name = "IPAdapterUnifiedLoaderFaceID"
86
+
87
+ final_loader_id = assembler._get_unique_id()
88
+ final_loader_node = assembler._get_node_template(final_loader_template_name)
89
+ final_loader_node['inputs']['model'] = current_model_connection
90
+ final_loader_node['inputs']['preset'] = final_settings.get('final_preset', 'STANDARD (medium strength)')
91
+ if final_loader_type == 'FaceID':
92
+ final_loader_node['inputs']['lora_strength'] = final_settings.get('final_lora_strength', 0.6)
93
+ assembler.workflow[final_loader_id] = final_loader_node
94
+
95
+ apply_embeds_id = assembler._get_unique_id()
96
+ apply_embeds_node = assembler._get_node_template("IPAdapterEmbeds")
97
+ apply_embeds_node['inputs']['weight'] = final_settings.get('final_weight', 1.0)
98
+ apply_embeds_node['inputs']['embeds_scaling'] = final_settings.get('final_embeds_scaling', 'V only')
99
+ apply_embeds_node['inputs']['model'] = [final_loader_id, 0]
100
+ apply_embeds_node['inputs']['ipadapter'] = [final_loader_id, 1]
101
+ apply_embeds_node['inputs']['pos_embed'] = [pos_combiner_id, 0]
102
+ apply_embeds_node['inputs']['neg_embed'] = [neg_combiner_id, 0]
103
+ assembler.workflow[apply_embeds_id] = apply_embeds_node
104
+
105
+ assembler.workflow[end_node_id]['inputs']['model'] = [apply_embeds_id, 0]
106
+ print(f"IPAdapter injector applied. Redirected '{end_node_name}' model input through {len(chain_items)} reference images.")
chain_injectors/newbie_lora_injector.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from copy import deepcopy
2
+
3
+ def inject(assembler, chain_definition, chain_items):
4
+ if not chain_items:
5
+ return
6
+
7
+ output_map = chain_definition.get('output_map', {})
8
+ current_connections = {}
9
+ for key, type_name in output_map.items():
10
+ if ':' in str(key):
11
+ node_name, idx_str = key.split(':')
12
+ if node_name not in assembler.node_map:
13
+ print(f"Warning: [NewBie LoRA Injector] Node '{node_name}' in chain's output_map not found. Skipping.")
14
+ continue
15
+ node_id = assembler.node_map[node_name]
16
+ start_output_idx = int(idx_str)
17
+ current_connections[type_name] = [node_id, start_output_idx]
18
+ else:
19
+ print(f"Warning: [NewBie LoRA Injector] output_map key '{key}' is not in 'node:index' format. Skipping this connection.")
20
+
21
+ template_name = chain_definition.get('template')
22
+ if not template_name:
23
+ print(f"Warning: [NewBie LoRA Injector] No 'template' defined for chain. Skipping.")
24
+ return
25
+
26
+ for item_data in chain_items:
27
+ template = assembler._get_node_template(template_name)
28
+ node_data = deepcopy(template)
29
+
30
+ node_data['inputs']['lora_name'] = item_data.get('lora_name')
31
+ node_data['inputs']['strength'] = item_data.get('strength_model', 1.0)
32
+ node_data['inputs']['enabled'] = True
33
+
34
+ if 'model' in current_connections:
35
+ node_data['inputs']['model'] = current_connections['model']
36
+ if 'clip' in current_connections:
37
+ node_data['inputs']['clip'] = current_connections['clip']
38
+
39
+ new_node_id = assembler._get_unique_id()
40
+ assembler.workflow[new_node_id] = node_data
41
+
42
+ current_connections['model'] = [new_node_id, 0]
43
+ current_connections['clip'] = [new_node_id, 1]
44
+
45
+ end_input_map = chain_definition.get('end_input_map', {})
46
+ for type_name, targets in end_input_map.items():
47
+ if type_name in current_connections:
48
+ if not isinstance(targets, list):
49
+ targets = [targets]
50
+
51
+ for target_str in targets:
52
+ try:
53
+ end_node_name, end_input_name = target_str.split(':')
54
+ if end_node_name in assembler.node_map:
55
+ end_node_id = assembler.node_map[end_node_name]
56
+ assembler.workflow[end_node_id]['inputs'][end_input_name] = current_connections[type_name]
57
+ else:
58
+ print(f"Warning: [NewBie LoRA Injector] End node '{end_node_name}' for dynamic chain not found. Skipping connection.")
59
+ except ValueError:
60
+ print(f"Warning: [NewBie LoRA Injector] Invalid target format '{target_str}' in end_input_map. Skipping.")
61
+
62
+ if chain_items:
63
+ print(f"NewBie LoRA injector applied. Re-routed model and clip through {len(chain_items)} LoRA(s).")
chain_injectors/reference_latent_injector.py CHANGED
@@ -2,15 +2,78 @@ def inject(assembler, chain_definition, chain_items):
2
  if not chain_items:
3
  return
4
 
5
- ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
6
- flux_guidance_name = chain_definition.get('flux_guidance_node')
 
7
  vae_node_name = chain_definition.get('vae_node', 'vae_loader')
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  if ksampler_name not in assembler.node_map:
10
- print(f"Warning: [ReferenceLatent] KSampler node '{ksampler_name}' not found. Skipping.")
11
  return
12
  if vae_node_name not in assembler.node_map:
13
- print(f"Warning: [ReferenceLatent] VAE loader node '{vae_node_name}' not found. Skipping.")
14
  return
15
 
16
  ksampler_id = assembler.node_map[ksampler_name]
@@ -23,44 +86,72 @@ def inject(assembler, chain_definition, chain_items):
23
  if 'conditioning' in assembler.workflow[flux_guidance_id]['inputs']:
24
  pos_target_node_id = flux_guidance_id
25
  pos_target_input_name = 'conditioning'
26
- print(f"ReferenceLatent injector targeting FluxGuidance node '{flux_guidance_name}'.")
27
 
28
  if not pos_target_node_id:
29
  if 'positive' in assembler.workflow[ksampler_id]['inputs']:
30
  pos_target_node_id = ksampler_id
31
  pos_target_input_name = 'positive'
32
- print(f"ReferenceLatent injector targeting KSampler node '{ksampler_name}'.")
33
  else:
34
- print(f"Warning: [ReferenceLatent] Could not find a valid positive injection point. Skipping.")
35
  return
36
 
37
  current_pos_conditioning = assembler.workflow[pos_target_node_id]['inputs'][pos_target_input_name]
38
 
39
- for i, img_filename in enumerate(chain_items):
40
- if not img_filename or not isinstance(img_filename, str):
41
- continue
 
 
 
 
 
 
42
 
 
43
  load_id = assembler._get_unique_id()
44
  load_node = assembler._get_node_template("LoadImage")
45
  load_node['inputs']['image'] = img_filename
 
46
  assembler.workflow[load_id] = load_node
47
 
 
 
 
 
 
 
 
 
48
  vae_encode_id = assembler._get_unique_id()
49
  vae_encode_node = assembler._get_node_template("VAEEncode")
50
- vae_encode_node['inputs']['pixels'] = [load_id, 0]
51
  vae_encode_node['inputs']['vae'] = [vae_node_id, 0]
 
52
  assembler.workflow[vae_encode_id] = vae_encode_node
53
 
54
  latent_conn = [vae_encode_id, 0]
55
 
56
- ref_latent_id = assembler._get_unique_id()
57
- ref_latent_node = assembler._get_node_template("ReferenceLatent")
58
- ref_latent_node['inputs']['conditioning'] = current_pos_conditioning
59
- ref_latent_node['inputs']['latent'] = latent_conn
60
- assembler.workflow[ref_latent_id] = ref_latent_node
61
-
62
- current_pos_conditioning = [ref_latent_id, 0]
 
 
 
 
 
 
 
 
 
63
 
64
  assembler.workflow[pos_target_node_id]['inputs'][pos_target_input_name] = current_pos_conditioning
 
 
65
 
66
- print(f"ReferenceLatent injector applied. Re-routed inputs through {len(chain_items)} reference image(s).")
 
2
  if not chain_items:
3
  return
4
 
5
+ guider_node_name = chain_definition.get('guider_node')
6
+ guider_target_inputs = chain_definition.get('guider_target_inputs', [])
7
+ start_connections_map = chain_definition.get('start_connections', {})
8
  vae_node_name = chain_definition.get('vae_node', 'vae_loader')
9
 
10
+ if guider_node_name and guider_node_name in assembler.node_map and guider_target_inputs:
11
+ guider_id = assembler.node_map[guider_node_name]
12
+ if vae_node_name not in assembler.node_map:
13
+ print(f"Warning: VAE node '{vae_node_name}' not found for Guider chain. Skipping.")
14
+ return
15
+ vae_node_id = assembler.node_map[vae_node_name]
16
+
17
+ print(f"ReferenceLatent injector targeting DualCFGGuider node '{guider_node_name}'.")
18
+
19
+ current_connections = {}
20
+ for target_input in guider_target_inputs:
21
+ conn_str = start_connections_map.get(target_input)
22
+ if not conn_str:
23
+ print(f"Warning: No start connection defined for '{target_input}' in Guider chain. Skipping this input.")
24
+ continue
25
+ try:
26
+ node_name, idx_str = conn_str.split(':')
27
+ node_id = assembler.node_map[node_name]
28
+ current_connections[target_input] = [node_id, int(idx_str)]
29
+ except (ValueError, KeyError):
30
+ print(f"Warning: Invalid start connection '{conn_str}' for '{target_input}'. Skipping.")
31
+
32
+ encoded_latents = []
33
+ for i, img_filename in enumerate(chain_items):
34
+ load_id = assembler._get_unique_id()
35
+ load_node = assembler._get_node_template("LoadImage")
36
+ load_node['inputs']['image'] = img_filename
37
+ assembler.workflow[load_id] = load_node
38
+
39
+ scale_id = assembler._get_unique_id()
40
+ scale_node = assembler._get_node_template("ImageScaleToTotalPixels")
41
+ scale_node['inputs']['megapixels'] = 1.0
42
+ scale_node['inputs']['upscale_method'] = "lanczos"
43
+ scale_node['inputs']['image'] = [load_id, 0]
44
+ assembler.workflow[scale_id] = scale_node
45
+
46
+ vae_encode_id = assembler._get_unique_id()
47
+ vae_encode_node = assembler._get_node_template("VAEEncode")
48
+ vae_encode_node['inputs']['pixels'] = [scale_id, 0]
49
+ vae_encode_node['inputs']['vae'] = [vae_node_id, 0]
50
+ assembler.workflow[vae_encode_id] = vae_encode_node
51
+ encoded_latents.append([vae_encode_id, 0])
52
+
53
+ for target_input_name, start_connection in current_connections.items():
54
+ current_chain_head = start_connection
55
+ for i, latent_conn in enumerate(encoded_latents):
56
+ ref_latent_id = assembler._get_unique_id()
57
+ ref_latent_node = assembler._get_node_template("ReferenceLatent")
58
+ ref_latent_node['inputs']['conditioning'] = current_chain_head
59
+ ref_latent_node['inputs']['latent'] = latent_conn
60
+ ref_latent_node['_meta']['title'] = f"{target_input_name} RefLatent {i+1}"
61
+ assembler.workflow[ref_latent_id] = ref_latent_node
62
+ current_chain_head = [ref_latent_id, 0]
63
+
64
+ assembler.workflow[guider_id]['inputs'][target_input_name] = current_chain_head
65
+ print(f" - Input '{target_input_name}' of node '{guider_node_name}' re-routed through {len(chain_items)} reference images.")
66
+
67
+ return
68
+
69
+ flux_guidance_name = chain_definition.get('flux_guidance_node')
70
+ ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
71
+
72
  if ksampler_name not in assembler.node_map:
73
+ print(f"Warning: KSampler node '{ksampler_name}' not found for ReferenceLatent chain. Skipping.")
74
  return
75
  if vae_node_name not in assembler.node_map:
76
+ print(f"Warning: VAE loader node '{vae_node_name}' not found for ReferenceLatent chain. Skipping.")
77
  return
78
 
79
  ksampler_id = assembler.node_map[ksampler_name]
 
86
  if 'conditioning' in assembler.workflow[flux_guidance_id]['inputs']:
87
  pos_target_node_id = flux_guidance_id
88
  pos_target_input_name = 'conditioning'
89
+ print(f"ReferenceLatent injector targeting FluxGuidance node '{flux_guidance_name}' for positive chain.")
90
 
91
  if not pos_target_node_id:
92
  if 'positive' in assembler.workflow[ksampler_id]['inputs']:
93
  pos_target_node_id = ksampler_id
94
  pos_target_input_name = 'positive'
95
+ print(f"ReferenceLatent injector targeting KSampler node '{ksampler_name}' for positive chain.")
96
  else:
97
+ print(f"Warning: Could not find a valid positive injection point for ReferenceLatent chain. Skipping.")
98
  return
99
 
100
  current_pos_conditioning = assembler.workflow[pos_target_node_id]['inputs'][pos_target_input_name]
101
 
102
+ neg_target_node_id = ksampler_id
103
+ neg_target_input_name = 'negative'
104
+ if 'negative' not in assembler.workflow[neg_target_node_id]['inputs']:
105
+ print(f"Warning: KSampler node '{ksampler_name}' has no 'negative' input. Skipping negative ReferenceLatent chain.")
106
+ neg_target_node_id = None
107
+
108
+ current_neg_conditioning = None
109
+ if neg_target_node_id:
110
+ current_neg_conditioning = assembler.workflow[neg_target_node_id]['inputs'][neg_target_input_name]
111
 
112
+ for i, img_filename in enumerate(chain_items):
113
  load_id = assembler._get_unique_id()
114
  load_node = assembler._get_node_template("LoadImage")
115
  load_node['inputs']['image'] = img_filename
116
+ load_node['_meta']['title'] = f"Load Reference Image {i+1}"
117
  assembler.workflow[load_id] = load_node
118
 
119
+ scale_id = assembler._get_unique_id()
120
+ scale_node = assembler._get_node_template("ImageScaleToTotalPixels")
121
+ scale_node['inputs']['megapixels'] = 1.0
122
+ scale_node['inputs']['upscale_method'] = "lanczos"
123
+ scale_node['inputs']['image'] = [load_id, 0]
124
+ scale_node['_meta']['title'] = f"Scale Reference {i+1}"
125
+ assembler.workflow[scale_id] = scale_node
126
+
127
  vae_encode_id = assembler._get_unique_id()
128
  vae_encode_node = assembler._get_node_template("VAEEncode")
129
+ vae_encode_node['inputs']['pixels'] = [scale_id, 0]
130
  vae_encode_node['inputs']['vae'] = [vae_node_id, 0]
131
+ vae_encode_node['_meta']['title'] = f"VAE Encode Reference {i+1}"
132
  assembler.workflow[vae_encode_id] = vae_encode_node
133
 
134
  latent_conn = [vae_encode_id, 0]
135
 
136
+ pos_ref_latent_id = assembler._get_unique_id()
137
+ pos_ref_latent_node = assembler._get_node_template("ReferenceLatent")
138
+ pos_ref_latent_node['inputs']['conditioning'] = current_pos_conditioning
139
+ pos_ref_latent_node['inputs']['latent'] = latent_conn
140
+ pos_ref_latent_node['_meta']['title'] = f"Positive ReferenceLatent {i+1}"
141
+ assembler.workflow[pos_ref_latent_id] = pos_ref_latent_node
142
+ current_pos_conditioning = [pos_ref_latent_id, 0]
143
+
144
+ if neg_target_node_id:
145
+ neg_ref_latent_id = assembler._get_unique_id()
146
+ neg_ref_latent_node = assembler._get_node_template("ReferenceLatent")
147
+ neg_ref_latent_node['inputs']['conditioning'] = current_neg_conditioning
148
+ neg_ref_latent_node['inputs']['latent'] = latent_conn
149
+ neg_ref_latent_node['_meta']['title'] = f"Negative ReferenceLatent {i+1}"
150
+ assembler.workflow[neg_ref_latent_id] = neg_ref_latent_node
151
+ current_neg_conditioning = [neg_ref_latent_id, 0]
152
 
153
  assembler.workflow[pos_target_node_id]['inputs'][pos_target_input_name] = current_pos_conditioning
154
+ if neg_target_node_id:
155
+ assembler.workflow[neg_target_node_id]['inputs'][neg_target_input_name] = current_neg_conditioning
156
 
157
+ print(f"ReferenceLatent injector applied. Re-routed inputs through {len(chain_items)} reference images.")
chain_injectors/sd3_ipadapter_injector.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def inject(assembler, chain_definition, chain_items):
2
+ if not chain_items:
3
+ return
4
+
5
+ ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
6
+ if ksampler_name not in assembler.node_map:
7
+ print(f"Warning: KSampler node '{ksampler_name}' not found for SD3 IPAdapter chain. Skipping.")
8
+ return
9
+
10
+ ksampler_id = assembler.node_map[ksampler_name]
11
+
12
+ if 'model' not in assembler.workflow[ksampler_id]['inputs']:
13
+ print(f"Warning: KSampler node '{ksampler_name}' is missing 'model' input. Skipping SD3 IPAdapter chain.")
14
+ return
15
+
16
+ current_model_connection = assembler.workflow[ksampler_id]['inputs']['model']
17
+
18
+ clip_vision_loader_id = assembler._get_unique_id()
19
+ clip_vision_loader_node = assembler._get_node_template("CLIPVisionLoader")
20
+ clip_vision_loader_node['inputs']['clip_name'] = "sigclip_vision_patch14_384.safetensors"
21
+ assembler.workflow[clip_vision_loader_id] = clip_vision_loader_node
22
+
23
+ ipadapter_loader_id = assembler._get_unique_id()
24
+ ipadapter_loader_node = assembler._get_node_template("IPAdapterSD3Loader")
25
+ ipadapter_loader_node['inputs']['ipadapter'] = "ip-adapter_sd35l_instantx.bin"
26
+ ipadapter_loader_node['inputs']['provider'] = "cuda"
27
+ assembler.workflow[ipadapter_loader_id] = ipadapter_loader_node
28
+
29
+ for item_data in chain_items:
30
+ image_loader_id = assembler._get_unique_id()
31
+ image_loader_node = assembler._get_node_template("LoadImage")
32
+ image_loader_node['inputs']['image'] = item_data['image']
33
+ assembler.workflow[image_loader_id] = image_loader_node
34
+
35
+ image_scaler_id = assembler._get_unique_id()
36
+ image_scaler_node = assembler._get_node_template("ImageScaleToTotalPixels")
37
+ image_scaler_node['inputs']['image'] = [image_loader_id, 0]
38
+ image_scaler_node['inputs']['upscale_method'] = 'nearest-exact'
39
+ image_scaler_node['inputs']['megapixels'] = 1.0
40
+ assembler.workflow[image_scaler_id] = image_scaler_node
41
+
42
+ clip_vision_encode_id = assembler._get_unique_id()
43
+ clip_vision_encode_node = assembler._get_node_template("CLIPVisionEncode")
44
+ clip_vision_encode_node['inputs']['crop'] = "center"
45
+ clip_vision_encode_node['inputs']['clip_vision'] = [clip_vision_loader_id, 0]
46
+ clip_vision_encode_node['inputs']['image'] = [image_scaler_id, 0]
47
+ assembler.workflow[clip_vision_encode_id] = clip_vision_encode_node
48
+
49
+ apply_ipa_id = assembler._get_unique_id()
50
+ apply_ipa_node = assembler._get_node_template("ApplyIPAdapterSD3")
51
+
52
+ apply_ipa_node['inputs']['weight'] = item_data.get('weight', 1.0)
53
+ apply_ipa_node['inputs']['start_percent'] = item_data.get('start_percent', 0.0)
54
+ apply_ipa_node['inputs']['end_percent'] = item_data.get('end_percent', 1.0)
55
+
56
+ apply_ipa_node['inputs']['model'] = current_model_connection
57
+ apply_ipa_node['inputs']['ipadapter'] = [ipadapter_loader_id, 0]
58
+ apply_ipa_node['inputs']['image_embed'] = [clip_vision_encode_id, 0]
59
+
60
+ assembler.workflow[apply_ipa_id] = apply_ipa_node
61
+
62
+ current_model_connection = [apply_ipa_id, 0]
63
+
64
+ assembler.workflow[ksampler_id]['inputs']['model'] = current_model_connection
65
+
66
+ print(f"SD3 IPAdapter injector applied. KSampler model input re-routed through {len(chain_items)} IPAdapter(s).")
chain_injectors/style_injector.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def inject(assembler, chain_definition, chain_items):
2
+ if not chain_items:
3
+ return
4
+
5
+ flux_guidance_name = chain_definition.get('flux_guidance_node')
6
+ ksampler_name = chain_definition.get('ksampler_node', 'ksampler')
7
+
8
+ target_node_id = None
9
+ target_input_name = None
10
+
11
+ if flux_guidance_name and flux_guidance_name in assembler.node_map:
12
+ flux_guidance_id = assembler.node_map[flux_guidance_name]
13
+ if 'conditioning' in assembler.workflow[flux_guidance_id]['inputs']:
14
+ target_node_id = flux_guidance_id
15
+ target_input_name = 'conditioning'
16
+
17
+ if not target_node_id:
18
+ if ksampler_name in assembler.node_map:
19
+ ksampler_id = assembler.node_map[ksampler_name]
20
+ if 'positive' in assembler.workflow[ksampler_id]['inputs']:
21
+ target_node_id = ksampler_id
22
+ target_input_name = 'positive'
23
+ else:
24
+ return
25
+
26
+ if not target_node_id:
27
+ return
28
+
29
+ current_conditioning = assembler.workflow[target_node_id]['inputs'][target_input_name]
30
+
31
+ style_model_loader_id = assembler._get_unique_id()
32
+ style_model_loader_node = assembler._get_node_template("StyleModelLoader")
33
+ style_model_loader_node['inputs']['style_model_name'] = "flux1-redux-dev.safetensors"
34
+ assembler.workflow[style_model_loader_id] = style_model_loader_node
35
+
36
+ clip_vision_loader_id = assembler._get_unique_id()
37
+ clip_vision_loader_node = assembler._get_node_template("CLIPVisionLoader")
38
+ clip_vision_loader_node['inputs']['clip_name'] = "sigclip_vision_patch14_384.safetensors"
39
+ assembler.workflow[clip_vision_loader_id] = clip_vision_loader_node
40
+
41
+ for item_data in chain_items:
42
+ image = item_data.get('image')
43
+ strength = item_data.get('strength', 1.0)
44
+ if not image or strength is None:
45
+ continue
46
+
47
+ load_image_id = assembler._get_unique_id()
48
+ clip_vision_encode_id = assembler._get_unique_id()
49
+ style_apply_id = assembler._get_unique_id()
50
+
51
+ load_image_node = assembler._get_node_template("LoadImage")
52
+ clip_vision_encode_node = assembler._get_node_template("CLIPVisionEncode")
53
+ style_apply_node = assembler._get_node_template("StyleModelApply")
54
+
55
+ load_image_node['inputs']['image'] = image
56
+ clip_vision_encode_node['inputs']['crop'] = "center"
57
+ clip_vision_encode_node['inputs']['clip_vision'] = [clip_vision_loader_id, 0]
58
+ clip_vision_encode_node['inputs']['image'] = [load_image_id, 0]
59
+
60
+ style_apply_node['inputs']['strength'] = strength
61
+ style_apply_node['inputs']['strength_type'] = "multiply"
62
+ style_apply_node['inputs']['conditioning'] = current_conditioning
63
+ style_apply_node['inputs']['style_model'] = [style_model_loader_id, 0]
64
+ style_apply_node['inputs']['clip_vision_output'] = [clip_vision_encode_id, 0]
65
+
66
+ assembler.workflow[load_image_id] = load_image_node
67
+ assembler.workflow[clip_vision_encode_id] = clip_vision_encode_node
68
+ assembler.workflow[style_apply_id] = style_apply_node
69
+ current_conditioning = [style_apply_id, 0]
70
+
71
+ assembler.workflow[target_node_id]['inputs'][target_input_name] = current_conditioning
yaml/ipadapter.yaml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ IPAdapter_presets:
2
+ SD1.5:
3
+ - "LIGHT - SD1.5 only (low strength)"
4
+ - "STANDARD (medium strength)"
5
+ - "VIT-G (medium strength)"
6
+ - "PLUS (high strength)"
7
+ - "PLUS FACE (portraits)"
8
+ - "FULL FACE - SD1.5 only (portraits stronger)"
9
+ SDXL:
10
+ - "STANDARD (medium strength)"
11
+ - "VIT-G (medium strength)"
12
+ - "PLUS (high strength)"
13
+ - "PLUS FACE (portraits)"
14
+
15
+ IPAdapter_FaceID_presets:
16
+ SD1.5:
17
+ - "FACEID"
18
+ - "FACEID PLUS - SD1.5 only"
19
+ - "FACEID PLUS V2"
20
+ - "FACEID PORTRAIT (style transfer)"
21
+ SDXL:
22
+ - "FACEID"
23
+ - "FACEID PLUS V2"
24
+ - "FACEID PORTRAIT (style transfer)"
25
+ - "FACEID PORTRAIT UNNORM - SDXL only (strong)"
yaml/ipadapter_models.yaml ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ - preset_name: "LIGHT - SD1.5 only (low strength)"
2
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
3
+ ipadapter: "ip-adapter_sd15_light_v11.bin"
4
+
5
+ - preset_name: "STANDARD (medium strength)"
6
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
7
+ ipadapter: "ip-adapter_sdxl_vit-h.safetensors"
8
+ ipadapter: "ip-adapter_sd15.safetensors"
9
+
10
+ - preset_name: "VIT-G (medium strength)"
11
+ clip_vision: "CLIP-ViT-bigG-14-laion2B-39B-b160k.safetensors"
12
+ ipadapter: "ip-adapter_sdxl.safetensors"
13
+ ipadapter: "ip-adapter_sd15_vit-G.safetensors"
14
+
15
+ - preset_name: "PLUS (high strength)"
16
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
17
+ ipadapter: "ip-adapter-plus_sdxl_vit-h.safetensors"
18
+ ipadapter: "ip-adapter-plus_sd15.safetensors"
19
+
20
+ - preset_name: "PLUS FACE (portraits)"
21
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
22
+ ipadapter: "ip-adapter-plus-face_sdxl_vit-h.safetensors"
23
+ ipadapter: "ip-adapter-plus-face_sd15.safetensors"
24
+
25
+ - preset_name: "FULL FACE - SD1.5 only (portraits stronger)"
26
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
27
+ ipadapter: "ip-adapter-full-face_sd15.safetensors"
28
+
29
+ - preset_name: "FACEID"
30
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
31
+ ipadapter: "ip-adapter-faceid_sdxl.bin"
32
+ loras: "ip-adapter-faceid_sdxl_lora.safetensors"
33
+ ipadapter: "ip-adapter-faceid_sd15.bin"
34
+ loras: "ip-adapter-faceid_sd15_lora.safetensors"
35
+
36
+ - preset_name: "FACEID PLUS - SD1.5 only"
37
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
38
+ ipadapter: "ip-adapter-faceid-plus_sd15.bin"
39
+ loras: "ip-adapter-faceid-plus_sd15_lora.safetensors"
40
+
41
+ - preset_name: "FACEID PLUS V2"
42
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
43
+ ipadapter: "ip-adapter-faceid-plusv2_sdxl.bin"
44
+ loras: "ip-adapter-faceid-plusv2_sdxl_lora.safetensors"
45
+ ipadapter: "ip-adapter-faceid-plusv2_sd15.bin"
46
+ loras: "ip-adapter-faceid-plusv2_sd15_lora.safetensors"
47
+
48
+ - preset_name: "FACEID PORTRAIT (style transfer)"
49
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
50
+ ipadapter: "ip-adapter-faceid-portrait_sdxl.bin"
51
+ ipadapter: "ip-adapter-faceid-portrait-v11_sd15.bin"
52
+
53
+ - preset_name: "FACEID PORTRAIT UNNORM - SDXL only (strong)"
54
+ clip_vision: "CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors"
55
+ ipadapter: "ip-adapter-faceid-portrait_sdxl_unnorm.bin"
yaml/ipadapter_sd3_models.yaml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ ipadapter: "ip-adapter_sd35l_instantx.bin"
2
+ clip_vision: "sigclip_vision_patch14_384.safetensors"