mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(nodes): change expected structure for custom nodes
This commit is contained in:
parent
8604943e89
commit
824702de99
@ -7,10 +7,13 @@ from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
|||||||
|
|
||||||
custom_nodes_path = Path(InvokeAIAppConfig.get_config().custom_nodes_path.absolute())
|
custom_nodes_path = Path(InvokeAIAppConfig.get_config().custom_nodes_path.absolute())
|
||||||
custom_nodes_path.mkdir(parents=True, exist_ok=True)
|
custom_nodes_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
custom_nodes_init_path = str(custom_nodes_path / "__init__.py")
|
custom_nodes_init_path = str(custom_nodes_path / "__init__.py")
|
||||||
|
custom_nodes_readme_path = str(custom_nodes_path / "README.md")
|
||||||
|
|
||||||
# copy our custom nodes __init__.py to the custom nodes directory
|
# copy our custom nodes __init__.py to the custom nodes directory
|
||||||
shutil.copy(Path(__file__).parent / "_custom_nodes_init.py", custom_nodes_init_path)
|
shutil.copy(Path(__file__).parent / "custom_nodes/init.py", custom_nodes_init_path)
|
||||||
|
shutil.copy(Path(__file__).parent / "custom_nodes/README.md", custom_nodes_readme_path)
|
||||||
|
|
||||||
# Import custom nodes, see https://docs.python.org/3/library/importlib.html#importing-programmatically
|
# Import custom nodes, see https://docs.python.org/3/library/importlib.html#importing-programmatically
|
||||||
spec = spec_from_file_location("custom_nodes", custom_nodes_init_path)
|
spec = spec_from_file_location("custom_nodes", custom_nodes_init_path)
|
||||||
@ -21,5 +24,5 @@ sys.modules[spec.name] = module
|
|||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
# add core nodes to __all__
|
# add core nodes to __all__
|
||||||
python_files = filter(lambda f: not f.name.startswith("_"), Path(__file__).parent.rglob("*.py"))
|
python_files = filter(lambda f: not f.name.startswith("_"), Path(__file__).parent.glob("*.py"))
|
||||||
__all__ = list(f.stem for f in python_files) # type: ignore
|
__all__ = list(f.stem for f in python_files) # type: ignore
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
"""
|
|
||||||
InvokeAI custom nodes initialization
|
|
||||||
|
|
||||||
This file is responsible for loading all custom nodes from this directory.
|
|
||||||
|
|
||||||
All python files are loaded on app startup. Custom nodes will be initialized and available for use
|
|
||||||
in workflows.
|
|
||||||
|
|
||||||
The app must be restarted for changes to be picked up.
|
|
||||||
|
|
||||||
This file is overwritten on launch. Do not edit this file directly.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from importlib import import_module
|
|
||||||
from importlib.util import module_from_spec, spec_from_file_location
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from invokeai.backend.util.logging import InvokeAILogger
|
|
||||||
|
|
||||||
logger = InvokeAILogger.get_logger()
|
|
||||||
count = 0
|
|
||||||
for f in Path(__file__).parent.rglob("*.py"):
|
|
||||||
module_name = f.stem
|
|
||||||
if (not module_name.startswith("_")) and (module_name not in globals()):
|
|
||||||
spec = spec_from_file_location(module_name, f.absolute())
|
|
||||||
if spec is None or spec.loader is None:
|
|
||||||
logger.warn(f"Could not load {f}")
|
|
||||||
continue
|
|
||||||
module = module_from_spec(spec)
|
|
||||||
sys.modules[spec.name] = module
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
count += 1
|
|
||||||
del f, module_name
|
|
||||||
|
|
||||||
logger.info(f"Loaded {count} modules from {Path(__file__).parent}")
|
|
||||||
|
|
||||||
del import_module, Path
|
|
51
invokeai/app/invocations/custom_nodes/README.md
Normal file
51
invokeai/app/invocations/custom_nodes/README.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Custom Nodes / Node Packs
|
||||||
|
|
||||||
|
Copy your node packs to this directory.
|
||||||
|
|
||||||
|
When nodes are added or changed, you must restart the app to see the changes.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
For a node pack to be loaded, it must be placed in a directory alongside this
|
||||||
|
file. Here's an example structure:
|
||||||
|
|
||||||
|
```py
|
||||||
|
.
|
||||||
|
├── __init__.py # Invoke-managed custom node loader
|
||||||
|
│
|
||||||
|
├── cool_node
|
||||||
|
│ ├── __init__.py # see example below
|
||||||
|
│ └── cool_node.py
|
||||||
|
│
|
||||||
|
└── my_node_pack
|
||||||
|
├── __init__.py # see example below
|
||||||
|
├── tasty_node.py
|
||||||
|
├── bodacious_node.py
|
||||||
|
├── utils.py
|
||||||
|
└── extra_nodes
|
||||||
|
└── fancy_node.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Node Pack `__init__.py`
|
||||||
|
|
||||||
|
Each node pack must have an `__init__.py` file that imports its nodes.
|
||||||
|
|
||||||
|
The structure of each node or node pack is otherwise not important.
|
||||||
|
|
||||||
|
Here are examples, based on the example directory structure.
|
||||||
|
|
||||||
|
### `cool_node/__init__.py`
|
||||||
|
|
||||||
|
```py
|
||||||
|
from .cool_node import CoolInvocation
|
||||||
|
```
|
||||||
|
|
||||||
|
### `my_node_pack/__init__.py`
|
||||||
|
|
||||||
|
```py
|
||||||
|
from .tasty_node import TastyInvocation
|
||||||
|
from .bodacious_node import BodaciousInvocation
|
||||||
|
from .extra_nodes.fancy_node import FancyInvocation
|
||||||
|
```
|
||||||
|
|
||||||
|
Only nodes imported in the `__init__.py` file are loaded.
|
51
invokeai/app/invocations/custom_nodes/init.py
Normal file
51
invokeai/app/invocations/custom_nodes/init.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Invoke-managed custom node loader. See README.md for more information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from importlib.util import module_from_spec, spec_from_file_location
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from invokeai.backend.util.logging import InvokeAILogger
|
||||||
|
|
||||||
|
logger = InvokeAILogger.get_logger()
|
||||||
|
loaded_count = 0
|
||||||
|
|
||||||
|
|
||||||
|
for d in Path(__file__).parent.iterdir():
|
||||||
|
# skip files
|
||||||
|
if not d.is_dir():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# skip hidden directories
|
||||||
|
if d.name.startswith("_") or d.name.startswith("."):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# skip directories without an `__init__.py`
|
||||||
|
init = d / "__init__.py"
|
||||||
|
if not init.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
module_name = init.parent.stem
|
||||||
|
|
||||||
|
# skip if already imported
|
||||||
|
if module_name in globals():
|
||||||
|
continue
|
||||||
|
|
||||||
|
# we have a legit module to import
|
||||||
|
spec = spec_from_file_location(module_name, init.absolute())
|
||||||
|
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
logger.warn(f"Could not load {init}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
module = module_from_spec(spec)
|
||||||
|
sys.modules[spec.name] = module
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
loaded_count += 1
|
||||||
|
|
||||||
|
del init, module_name
|
||||||
|
|
||||||
|
|
||||||
|
logger.info(f"Loaded {loaded_count} modules from {Path(__file__).parent}")
|
Loading…
Reference in New Issue
Block a user