Skip to content

Commit 5b314a9

Browse files
committed
Merge branch 'pr/10' into 'main'
Add Nodes for Opening and Closing Projects See merge request lightspeedrtx/lss-ai-tools/comfyui-rtx_remix!27
2 parents 22b72c9 + 8672c4f commit 5b314a9

File tree

8 files changed

+10400
-294
lines changed

8 files changed

+10400
-294
lines changed

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
### Changed
13-
- Update nodes to be compatible with the renamed models
1413

1514
### Fixed
1615

1716
### Removed
1817

18+
## [1.1.0] - 2025-07-24
19+
20+
### Added
21+
- **Open Project Node**: Opens RTX Remix projects by layer ID for workflow management
22+
- **Get Loaded Project Node**: Gets layer ID of current open project for use with Open Project node
23+
- **Close Project Node**: Closes RTX Remix projects and a force boolean parameter default false
24+
- **Get Default Directory Node**: Captures the RTX Remix default output directory before closing projects
25+
- Low VRAM workflow example: "rtx_remix_pbrify_workflow_LowVRAM.json" demonstrating new project management nodes
26+
27+
### Changed
28+
- **Ingest Texture Node**: Made output_folder parameter mandatory, must be provided by user or Get Default Directory Node
29+
- Updated normal PBRify workflow to work with new ingest texture changes
30+
31+
### Fixed
32+
- Path handling compatibility for URLs with spaces when calling toolkit
33+
34+
### Removed
35+
1936
## [2024.0.0]
2037

2138
### Added

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ You should see RTX Remix nodes:
1919
- **RTX Remix Texture Types**: Select multiple texture types from a list of supported texture types.
2020

2121
### Ingestion
22-
- **RTX Remix Ingest Texture**: Ingest an image as a texture and save it to disk
22+
- **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
23+
- **RTX Remix Get Default Directory**: Captures the RTX Remix default output directory before closing projects for use with texture ingestion
2324

2425
### Common
2526
- **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:
3839
- **RTX Remix Get Edit Target**: Get the edit target from the currently open project
3940
- **RTX Remix Layer Types**: Select multiple layer types from a list of supported layer types.
4041
- **RTX Remix Layer Type**: Select from a list of supported layer types.
42+
- **RTX Remix Open Project**: Opens RTX Remix projects by layer ID for workflow management and VRAM optimization
43+
- **RTX Remix Get Loaded Project**: Gets layer ID of current open project for use with Open Project node
44+
- **RTX Remix Close Project**: Closes RTX Remix projects to save VRAM during AI processing
4145

4246
## Example Workflows and Templates
4347

nodes/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@
2626
Switch,
2727
)
2828
from .file import DeleteFile
29-
from .ingestion import IngestTexture
29+
from .ingestion import GetDefaultDirectory, IngestTexture
3030
from .layers import (
31+
CloseProject,
3132
CreateLayer,
3233
DefineLayerId,
3334
GetEditTarget,
3435
GetLayers,
36+
GetLoadedProject,
3537
LayerType,
3638
LayerTypes,
3739
MuteLayer,
40+
OpenProject,
3841
RemoveLayer,
3942
SaveLayer,
4043
SetEditTarget,
@@ -50,18 +53,22 @@
5053
# A dictionary that contains all nodes you want to export with their names
5154
# NOTE: names should be globally unique
5255
NODE_CLASS_MAPPINGS = {
56+
"RTXRemixCloseProject": CloseProject,
5357
"RTXRemixCreateLayer": CreateLayer,
5458
"RTXRemixDefineLayerId": DefineLayerId,
5559
"RTXRemixDeleteFile": DeleteFile,
5660
"RTXRemixEndContext": EndContext,
61+
"RTXRemixGetDefaultDirectory": GetDefaultDirectory,
5762
"RTXRemixGetEditTarget": GetEditTarget,
5863
"RTXRemixGetLayers": GetLayers,
64+
"RTXRemixGetLoadedProject": GetLoadedProject,
5965
"RTXRemixGetTextures": GetTextures,
6066
"RTXRemixIngestTexture": IngestTexture,
6167
"RTXRemixInvertBool": InvertBool,
6268
"RTXRemixLayerType": LayerType,
6369
"RTXRemixLayerTypes": LayerTypes,
6470
"RTXRemixMuteLayer": MuteLayer,
71+
"RTXRemixOpenProject": OpenProject,
6572
"RTXRemixRemoveLayer": RemoveLayer,
6673
"RTXRemixRestAPIDetails": RestAPIDetails,
6774
"RTXRemixSaveLayer": SaveLayer,
@@ -79,18 +86,22 @@
7986

8087
# A dictionary that contains the friendly/humanly readable titles for the nodes
8188
NODE_DISPLAY_NAME_MAPPINGS = {
89+
"RTXRemixCloseProject": "RTX Remix Close Project",
8290
"RTXRemixCreateLayer": "RTX Remix Create Layer",
8391
"RTXRemixDefineLayerId": "RTX Remix Define Layer ID",
8492
"RTXRemixDeleteFile": "RTX Remix Delete File",
8593
"RTXRemixEndContext": "RTX Remix End Context",
94+
"RTXRemixGetDefaultDirectory": "RTX Remix Get Default Directory",
8695
"RTXRemixGetEditTarget": "RTX Remix Get Edit Target",
8796
"RTXRemixGetLayers": "RTX Remix Get Layers",
97+
"RTXRemixGetLoadedProject": "RTX Remix Get Loaded Project",
8898
"RTXRemixGetTextures": "RTX Remix Get Textures",
8999
"RTXRemixIngestTexture": "RTX Remix Ingest Texture",
90100
"RTXRemixInvertBool": "RTX Remix Invert Boolean Value",
91101
"RTXRemixLayerType": "RTX Remix Layer Type",
92102
"RTXRemixLayerTypes": "RTX Remix Layer Types",
93103
"RTXRemixMuteLayer": "RTX Remix Mute Layer",
104+
"RTXRemixOpenProject": "RTX Remix Open Project",
94105
"RTXRemixRemoveLayer": "RTX Remix Remove Layer",
95106
"RTXRemixRestAPIDetails": "RTX Remix Rest API Details",
96107
"RTXRemixSaveLayer": "RTX Remix Save Layer",

nodes/ingestion.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
from .common import add_context_input_enabled_and_output
2929
from .constant import HEADER_LSS_REMIX_VERSION_1_0, PREFIX_MENU
30-
from .utils import check_response_status_code
30+
from .utils import check_response_status_code, posix
3131

3232
_file_name = pathlib.Path(__file__).stem
3333

@@ -57,21 +57,12 @@ def INPUT_TYPES(cls): # noqa N802
5757
"forceInput": True,
5858
},
5959
),
60-
},
61-
"optional": {
62-
"enable_override_output_folder": (
63-
"BOOLEAN",
64-
{
65-
"default": False,
66-
"label_on": "enabled",
67-
"label_off": "disabled",
68-
},
69-
),
70-
"override_output_folder": (
60+
"output_directory": (
7161
"STRING",
7262
{
7363
# node
7464
"default": "",
65+
"forceInput": True,
7566
},
7667
),
7768
},
@@ -91,24 +82,14 @@ def ingest_texture(
9182
texture: torch.Tensor,
9283
texture_type: str,
9384
texture_name: str,
94-
enable_override_output_folder: bool,
95-
override_output_folder: str,
85+
output_directory: str,
9686
):
9787
if not self.enable_this_node: # noqa
9888
return ("",)
9989
address, port = self.context # noqa
100-
if enable_override_output_folder:
101-
if not pathlib.Path(override_output_folder).exists():
102-
raise FileNotFoundError("Can't overwrite output folder, folder doesn't exist.")
103-
output_folder = override_output_folder
104-
else:
105-
# call RestAPI to get the default output folder
106-
r = requests.get(
107-
f"http://{address}:{port}/stagecraft/assets/default-directory",
108-
headers=HEADER_LSS_REMIX_VERSION_1_0,
109-
)
110-
check_response_status_code(r)
111-
output_folder = json.loads(r.text).get("directory_path", {})
90+
if not pathlib.Path(output_directory).exists():
91+
raise FileNotFoundError(f"Output directory doesn't exist: {output_directory}")
92+
output_folder = output_directory
11293

11394
full_output_folder, filename, _counter, _subfolder, _filename_prefix = folder_paths.get_save_image_path(
11495
texture_name, folder_paths.get_output_directory(), texture[0].shape[1], texture[0].shape[0]
@@ -161,3 +142,40 @@ def ingest_texture(
161142
raise FileNotFoundError(f"Can't find the texture {result_path}")
162143

163144
return (str(result_path),)
145+
146+
147+
@add_context_input_enabled_and_output
148+
class GetDefaultDirectory:
149+
"""Get the default directory from RTX Remix before closing project"""
150+
151+
@classmethod
152+
def INPUT_TYPES(cls): # noqa N802
153+
inputs = {
154+
"required": {},
155+
}
156+
return inputs
157+
158+
RETURN_TYPES = ("STRING",)
159+
RETURN_NAMES = ("default_directory",)
160+
161+
FUNCTION = "get_default_directory"
162+
163+
OUTPUT_NODE = False
164+
165+
CATEGORY = f"{PREFIX_MENU}/{_file_name}"
166+
167+
def _get_default_output_directory(self) -> str:
168+
"""Utility method to get default output directory from RTX Remix API."""
169+
address, port = self.context # noqa
170+
r = requests.get(
171+
f"http://{address}:{port}/stagecraft/assets/default-directory",
172+
headers=HEADER_LSS_REMIX_VERSION_1_0,
173+
)
174+
check_response_status_code(r)
175+
return json.loads(r.text).get("directory_path", "")
176+
177+
def get_default_directory(self):
178+
if not self.enable_this_node: # noqa
179+
return ("",)
180+
default_directory = self._get_default_output_directory()
181+
return (default_directory,)

nodes/layers.py

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import json
2121
import pathlib
2222
import re
23-
from urllib.parse import quote_plus, unquote
23+
from urllib.parse import quote, unquote
2424

2525
import requests
2626

@@ -329,7 +329,7 @@ def execute(
329329
address, port = self.context # noqa
330330
if parent_layer_id:
331331
r = requests.get(
332-
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(parent_layer_id))}/sublayers",
332+
f"http://{address}:{port}/stagecraft/layers/{quote(posix(parent_layer_id), safe='')}/sublayers",
333333
params=params,
334334
headers=HEADER_LSS_REMIX_VERSION_1_0,
335335
)
@@ -433,7 +433,7 @@ def execute(self, layer_id: str, mute: bool) -> tuple[str]: # noqa
433433
payload = {"value": mute}
434434
address, port = self.context # noqa
435435
r = requests.put(
436-
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(layer_id))}/mute",
436+
f"http://{address}:{port}/stagecraft/layers/{quote(posix(layer_id), safe='')}/mute",
437437
data=json.dumps(payload),
438438
headers=HEADER_LSS_REMIX_VERSION_1_0,
439439
)
@@ -464,7 +464,7 @@ def execute(self, layer_id: str, parent_layer_id: str) -> tuple[str]: # noqa
464464
payload = {"parent_layer_id": posix(parent_layer_id)}
465465
address, port = self.context # noqa
466466
r = requests.delete(
467-
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(layer_id))}",
467+
f"http://{address}:{port}/stagecraft/layers/{quote(posix(layer_id), safe='')}",
468468
data=json.dumps(payload),
469469
headers=HEADER_LSS_REMIX_VERSION_1_0,
470470
)
@@ -481,7 +481,7 @@ def execute(self, layer_id: str) -> tuple[str]:
481481
return ("",)
482482
address, port = self.context # noqa
483483
r = requests.post(
484-
f"http://{address}:{port}/stagecraft/layers/{quote_plus(posix(layer_id))}/save",
484+
f"http://{address}:{port}/stagecraft/layers/{quote(posix(layer_id), safe='')}/save",
485485
headers=HEADER_LSS_REMIX_VERSION_1_0,
486486
)
487487
check_response_status_code(r)
@@ -531,8 +531,98 @@ def execute(self, layer_id: str) -> tuple[str]:
531531
return ("",)
532532
address, port = self.context # noqa
533533
r = requests.put(
534-
f"http://{address}:{port}/stagecraft/layers/target/{quote_plus(posix(layer_id))}",
534+
f"http://{address}:{port}/stagecraft/layers/target/{quote(posix(layer_id), safe='')}",
535535
headers=HEADER_LSS_REMIX_VERSION_1_0,
536536
)
537537
check_response_status_code(r)
538538
return (layer_id,) # return an output so that you can make sure this executes before other nodes
539+
540+
541+
@add_context_input_enabled_and_output
542+
class CloseProject:
543+
"""Close the currently open project"""
544+
545+
@classmethod
546+
def INPUT_TYPES(cls): # noqa N802
547+
inputs = get_context_inputs()
548+
inputs["optional"] = {
549+
"force": ("BOOLEAN", {"default": False}),
550+
}
551+
return inputs
552+
553+
RETURN_TYPES = ("STRING",)
554+
RETURN_NAMES = ("status",)
555+
556+
FUNCTION = "close_project"
557+
558+
OUTPUT_NODE = False
559+
560+
CATEGORY = f"{PREFIX_MENU}/{_file_name}"
561+
562+
def close_project(self, force: bool = False) -> tuple[str]:
563+
if not self.enable_this_node: # noqa
564+
return ("disabled",)
565+
address, port = self.context # noqa
566+
url = f"http://{address}:{port}/stagecraft/project"
567+
if force:
568+
url += "?force=true"
569+
r = requests.delete(
570+
url,
571+
headers=HEADER_LSS_REMIX_VERSION_1_0,
572+
)
573+
check_response_status_code(r)
574+
return ("closed",)
575+
576+
577+
@add_context_input_enabled_and_output
578+
class OpenProject(_LayerOp):
579+
"""Open a project using the specified layer ID"""
580+
581+
def execute(self, layer_id: str) -> tuple[str]:
582+
if not self.enable_this_node: # noqa
583+
return ("",)
584+
address, port = self.context # noqa
585+
r = requests.put(
586+
f"http://{address}:{port}/stagecraft/project/{quote(posix(layer_id), safe='')}",
587+
headers=HEADER_LSS_REMIX_VERSION_1_0,
588+
)
589+
check_response_status_code(r)
590+
return (layer_id,)
591+
592+
593+
@add_context_input_enabled_and_output
594+
class GetLoadedProject:
595+
"""Get the currently loaded project"""
596+
597+
@classmethod
598+
def INPUT_TYPES(cls): # noqa N802
599+
return get_context_inputs()
600+
601+
RETURN_TYPES = ("STRING",)
602+
RETURN_NAMES = ("layer_id",)
603+
604+
FUNCTION = "get_loaded_project"
605+
606+
OUTPUT_NODE = False
607+
608+
CATEGORY = f"{PREFIX_MENU}/{_file_name}"
609+
610+
def get_loaded_project(self) -> tuple[str]:
611+
if not self.enable_this_node: # noqa
612+
return ("",)
613+
address, port = self.context # noqa
614+
r = requests.get(
615+
f"http://{address}:{port}/stagecraft/project/",
616+
headers=HEADER_LSS_REMIX_VERSION_1_0,
617+
)
618+
check_response_status_code(r)
619+
response_data = json.loads(r.text)
620+
layer_id = response_data.get("layer_id", "")
621+
return (layer_id,)
622+
623+
@classmethod
624+
def IS_CHANGED(cls, **kwargs): # noqa N802
625+
"""
626+
Always process the node in case the loaded project in the RTX Remix app changed
627+
"""
628+
return float("nan")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-rtx-remix"
33
description = "Use ComfyUI with RTX Remix to remaster classic games [a/https://github.com/NVIDIAGameWorks/rtx-remix](https://github.com/NVIDIAGameWorks/rtx-remix)"
4-
version = "1.0.1"
4+
version = "1.1.0"
55
license = {file = "LICENSE"}
66
dependencies = ["numpy", "pillow>=10.0.1", "requests", "torch"]
77

0 commit comments

Comments
 (0)