Files
gig-line/Tools/tests/test_comfyui_texture.py
T
zwitschi 70a7e19841 feat(tools): texture generation through the documented Stability Matrix workflows
Re-read /home/zwitschi/StabilityMatrix/Data/README.md - it now documents 141 GB of models and four
verified, core-node image/texture graphs - and wired the ones that matter into the toolchain.

comfyui_generate.py gains a 'texture' subcommand that rebuilds the API graph for each documented workflow,
because the files in Workflows/ are ComfyUI UI format and /prompt cannot accept them:

  albedobase  AlbedoBase XL v3.1, 30 steps, CFG 6.0, dpmpp_2m/karras, baked VAE
  krea2       Krea-2 Turbo NVFP4, 8 steps, CFG 1.0, euler/simple, ConditioningZeroOut, texture-fix VAE
  qwen21      Qwen-Image 2.1 int8, 25 steps, CFG 1.0, euler/simple, TextEncodeQwenImage21, texture-fix VAE

Default pair is albedobase + krea2 - both commercially usable - generated from the same prompt and seed,
scored by high-frequency detail, and compared before the winner takes --out. Smoke-tested at 512 px: both
rendered, albedobase won 18.91 against 17.51, and the resulting limestone albedo is a plausible material
sheet.

Tools/README.md documents the profiles with their settings, a licence row per profile (Qwen-Image 2.1 is
research-only and must not ship), and the two limits the workflows carry: they produce albedo only, and
ComfyUI 0.44 has no seamless sampler, so tiling has to be checked by offsetting the image 50%. Five tests
cover the graph shapes; the suite is at 108.
2026-11-10 11:40:23 +01:00

45 lines
2.0 KiB
Python

"""Graph shape for the documented texture workflows (no ComfyUI process involved)."""
import unittest
from comfyui_generate import TEXTURE_PROFILES, texture_workflow
class TextureWorkflowTests(unittest.TestCase):
def test_sdxl_profile_uses_a_checkpoint_and_a_real_negative(self):
graph = texture_workflow("albedobase", "stone", 1024, 1024, 7)
self.assertEqual(graph["1"]["class_type"], "CheckpointLoaderSimple")
self.assertEqual(graph["5"]["class_type"], "CLIPTextEncode")
sampler = graph["7"]["inputs"]
self.assertEqual(sampler["steps"], TEXTURE_PROFILES["albedobase"]["steps"])
self.assertEqual(sampler["negative"], ["5", 0])
def test_unet_profile_loads_unet_clip_and_vae(self):
graph = texture_workflow("krea2", "stone", 1024, 1024, 7)
self.assertEqual(graph["1"]["class_type"], "UNETLoader")
self.assertEqual(graph["2"]["class_type"], "CLIPLoader")
self.assertEqual(graph["2"]["inputs"]["type"], "krea2")
self.assertEqual(graph["3"]["class_type"], "VAELoader")
self.assertIn("texture_fix", graph["3"]["inputs"]["vae_name"])
def test_krea2_zeroes_the_negative_at_cfg_one(self):
graph = texture_workflow("krea2", "stone", 1024, 1024, 7)
self.assertEqual(graph["5"]["class_type"], "ConditioningZeroOut")
self.assertEqual(graph["7"]["inputs"]["negative"], ["5", 0])
def test_qwen_profile_uses_its_own_text_encode_node(self):
graph = texture_workflow("qwen21", "stone", 1024, 1024, 7)
self.assertEqual(graph["4"]["class_type"], "TextEncodeQwenImage21")
# CFG 1 with no negative: the positive conditioning is reused.
self.assertEqual(graph["7"]["inputs"]["negative"], ["4", 0])
def test_every_profile_names_installed_models(self):
for name, spec in TEXTURE_PROFILES.items():
self.assertTrue(spec.get("checkpoint") or spec.get("unet"), name)
if "unet" in spec:
self.assertTrue(spec["clip"] and spec["vae"], name)
if __name__ == "__main__":
unittest.main()