bugs fixed
This commit is contained in:
@@ -26,6 +26,15 @@ CATEGORY = "IAMCCS/MiniMax H3/Face Detailer"
|
||||
_EXTERNAL_FACE_NODE_PATH = Path(__file__).resolve().parents[1] / "ComfyUI-H3-FaceRefine" / "nodes.py"
|
||||
|
||||
|
||||
def _face_nodes_available(*node_names: str) -> bool:
|
||||
"""Probe optional registration without importing the external backend."""
|
||||
if _EXTERNAL_FACE_NODE_PATH.is_file():
|
||||
return True
|
||||
registered = getattr(sys.modules.get("nodes"), "NODE_CLASS_MAPPINGS", {})
|
||||
external = getattr(sys.modules.get("iamccs_external_h3_face_refine"), "NODE_CLASS_MAPPINGS", {})
|
||||
return all(name in registered or name in external for name in node_names)
|
||||
|
||||
|
||||
def _face_node_class(node_name: str):
|
||||
"""Resolve the installed FaceRefine node without duplicating its algorithms."""
|
||||
try:
|
||||
@@ -224,3 +233,10 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IAMCCS_MiniMaxH3FaceTransformInfo": "IAMCCS H3 Face Transform Info",
|
||||
"IAMCCS_MiniMaxH3FaceDetailerRouter": "IAMCCS H3 Optional Face Detailer Router",
|
||||
}
|
||||
|
||||
# ComfyUI queries every registered INPUT_TYPES on startup/frontend reload.
|
||||
# Missing optional backends must not turn that discovery into repeated errors.
|
||||
for _name, _class in list(NODE_CLASS_MAPPINGS.items()):
|
||||
if issubclass(_class, _FaceRefineAlias) and not _face_nodes_available(_class.EXTERNAL_NODE):
|
||||
del NODE_CLASS_MAPPINGS[_name]
|
||||
del NODE_DISPLAY_NAME_MAPPINGS[_name]
|
||||
|
||||
@@ -722,7 +722,10 @@ from .iamccs_minimax_h3_face_delivery_variant import (
|
||||
IAMCCS_MiniMaxH3FaceDeliveryR38B,
|
||||
IAMCCS_MiniMaxH3WideCharacterDetailer12GB,
|
||||
)
|
||||
NODE_CLASS_MAPPINGS["IAMCCS_MiniMaxH3FaceDeliveryR38B"] = IAMCCS_MiniMaxH3FaceDeliveryR38B
|
||||
NODE_DISPLAY_NAME_MAPPINGS["IAMCCS_MiniMaxH3FaceDeliveryR38B"] = "MiniMax H3 · Optional FaceRefine · Before Upscale"
|
||||
NODE_CLASS_MAPPINGS["IAMCCS_MiniMaxH3WideCharacterDetailer12GB"] = IAMCCS_MiniMaxH3WideCharacterDetailer12GB
|
||||
NODE_DISPLAY_NAME_MAPPINGS["IAMCCS_MiniMaxH3WideCharacterDetailer12GB"] = "IAMCCS WIDE CHARACTER DETAILER · 12 GB"
|
||||
from .iamccs_minimax_h3_face_detailer import _face_nodes_available
|
||||
|
||||
if _face_nodes_available("H3FaceTrackCrop", "H3FaceStitch"):
|
||||
NODE_CLASS_MAPPINGS["IAMCCS_MiniMaxH3FaceDeliveryR38B"] = IAMCCS_MiniMaxH3FaceDeliveryR38B
|
||||
NODE_DISPLAY_NAME_MAPPINGS["IAMCCS_MiniMaxH3FaceDeliveryR38B"] = "MiniMax H3 · Optional FaceRefine · Before Upscale"
|
||||
NODE_CLASS_MAPPINGS["IAMCCS_MiniMaxH3WideCharacterDetailer12GB"] = IAMCCS_MiniMaxH3WideCharacterDetailer12GB
|
||||
NODE_DISPLAY_NAME_MAPPINGS["IAMCCS_MiniMaxH3WideCharacterDetailer12GB"] = "IAMCCS WIDE CHARACTER DETAILER · 12 GB"
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
"""Exercise optional FaceRefine registration without loading models or ComfyUI."""
|
||||
import ast
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ALIAS_NAMES = (
|
||||
"H3FaceTrackCrop", "H3FaceStitch", "H3InjectVideoLatent",
|
||||
"H3PerFrameDenoise", "H3FaceMaskSAM", "H3FaceTransformInfo",
|
||||
)
|
||||
|
||||
|
||||
class FaceRefineOptionalTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.modules = patch.dict(sys.modules)
|
||||
self.modules.start()
|
||||
self.addCleanup(self.modules.stop)
|
||||
self.package = "_iamccs_optional_face_test"
|
||||
package = types.ModuleType(self.package)
|
||||
package.__path__ = [str(ROOT)]
|
||||
sys.modules[self.package] = package
|
||||
for name in ("iamccs_prompter", "iamccs_minimax_h3_atomic_backend"):
|
||||
module = types.ModuleType(f"{self.package}.{name}")
|
||||
module.SUPERNODE_LINX_TYPE = "SUPERNODE_LINX"
|
||||
module._resolve_shotplan = lambda linx: linx
|
||||
sys.modules[module.__name__] = module
|
||||
comfy_nodes = types.ModuleType("nodes")
|
||||
comfy_nodes.NODE_CLASS_MAPPINGS = {}
|
||||
sys.modules["nodes"] = comfy_nodes
|
||||
sys.modules.pop("iamccs_external_h3_face_refine", None)
|
||||
|
||||
def load_detailer(self):
|
||||
name = f"{self.package}.iamccs_minimax_h3_face_detailer"
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(Path(self.temp.name) / "IAMCCS-nodes" / "iamccs_minimax_h3_face_detailer.py")
|
||||
module.__package__ = self.package
|
||||
sys.modules[name] = module
|
||||
source = ROOT / "iamccs_minimax_h3_face_detailer.py"
|
||||
exec(compile(source.read_text(encoding="utf-8"), str(source), "exec"), module.__dict__)
|
||||
return module
|
||||
|
||||
def delivery_registration(self):
|
||||
# Execute the real registration tail, excluding unrelated pixel inference code.
|
||||
source = ROOT / "iamccs_minimax_h3_pixel_refine_variant.py"
|
||||
tree = ast.parse(source.read_text(encoding="utf-8"))
|
||||
start = next(i for i, node in enumerate(tree.body) if isinstance(node, ast.ImportFrom)
|
||||
and node.module == "iamccs_minimax_h3_face_delivery_variant")
|
||||
tail = ast.Module(body=tree.body[start:], type_ignores=[])
|
||||
namespace = {"__package__": self.package, "NODE_CLASS_MAPPINGS": {}, "NODE_DISPLAY_NAME_MAPPINGS": {}}
|
||||
exec(compile(tail, str(source), "exec"), namespace)
|
||||
return namespace
|
||||
|
||||
def test_absent_dependency_registers_only_independent_router(self):
|
||||
detailer = self.load_detailer()
|
||||
self.assertEqual(set(detailer.NODE_CLASS_MAPPINGS), {"IAMCCS_MiniMaxH3FaceDetailerRouter"})
|
||||
for node in detailer.NODE_CLASS_MAPPINGS.values():
|
||||
self.assertIn("required", node.INPUT_TYPES())
|
||||
delivery = self.delivery_registration()
|
||||
self.assertEqual(delivery["NODE_CLASS_MAPPINGS"], {})
|
||||
self.assertEqual(delivery["NODE_DISPLAY_NAME_MAPPINGS"], {})
|
||||
|
||||
def test_disabled_router_passes_native_objects_without_dependency(self):
|
||||
detailer = self.load_detailer()
|
||||
router = detailer.IAMCCS_MiniMaxH3FaceDetailerRouter()
|
||||
frames, audio = object(), {"waveform": object()}
|
||||
result = router.route({}, frames, audio)
|
||||
self.assertIs(result[0], frames)
|
||||
self.assertIs(result[1], audio)
|
||||
self.assertEqual(router.check_lazy_status({}, frames, audio), [])
|
||||
|
||||
def test_explicit_use_still_reports_missing_dependency(self):
|
||||
detailer = self.load_detailer()
|
||||
with self.assertRaisesRegex(RuntimeError, "before using IAMCCS H3 Face Detailer"):
|
||||
detailer.IAMCCS_MiniMaxH3FaceTrackCrop().run(images=object())
|
||||
|
||||
def test_registered_provider_preserves_alias_schema_and_execution(self):
|
||||
schema = {"required": {"images": ("IMAGE",), "detector": (["installed-detector"],)}}
|
||||
class Provider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return schema
|
||||
|
||||
def run(self, **kwargs):
|
||||
return (kwargs["images"],)
|
||||
|
||||
sys.modules["nodes"].NODE_CLASS_MAPPINGS = dict.fromkeys(ALIAS_NAMES, Provider)
|
||||
detailer = self.load_detailer()
|
||||
self.assertEqual(len(detailer.NODE_CLASS_MAPPINGS), 7)
|
||||
for name in ALIAS_NAMES:
|
||||
alias = detailer.NODE_CLASS_MAPPINGS[f"IAMCCS_MiniMax{name}"]
|
||||
result = alias.INPUT_TYPES()
|
||||
self.assertEqual(result, schema)
|
||||
self.assertIsNot(result, schema)
|
||||
marker = object()
|
||||
self.assertIs(alias().run(images=marker)[0], marker)
|
||||
self.assertEqual(len(self.delivery_registration()["NODE_CLASS_MAPPINGS"]), 2)
|
||||
|
||||
def test_installed_package_registers_aliases_without_eager_import(self):
|
||||
provider = Path(self.temp.name) / "ComfyUI-H3-FaceRefine" / "nodes.py"
|
||||
provider.parent.mkdir()
|
||||
provider.write_text("raise AssertionError('Provider imported during registration')", encoding="utf-8")
|
||||
detailer = self.load_detailer()
|
||||
self.assertEqual(len(detailer.NODE_CLASS_MAPPINGS), 7)
|
||||
self.assertEqual(len(self.delivery_registration()["NODE_CLASS_MAPPINGS"]), 2)
|
||||
self.assertNotIn("iamccs_external_h3_face_refine", sys.modules)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user