Merge branch 'pr/10' into 'main'
Add Nodes for Opening and Closing Projects See merge request lightspeedrtx/lss-ai-tools/comfyui-rtx_remix!28
This commit is contained in:
+18
-1
@@ -10,12 +10,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
- Update nodes to be compatible with the renamed models
|
||||
|
||||
### Fixed
|
||||
|
||||
### Removed
|
||||
|
||||
## [1.1.0] - 2025-07-24
|
||||
|
||||
### Added
|
||||
- **Open Project Node**: Opens RTX Remix projects by layer ID for workflow management
|
||||
- **Get Loaded Project Node**: Gets layer ID of current open project for use with Open Project node
|
||||
- **Close Project Node**: Closes RTX Remix projects and a force boolean parameter default false
|
||||
- **Get Default Directory Node**: Captures the RTX Remix default output directory before closing projects
|
||||
- Low VRAM workflow example: "rtx_remix_pbrify_workflow_LowVRAM.json" demonstrating new project management nodes
|
||||
|
||||
### Changed
|
||||
- **Ingest Texture Node**: Made output_folder parameter mandatory, must be provided by user or Get Default Directory Node
|
||||
- Updated normal PBRify workflow to work with new ingest texture changes
|
||||
|
||||
### Fixed
|
||||
- Path handling compatibility for URLs with spaces when calling toolkit
|
||||
|
||||
### Removed
|
||||
|
||||
## [2024.0.0]
|
||||
|
||||
### Added
|
||||
|
||||
@@ -19,7 +19,8 @@ You should see RTX Remix nodes:
|
||||
- **RTX Remix Texture Types**: Select multiple texture types from a list of supported texture types.
|
||||
|
||||
### Ingestion
|
||||
- **RTX Remix Ingest Texture**: Ingest an image as a texture and save it to disk
|
||||
- **RTX Remix Ingest Texture**: Ingest an image as a texture and save it to disk. Output folder is now mandatory and must be provided by user or Get Default Directory node
|
||||
- **RTX Remix Get Default Directory**: Captures the RTX Remix default output directory before closing projects for use with texture ingestion
|
||||
|
||||
### Common
|
||||
- **RTX Remix Rest API Details**: Provide the port information to connect to the RTX Remix Toolkit
|
||||
@@ -38,6 +39,9 @@ You should see RTX Remix nodes:
|
||||
- **RTX Remix Get Edit Target**: Get the edit target from the currently open project
|
||||
- **RTX Remix Layer Types**: Select multiple layer types from a list of supported layer types.
|
||||
- **RTX Remix Layer Type**: Select from a list of supported layer types.
|
||||
- **RTX Remix Open Project**: Opens RTX Remix projects by layer ID for workflow management and VRAM optimization
|
||||
- **RTX Remix Get Loaded Project**: Gets layer ID of current open project for use with Open Project node
|
||||
- **RTX Remix Close Project**: Closes RTX Remix projects to save VRAM during AI processing
|
||||
|
||||
## Example Workflows and Templates
|
||||
|
||||
|
||||
+12
-1
@@ -26,15 +26,18 @@ from .common import (
|
||||
Switch,
|
||||
)
|
||||
from .file import DeleteFile
|
||||
from .ingestion import IngestTexture
|
||||
from .ingestion import GetDefaultDirectory, IngestTexture
|
||||
from .layers import (
|
||||
CloseProject,
|
||||
CreateLayer,
|
||||
DefineLayerId,
|
||||
GetEditTarget,
|
||||
GetLayers,
|
||||
GetLoadedProject,
|
||||
LayerType,
|
||||
LayerTypes,
|
||||
MuteLayer,
|
||||
OpenProject,
|
||||
RemoveLayer,
|
||||
SaveLayer,
|
||||
SetEditTarget,
|
||||
@@ -50,18 +53,22 @@ from .textures import (
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
# NOTE: names should be globally unique
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RTXRemixCloseProject": CloseProject,
|
||||
"RTXRemixCreateLayer": CreateLayer,
|
||||
"RTXRemixDefineLayerId": DefineLayerId,
|
||||
"RTXRemixDeleteFile": DeleteFile,
|
||||
"RTXRemixEndContext": EndContext,
|
||||
"RTXRemixGetDefaultDirectory": GetDefaultDirectory,
|
||||
"RTXRemixGetEditTarget": GetEditTarget,
|
||||
"RTXRemixGetLayers": GetLayers,
|
||||
"RTXRemixGetLoadedProject": GetLoadedProject,
|
||||
"RTXRemixGetTextures": GetTextures,
|
||||
"RTXRemixIngestTexture": IngestTexture,
|
||||
"RTXRemixInvertBool": InvertBool,
|
||||
"RTXRemixLayerType": LayerType,
|
||||
"RTXRemixLayerTypes": LayerTypes,
|
||||
"RTXRemixMuteLayer": MuteLayer,
|
||||
"RTXRemixOpenProject": OpenProject,
|
||||
"RTXRemixRemoveLayer": RemoveLayer,
|
||||
"RTXRemixRestAPIDetails": RestAPIDetails,
|
||||
"RTXRemixSaveLayer": SaveLayer,
|
||||
@@ -79,18 +86,22 @@ NODE_CLASS_MAPPINGS = {
|
||||
|
||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RTXRemixCloseProject": "RTX Remix Close Project",
|
||||
"RTXRemixCreateLayer": "RTX Remix Create Layer",
|
||||
"RTXRemixDefineLayerId": "RTX Remix Define Layer ID",
|
||||
"RTXRemixDeleteFile": "RTX Remix Delete File",
|
||||
"RTXRemixEndContext": "RTX Remix End Context",
|
||||
"RTXRemixGetDefaultDirectory": "RTX Remix Get Default Directory",
|
||||
"RTXRemixGetEditTarget": "RTX Remix Get Edit Target",
|
||||
"RTXRemixGetLayers": "RTX Remix Get Layers",
|
||||
"RTXRemixGetLoadedProject": "RTX Remix Get Loaded Project",
|
||||
"RTXRemixGetTextures": "RTX Remix Get Textures",
|
||||
"RTXRemixIngestTexture": "RTX Remix Ingest Texture",
|
||||
"RTXRemixInvertBool": "RTX Remix Invert Boolean Value",
|
||||
"RTXRemixLayerType": "RTX Remix Layer Type",
|
||||
"RTXRemixLayerTypes": "RTX Remix Layer Types",
|
||||
"RTXRemixMuteLayer": "RTX Remix Mute Layer",
|
||||
"RTXRemixOpenProject": "RTX Remix Open Project",
|
||||
"RTXRemixRemoveLayer": "RTX Remix Remove Layer",
|
||||
"RTXRemixRestAPIDetails": "RTX Remix Rest API Details",
|
||||
"RTXRemixSaveLayer": "RTX Remix Save Layer",
|
||||
|
||||
+44
-26
@@ -27,7 +27,7 @@ from PIL import Image
|
||||
|
||||
from .common import add_context_input_enabled_and_output
|
||||
from .constant import HEADER_LSS_REMIX_VERSION_1_0, PREFIX_MENU
|
||||
from .utils import check_response_status_code
|
||||
from .utils import check_response_status_code, posix
|
||||
|
||||
_file_name = pathlib.Path(__file__).stem
|
||||
|
||||
@@ -57,21 +57,12 @@ class IngestTexture:
|
||||
"forceInput": True,
|
||||
},
|
||||
),
|
||||
},
|
||||
"optional": {
|
||||
"enable_override_output_folder": (
|
||||
"BOOLEAN",
|
||||
{
|
||||
"default": False,
|
||||
"label_on": "enabled",
|
||||
"label_off": "disabled",
|
||||
},
|
||||
),
|
||||
"override_output_folder": (
|
||||
"output_directory": (
|
||||
"STRING",
|
||||
{
|
||||
# node
|
||||
"default": "",
|
||||
"forceInput": True,
|
||||
},
|
||||
),
|
||||
},
|
||||
@@ -91,24 +82,14 @@ class IngestTexture:
|
||||
texture: torch.Tensor,
|
||||
texture_type: str,
|
||||
texture_name: str,
|
||||
enable_override_output_folder: bool,
|
||||
override_output_folder: str,
|
||||
output_directory: str,
|
||||
):
|
||||
if not self.enable_this_node: # noqa
|
||||
return ("",)
|
||||
address, port = self.context # noqa
|
||||
if enable_override_output_folder:
|
||||
if not pathlib.Path(override_output_folder).exists():
|
||||
raise FileNotFoundError("Can't overwrite output folder, folder doesn't exist.")
|
||||
output_folder = override_output_folder
|
||||
else:
|
||||
# call RestAPI to get the default output folder
|
||||
r = requests.get(
|
||||
f"http://{address}:{port}/stagecraft/assets/default-directory",
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
check_response_status_code(r)
|
||||
output_folder = json.loads(r.text).get("directory_path", {})
|
||||
if not pathlib.Path(output_directory).exists():
|
||||
raise FileNotFoundError(f"Output directory doesn't exist: {output_directory}")
|
||||
output_folder = output_directory
|
||||
|
||||
full_output_folder, filename, _counter, _subfolder, _filename_prefix = folder_paths.get_save_image_path(
|
||||
texture_name, folder_paths.get_output_directory(), texture[0].shape[1], texture[0].shape[0]
|
||||
@@ -161,3 +142,40 @@ class IngestTexture:
|
||||
raise FileNotFoundError(f"Can't find the texture {result_path}")
|
||||
|
||||
return (str(result_path),)
|
||||
|
||||
|
||||
@add_context_input_enabled_and_output
|
||||
class GetDefaultDirectory:
|
||||
"""Get the default directory from RTX Remix before closing project"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls): # noqa N802
|
||||
inputs = {
|
||||
"required": {},
|
||||
}
|
||||
return inputs
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("default_directory",)
|
||||
|
||||
FUNCTION = "get_default_directory"
|
||||
|
||||
OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = f"{PREFIX_MENU}/{_file_name}"
|
||||
|
||||
def _get_default_output_directory(self) -> str:
|
||||
"""Utility method to get default output directory from RTX Remix API."""
|
||||
address, port = self.context # noqa
|
||||
r = requests.get(
|
||||
f"http://{address}:{port}/stagecraft/assets/default-directory",
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
check_response_status_code(r)
|
||||
return json.loads(r.text).get("directory_path", "")
|
||||
|
||||
def get_default_directory(self):
|
||||
if not self.enable_this_node: # noqa
|
||||
return ("",)
|
||||
default_directory = self._get_default_output_directory()
|
||||
return (default_directory,)
|
||||
|
||||
+96
-6
@@ -20,7 +20,7 @@ import collections
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
from urllib.parse import quote_plus, unquote
|
||||
from urllib.parse import quote, unquote
|
||||
|
||||
import requests
|
||||
|
||||
@@ -329,7 +329,7 @@ class GetLayers:
|
||||
address, port = self.context # noqa
|
||||
if parent_layer_id:
|
||||
r = requests.get(
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(parent_layer_id))}/sublayers",
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote(posix(parent_layer_id), safe='')}/sublayers",
|
||||
params=params,
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
@@ -433,7 +433,7 @@ class MuteLayer(_LayerOp):
|
||||
payload = {"value": mute}
|
||||
address, port = self.context # noqa
|
||||
r = requests.put(
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(layer_id))}/mute",
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote(posix(layer_id), safe='')}/mute",
|
||||
data=json.dumps(payload),
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
@@ -464,7 +464,7 @@ class RemoveLayer(_LayerOp):
|
||||
payload = {"parent_layer_id": posix(parent_layer_id)}
|
||||
address, port = self.context # noqa
|
||||
r = requests.delete(
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(layer_id))}",
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote(posix(layer_id), safe='')}",
|
||||
data=json.dumps(payload),
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
@@ -481,7 +481,7 @@ class SaveLayer(_LayerOp):
|
||||
return ("",)
|
||||
address, port = self.context # noqa
|
||||
r = requests.post(
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(layer_id))}/save",
|
||||
f"http://{address}:{port}/stagecraft/layers/{quote(posix(layer_id), safe='')}/save",
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
check_response_status_code(r)
|
||||
@@ -531,8 +531,98 @@ class SetEditTarget(_LayerOp):
|
||||
return ("",)
|
||||
address, port = self.context # noqa
|
||||
r = requests.put(
|
||||
f"http://{address}:{port}/stagecraft/layers/target/{quote_plus(posix(layer_id))}",
|
||||
f"http://{address}:{port}/stagecraft/layers/target/{quote(posix(layer_id), safe='')}",
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
check_response_status_code(r)
|
||||
return (layer_id,) # return an output so that you can make sure this executes before other nodes
|
||||
|
||||
|
||||
@add_context_input_enabled_and_output
|
||||
class CloseProject:
|
||||
"""Close the currently open project"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls): # noqa N802
|
||||
inputs = get_context_inputs()
|
||||
inputs["optional"] = {
|
||||
"force": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
return inputs
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("status",)
|
||||
|
||||
FUNCTION = "close_project"
|
||||
|
||||
OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = f"{PREFIX_MENU}/{_file_name}"
|
||||
|
||||
def close_project(self, force: bool = False) -> tuple[str]:
|
||||
if not self.enable_this_node: # noqa
|
||||
return ("disabled",)
|
||||
address, port = self.context # noqa
|
||||
url = f"http://{address}:{port}/stagecraft/project"
|
||||
if force:
|
||||
url += "?force=true"
|
||||
r = requests.delete(
|
||||
url,
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
check_response_status_code(r)
|
||||
return ("closed",)
|
||||
|
||||
|
||||
@add_context_input_enabled_and_output
|
||||
class OpenProject(_LayerOp):
|
||||
"""Open a project using the specified layer ID"""
|
||||
|
||||
def execute(self, layer_id: str) -> tuple[str]:
|
||||
if not self.enable_this_node: # noqa
|
||||
return ("",)
|
||||
address, port = self.context # noqa
|
||||
r = requests.put(
|
||||
f"http://{address}:{port}/stagecraft/project/{quote(posix(layer_id), safe='')}",
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
check_response_status_code(r)
|
||||
return (layer_id,)
|
||||
|
||||
|
||||
@add_context_input_enabled_and_output
|
||||
class GetLoadedProject:
|
||||
"""Get the currently loaded project"""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls): # noqa N802
|
||||
return get_context_inputs()
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("layer_id",)
|
||||
|
||||
FUNCTION = "get_loaded_project"
|
||||
|
||||
OUTPUT_NODE = False
|
||||
|
||||
CATEGORY = f"{PREFIX_MENU}/{_file_name}"
|
||||
|
||||
def get_loaded_project(self) -> tuple[str]:
|
||||
if not self.enable_this_node: # noqa
|
||||
return ("",)
|
||||
address, port = self.context # noqa
|
||||
r = requests.get(
|
||||
f"http://{address}:{port}/stagecraft/project/",
|
||||
headers=HEADER_LSS_REMIX_VERSION_1_0,
|
||||
)
|
||||
check_response_status_code(r)
|
||||
response_data = json.loads(r.text)
|
||||
layer_id = response_data.get("layer_id", "")
|
||||
return (layer_id,)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs): # noqa N802
|
||||
"""
|
||||
Always process the node in case the loaded project in the RTX Remix app changed
|
||||
"""
|
||||
return float("nan")
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-rtx-remix"
|
||||
description = "Use ComfyUI with RTX Remix to remaster classic games [a/https://github.com/NVIDIAGameWorks/rtx-remix](https://github.com/NVIDIAGameWorks/rtx-remix)"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["numpy", "pillow>=10.0.1", "requests", "torch"]
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user