unitytk 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitytk-0.2.1/unitytk.egg-info → unitytk-0.3.0}/PKG-INFO +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_articulated_rig_runtime.py +2 -4
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_asset_delivery.py +26 -8
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_audio_events_integration.py +168 -17
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_base_test.py +78 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_blender_render_effects_e2e.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_blender_shadow_plane_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_blender_shot_metadata_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_clip_name_contract_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_controller_runtime_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_deploy_templates.py +99 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_embedded_dependencies_integration.py +184 -2
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_emissive_groups_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_fade_window_preservation.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_keyed_visibility.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_lightmap_metadata_integration.py +852 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_maya_surface_materials_integration.py +120 -62
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_multi_section_audio_integration.py +2 -2
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_render_effects_e2e.py +2 -2
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_render_opacity_controller.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_render_opacity_e2e.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_render_opacity_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_shadow_plane_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_shadow_plane_runtime_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_shared_material_standalone.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_shot_metadata_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_shots_audio_sidebyside_integration.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_standalone_enduser.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_temp_projects.py +26 -3
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/__init__.py +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/asset_delivery.py +1 -2
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulationRecord.g.cs +5 -2
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/AudioEvent/AudioEventController.cs +95 -21
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupsLit.shader +36 -1
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableCubeUV.hlsl +119 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableCubeUV.hlsl.meta +7 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableLit.shader +373 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableLit.shader.meta +9 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverablePmrem.cs +307 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverablePmrem.cs.meta +11 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableShading.cs +460 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableShading.cs.meta +11 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMap.shader +65 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMap.shader.meta +9 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMapFeature.cs +53 -0
- unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMapFeature.cs.meta +11 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/LightmapMetadata/LightmapMetadataController.cs +496 -48
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/M3trik.Unitytk.asmdef +8 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/MayaSurface/MayaSurfaceMaterialImporter.cs +69 -16
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/README.md +69 -27
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneController.cs +19 -16
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/UnitytkSettings.cs +138 -21
- {unitytk-0.2.1 → unitytk-0.3.0/unitytk.egg-info}/PKG-INFO +1 -1
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk.egg-info/SOURCES.txt +12 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/LICENSE +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/MANIFEST.in +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/docs/README.md +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/pyproject.toml +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/setup.cfg +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_bool_to_fade_curve.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_launcher.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_run_tests.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/test/test_scene_builder.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/launcher.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/scene_builder.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/template_deployer.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigController.cs +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigController.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigPointerGrab.cs +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigPointerGrab.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulationRecord.g.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRGrab.cs +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRGrab.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRSetup.cs +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRSetup.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/M3trik.Unitytk.XR.asmdef +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/M3trik.Unitytk.XR.asmdef.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/AudioEvent/AudioEventController.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/AudioEvent.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupController.cs +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupController.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroups.hlsl +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroups.hlsl.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupsLit.shader.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/LightmapMetadata/LightmapMetadataController.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/LightmapMetadata.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/M3trik.Unitytk.asmdef.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/MayaSurface/MayaSurfaceMaterialImporter.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/MayaSurface.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/README.md.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/RenderEffects/RenderEffectsController.cs +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/RenderEffects/RenderEffectsController.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/RenderEffects.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlane.shader +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlane.shader.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneController.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneHorizon.hlsl +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneHorizon.hlsl.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShadowPlane.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShotMetadata/ShotMetadataController.cs +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShotMetadata/ShotMetadataController.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/ShotMetadata.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/UnitytkSettings.cs.meta +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk/templates/__init__.py +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk.egg-info/dependency_links.txt +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk.egg-info/requires.txt +0 -0
- {unitytk-0.2.1 → unitytk-0.3.0}/unitytk.egg-info/top_level.txt +0 -0
|
@@ -1671,7 +1671,7 @@ def _run_unity(test, temp_dir, execute_method, prepare, timeout=600):
|
|
|
1671
1671
|
verifier; returns the parsed results (or skips on the license wall)."""
|
|
1672
1672
|
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
1673
1673
|
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
1674
|
-
project_path =
|
|
1674
|
+
project_path = TempProjects.project(temp_dir)
|
|
1675
1675
|
if not launcher.create_project(project_path, batch_mode=True):
|
|
1676
1676
|
test.skipTest(UnityEnv.project_creation_reason())
|
|
1677
1677
|
results_path = os.path.join(temp_dir, "unity_results.json")
|
|
@@ -1865,9 +1865,7 @@ class TestArticulatedRigRuntime(unittest.TestCase):
|
|
|
1865
1865
|
for name in _bundle_assets()
|
|
1866
1866
|
}
|
|
1867
1867
|
self.assertEqual(got, expected, "Unity named a deployed asset differently")
|
|
1868
|
-
package = TemplateDeployer.package_dir(
|
|
1869
|
-
os.path.join(self.temp_dir, "UnityProject")
|
|
1870
|
-
)
|
|
1868
|
+
package = TemplateDeployer.package_dir(TempProjects.project(self.temp_dir))
|
|
1871
1869
|
rewritten = []
|
|
1872
1870
|
for name in _bundle_assets():
|
|
1873
1871
|
path = os.path.join(package, *name.split("/")) + ".meta"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# !/usr/bin/python
|
|
2
2
|
# coding=utf-8
|
|
3
3
|
"""Tests for unitytk.CopyToAssetsDeliverer -- copy-to-Assets hand-off Strategy (no DCC)."""
|
|
4
|
+
|
|
4
5
|
import json
|
|
5
6
|
import os
|
|
6
7
|
import struct
|
|
@@ -20,6 +21,7 @@ class _Logger:
|
|
|
20
21
|
self.errors = []
|
|
21
22
|
self.warnings = []
|
|
22
23
|
self.infos = []
|
|
24
|
+
self.debugs = []
|
|
23
25
|
|
|
24
26
|
def error(self, m, *args):
|
|
25
27
|
self.errors.append(m % args if args else m)
|
|
@@ -30,6 +32,9 @@ class _Logger:
|
|
|
30
32
|
def info(self, m, *args):
|
|
31
33
|
self.infos.append(m % args if args else m)
|
|
32
34
|
|
|
35
|
+
def debug(self, m, *args):
|
|
36
|
+
self.debugs.append(m % args if args else m)
|
|
37
|
+
|
|
33
38
|
|
|
34
39
|
def _carrier_fbx(path: str, channels) -> str:
|
|
35
40
|
"""A binary FBX (7700 layout) whose ``data_export`` carrier holds
|
|
@@ -108,7 +113,9 @@ def _payload(fbx, default_name="Export"):
|
|
|
108
113
|
|
|
109
114
|
class SanitizeTest(unittest.TestCase):
|
|
110
115
|
def test_strips_invalid_and_paths(self):
|
|
111
|
-
self.assertEqual(
|
|
116
|
+
self.assertEqual(
|
|
117
|
+
CopyToAssetsDeliverer.sanitize_asset_name("my/model:v2"), "my_model_v2"
|
|
118
|
+
)
|
|
112
119
|
|
|
113
120
|
def test_collapses_and_trims(self):
|
|
114
121
|
self.assertEqual(CopyToAssetsDeliverer.sanitize_asset_name(" a b "), "a b")
|
|
@@ -193,7 +200,9 @@ class DeliveryTest(unittest.TestCase):
|
|
|
193
200
|
)
|
|
194
201
|
|
|
195
202
|
def test_explicit_asset_name_overrides_and_sanitizes(self):
|
|
196
|
-
_bridge, result = self._deliver(
|
|
203
|
+
_bridge, result = self._deliver(
|
|
204
|
+
{"ASSET_NAME": "Hero/Mesh:01"}, default_name="Cube"
|
|
205
|
+
)
|
|
197
206
|
self.assertEqual(os.path.basename(result["asset"]), "Hero_Mesh_01.fbx")
|
|
198
207
|
|
|
199
208
|
def test_launch_mode_open_invokes_windowed_launch(self):
|
|
@@ -282,6 +291,7 @@ class DeliveryTest(unittest.TestCase):
|
|
|
282
291
|
orig = ad.CopyToAssetsDeliverer._launch_editor
|
|
283
292
|
# Patch UnityLauncher inside the real hook (deferred import target).
|
|
284
293
|
import unitytk.launcher as launcher_mod
|
|
294
|
+
|
|
285
295
|
real = launcher_mod.UnityLauncher
|
|
286
296
|
launcher_mod.UnityLauncher = _FakeLauncher
|
|
287
297
|
try:
|
|
@@ -344,15 +354,14 @@ class DeliveryTest(unittest.TestCase):
|
|
|
344
354
|
)
|
|
345
355
|
# Genuinely under Assets/ (no drive escape).
|
|
346
356
|
assets = os.path.realpath(os.path.join(self.project, "Assets"))
|
|
347
|
-
self.assertTrue(
|
|
348
|
-
os.path.realpath(dest).startswith(assets), f"raw={raw!r}"
|
|
349
|
-
)
|
|
357
|
+
self.assertTrue(os.path.realpath(dest).startswith(assets), f"raw={raw!r}")
|
|
350
358
|
|
|
351
359
|
def test_explicit_subdir_reducing_to_nothing_drops_into_assets_root(self):
|
|
352
360
|
for raw in (".", "/", "../.."):
|
|
353
361
|
_bridge, result = self._deliver({"ASSETS_SUBDIR": raw}, default_name="Cube")
|
|
354
362
|
self.assertEqual(
|
|
355
|
-
result["asset"],
|
|
363
|
+
result["asset"],
|
|
364
|
+
os.path.join(self.project, "Assets", "Cube.fbx"),
|
|
356
365
|
f"raw={raw!r}",
|
|
357
366
|
)
|
|
358
367
|
self.assertEqual(result["subdir"], "")
|
|
@@ -388,7 +397,11 @@ class SelfContainedDeliveryTest(unittest.TestCase):
|
|
|
388
397
|
"lightmap_metadata": {
|
|
389
398
|
"version": 1,
|
|
390
399
|
"objects": [{"name": "Crate", "map": "Crate_Lightmap.exr", "uvIndex": 1}],
|
|
391
|
-
"probe": {
|
|
400
|
+
"probe": {
|
|
401
|
+
"map": "Room_Probe.exr",
|
|
402
|
+
"position": [0, 0, 0],
|
|
403
|
+
"unit_scale": 0.01,
|
|
404
|
+
},
|
|
392
405
|
},
|
|
393
406
|
"shadow_metadata": {
|
|
394
407
|
"version": 2,
|
|
@@ -411,7 +424,12 @@ class SelfContainedDeliveryTest(unittest.TestCase):
|
|
|
411
424
|
self.maps = os.path.join(self.tmp, "sourceimages")
|
|
412
425
|
os.makedirs(self.maps)
|
|
413
426
|
self.bytes = {}
|
|
414
|
-
for name in (
|
|
427
|
+
for name in (
|
|
428
|
+
"Crate_Lightmap.exr",
|
|
429
|
+
"Room_Probe.exr",
|
|
430
|
+
"Crate_shadow.png",
|
|
431
|
+
"Crate_horizon.png",
|
|
432
|
+
):
|
|
415
433
|
self.bytes[name] = name.encode() * 8
|
|
416
434
|
with open(os.path.join(self.maps, name), "wb") as fh:
|
|
417
435
|
fh.write(self.bytes[name])
|
|
@@ -24,6 +24,7 @@ Requires:
|
|
|
24
24
|
- Maya 2025 installed (mayapy.exe)
|
|
25
25
|
- Unity Hub with at least one editor installed
|
|
26
26
|
"""
|
|
27
|
+
|
|
27
28
|
import os
|
|
28
29
|
import json
|
|
29
30
|
import shutil
|
|
@@ -34,7 +35,15 @@ import logging
|
|
|
34
35
|
logger = logging.getLogger(__name__)
|
|
35
36
|
|
|
36
37
|
import base_test # noqa: F401 — sys.path bootstrap for the sibling repos
|
|
37
|
-
from base_test import
|
|
38
|
+
from base_test import (
|
|
39
|
+
BlenderEnv,
|
|
40
|
+
CarrierFbx,
|
|
41
|
+
EnvSkip,
|
|
42
|
+
MayaEnv,
|
|
43
|
+
TempProjects,
|
|
44
|
+
UnityEnv,
|
|
45
|
+
UserPropsProbe,
|
|
46
|
+
)
|
|
38
47
|
|
|
39
48
|
scripts_dir = base_test.SCRIPTS_ROOT
|
|
40
49
|
|
|
@@ -471,9 +480,7 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
471
480
|
events,
|
|
472
481
|
{("", 9, "footstep"), ("", 29, "jump"), ("", 44, "land")},
|
|
473
482
|
)
|
|
474
|
-
self.assertEqual(
|
|
475
|
-
sorted(r["tracks"]), ["footstep", "jump", "land"]
|
|
476
|
-
)
|
|
483
|
+
self.assertEqual(sorted(r["tracks"]), ["footstep", "jump", "land"])
|
|
477
484
|
|
|
478
485
|
# ------------------------------------------------------------------
|
|
479
486
|
# Step 2: Unity import and readback
|
|
@@ -492,7 +499,7 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
492
499
|
# as the sibling integration tests.
|
|
493
500
|
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
494
501
|
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
495
|
-
project_path =
|
|
502
|
+
project_path = TempProjects.project(self.temp_dir)
|
|
496
503
|
|
|
497
504
|
logger.info(f"Creating Unity project: {project_path}")
|
|
498
505
|
if not launcher.create_project(project_path, batch_mode=True):
|
|
@@ -552,17 +559,26 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
552
559
|
full_log = f.read()
|
|
553
560
|
# Surface AudioEvent / error / warning lines for analysis
|
|
554
561
|
relevant = [
|
|
555
|
-
ln
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
562
|
+
ln
|
|
563
|
+
for ln in full_log.splitlines()
|
|
564
|
+
if any(
|
|
565
|
+
s in ln
|
|
566
|
+
for s in (
|
|
567
|
+
"AudioEvent",
|
|
568
|
+
"ERROR",
|
|
569
|
+
"Error",
|
|
570
|
+
"Warning",
|
|
571
|
+
"NullReferenceException",
|
|
572
|
+
"Failed",
|
|
573
|
+
)
|
|
574
|
+
)
|
|
560
575
|
]
|
|
561
576
|
self.__class__.unity_log_relevant = relevant
|
|
562
577
|
if relevant:
|
|
563
578
|
logger.info(
|
|
564
579
|
"Unity log relevant lines (%d):\n %s",
|
|
565
|
-
len(relevant),
|
|
580
|
+
len(relevant),
|
|
581
|
+
"\n ".join(relevant[-40:]),
|
|
566
582
|
)
|
|
567
583
|
else:
|
|
568
584
|
logger.info("Unity log: no AudioEvent/Warning/Error lines.")
|
|
@@ -571,8 +587,7 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
571
587
|
with open(results_path) as f:
|
|
572
588
|
self.__class__.unity_results = json.load(f)
|
|
573
589
|
|
|
574
|
-
postproc_path = os.path.join(
|
|
575
|
-
self.temp_dir, UserPropsProbe.RESULTS_FILENAME)
|
|
590
|
+
postproc_path = os.path.join(self.temp_dir, UserPropsProbe.RESULTS_FILENAME)
|
|
576
591
|
if os.path.exists(postproc_path):
|
|
577
592
|
with open(postproc_path) as f:
|
|
578
593
|
self.__class__.postproc = json.load(f)
|
|
@@ -608,7 +623,8 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
608
623
|
f"in postprocessor results: {list(postproc)}",
|
|
609
624
|
)
|
|
610
625
|
self.assertIn(
|
|
611
|
-
"audio_manifest",
|
|
626
|
+
"audio_manifest",
|
|
627
|
+
props,
|
|
612
628
|
f"audio_manifest user-property missing on {name}; saw: {list(props)}",
|
|
613
629
|
)
|
|
614
630
|
manifest = props["audio_manifest"]
|
|
@@ -655,7 +671,8 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
655
671
|
|
|
656
672
|
# (d) AnimationEvents injected on the imported clip
|
|
657
673
|
self.assertGreaterEqual(
|
|
658
|
-
unity.get("clip_count", 0),
|
|
674
|
+
unity.get("clip_count", 0),
|
|
675
|
+
1,
|
|
659
676
|
"Expected at least one AnimationClip in the imported FBX",
|
|
660
677
|
)
|
|
661
678
|
events = unity.get("animation_events", [])
|
|
@@ -676,8 +693,8 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
676
693
|
# In Unity-frame terms the events are at clip-relative frames 9,
|
|
677
694
|
# 29, 44 — which is what AnimationEvent.time * frameRate yields.
|
|
678
695
|
self.assertEqual(events_by_name.get("footstep"), 9)
|
|
679
|
-
self.assertEqual(events_by_name.get("jump"),
|
|
680
|
-
self.assertEqual(events_by_name.get("land"),
|
|
696
|
+
self.assertEqual(events_by_name.get("jump"), 29)
|
|
697
|
+
self.assertEqual(events_by_name.get("land"), 44)
|
|
681
698
|
|
|
682
699
|
# (e) Reimport idempotency. Two probes injected before the
|
|
683
700
|
# reimport tell us about both correctness and Unity behavior:
|
|
@@ -721,6 +738,140 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
721
738
|
)
|
|
722
739
|
|
|
723
740
|
|
|
741
|
+
# ======================================================================
|
|
742
|
+
# Carrier precedence: one audio_manifest per model, the scene's own first
|
|
743
|
+
# ======================================================================
|
|
744
|
+
|
|
745
|
+
CARRIER_VERIFIER_CS = r"""
|
|
746
|
+
using UnityEngine;
|
|
747
|
+
using UnityEditor;
|
|
748
|
+
using System;
|
|
749
|
+
using System.IO;
|
|
750
|
+
using System.Linq;
|
|
751
|
+
using System.Collections.Generic;
|
|
752
|
+
|
|
753
|
+
public class AudioCarrierVerifier
|
|
754
|
+
{
|
|
755
|
+
static string Q(string s) { return "\"" + (s ?? "").Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; }
|
|
756
|
+
|
|
757
|
+
public static void Verify()
|
|
758
|
+
{
|
|
759
|
+
var o = new List<string>();
|
|
760
|
+
try
|
|
761
|
+
{
|
|
762
|
+
// Again, now the clips are in the database: the startup import
|
|
763
|
+
// may have met the model before the audio.
|
|
764
|
+
AssetDatabase.ImportAsset("__FBX__",
|
|
765
|
+
ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
|
|
766
|
+
AssetDatabase.Refresh();
|
|
767
|
+
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>("__FBX__");
|
|
768
|
+
o.Add("\"prefab\":" + (prefab != null ? "true" : "false"));
|
|
769
|
+
var nodes = prefab != null
|
|
770
|
+
? prefab.GetComponentsInChildren<Transform>(true).Select(t => Q(t.name))
|
|
771
|
+
: Enumerable.Empty<string>();
|
|
772
|
+
o.Add("\"nodes\":[" + string.Join(",", nodes) + "]");
|
|
773
|
+
var ctrl = prefab != null ? prefab.GetComponent<AudioEventController>() : null;
|
|
774
|
+
o.Add("\"controller\":" + (ctrl != null ? "true" : "false"));
|
|
775
|
+
var clips = ctrl != null ? ctrl.clips : new List<AudioClip>();
|
|
776
|
+
o.Add("\"clips\":[" + string.Join(",", clips.Select(c => Q(c != null ? c.name : ""))) + "]");
|
|
777
|
+
}
|
|
778
|
+
catch (Exception e) { o.Add("\"error\":" + Q(e.GetType().Name + ": " + e.Message)); }
|
|
779
|
+
File.WriteAllText(@"__RESULTS__", "{" + string.Join(",\n", o) + "}");
|
|
780
|
+
EditorApplication.Exit(0);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
class TestAudioCarrierPrecedence(unittest.TestCase):
|
|
787
|
+
"""An FBX carrying ``audio_manifest`` on several carriers plays the
|
|
788
|
+
scene's own only.
|
|
789
|
+
|
|
790
|
+
An assembly ships a stray ``data_export1`` (and a module's
|
|
791
|
+
``NS:data_export``) beside the scene's own, each holding the events as
|
|
792
|
+
last exported alone -- measured on an assembly, 2026-10-13. The scene's
|
|
793
|
+
own record is re-derived from the whole merged scene, so every reader
|
|
794
|
+
takes ONE carrier per channel, the scene's own first: the other
|
|
795
|
+
importers (``CarrierImport.Collect``), the GLB applier, and pythontk's
|
|
796
|
+
``FbxMedia.audio_events``. The audio importer concatenated every
|
|
797
|
+
carrier's events instead, so the stray's stale event was bound and fired.
|
|
798
|
+
|
|
799
|
+
The fixture is a real FBX with both carriers (``base_test.CarrierFbx``,
|
|
800
|
+
written by a fresh Blender); pythontk's ``FbxMedia.audio_events`` pins the
|
|
801
|
+
same rule on the same shape (``test_fbx_media``).
|
|
802
|
+
"""
|
|
803
|
+
|
|
804
|
+
FBX = "Assets/Delivered/assembly.fbx"
|
|
805
|
+
|
|
806
|
+
@classmethod
|
|
807
|
+
def setUpClass(cls):
|
|
808
|
+
cls.temp_dir = TempProjects.allocate("carrier_precedence")
|
|
809
|
+
|
|
810
|
+
@BlenderEnv.skip_unless_available()
|
|
811
|
+
@EnvSkip.unless(_unity_available(), "no Unity editor found")
|
|
812
|
+
def test_the_scenes_own_carrier_wins(self):
|
|
813
|
+
own = {"version": 2, "events": [{"clip": "", "frame": 0, "name": "footstep"}]}
|
|
814
|
+
stray = {"version": 2, "events": [{"clip": "", "frame": 3, "name": "door"}]}
|
|
815
|
+
fixture = CarrierFbx.build(
|
|
816
|
+
os.path.join(self.temp_dir, "assembly.fbx"),
|
|
817
|
+
{
|
|
818
|
+
"data_export": {"audio_manifest": own},
|
|
819
|
+
"data_export1": {"audio_manifest": stray},
|
|
820
|
+
},
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
launcher = UnityLauncher()
|
|
824
|
+
project = TempProjects.project(self.temp_dir)
|
|
825
|
+
if not launcher.create_project(project, batch_mode=True):
|
|
826
|
+
self.skipTest(UnityEnv.project_creation_reason())
|
|
827
|
+
delivered = os.path.join(project, "Assets", "Delivered")
|
|
828
|
+
os.makedirs(delivered, exist_ok=True)
|
|
829
|
+
shutil.copy2(fixture, os.path.join(delivered, "assembly.fbx"))
|
|
830
|
+
# Both tracks resolve, so only the carrier rule decides what binds.
|
|
831
|
+
for name in ("footstep", "door"):
|
|
832
|
+
CarrierFbx.wav(
|
|
833
|
+
os.path.join(project, "Assets", "Audio", f"{name}.wav"), name.encode()
|
|
834
|
+
)
|
|
835
|
+
TemplateDeployer.deploy_package(project, components=["audio_event"])
|
|
836
|
+
results = os.path.join(self.temp_dir, "unity_results.json")
|
|
837
|
+
editor_dir = os.path.join(project, "Assets", "Editor")
|
|
838
|
+
os.makedirs(editor_dir, exist_ok=True)
|
|
839
|
+
with open(
|
|
840
|
+
os.path.join(editor_dir, "AudioCarrierVerifier.cs"), "w", encoding="utf-8"
|
|
841
|
+
) as fh:
|
|
842
|
+
fh.write(
|
|
843
|
+
CARRIER_VERIFIER_CS.replace("__FBX__", self.FBX).replace(
|
|
844
|
+
"__RESULTS__", results
|
|
845
|
+
)
|
|
846
|
+
)
|
|
847
|
+
log = os.path.join(self.temp_dir, "unity_log.txt")
|
|
848
|
+
launcher.project_path = project
|
|
849
|
+
proc = launcher.launch_editor(
|
|
850
|
+
batch_mode=True,
|
|
851
|
+
execute_method="AudioCarrierVerifier.Verify",
|
|
852
|
+
log_file=log,
|
|
853
|
+
extra_args=["-quit"],
|
|
854
|
+
detached=False,
|
|
855
|
+
)
|
|
856
|
+
returncode = None
|
|
857
|
+
if proc:
|
|
858
|
+
try:
|
|
859
|
+
returncode = proc.wait(timeout=900)
|
|
860
|
+
except subprocess.TimeoutExpired:
|
|
861
|
+
proc.kill()
|
|
862
|
+
raise TimeoutError("Unity timed out")
|
|
863
|
+
UnityEnv.require_results(self, results, returncode, log)
|
|
864
|
+
with open(results) as fh:
|
|
865
|
+
unity = json.load(fh)
|
|
866
|
+
logger.info(f"Unity results:\n{json.dumps(unity, indent=2)}")
|
|
867
|
+
self.assertNotIn("error", unity, unity.get("error"))
|
|
868
|
+
self.assertTrue(unity["prefab"], unity)
|
|
869
|
+
# Premise: both carriers reached the import.
|
|
870
|
+
self.assertTrue({"data_export", "data_export1"} <= set(unity["nodes"]), unity)
|
|
871
|
+
self.assertTrue(unity["controller"], unity)
|
|
872
|
+
self.assertEqual(unity["clips"], ["footstep"], unity)
|
|
873
|
+
|
|
874
|
+
|
|
724
875
|
if __name__ == "__main__":
|
|
725
876
|
logging.basicConfig(level=logging.INFO)
|
|
726
877
|
unittest.main()
|
|
@@ -42,6 +42,7 @@ from base_test import (
|
|
|
42
42
|
MayaEnv,
|
|
43
43
|
SuiteOptions,
|
|
44
44
|
TemplateSources,
|
|
45
|
+
TempProjects,
|
|
45
46
|
TestAssets,
|
|
46
47
|
UnityEnv,
|
|
47
48
|
UserPropsProbe,
|
|
@@ -486,6 +487,83 @@ class UnityEnvTest(unittest.TestCase):
|
|
|
486
487
|
self.assertEqual(result.errors, [])
|
|
487
488
|
self.assertEqual(result.testsRun, 3)
|
|
488
489
|
|
|
490
|
+
def test_make_urp_fails_fast_on_a_project_too_deep_for_the_editor(self):
|
|
491
|
+
"""The editor is not long-path aware: past MAX_PATH its package imports
|
|
492
|
+
fail, URP's Autodesk Interactive graph resolves to nothing, and the
|
|
493
|
+
material suite read the fallback pick as a product bug (2026-10-19)."""
|
|
494
|
+
deep = "C:\\" + "d" * (260 - UnityEnv.URP_IMPORT_DEPTH - 3) # one past the limit
|
|
495
|
+
|
|
496
|
+
class Case(unittest.TestCase):
|
|
497
|
+
def test_deep(self):
|
|
498
|
+
UnityEnv.make_urp(self, None, deep, "unused.log")
|
|
499
|
+
|
|
500
|
+
result = _run_case(Case)
|
|
501
|
+
self.assertEqual(result.errors, [])
|
|
502
|
+
self.assertEqual(len(result.failures), 1)
|
|
503
|
+
self.assertIn("UNITYTK_TEMP_PROJECTS", result.failures[0][1])
|
|
504
|
+
|
|
505
|
+
def test_the_depth_gate_covers_every_shader_graph_urp_ships(self):
|
|
506
|
+
"""The gate measured the Autodesk Interactive graph; URP's Arnold
|
|
507
|
+
transparent graph sits 25 characters deeper, so a 107-character project
|
|
508
|
+
passed it, that graph failed to import, URP's Arnold mapper left
|
|
509
|
+
aiStandardSurface on plain Lit and the material suite failed on every
|
|
510
|
+
run from the in-repo base (2026-10-19). Measured against each installed
|
|
511
|
+
editor's bundled URP, not a list of the graphs the suites use today."""
|
|
512
|
+
from unitytk import UnityFinder
|
|
513
|
+
|
|
514
|
+
packages = [
|
|
515
|
+
p
|
|
516
|
+
for p in map(UnityEnv.bundled_urp, UnityFinder.find_editors().values())
|
|
517
|
+
if os.path.isdir(p)
|
|
518
|
+
]
|
|
519
|
+
if not packages:
|
|
520
|
+
self.skipTest(EnvSkip.reason(UnityEnv.NO_BUNDLED_URP))
|
|
521
|
+
deepest = UnityEnv.URP_IMPORT_DEPTH - len(UnityEnv.URP_PACKAGE_CACHE)
|
|
522
|
+
for package in packages:
|
|
523
|
+
for dirpath, dirnames, filenames in os.walk(package):
|
|
524
|
+
# A folder ending "~" (samples, docs) or hidden is never imported.
|
|
525
|
+
dirnames[:] = [d for d in dirnames if not d.endswith("~") and not d.startswith(".")]
|
|
526
|
+
for name in filenames:
|
|
527
|
+
if name.endswith((".shadergraph", ".shadersubgraph")):
|
|
528
|
+
below = os.path.join(dirpath, name)[len(package):]
|
|
529
|
+
self.assertLessEqual(len(below), deepest, below)
|
|
530
|
+
|
|
531
|
+
@unittest.skipUnless(
|
|
532
|
+
os.name == "nt", "MAX_PATH is the Windows editor's limit; no Unity runs here"
|
|
533
|
+
)
|
|
534
|
+
def test_every_suite_fits_a_urp_project_under_the_default_base(self):
|
|
535
|
+
"""A plain ``run_tests.py --all`` builds its projects under the in-repo
|
|
536
|
+
base, where ``<run>\\urp\\UnityProject`` put the material suite's URP
|
|
537
|
+
project at 107 characters: past the gate, so every URP suite failed at
|
|
538
|
+
once (2026-10-19). Every suite's prefix, not only today's URP suites:
|
|
539
|
+
any of them may call ``make_urp`` on its project, or add a ``"urp"`` one."""
|
|
540
|
+
prefixes = set()
|
|
541
|
+
# The allocator's own tests allocate into a sandboxed base, retired prefixes included.
|
|
542
|
+
for path in _source_files(".py", exclude=("test_temp_projects.py", "test_run_tests.py")):
|
|
543
|
+
with open(path, encoding="utf-8", errors="replace") as fh:
|
|
544
|
+
tree = ast.parse(fh.read(), filename=path)
|
|
545
|
+
for node in ast.walk(tree):
|
|
546
|
+
if (
|
|
547
|
+
isinstance(node, ast.Call)
|
|
548
|
+
and isinstance(node.func, ast.Attribute)
|
|
549
|
+
and node.func.attr == "allocate"
|
|
550
|
+
and getattr(node.func.value, "id", None) == "TempProjects"
|
|
551
|
+
and node.args
|
|
552
|
+
and isinstance(node.args[0], ast.Constant)
|
|
553
|
+
and isinstance(node.args[0].value, str)
|
|
554
|
+
):
|
|
555
|
+
prefixes.add(node.args[0].value)
|
|
556
|
+
self.assertGreater(len(prefixes), 10, "the suites' allocations were not found")
|
|
557
|
+
# TempArtifacts' tag: the clock (16 hex digits), the pid in hex (6: Linux's
|
|
558
|
+
# pid_max ceiling; Windows ids run below it) and 4 random digits.
|
|
559
|
+
tag = "f" * (16 + 6 + 4)
|
|
560
|
+
budget = 259 - UnityEnv.URP_IMPORT_DEPTH
|
|
561
|
+
for prefix in sorted(prefixes):
|
|
562
|
+
run = os.path.join(TempProjects.DEFAULT_BASE, f"{prefix}_{tag}")
|
|
563
|
+
for project in (TempProjects.project(run), TempProjects.project(run, "urp")):
|
|
564
|
+
with self.subTest(project=project):
|
|
565
|
+
self.assertLessEqual(len(project), budget, project)
|
|
566
|
+
|
|
489
567
|
|
|
490
568
|
class SuiteOptionsTest(unittest.TestCase):
|
|
491
569
|
"""The opt-in switches are named once, neutrally."""
|
|
@@ -351,7 +351,7 @@ class TestBlenderRenderEffectsE2E(unittest.TestCase):
|
|
|
351
351
|
self.skipTest("Blender results not available -- test_01 must run first")
|
|
352
352
|
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
353
353
|
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
354
|
-
project_path =
|
|
354
|
+
project_path = TempProjects.project(self.temp_dir)
|
|
355
355
|
if not launcher.create_project(project_path, batch_mode=True):
|
|
356
356
|
self.skipTest(UnityEnv.project_creation_reason())
|
|
357
357
|
assets_path = os.path.join(project_path, "Assets")
|
|
@@ -606,7 +606,7 @@ class TestBlenderShadowPlaneIntegration(unittest.TestCase):
|
|
|
606
606
|
blender = self._blender()
|
|
607
607
|
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
608
608
|
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
609
|
-
project_path =
|
|
609
|
+
project_path = TempProjects.project(self.temp_dir)
|
|
610
610
|
if not launcher.create_project(project_path, batch_mode=True):
|
|
611
611
|
self.skipTest(UnityEnv.project_creation_reason())
|
|
612
612
|
|
|
@@ -232,7 +232,7 @@ class TestBlenderShotMetadataIntegration(unittest.TestCase):
|
|
|
232
232
|
self.skipTest("Blender results not available -- test_01 must run first")
|
|
233
233
|
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
234
234
|
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
235
|
-
project_path =
|
|
235
|
+
project_path = TempProjects.project(self.temp_dir)
|
|
236
236
|
if not launcher.create_project(project_path, batch_mode=True):
|
|
237
237
|
self.skipTest(UnityEnv.project_creation_reason())
|
|
238
238
|
|
|
@@ -264,7 +264,7 @@ class TestClipNameContract(unittest.TestCase):
|
|
|
264
264
|
|
|
265
265
|
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
266
266
|
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
267
|
-
project_path =
|
|
267
|
+
project_path = TempProjects.project(self.temp_dir)
|
|
268
268
|
if not launcher.create_project(project_path, batch_mode=True):
|
|
269
269
|
self.skipTest(UnityEnv.project_creation_reason())
|
|
270
270
|
|
|
@@ -181,7 +181,7 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
|
|
|
181
181
|
"""Drive both controllers in a real Unity; capture the readback."""
|
|
182
182
|
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
183
183
|
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
184
|
-
project_path =
|
|
184
|
+
project_path = TempProjects.project(self.temp_dir)
|
|
185
185
|
if not launcher.create_project(project_path, batch_mode=True):
|
|
186
186
|
self.skipTest(UnityEnv.project_creation_reason())
|
|
187
187
|
|
|
@@ -127,6 +127,12 @@ class DeployTemplateTest(unittest.TestCase):
|
|
|
127
127
|
"EmissiveGroup/EmissiveGroupController.cs",
|
|
128
128
|
"EmissiveGroup/EmissiveGroups.hlsl",
|
|
129
129
|
"EmissiveGroup/EmissiveGroupsLit.shader",
|
|
130
|
+
"LightmapMetadata/DeliverableCubeUV.hlsl",
|
|
131
|
+
"LightmapMetadata/DeliverableLit.shader",
|
|
132
|
+
"LightmapMetadata/DeliverablePmrem.cs",
|
|
133
|
+
"LightmapMetadata/DeliverableShading.cs",
|
|
134
|
+
"LightmapMetadata/DeliverableToneMap.shader",
|
|
135
|
+
"LightmapMetadata/DeliverableToneMapFeature.cs",
|
|
130
136
|
"LightmapMetadata/LightmapMetadataController.cs",
|
|
131
137
|
"M3trik.Unitytk.asmdef",
|
|
132
138
|
"MayaSurface/MayaSurfaceMaterialImporter.cs",
|
|
@@ -182,6 +188,63 @@ class DeployTemplateTest(unittest.TestCase):
|
|
|
182
188
|
f"{include} is not beside {shader}",
|
|
183
189
|
)
|
|
184
190
|
|
|
191
|
+
@staticmethod
|
|
192
|
+
def _shader_pass(text, name):
|
|
193
|
+
"""The ``Pass { ... }`` block of shader *text* named *name*, or None."""
|
|
194
|
+
at = text.find(f'Name "{name}"')
|
|
195
|
+
if at < 0:
|
|
196
|
+
return None
|
|
197
|
+
start = [m.start() for m in re.finditer(r"\bPass\s*\{", text[:at])][-1]
|
|
198
|
+
depth = 0
|
|
199
|
+
for i in range(text.index("{", start), len(text)):
|
|
200
|
+
depth += {"{": 1, "}": -1}.get(text[i], 0)
|
|
201
|
+
if depth == 0:
|
|
202
|
+
return text[start : i + 1]
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
def test_forward_only_urp_shaders_draw_in_both_prepasses(self):
|
|
206
|
+
"""A URP shader drawn by a ``UniversalForwardOnly`` pass also draws in
|
|
207
|
+
the depth prepass and the depth-normals prepass (SSAO, any feature
|
|
208
|
+
asking for normals), as URP's Unlit does: under depth priming the
|
|
209
|
+
forward pass tests depth for equality, so a shader missing the prepass's
|
|
210
|
+
pass drew nothing there. Each carries the instance and eye macros
|
|
211
|
+
single-pass instanced XR needs (its own, or those of URP's own pass it
|
|
212
|
+
includes); the normals pass writes the packed normals a Deferred
|
|
213
|
+
renderer asks for."""
|
|
214
|
+
def walk(ref, prefix=""):
|
|
215
|
+
for child in ref.iterdir():
|
|
216
|
+
if child.is_dir():
|
|
217
|
+
yield from walk(child, prefix + child.name + "/")
|
|
218
|
+
elif child.name.endswith(".shader"):
|
|
219
|
+
yield prefix + child.name, child.read_text(encoding="utf-8")
|
|
220
|
+
|
|
221
|
+
forward_only = {
|
|
222
|
+
name: text
|
|
223
|
+
for name, text in walk(resources.files(TEMPLATE_PACKAGE))
|
|
224
|
+
if re.search(r'"LightMode"\s*=\s*"UniversalForwardOnly"', text)
|
|
225
|
+
}
|
|
226
|
+
self.assertEqual(
|
|
227
|
+
sorted(forward_only),
|
|
228
|
+
["EmissiveGroup/EmissiveGroupsLit.shader", "LightmapMetadata/DeliverableLit.shader"],
|
|
229
|
+
)
|
|
230
|
+
for name, text in forward_only.items():
|
|
231
|
+
for prepass in ("DepthOnly", "DepthNormalsOnly"):
|
|
232
|
+
block = self._shader_pass(text, prepass)
|
|
233
|
+
self.assertIsNotNone(block, f"{name} has no {prepass} pass")
|
|
234
|
+
self.assertRegex(block, rf'"LightMode"\s*=\s*"{prepass}"', f"{name} {prepass}")
|
|
235
|
+
self.assertRegex(block, r"ZWrite\s+On", f"{name} {prepass}")
|
|
236
|
+
if "universal/Shaders/UnlitDepthNormalsPass.hlsl" in block:
|
|
237
|
+
continue # URP's own pass, macros and all
|
|
238
|
+
for macro in (
|
|
239
|
+
"UNITY_VERTEX_INPUT_INSTANCE_ID",
|
|
240
|
+
"UNITY_VERTEX_OUTPUT_STEREO",
|
|
241
|
+
"UNITY_SETUP_INSTANCE_ID",
|
|
242
|
+
"UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO",
|
|
243
|
+
):
|
|
244
|
+
self.assertIn(macro, block, f"{name} {prepass}: no {macro}")
|
|
245
|
+
normals = self._shader_pass(text, "DepthNormalsOnly")
|
|
246
|
+
self.assertIn("_GBUFFER_NORMALS_OCT", normals, f"{name} DepthNormalsOnly")
|
|
247
|
+
|
|
185
248
|
# ---------------------------------------------------------------- components
|
|
186
249
|
def test_components_derive_from_the_bundle(self):
|
|
187
250
|
"""One required core plus one optional component per top-level folder,
|
|
@@ -366,6 +429,42 @@ class DeployTemplateTest(unittest.TestCase):
|
|
|
366
429
|
self.assertIn("M3trik.Unitytk", data["references"])
|
|
367
430
|
self.assertEqual(data["defineConstraints"], ["UNITYTK_XRI"])
|
|
368
431
|
|
|
432
|
+
def test_the_urp_define_starts_where_the_full_screen_pass_can_run_the_look(self):
|
|
433
|
+
"""``UNITYTK_URP`` compiles ``DeliverableLook``, which sets
|
|
434
|
+
``FullScreenPassRendererFeature.fetchColorBuffer`` and draws through
|
|
435
|
+
core's ``Blit.hlsl`` ``Vert``. Both arrived with URP 2022.3's backport
|
|
436
|
+
of the feature's fixes (in 14.0.9; 14.0.0-14.0.7 have no such field and
|
|
437
|
+
never set the ``_BlitScaleBias`` that vertex reads). A lower floor fails
|
|
438
|
+
the whole package's compile on those versions, not just the look."""
|
|
439
|
+
data = json.loads(bundle_ref("M3trik.Unitytk.asmdef").read_text(encoding="utf-8"))
|
|
440
|
+
urp = [d for d in data["versionDefines"] if d["define"] == "UNITYTK_URP"]
|
|
441
|
+
self.assertEqual(len(urp), 1, data["versionDefines"])
|
|
442
|
+
self.assertEqual(urp[0]["name"], "com.unity.render-pipelines.universal")
|
|
443
|
+
self.assertEqual(urp[0]["expression"], "14.0.9")
|
|
444
|
+
# A reference URP does not have is skipped: a Built-in project compiles.
|
|
445
|
+
self.assertIn("Unity.RenderPipelines.Universal.Runtime", data["references"])
|
|
446
|
+
|
|
447
|
+
def test_the_full_screen_pass_is_named_as_both_urps_declare_it(self):
|
|
448
|
+
"""URP 14 declares ``FullScreenPassRendererFeature`` in the global
|
|
449
|
+
namespace, URP 17 in ``UnityEngine.Rendering.Universal`` (its
|
|
450
|
+
``[MovedFrom("")]``). Only the short name, under a ``using`` of that
|
|
451
|
+
namespace, compiles on both: the qualified name failed the whole
|
|
452
|
+
package's compile on URP 14, where ``UNITYTK_URP`` starts."""
|
|
453
|
+
def walk(ref, prefix=""):
|
|
454
|
+
for child in ref.iterdir():
|
|
455
|
+
if child.is_dir():
|
|
456
|
+
yield from walk(child, prefix + child.name + "/")
|
|
457
|
+
elif child.name.endswith(".cs"):
|
|
458
|
+
yield prefix + child.name, child.read_text(encoding="utf-8")
|
|
459
|
+
|
|
460
|
+
named = []
|
|
461
|
+
for name, text in walk(resources.files(TEMPLATE_PACKAGE)):
|
|
462
|
+
self.assertNotIn("Universal.FullScreenPassRendererFeature", text, name)
|
|
463
|
+
if re.search(r"\bFullScreenPassRendererFeature\b", text):
|
|
464
|
+
named.append(name)
|
|
465
|
+
self.assertIn("using UnityEngine.Rendering.Universal;", text, name)
|
|
466
|
+
self.assertEqual(named, ["LightmapMetadata/DeliverableToneMapFeature.cs"])
|
|
467
|
+
|
|
369
468
|
def test_status_sees_subfolder_files(self):
|
|
370
469
|
"""A missing file inside a component's subfolder is a partial deploy
|
|
371
470
|
of that component, named by its bundle-relative name."""
|