Add Nodes for Opening and Closing Projects
Added some more project workflow management to RTX Remix nodes to handle execution flow. Mainly to save VRAM for AI processing that remix is taking up in viewport. New Features: • Open Project Node - Opens RTX Remix projects by layer ID • Get Loaded Project - Gets layer ID of current open project for Open Project node to use later • Close Project Node - Closes RTX Remix projects • Get Default Directory Node - Captures the RTX Remix default output directory before closing projects • Edited Ingest Texture Node - Made Optional Output_folder mandatory, provided by user or Get Default Direcotry Node • Fixed Path handling compatibility for URLs with spaces when calling toolkit • Added a PBRify workflow "rtx_remix_pbrify_workflow_LowVRAM" showing this at work and for users with low vram • Edited normal PBRify workflow to work with new ingest texture changes
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