Chaoses-Ib / ComfyScript

A Python frontend and library for ComfyUI
https://discord.gg/arqJbtEg7w
MIT License
414 stars 22 forks source link

Import issues and type stubs #15

Closed lingondricka2 closed 9 months ago

lingondricka2 commented 9 months ago

This might be an issue with me lacking python skills.

import random
import sys
import os
#sys.path.insert(0, 'script/runtime')
#from nodes import *
#from script.runtime.nodes import *

sys.path.insert(0, '../../')
import folder_paths
sys.path.insert(0, 'src')
from comfy_script.runtime import *
load()
from comfy_script.runtime.nodes import *

####################
# Randomize script #
####################

#
# Config
#

# set to true if sdxl, false if sd 1.5
xl = True 

# needs to have checkpoints and loras divided in SDXL and SD 1.5 folders, embeddings needs to be divided into positive and negative folders
xl_folder_name = "xl"
sd1_5_folder_name = "sd1.5"

pos_folder_name = "pos"
neg_folder_name = "neg"

# number of images to create
images = 10

# checkpoint
randomize_checkpoint = True
# used if randomize_checkpoint is false
default_checkpoint = "xl\turbovisionxlSuperFastXLBasedOnNew_alphaV0101Bakedvae.safetensors"

# lora
randomize_lora = True
number_of_loras = 3
lora_min_value = 0.1
lora_max_value = 2
# default lora setup using CRLoRAStack, used if randomize_lora is false, if you have more then 3 loras you need to modify the code
default_lora_stack = ('On', r'xl\LCMTurboMix_LCM_Sampler.safetensors', 1, 1, 'On', r'xl\xl_more_art-full_v1.safetensors', 1, 1, 'On', r'xl\add-detail-xl.safetensors', 1, 1)

# prompt
fully_randomized_prompt = False # TODO use https://github.com/adieyal/comfyui-dynamicprompts to generate random prompt
positive_prompt = "Shot Size - extreme wide shot,( Marrakech market at night time:1.5), Moroccan young beautiful woman, smiling, exotic, (loose hijab:0.1)"
negative_prompt = "(worst quality, low quality, normal quality:2), blurry, depth of field, nsfw"
randomize_positive_embeddings = True
randomize_negative_embeddings = True
embeddings_positive_min_value = 0.1
embeddings_positive_max_value = 2
embeddings_negative_min_value = 0.1
embeddings_negative_max_value = 2

# freeu
randomize_freeu = True
min_freeu_values = [0.5, 0.5, 0.2, 0.1]
max_freeu_values = [3, 3, 2, 1]
# used if randomize_freeu is set to false
default_freeu_values = (1.3, 1.4, 0.9, 0.2)

# code taken from impact.utils
def add_folder_path_and_extensions(folder_name, full_folder_paths, extensions):
    # Iterate over the list of full folder paths
    for full_folder_path in full_folder_paths:
        # Use the provided function to add each model folder path
        folder_paths.add_model_folder_path(folder_name, full_folder_path)

    # Now handle the extensions. If the folder name already exists, update the extensions
    if folder_name in folder_paths.folder_names_and_paths:
        # Unpack the current paths and extensions
        current_paths, current_extensions = folder_paths.folder_names_and_paths[folder_name]
        # Update the extensions set with the new extensions
        updated_extensions = current_extensions | extensions
        # Reassign the updated tuple back to the dictionary
        folder_paths.folder_names_and_paths[folder_name] = (current_paths, updated_extensions)
    else:
        # If the folder name was not present, add_model_folder_path would have added it with the last path
        # Now we just need to update the set of extensions as it would be an empty set
        # Also ensure that all paths are included (since add_model_folder_path adds only one path at a time)
        folder_paths.folder_names_and_paths[folder_name] = (full_folder_paths, extensions)

model_path = folder_paths.models_dir
add_folder_path_and_extensions("loras", [os.path.join(model_path, "loras")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("loras_xl", [os.path.join(model_path, "loras", xl_folder_name)], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("loras_sd1.5", [os.path.join(model_path, "loras", sd1_5_folder_name)], folder_paths.supported_pt_extensions)

add_folder_path_and_extensions("checkpoints", [os.path.join(model_path, "checkpoints")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("checkpoints_xl", [os.path.join(model_path, "checkpoints", xl_folder_name)], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("checkpoints_sd1.5", [os.path.join(model_path, "checkpoints", sd1_5_folder_name)], folder_paths.supported_pt_extensions)

add_folder_path_and_extensions("embeddings", [os.path.join(model_path, "embeddings")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("embeddings_pos", [os.path.join(model_path, "embeddings", pos_folder_name)], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("embeddings_neg", [os.path.join(model_path, "embeddings", neg_folder_name)], folder_paths.supported_pt_extensions)

def get_random_loras():
    if xl == True:
        loras = [xl_folder_name + "/" + x for x in folder_paths.get_filename_list("loras_xl")]
    else:
        loras = [sd1_5_folder_name + "/" + x for x in folder_paths.get_filename_list("loras_sd1.5")]
    return random.sample(loras, number_of_loras)

def get_lora_strength():
    return random.uniform(lora_min_value, lora_max_value)

def get_random_checkpoint():
    if xl == True:
        checkpoints = [xl_folder_name + "/" + x for x in folder_paths.get_filename_list("checkpoints_xl")]
    else:
        checkpoints = [sd1_5_folder_name + "/" + x for x in folder_paths.get_filename_list("checkpoints_sd1.5")]
    return random.choice(checkpoints)

def get_positive_embedding_strength():
    return random.uniform(embeddings_positive_min_value, embeddings_positive_max_value)

def get_random_pos_embedding():
    pos_embeddings = [pos_folder_name + "/" + x for x in folder_paths.get_filename_list("embeddings_pos")]
    num_of_embeddings = random.randint(0, len(pos_embeddings))
    if num_of_embeddings == 0:
        return ""
    samples = random.sample(pos_embeddings, num_of_embeddings)
    string = ""
    for sample in samples:
        string += ", (embedding:" + sample + ":" + str(get_positive_embedding_strength()) + ")"
    return string

def get_negative_embedding_strength():
    return random.uniform(embeddings_negative_min_value, embeddings_negative_max_value)

def get_random_neg_embedding():
    neg_embeddings = [neg_folder_name + "/" + x for x in folder_paths.get_filename_list("embeddings_neg")]
    num_of_embeddings = random.randint(0, len(neg_embeddings))
    if num_of_embeddings == 0:
        return ""
    samples = random.sample(neg_embeddings, num_of_embeddings)
    string = ""
    for sample in samples:
        string += ", (embedding:" + sample + ":" + str(get_negative_embedding_strength()) + ")"
    return string

def get_random_freeu_values():
    return (random.uniform(min_freeu_values[0],max_freeu_values[0]),random.uniform(min_freeu_values[1],max_freeu_values[1]),
            random.uniform(min_freeu_values[2],max_freeu_values[2]),random.uniform(min_freeu_values[3],max_freeu_values[3]))

with Workflow():
    # checkpoint
    if randomize_checkpoint == True:
        model, clip, vae = CheckpointLoaderSimple(get_random_checkpoint())
    else:
        model, clip, vae = CheckpointLoaderSimple(default_checkpoint)

    # loras
    if randomize_lora == True:
        loras = get_random_loras()
        for lora in loras:
            model, clip = LoraLoader(model, clip, lora, get_lora_strength(), get_lora_strength())
    else:
        lora_stack, _ = CRLoRAStack(default_lora_stack)
        model, clip, _ = CRApplyLoRAStack(model, clip, lora_stack)

    # freeu
    if randomize_freeu == True:
        model = FreeUV2(model, get_random_freeu_values())
    else:
        model = FreeUV2(model, default_freeu_values)

    # positive prompt
    if randomize_positive_embeddings == True:
        pos_string = positive_prompt + get_random_pos_embedding()
    else:
        pos_string = positive_prompt

    pos_cond = CLIPTextEncode(pos_string, clip)

    # negative prompt
    if randomize_negative_embeddings == True:
        neg_string = negative_prompt + get_random_neg_embedding()
    else:
        neg_string = negative_prompt

    neg_cond = CLIPTextEncode(neg_string, clip)

Gives error:

Traceback (most recent call last):
  File "C:\Users\lingo\Desktop\ComfyUI\custom_nodes\ComfyScript\test.py", line 170, in <module>
    model = FreeUV2(model, get_random_freeu_values())
            ^^^^^^^
NameError: name 'FreeUV2' is not defined

Uncomment line 4 and 5 and it works

FreeUV2 is not recognized in VS Code, uncomment line 6 and it is recognized but script gives error:

Traceback (most recent call last):
  File "C:\Users\lingo\Desktop\ComfyUI\custom_nodes\ComfyScript\test.py", line 6, in <module>
    from script.runtime.nodes import *
ModuleNotFoundError: No module named 'script.runtime.nodes'

Not sure if this is how type stubs should be imported, new to python. The script is work in progress btw.

Chaoses-Ib commented 9 months ago

It's strange if CheckpointLoaderSimple works but FreeUV2 doesn't. And line 4 and 5 shouldn't work since script/runtime/nodes.py doesn't exist after v0.3 (there is a nodes directory at the repository root, but it doesn't have FreeUV2).

As for VS Code, it uses a static Python analyzer, so sys.path.insert(0, 'src') won't make the type stub file be used. (Although type stubs in src should be automatically used normally.)

Can you delete the script directory, run python -m pip install -e . at the repository root, and try again?

lingondricka2 commented 9 months ago

I did a fresh install.

sys.path.insert(0, 'src\comfy_script\runtime')
from nodes import *

Is needed to avoid NameError: name 'FreeUV2' is not defined

lingondricka2 commented 9 months ago

All classes are recognized in VS Code now though which is good.

Chaoses-Ib commented 9 months ago

image

I've tried your script on my machine. It works without issue (the error is because no output node), both with sys.path.insert(0, 'src') and without.

lingondricka2 commented 9 months ago

Weird, I will complete the script and get back to you if the error still persists.

Chaoses-Ib commented 9 months ago
sys.path.insert(0, 'src\comfy_script\runtime')
from nodes import *

This shouldn't work if it's copied from the script. \r will be treated as special chars, r'src\comfy_script\runtime' is what actually needed. With this path, from nodes import * will import nodes/__init__.py and do nothing since it is empty:

image

image

Chaoses-Ib commented 9 months ago

Anyway, nodes/__init__.py is now removed to avoid any further confusion. It's now impossible to import it by accident.

lingondricka2 commented 9 months ago

Changed from:

sys.path.insert(0, '../../')
import folder_paths
sys.path.insert(0, 'src')
from comfy_script.runtime import *
load()
from comfy_script.runtime.nodes import *

to:

sys.path.insert(0, 'src')
from comfy_script.runtime import *
load()
from comfy_script.runtime.nodes import *
sys.path.insert(0, '../../')
import folder_paths

and it works now, I noticed no custom nodes was loaded with the top code.

Chaoses-Ib commented 9 months ago

Now I get it. When the runtime is used with ComfyUI loaded, it will get nodes info by import nodes instead of accessing the API. And sys.path.insert(0, '../../') will make the runtime think ComfyUI is loaded. As for FreeUV2, it's actually FreeU_V2 in ComfyUI, so it can't be used but CheckpointLoaderSimple can.

It's now fixed. Thanks very much for your test.