diff --git a/extra_model_paths.yaml.example b/extra_model_paths.yaml.example index b55913a5a..d7d4b07aa 100644 --- a/extra_model_paths.yaml.example +++ b/extra_model_paths.yaml.example @@ -1,47 +1,111 @@ -#Rename this to extra_model_paths.yaml and ComfyUI will load it +# Copy this to extra_model_paths.yaml and ComfyUI will load it, or pass +# --extra-model-paths-config file.yaml +# Config for ComfyUI +# +# In all paths below: +# - ~ will expand out to the current user's home directory (typically C:\Users\username on Windows, +# /home/username on Linux, /Users/username on MacOS). +# - $ENV_VAR will expand out to the value of the environment variable ENV_VAR. However, if +# ENV_VAR is not set, the path will be ignored ($ENV_VAR will not expand out to an empty string). +# - environment variables are expanded before ~, so ENV_VAR=~/foo will work properly +# - if a path beings with @ it is treated as an explicit path, and no base_path will be +# prepended to it (but environment variable and ~ expansion still apply) +# +comfyui: + # If is_default is set to true, all the directories below will be prepended + # instead of appended to the default ComfyUI lists. + # instead of appending + is_default: true -#config for a1111 ui -#all you have to do is change the base_path to where yours is installed -a111: - base_path: path/to/stable-diffusion-webui/ + # If base_path is specified, all the directories referenced below will be + # relative to this path. For example, if you want to move comfy's models etc + # to ~/comfy_data, set base_path to ~/comfy_data + #base_path: ~/comfy_data - checkpoints: models/Stable-diffusion - configs: models/Stable-diffusion - vae: models/VAE - loras: | - models/Lora - models/LyCORIS - upscale_models: | - models/ESRGAN - models/RealESRGAN - models/SwinIR - embeddings: embeddings - hypernetworks: models/hypernetworks - controlnet: models/ControlNet + # If base_paths is specified, it's treated as a multiple set of base_paths. Each + # path in following entries is appended to each element of base_paths. If base_path is + # also present, it will be included as the first element. + # + # This is useful to be able to easily specify multiple roots -- for example, + # you could have one /shared_comfy_data that is a large network mount with + # models shared amongst many users/machines, and then ~/local_comfy_data + # where you can place models you're locally experimenting with. Specifying + # base_paths: ~/local_comfy_data /shared_comfy_data + # will let Comfy search both paths. + # + #base_paths: | + # $LOCAL_COMFY_ROOT + # $COMFY_ROOT -#config for comfyui -#your base path should be either an existing comfy install or a central folder where you store all of your models, loras, etc. + # The singular models/output/temp/input/user directories can be overriden as well. + # These are single paths; they can't be a list. Base path handling still applies, but the + # first directory that exists will be used. If none exist, the value isn't touched. + # + # For example, if base_paths is set to "~/local_comfy_data /shared_comfy_data", + # and output_directory is set to "output", Comfy will look for for ~/local_comfy_data/output + # first. If it exists, it will be used as the output directory. If models_dir is set to "models", + # Comfy will look for ~/local_comfy_data/models -- if that doesn't exist, it will look for + # /shared_comfy_data/models next. -#comfyui: -# base_path: path/to/comfyui/ -# # You can use is_default to mark that these folders should be listed first, and used as the default dirs for eg downloads -# #is_default: true -# checkpoints: models/checkpoints/ -# clip: models/clip/ -# clip_vision: models/clip_vision/ -# configs: models/configs/ -# controlnet: models/controlnet/ -# diffusion_models: | -# models/diffusion_models -# models/unet -# embeddings: models/embeddings/ -# loras: models/loras/ -# upscale_models: models/upscale_models/ -# vae: models/vae/ + # The `models_dir`. This is used by some custom nodes to find the "models" directory + # into which to place downloads (e.g. CogVideoX does this). + #models_dir: models + #output_directory: output + #temp_directory: temp + #input_directory: input + #user_directory: user + + # Where to look for custom nodes. The @custom_nodes ensures that the custom_nodes + # directory relative to the comfy install will be searched. The custom_nodes without + # the @ means a directory called custom_nodes will also be searched in each of the base + # paths. + custom_nodes: | + @custom_nodes + custom_nodes + + # Different model types in use by Comfy. These are the defaults. + # If you want the "base path" handling you _must_ respecify these here, + # even if you want to keep the default suffix values. Specifying + # just base_path (or base_paths) is not enough. + # + # Other model types can be added to this list, and some Comfy custom + # nodes may add their own model types. + checkpoints: models/checkpoints + clip: models/clip + text_encodings: models/text_encodings + clip_vision: models/clip_vision + configs: models/configs + controlnet: models/controlnet + diffusion_models: | + models/diffusion_models + models/unet + embeddings: models/embeddings + loras: models/loras + upscale_models: models/upscale_models + vae: models/vae #other_ui: # base_path: path/to/ui # checkpoints: models/checkpoints # gligen: models/gligen # custom_nodes: path/custom_nodes +#config for a1111 ui +#all you have to do is change the base_path to where yours is installed +#a111: +# base_path: path/to/stable-diffusion-webui/ +# +# checkpoints: models/Stable-diffusion +# configs: models/Stable-diffusion +# vae: models/VAE +# loras: | +# models/Lora +# models/LyCORIS +# upscale_models: | +# models/ESRGAN +# models/RealESRGAN +# models/SwinIR +# embeddings: embeddings +# hypernetworks: models/hypernetworks +# controlnet: models/ControlNet +# \ No newline at end of file diff --git a/nodes.py b/nodes.py index 3a68d43ce..40c9430bb 100644 --- a/nodes.py +++ b/nodes.py @@ -2061,6 +2061,9 @@ def init_external_custom_nodes(): node_paths = folder_paths.get_folder_paths("custom_nodes") node_import_times = [] for custom_node_path in node_paths: + if not os.path.isdir(custom_node_path): + logging.warning(f"Custom node path {custom_node_path} not found or is not a directory, ignoring") + continue possible_modules = os.listdir(os.path.realpath(custom_node_path)) if "__pycache__" in possible_modules: possible_modules.remove("__pycache__") diff --git a/utils/extra_config.py b/utils/extra_config.py index 908765902..ab99e23cf 100644 --- a/utils/extra_config.py +++ b/utils/extra_config.py @@ -10,19 +10,86 @@ def load_extra_path_config(yaml_path): conf = config[c] if conf is None: continue - base_path = None + base_paths = [] if "base_path" in conf: - base_path = conf.pop("base_path") + base_path = conf.pop("base_path").strip() base_path = os.path.expandvars(os.path.expanduser(base_path)) + if len(base_path) > 0: + base_paths.append(base_path) + if "base_paths" in conf: + base_paths_str = conf.pop("base_paths").strip() + for bp in base_paths_str.split("\n"): + bp = os.path.expanduser(os.path.expandvars(bp)) + if bp.find("$") >= 0: + logging.warning(f"Skipping base path {bp} as it contains an undefined variable") + continue + if len(bp) > 0: + base_paths.append(bp) + + # simplify the logic below. os.path.join("", foo) == foo + if len(base_paths) == 0: + base_paths.append("") + + # allow overriding paths that are not normally overriden. + # these can only be overriden to just one path (not a set). + # the first one that exists wins. + override_list = [ "models_dir", "output_directory", "temp_directory", "input_directory", "user_directory" ] + for okey in override_list: + if okey in conf: + opath = conf.pop(okey).strip() + is_absolute = False + if opath[0] == '@': + opath = opath[1:] + is_absolute = True + opath = os.path.expanduser(os.path.expandvars(opath)) + if is_absolute: + setattr(folder_paths, okey, opath) + logging.info(f"Set {okey} tp {opath}") + continue + for bp in base_paths: + path = os.path.join(bp, opath) + if os.path.isdir(path): + setattr(folder_paths, okey, path) + logging.info(f"Set {okey} tp {path}") + break + is_default = False if "is_default" in conf: is_default = conf.pop("is_default") + for x in conf: for y in conf[x].split("\n"): - if len(y) == 0: + if len(y.strip()) == 0: continue - full_path = y - if base_path is not None: - full_path = os.path.join(base_path, full_path) - logging.info("Adding extra search path {} {}".format(x, full_path)) - folder_paths.add_model_folder_path(x, full_path, is_default) + is_absolute = False + if y[0] == '@': + y = y[1:] + is_absolute = True + path = os.path.expanduser(os.path.expandvars(y)) + if path.find("$") >= 0: + logging.warning(f"Skipping path {path} for {x} as it contains an undefined variable") + continue + if is_absolute: + all_paths = [path] + else: + all_paths = [os.path.join(bp, path) for bp in base_paths] + + # pull out only unique elements in order. Do this so that we don't add the same dir twice, + # if for example two undefined variables are used, e.g. "$X/foo" and "$Y/foo", + # both will evaluate to "/foo" if neither is set + all_paths = [x for i, x in enumerate(all_paths) if x not in all_paths[:i]] + + # custom_nodes is special; it's the root directory of custom nodes, and Comfy calls listdir + # on it assuming it exists which throws otherwise + if x == "custom_nodes": + all_paths = [x for x in all_paths if os.path.isdir(x)] + + if is_default: + # if we're using is_default, the add_model_folder_path inserts at 0. + # so reverse this list, so that the (original) first element that the user + # specifies in the extra paths file actually ends up at 0 + all_paths.reverse() + for p in all_paths: + logging.info("Adding extra search path {} {}".format(x, p)) + folder_paths.add_model_folder_path(x, p, is_default) +