unitytk 0.2.2__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.
Files changed (110) hide show
  1. {unitytk-0.2.2/unitytk.egg-info → unitytk-0.3.0}/PKG-INFO +1 -1
  2. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_articulated_rig_runtime.py +2 -4
  3. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_audio_events_integration.py +3 -3
  4. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_base_test.py +78 -0
  5. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_blender_render_effects_e2e.py +1 -1
  6. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_blender_shadow_plane_integration.py +1 -1
  7. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_blender_shot_metadata_integration.py +1 -1
  8. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_clip_name_contract_integration.py +1 -1
  9. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_controller_runtime_integration.py +1 -1
  10. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_deploy_templates.py +99 -0
  11. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_embedded_dependencies_integration.py +2 -2
  12. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_emissive_groups_integration.py +1 -1
  13. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_fade_window_preservation.py +1 -1
  14. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_keyed_visibility.py +1 -1
  15. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_lightmap_metadata_integration.py +852 -1
  16. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_maya_surface_materials_integration.py +120 -62
  17. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_multi_section_audio_integration.py +2 -2
  18. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_render_effects_e2e.py +2 -2
  19. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_render_opacity_controller.py +1 -1
  20. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_render_opacity_e2e.py +1 -1
  21. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_render_opacity_integration.py +1 -1
  22. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_shadow_plane_integration.py +1 -1
  23. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_shadow_plane_runtime_integration.py +1 -1
  24. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_shared_material_standalone.py +1 -1
  25. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_shot_metadata_integration.py +1 -1
  26. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_shots_audio_sidebyside_integration.py +1 -1
  27. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_standalone_enduser.py +1 -1
  28. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_temp_projects.py +26 -3
  29. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/__init__.py +1 -1
  30. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/AudioEvent/AudioEventController.cs +49 -4
  31. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupsLit.shader +36 -1
  32. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableCubeUV.hlsl +119 -0
  33. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableCubeUV.hlsl.meta +7 -0
  34. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableLit.shader +373 -0
  35. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableLit.shader.meta +9 -0
  36. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverablePmrem.cs +307 -0
  37. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverablePmrem.cs.meta +11 -0
  38. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableShading.cs +460 -0
  39. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableShading.cs.meta +11 -0
  40. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMap.shader +65 -0
  41. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMap.shader.meta +9 -0
  42. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMapFeature.cs +53 -0
  43. unitytk-0.3.0/unitytk/templates/LightmapMetadata/DeliverableToneMapFeature.cs.meta +11 -0
  44. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/LightmapMetadata/LightmapMetadataController.cs +496 -48
  45. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/M3trik.Unitytk.asmdef +8 -1
  46. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/MayaSurface/MayaSurfaceMaterialImporter.cs +69 -16
  47. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/README.md +68 -26
  48. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneController.cs +19 -16
  49. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/UnitytkSettings.cs +80 -15
  50. {unitytk-0.2.2 → unitytk-0.3.0/unitytk.egg-info}/PKG-INFO +1 -1
  51. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk.egg-info/SOURCES.txt +12 -0
  52. {unitytk-0.2.2 → unitytk-0.3.0}/LICENSE +0 -0
  53. {unitytk-0.2.2 → unitytk-0.3.0}/MANIFEST.in +0 -0
  54. {unitytk-0.2.2 → unitytk-0.3.0}/docs/README.md +0 -0
  55. {unitytk-0.2.2 → unitytk-0.3.0}/pyproject.toml +0 -0
  56. {unitytk-0.2.2 → unitytk-0.3.0}/setup.cfg +0 -0
  57. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_asset_delivery.py +0 -0
  58. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_bool_to_fade_curve.py +0 -0
  59. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_launcher.py +0 -0
  60. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_run_tests.py +0 -0
  61. {unitytk-0.2.2 → unitytk-0.3.0}/test/test_scene_builder.py +0 -0
  62. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/asset_delivery.py +0 -0
  63. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/launcher.py +0 -0
  64. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/scene_builder.py +0 -0
  65. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/template_deployer.py +0 -0
  66. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigController.cs +0 -0
  67. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigController.cs.meta +0 -0
  68. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigPointerGrab.cs +0 -0
  69. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulatedRigPointerGrab.cs.meta +0 -0
  70. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulationRecord.g.cs +0 -0
  71. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/ArticulationRecord.g.cs.meta +0 -0
  72. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRGrab.cs +0 -0
  73. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRGrab.cs.meta +0 -0
  74. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRSetup.cs +0 -0
  75. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRSetup.cs.meta +0 -0
  76. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/M3trik.Unitytk.XR.asmdef +0 -0
  77. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR/M3trik.Unitytk.XR.asmdef.meta +0 -0
  78. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig/XR.meta +0 -0
  79. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ArticulatedRig.meta +0 -0
  80. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/AudioEvent/AudioEventController.cs.meta +0 -0
  81. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/AudioEvent.meta +0 -0
  82. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupController.cs +0 -0
  83. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupController.cs.meta +0 -0
  84. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroups.hlsl +0 -0
  85. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroups.hlsl.meta +0 -0
  86. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup/EmissiveGroupsLit.shader.meta +0 -0
  87. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/EmissiveGroup.meta +0 -0
  88. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/LightmapMetadata/LightmapMetadataController.cs.meta +0 -0
  89. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/LightmapMetadata.meta +0 -0
  90. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/M3trik.Unitytk.asmdef.meta +0 -0
  91. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/MayaSurface/MayaSurfaceMaterialImporter.cs.meta +0 -0
  92. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/MayaSurface.meta +0 -0
  93. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/README.md.meta +0 -0
  94. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/RenderEffects/RenderEffectsController.cs +0 -0
  95. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/RenderEffects/RenderEffectsController.cs.meta +0 -0
  96. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/RenderEffects.meta +0 -0
  97. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlane.shader +0 -0
  98. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlane.shader.meta +0 -0
  99. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneController.cs.meta +0 -0
  100. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneHorizon.hlsl +0 -0
  101. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShadowPlane/ShadowPlaneHorizon.hlsl.meta +0 -0
  102. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShadowPlane.meta +0 -0
  103. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShotMetadata/ShotMetadataController.cs +0 -0
  104. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShotMetadata/ShotMetadataController.cs.meta +0 -0
  105. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/ShotMetadata.meta +0 -0
  106. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/UnitytkSettings.cs.meta +0 -0
  107. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk/templates/__init__.py +0 -0
  108. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk.egg-info/dependency_links.txt +0 -0
  109. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk.egg-info/requires.txt +0 -0
  110. {unitytk-0.2.2 → unitytk-0.3.0}/unitytk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitytk
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A modular Python toolkit for Unity interaction.
5
5
  Author-email: Ryan Simpson <m3trik@outlook.com>
6
6
  License: MIT
@@ -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 = os.path.join(temp_dir, "UnityProject")
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"
@@ -499,7 +499,7 @@ class TestAudioEventsIntegration(unittest.TestCase):
499
499
  # as the sibling integration tests.
500
500
  editor = os.environ.get("UNITYTK_TEST_EDITOR")
501
501
  launcher = UnityLauncher(editor) if editor else UnityLauncher()
502
- project_path = os.path.join(self.temp_dir, "UnityProject")
502
+ project_path = TempProjects.project(self.temp_dir)
503
503
 
504
504
  logger.info(f"Creating Unity project: {project_path}")
505
505
  if not launcher.create_project(project_path, batch_mode=True):
@@ -805,7 +805,7 @@ class TestAudioCarrierPrecedence(unittest.TestCase):
805
805
 
806
806
  @classmethod
807
807
  def setUpClass(cls):
808
- cls.temp_dir = TempProjects.allocate("audio_carrier_precedence")
808
+ cls.temp_dir = TempProjects.allocate("carrier_precedence")
809
809
 
810
810
  @BlenderEnv.skip_unless_available()
811
811
  @EnvSkip.unless(_unity_available(), "no Unity editor found")
@@ -821,7 +821,7 @@ class TestAudioCarrierPrecedence(unittest.TestCase):
821
821
  )
822
822
 
823
823
  launcher = UnityLauncher()
824
- project = os.path.join(self.temp_dir, "UnityProject")
824
+ project = TempProjects.project(self.temp_dir)
825
825
  if not launcher.create_project(project, batch_mode=True):
826
826
  self.skipTest(UnityEnv.project_creation_reason())
827
827
  delivered = os.path.join(project, "Assets", "Delivered")
@@ -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 = os.path.join(self.temp_dir, "UnityProject")
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 = os.path.join(self.temp_dir, "UnityProject")
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 = os.path.join(self.temp_dir, "UnityProject")
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 = os.path.join(self.temp_dir, "UnityProject")
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 = os.path.join(self.temp_dir, "UnityProject")
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."""
@@ -377,7 +377,7 @@ class TestEmbeddedDependenciesIntegration(unittest.TestCase):
377
377
 
378
378
  editor = os.environ.get("UNITYTK_TEST_EDITOR")
379
379
  launcher = UnityLauncher(editor) if editor else UnityLauncher()
380
- project = os.path.join(self.temp_dir, "UnityProject")
380
+ project = TempProjects.project(self.temp_dir)
381
381
  if not launcher.create_project(project, batch_mode=True):
382
382
  self.skipTest(UnityEnv.project_creation_reason())
383
383
  delivered = os.path.join(project, *ASSET_DIR.split("/"))
@@ -596,7 +596,7 @@ class TestEmbeddedRequestPolicy(unittest.TestCase):
596
596
  ptk.FbxMedia.embed(bare, sources[stem], files=[(f"{embed}.wav", clip)])
597
597
 
598
598
  launcher = UnityLauncher()
599
- project = os.path.join(self.temp_dir, "UnityProject")
599
+ project = TempProjects.project(self.temp_dir)
600
600
  if not launcher.create_project(project, batch_mode=True):
601
601
  self.skipTest(UnityEnv.project_creation_reason())
602
602
  CarrierFbx.wav(
@@ -292,7 +292,7 @@ class TestEmissiveGroupsIntegration(unittest.TestCase):
292
292
 
293
293
  editor = os.environ.get("UNITYTK_TEST_EDITOR")
294
294
  launcher = UnityLauncher(editor) if editor else UnityLauncher()
295
- project_path = os.path.join(self.temp_dir, "UnityProject")
295
+ project_path = TempProjects.project(self.temp_dir)
296
296
  if not launcher.create_project(project_path, batch_mode=True):
297
297
  self.skipTest(UnityEnv.project_creation_reason())
298
298
 
@@ -244,7 +244,7 @@ class TestFadeWindowPreservation(unittest.TestCase):
244
244
  self.skipTest("FBX missing — test_01 must run first")
245
245
 
246
246
  launcher = UnityLauncher()
247
- project_path = os.path.join(self.temp_dir, "UnityProject")
247
+ project_path = TempProjects.project(self.temp_dir)
248
248
  if not launcher.create_project(project_path, batch_mode=True):
249
249
  self.skipTest(UnityEnv.project_creation_reason())
250
250
 
@@ -406,7 +406,7 @@ class TestKeyedVisibility(unittest.TestCase):
406
406
  @classmethod
407
407
  def setUpClass(cls):
408
408
  cls.temp_dir = TempProjects.allocate("keyed_vis_test")
409
- cls.project_path = os.path.join(cls.temp_dir, "UnityProject")
409
+ cls.project_path = TempProjects.project(cls.temp_dir)
410
410
  cls.results = None
411
411
  logger.info(f"Temp dir: {cls.temp_dir}")
412
412