One directory with sixty scripts stopped being navigable, and the grouping is also the sandbox boundary:
Tools/unreal/ commandlets and the two map builders, with ue_common.py and ue_placement.py as the
shared imports (they need the engine)
Tools/blender/ builders, exporters, the FBX preset, the MPFB character pipeline, blender_screenshots
(they need the Blender Flatpak)
Tools/review/ the visual loop: render_screenshots.sh, judge_screenshot.py, hero_compare.py,
build_comparison_md.py, e2e_asset_check.py, hero_iterate.sh, visual_loop.py,
visual_targets.json
Tools/gen/ image and texture generation: the ComfyUI driver, deterministic texture writers,
reference fetching
Tools/input/ OS-level input injection
root _common.py, check-lfs.sh, addon_compat_probe.py, sibling_asset_audit.py, tests/
Every moved script now resolves the repository root as ../.. and puts the tools root plus its siblings on
sys.path, so 'import ue_common' still works from unreal/. The test suite bootstrap does the same for the five
folders. render_screenshots.sh takes the folder as a third argument to run_cmdlet, and the e2e driver,
hero_iterate.sh and the comparison generator were repointed.
51 README rows now link to their new location, the README gains a Layout section naming each folder's
sandbox, and the architecture notes and Blender README follow the moves.
Verified: 108 tests pass, prettier is clean, judge_screenshot.py and hero_compare.py run from their new
homes, and the e2e run was aborted mid-way (it had been rebuilding an empty DemoMap for 15 minutes) and is
being rerun against the new layout.
118 lines
4.2 KiB
Python
118 lines
4.2 KiB
Python
"""Tests for Tools/facade_detail_concepts.py — the pure parts.
|
|
|
|
No network calls: the OpenRouter request and the image bytes are represented as
|
|
the small structures the API uses. Generation itself is manual and paid.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
_TOOLS = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, _TOOLS)
|
|
for _sub in ("review", "gen", "unreal", "blender", "input"):
|
|
sys.path.insert(0, os.path.join(_TOOLS, _sub))
|
|
|
|
|
|
import base64
|
|
import json
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
sys.path.insert(0, os.path.join(ROOT, "Tools"))
|
|
|
|
import facade_detail_concepts as fc # noqa: E402
|
|
|
|
|
|
class StudyTableTest(unittest.TestCase):
|
|
def test_every_study_has_a_reference_and_a_prompt(self):
|
|
self.assertTrue(fc.STUDIES)
|
|
for name, (ref, prompt) in fc.STUDIES.items():
|
|
self.assertTrue(ref, f"{name} has no reference")
|
|
self.assertTrue(prompt.strip(), f"{name} has no prompt")
|
|
# References are relative to Art/reference and never escape it.
|
|
self.assertFalse(os.path.isabs(ref), f"{name} reference is absolute")
|
|
self.assertNotIn("..", ref, f"{name} reference escapes the directory")
|
|
|
|
def test_prompt_always_carries_the_anchor_suffix(self):
|
|
for name in fc.STUDIES:
|
|
prompt = fc.build_prompt(name)
|
|
self.assertIn(fc.ANCHOR_SUFFIX, prompt)
|
|
self.assertIn(fc.STUDIES[name][1], prompt)
|
|
|
|
def test_unknown_study_raises(self):
|
|
with self.assertRaises(KeyError):
|
|
fc.build_prompt("no-such-study")
|
|
|
|
|
|
class NextFreePathTest(unittest.TestCase):
|
|
def test_uses_the_plain_name_when_free(self):
|
|
import tempfile
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
self.assertEqual(os.path.basename(fc.next_free_path("x", tmp)), "x.png")
|
|
|
|
def test_never_overwrites(self):
|
|
import tempfile
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
open(os.path.join(tmp, "x.png"), "w").close()
|
|
self.assertEqual(os.path.basename(fc.next_free_path("x", tmp)), "x-v2.png")
|
|
open(os.path.join(tmp, "x-v2.png"), "w").close()
|
|
self.assertEqual(os.path.basename(fc.next_free_path("x", tmp)), "x-v3.png")
|
|
|
|
|
|
class DecodeImageTest(unittest.TestCase):
|
|
PAYLOAD_BYTES = b"\x89PNG\r\n\x1a\nfake"
|
|
|
|
def _data_url(self):
|
|
return "data:image/png;base64," + base64.b64encode(self.PAYLOAD_BYTES).decode()
|
|
|
|
def test_reads_message_images(self):
|
|
payload = {"choices": [{"message": {
|
|
"images": [{"image_url": {"url": self._data_url()}}]}}]}
|
|
self.assertEqual(fc.decode_image(payload), self.PAYLOAD_BYTES)
|
|
|
|
def test_reads_a_data_url_embedded_in_content(self):
|
|
payload = {"choices": [{"message": {"content": f"here: {self._data_url()}"}}]}
|
|
self.assertEqual(fc.decode_image(payload), self.PAYLOAD_BYTES)
|
|
|
|
def test_refusal_raises_with_the_text(self):
|
|
payload = {"choices": [{"message": {"content": "I cannot do that."}}]}
|
|
with self.assertRaises(SystemExit):
|
|
fc.decode_image(payload)
|
|
|
|
|
|
class DataUrlTest(unittest.TestCase):
|
|
def test_encodes_a_jpeg(self):
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as handle:
|
|
handle.write(b"jpegbytes")
|
|
path = handle.name
|
|
try:
|
|
url = fc.data_url(path)
|
|
self.assertTrue(url.startswith("data:image/jpeg;base64,"))
|
|
finally:
|
|
os.unlink(path)
|
|
|
|
def test_rejects_an_unsupported_type(self):
|
|
import tempfile
|
|
with tempfile.NamedTemporaryFile(suffix=".gif", delete=False) as handle:
|
|
path = handle.name
|
|
try:
|
|
with self.assertRaises(SystemExit):
|
|
fc.data_url(path)
|
|
finally:
|
|
os.unlink(path)
|
|
|
|
|
|
class SidecarShapeTest(unittest.TestCase):
|
|
def test_studies_and_outputs_are_local_only(self):
|
|
# The output directory lives under the gitignored Art/reference tree, so
|
|
# neither the images nor the sidecars can be committed.
|
|
self.assertIn(os.path.join("Art", "reference"), fc.OUT_DIR)
|
|
self.assertIn("gitignored", fc.__doc__)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|