unitytk 0.0.9__tar.gz → 0.0.11__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 (59) hide show
  1. {unitytk-0.0.9/unitytk.egg-info → unitytk-0.0.11}/PKG-INFO +1 -1
  2. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_bool_to_fade_curve.py +16 -19
  3. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_controller_runtime_integration.py +40 -22
  4. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_deploy_templates.py +92 -12
  5. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_fade_window_preservation.py +32 -20
  6. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_keyed_visibility.py +9 -8
  7. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_multi_section_audio_integration.py +194 -130
  8. unitytk-0.0.11/test/test_render_effects_e2e.py +420 -0
  9. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_render_opacity_controller.py +17 -16
  10. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_render_opacity_e2e.py +50 -33
  11. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_run_tests.py +106 -1
  12. unitytk-0.0.11/test/test_shadow_plane_integration.py +754 -0
  13. unitytk-0.0.11/test/test_shadow_plane_runtime_integration.py +1039 -0
  14. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_shared_material_standalone.py +11 -12
  15. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_standalone_enduser.py +151 -98
  16. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_temp_projects.py +67 -1
  17. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/__init__.py +1 -1
  18. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/template_deployer.py +81 -22
  19. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/README.md +18 -11
  20. unitytk-0.0.11/unitytk/templates/RenderEffectsController.cs +733 -0
  21. unitytk-0.0.11/unitytk/templates/ShadowPlane.shader +223 -0
  22. unitytk-0.0.11/unitytk/templates/ShadowPlaneController.cs +1201 -0
  23. unitytk-0.0.11/unitytk/templates/ShadowPlaneHorizon.hlsl +344 -0
  24. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/UnitytkSettings.cs +40 -3
  25. {unitytk-0.0.9 → unitytk-0.0.11/unitytk.egg-info}/PKG-INFO +1 -1
  26. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk.egg-info/SOURCES.txt +5 -1
  27. unitytk-0.0.9/test/test_shadow_plane_integration.py +0 -404
  28. unitytk-0.0.9/unitytk/templates/RenderOpacityController.cs +0 -611
  29. unitytk-0.0.9/unitytk/templates/ShadowPlaneController.cs +0 -307
  30. {unitytk-0.0.9 → unitytk-0.0.11}/LICENSE +0 -0
  31. {unitytk-0.0.9 → unitytk-0.0.11}/MANIFEST.in +0 -0
  32. {unitytk-0.0.9 → unitytk-0.0.11}/docs/README.md +0 -0
  33. {unitytk-0.0.9 → unitytk-0.0.11}/pyproject.toml +0 -0
  34. {unitytk-0.0.9 → unitytk-0.0.11}/setup.cfg +0 -0
  35. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_asset_delivery.py +0 -0
  36. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_audio_events_integration.py +0 -0
  37. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_base_test.py +0 -0
  38. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_clip_name_contract_integration.py +0 -0
  39. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_emissive_groups_integration.py +0 -0
  40. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_launcher.py +0 -0
  41. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_lightmap_metadata_integration.py +0 -0
  42. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_render_opacity_integration.py +0 -0
  43. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_scene_builder.py +0 -0
  44. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_shot_metadata_integration.py +0 -0
  45. {unitytk-0.0.9 → unitytk-0.0.11}/test/test_shots_audio_sidebyside_integration.py +0 -0
  46. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/asset_delivery.py +0 -0
  47. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/launcher.py +0 -0
  48. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/scene_builder.py +0 -0
  49. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/AudioEventController.cs +0 -0
  50. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/EmissiveGroupController.cs +0 -0
  51. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/EmissiveGroups.hlsl +0 -0
  52. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/EmissiveGroupsLit.shader +0 -0
  53. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/LightmapMetadataController.cs +0 -0
  54. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/M3trik.Unitytk.asmdef +0 -0
  55. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/ShotMetadataController.cs +0 -0
  56. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk/templates/__init__.py +0 -0
  57. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk.egg-info/dependency_links.txt +0 -0
  58. {unitytk-0.0.9 → unitytk-0.0.11}/unitytk.egg-info/requires.txt +0 -0
  59. {unitytk-0.0.9 → unitytk-0.0.11}/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.0.9
3
+ Version: 0.0.11
4
4
  Summary: A modular Python toolkit for Unity interaction.
5
5
  Author-email: Ryan Simpson <m3trik@outlook.com>
6
6
  License: MIT
@@ -1,7 +1,7 @@
1
1
  # !/usr/bin/python
2
2
  # coding=utf-8
3
3
  """Algorithm-level unit tests for the C# ``BoolToFadeCurve`` reconstruction
4
- logic in ``RenderOpacityController.cs``.
4
+ logic in ``RenderEffectsController.cs``.
5
5
 
6
6
  This module ports the C# algorithm to Python and exercises it against the
7
7
  edge cases the Unity importer must handle correctly. Catching algorithm
@@ -11,13 +11,14 @@ needs no Unity license.
11
11
  If the C# implementation drifts from this port, update both — the C# is
12
12
  the production source of truth, this is the executable spec.
13
13
  """
14
+
14
15
  import unittest
15
16
  from typing import List, Tuple
16
17
 
17
18
 
18
19
  # ---------------------------------------------------------------------------
19
20
  # Python port of the C# algorithm — keep behaviourally identical to
20
- # RenderOpacityImporter.BoolToFadeCurve in RenderOpacityController.cs.
21
+ # RenderEffectsImporter.BoolToFadeCurve in RenderEffectsController.cs.
21
22
  # ---------------------------------------------------------------------------
22
23
 
23
24
  EPS = 0.001
@@ -143,12 +144,8 @@ class TestBoolToFadeCurve(unittest.TestCase):
143
144
  def test_fade_in_hold_fade_out(self):
144
145
  """The standard show-then-hide pattern: 0@1, 1@30, 1@100, 0@130.
145
146
  Output must be a clean 4-key curve preserving each segment."""
146
- result = bool_to_fade_curve(
147
- [(1, 0), (30, 1), (100, 1), (130, 0)]
148
- )
149
- self.assertEqual(
150
- result, [(1, 0.0), (30, 1.0), (100, 1.0), (130, 0.0)]
151
- )
147
+ result = bool_to_fade_curve([(1, 0), (30, 1), (100, 1), (130, 0)])
148
+ self.assertEqual(result, [(1, 0.0), (30, 1.0), (100, 1.0), (130, 0.0)])
152
149
 
153
150
  def test_complex_multi_segment(self):
154
151
  """fade-in → hold → fade-out → hold → fade-in"""
@@ -194,9 +191,7 @@ class TestBoolToFadeCurve(unittest.TestCase):
194
191
  def test_multiple_holds_track_latest_anchor(self):
195
192
  """When several same-value keys precede a transition, the ramp
196
193
  starts at the most-recent anchor (not the first)."""
197
- result = bool_to_fade_curve(
198
- [(0, 1), (10, 1), (20, 1), (50, 1), (60, 0)]
199
- )
194
+ result = bool_to_fade_curve([(0, 1), (10, 1), (20, 1), (50, 1), (60, 0)])
200
195
  # prev_same_value_time advances to 50; ramp 50→60
201
196
  self.assertEqual(result, [(0, 1.0), (50, 1.0), (60, 0.0)])
202
197
 
@@ -204,9 +199,7 @@ class TestBoolToFadeCurve(unittest.TestCase):
204
199
  """When two transitions are very close, the second ramp must not
205
200
  produce a key that collides with the previous transition's end."""
206
201
  # Tight pattern: transition at t=10, immediately back at t=10.001
207
- result = bool_to_fade_curve(
208
- [(0, 0), (10, 1), (10.0008, 0)]
209
- )
202
+ result = bool_to_fade_curve([(0, 0), (10, 1), (10.0008, 0)])
210
203
  # All result times must be strictly increasing
211
204
  times = [k[0] for k in result]
212
205
  for a, b in zip(times, times[1:]):
@@ -214,9 +207,7 @@ class TestBoolToFadeCurve(unittest.TestCase):
214
207
 
215
208
  def test_strictly_monotonic_output(self):
216
209
  """All emitted keys must be in strictly increasing time order."""
217
- result = bool_to_fade_curve(
218
- [(0, 0), (5, 1), (5, 1), (10, 0), (20, 1)]
219
- )
210
+ result = bool_to_fade_curve([(0, 0), (5, 1), (5, 1), (10, 0), (20, 1)])
220
211
  times = [k[0] for k in result]
221
212
  for a, b in zip(times, times[1:]):
222
213
  self.assertLess(a, b)
@@ -267,8 +258,14 @@ class TestBoolToFadeCurve(unittest.TestCase):
267
258
  Reconstruction must yield 4 keys representing fade-in (0→10),
268
259
  hold (10→90), fade-out (90→150)."""
269
260
  # Times in seconds at 24fps would be 0, 0.4167, 0.4167, 3.75, 6.25, 6.25
270
- raw = [(0.0, 0.0), (0.4167, 0.0), (0.4167, 1.0),
271
- (3.75, 1.0), (6.25, 1.0), (6.25, 0.0)]
261
+ raw = [
262
+ (0.0, 0.0),
263
+ (0.4167, 0.0),
264
+ (0.4167, 1.0),
265
+ (3.75, 1.0),
266
+ (6.25, 1.0),
267
+ (6.25, 0.0),
268
+ ]
272
269
  result = bool_to_fade_curve(raw)
273
270
  self.assertEqual(
274
271
  result,
@@ -9,7 +9,7 @@ Maya-free batch Unity run:
9
9
 
10
10
  1. Create a Unity project and deploy the real template package.
11
11
  2. In an Editor script, build a cube with a Standard material, drive
12
- ``RenderOpacityController`` / ``EmissiveGroupController`` directly, and
12
+ ``RenderEffectsController`` / ``EmissiveGroupController`` directly, and
13
13
  read the resulting MaterialPropertyBlock back.
14
14
 
15
15
  Covers three behaviours that inspection alone can't settle:
@@ -34,8 +34,8 @@ Covers three behaviours that inspection alone can't settle:
34
34
  Requires:
35
35
  - Unity Hub with at least one editor installed (skips on license wall)
36
36
  """
37
+
37
38
  import os
38
- import sys
39
39
  import json
40
40
  import unittest
41
41
  import subprocess
@@ -92,7 +92,7 @@ public class ControllerRuntimeVerifier
92
92
  + (mat.HasProperty("_Color") ? "true," : "false,"));
93
93
 
94
94
  // ---- (a) Partial fade must reach the property block --------------
95
- var opa = go.AddComponent<RenderOpacityController>();
95
+ var opa = go.AddComponent<RenderEffectsController>();
96
96
  // AddComponent in the editor DOES run Reset(), which auto-detects the
97
97
  // property — recorded here, but it is not something the import path can
98
98
  // rely on (it only helps when a material is already assigned at that
@@ -192,7 +192,8 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
192
192
  os.makedirs(editor_path, exist_ok=True)
193
193
  results_path = os.path.join(self.temp_dir, "unity_results.json")
194
194
  with open(
195
- os.path.join(editor_path, "ControllerRuntimeVerifier.cs"), "w",
195
+ os.path.join(editor_path, "ControllerRuntimeVerifier.cs"),
196
+ "w",
196
197
  encoding="utf-8",
197
198
  ) as f:
198
199
  f.write(UNITY_VERIFIER_CS.replace("__RESULTS_PATH__", results_path))
@@ -222,7 +223,8 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
222
223
  if UnityLauncher.is_license_failure(returncode, log_text):
223
224
  logger.info(
224
225
  "Unity batch license failure (rc=%s) — see %s",
225
- returncode, log_path,
226
+ returncode,
227
+ log_path,
226
228
  )
227
229
  self.skipTest(UnityEnv.license_reason())
228
230
  self.fail(
@@ -243,18 +245,23 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
243
245
  self.skipTest("Unity results not available — test_01 must run first")
244
246
 
245
247
  # Premise: a fresh batch project is Built-in RP.
246
- self.assertTrue(unity.get("mat_has_color"),
247
- "Standard material should expose _Color")
248
- self.assertFalse(unity.get("mat_has_base_color"),
249
- "premise broken: this project is no longer Built-in RP")
248
+ self.assertTrue(
249
+ unity.get("mat_has_color"), "Standard material should expose _Color"
250
+ )
251
+ self.assertFalse(
252
+ unity.get("mat_has_base_color"),
253
+ "premise broken: this project is no longer Built-in RP",
254
+ )
250
255
 
251
256
  # The regression: colorProperty is "_BaseColor" (the URP default the
252
257
  # component serializes) and the material has no such property, so
253
258
  # without runtime resolution the alpha never reaches it and this is 0.0.
254
259
  self.assertAlmostEqual(
255
- unity.get("alpha_applied", 0.0), 0.5, places=3,
260
+ unity.get("alpha_applied", 0.0),
261
+ 0.5,
262
+ places=3,
256
263
  msg="opacity never reached the material — colorProperty did not "
257
- "resolve to the Built-in _Color",
264
+ "resolve to the Built-in _Color",
258
265
  )
259
266
 
260
267
  @EnvSkip.unless(_unity_available(), "no Unity editor found")
@@ -264,10 +271,13 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
264
271
  if not unity:
265
272
  self.skipTest("Unity results not available — test_01 must run first")
266
273
 
267
- self.assertTrue(unity.get("hidden_at_zero"),
268
- "Renderer should be disabled at zero opacity")
269
- self.assertTrue(unity.get("restored_above_zero"),
270
- "Renderer should be re-enabled above zero opacity")
274
+ self.assertTrue(
275
+ unity.get("hidden_at_zero"), "Renderer should be disabled at zero opacity"
276
+ )
277
+ self.assertTrue(
278
+ unity.get("restored_above_zero"),
279
+ "Renderer should be re-enabled above zero opacity",
280
+ )
271
281
 
272
282
  @EnvSkip.unless(_unity_available(), "no Unity editor found")
273
283
  def test_04_property_block_coexistence(self):
@@ -278,19 +288,25 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
278
288
 
279
289
  # Emissive writing _GroupWeights must not drop the opacity alpha...
280
290
  self.assertAlmostEqual(
281
- unity.get("alpha_after_emissive", 0.0), 0.5, places=3,
282
- msg="EmissiveGroupController wiped RenderOpacityController's alpha",
291
+ unity.get("alpha_after_emissive", 0.0),
292
+ 0.5,
293
+ places=3,
294
+ msg="EmissiveGroupController wiped RenderEffectsController's alpha",
283
295
  )
284
296
  # ...and vice versa.
285
297
  self.assertAlmostEqual(
286
- unity.get("weight_after_emissive", 0.0), 0.25, places=3,
298
+ unity.get("weight_after_emissive", 0.0),
299
+ 0.25,
300
+ places=3,
287
301
  msg="_GroupWeights did not reach the property block",
288
302
  )
289
303
 
290
304
  # Destroying the controller must leave the weights intact either way.
291
305
  self.assertAlmostEqual(
292
- unity.get("weight_after_destroy", 0.0), 0.25, places=3,
293
- msg="destroying RenderOpacityController wiped the emissive weights",
306
+ unity.get("weight_after_destroy", 0.0),
307
+ 0.25,
308
+ places=3,
309
+ msg="destroying RenderEffectsController wiped the emissive weights",
294
310
  )
295
311
 
296
312
  # KNOWN COVERAGE GAP, asserted rather than assumed: Unity does not call
@@ -305,9 +321,11 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
305
321
  # future Unity DOES start firing OnDisable in edit mode, this test
306
322
  # fails loudly and the assertion above becomes real coverage.
307
323
  self.assertAlmostEqual(
308
- unity.get("alpha_after_destroy", -1.0), 0.5, places=3,
324
+ unity.get("alpha_after_destroy", -1.0),
325
+ 0.5,
326
+ places=3,
309
327
  msg="OnDisable now fires in edit mode — retarget this test at the "
310
- "teardown path, which is finally reachable",
328
+ "teardown path, which is finally reachable",
311
329
  )
312
330
 
313
331
 
@@ -10,6 +10,7 @@ the Python side — the only side that can be tested without a licensed Unity
10
10
  install — including the per-component selection the DCC panels' script
11
11
  checklist drives.
12
12
  """
13
+
13
14
  import json
14
15
  import os
15
16
  import shutil
@@ -19,6 +20,7 @@ from unittest import mock
19
20
 
20
21
  import unitytk
21
22
  from unitytk import TemplateDeployer
23
+ from unitytk import template_deployer
22
24
  from unitytk.template_deployer import UPM_PACKAGE_NAME
23
25
 
24
26
 
@@ -54,8 +56,10 @@ class DeployTemplateTest(unittest.TestCase):
54
56
  "LightmapMetadataController.cs",
55
57
  "M3trik.Unitytk.asmdef",
56
58
  "README.md",
57
- "RenderOpacityController.cs",
59
+ "RenderEffectsController.cs",
60
+ "ShadowPlane.shader",
58
61
  "ShadowPlaneController.cs",
62
+ "ShadowPlaneHorizon.hlsl",
59
63
  "ShotMetadataController.cs",
60
64
  "UnitytkSettings.cs",
61
65
  "package.json",
@@ -116,7 +120,7 @@ class DeployTemplateTest(unittest.TestCase):
116
120
  "audio_event",
117
121
  "emissive_group",
118
122
  "lightmap_metadata",
119
- "render_opacity",
123
+ "render_effects",
120
124
  "shadow_plane",
121
125
  "shot_metadata",
122
126
  ],
@@ -132,15 +136,29 @@ class DeployTemplateTest(unittest.TestCase):
132
136
  """``EmissiveGroupsLit.shader`` includes the .hlsl by RELATIVE path, so
133
137
  the pair travels with the controller that needs it — never on its own,
134
138
  never left behind."""
135
- emissive = {c.key: c for c in TemplateDeployer.components()}["emissive_group"]
139
+ comps = {c.key: c for c in TemplateDeployer.components()}
136
140
  self.assertEqual(
137
- sorted(emissive.files),
141
+ sorted(comps["emissive_group"].files),
138
142
  [
139
143
  "EmissiveGroupController.cs",
140
144
  "EmissiveGroups.hlsl",
141
145
  "EmissiveGroupsLit.shader",
142
146
  ],
143
147
  )
148
+ # The shadow importer rewires planes to ``m3trik/ShadowPlane`` by
149
+ # Shader.Find, so the shader rides its controller the same way -- and
150
+ # so does the ``.hlsl`` the shader includes, which is what keeps the
151
+ # generated horizon body out of the mandatory ``core`` component (a
152
+ # name that did not start with the ``ShadowPlane`` stem would land
153
+ # there and deploy into every project).
154
+ self.assertEqual(
155
+ sorted(comps["shadow_plane"].files),
156
+ [
157
+ "ShadowPlane.shader",
158
+ "ShadowPlaneController.cs",
159
+ "ShadowPlaneHorizon.hlsl",
160
+ ],
161
+ )
144
162
 
145
163
  def test_satellite_goes_to_the_longest_matching_controller(self):
146
164
  """Ownership is longest-stem-wins, so adding an `EmissiveController.cs`
@@ -154,7 +172,9 @@ class DeployTemplateTest(unittest.TestCase):
154
172
  "EmissiveGroupsLit.shader",
155
173
  "UnitytkSettings.cs",
156
174
  ]
157
- with mock.patch.object(TemplateDeployer, "_bundle_names", staticmethod(lambda: bundle)):
175
+ with mock.patch.object(
176
+ TemplateDeployer, "_bundle_names", staticmethod(lambda: bundle)
177
+ ):
158
178
  comps = {c.key: c for c in TemplateDeployer.components()}
159
179
  self.assertEqual(comps["emissive"].files, ("EmissiveController.cs",))
160
180
  self.assertEqual(
@@ -168,12 +188,71 @@ class DeployTemplateTest(unittest.TestCase):
168
188
  owned = [n for c in comps.values() for n in c.files]
169
189
  self.assertEqual(len(owned), len(set(owned)), "a file has two owners")
170
190
 
191
+ def test_component_graph_is_derived_once_per_bundle(self):
192
+ """The bundle is fixed at install time, so the graph is derived ONCE
193
+ and the templates dir is scanned once -- not per call.
194
+
195
+ Every entry point re-enters :meth:`components`: ``resolve_components``
196
+ -> ``resolve_files`` -> ``deploy_package``/``status``, and the panels
197
+ rebuild their checklist from it on every UI refresh. Measured before
198
+ this was cached, one ``uninstall_package`` re-derived the graph 3x and
199
+ re-scanned the bundle dir 3x (``run_action('uninstall')``: 4x).
200
+ """
201
+ counts = {"iterdir": 0}
202
+ real_resources = template_deployer.resources
203
+
204
+ class _Traversable:
205
+ def __init__(self, wrapped):
206
+ self._wrapped = wrapped
207
+
208
+ def iterdir(self):
209
+ counts["iterdir"] += 1
210
+ return self._wrapped.iterdir()
211
+
212
+ def __getattr__(self, name):
213
+ return getattr(self._wrapped, name)
214
+
215
+ class _Resources:
216
+ def files(self, package):
217
+ return _Traversable(real_resources.files(package))
218
+
219
+ def __getattr__(self, name):
220
+ return getattr(real_resources, name)
221
+
222
+ TemplateDeployer.deploy_package(self.project)
223
+ with mock.patch.object(template_deployer, "resources", _Resources()):
224
+ TemplateDeployer.components() # warm, whatever the process order
225
+ before = counts["iterdir"]
226
+ graph = TemplateDeployer.components()
227
+ self.assertIs(graph, TemplateDeployer.components(), "graph rebuilt")
228
+ TemplateDeployer.resolve_files(["audio_event"])
229
+ TemplateDeployer.status(self.project)
230
+ TemplateDeployer.uninstall_package(self.project, ["audio_event"])
231
+ self.assertEqual(
232
+ counts["iterdir"] - before, 0, "the bundle dir was re-scanned"
233
+ )
234
+
235
+ def test_component_graph_tracks_the_bundle_it_was_derived_from(self):
236
+ """The memo is keyed on the bundle names, so a different bundle yields
237
+ a different graph -- the cache can never serve a stale set."""
238
+ real = TemplateDeployer.components()
239
+ bundle = ["FooController.cs", "UnitytkSettings.cs"]
240
+ with mock.patch.object(
241
+ TemplateDeployer, "_bundle_names", staticmethod(lambda: bundle)
242
+ ):
243
+ self.assertEqual(
244
+ sorted(c.key for c in TemplateDeployer.components()), ["core", "foo"]
245
+ )
246
+ self.assertIs(TemplateDeployer.components(), real)
247
+
171
248
  def test_keys_and_labels_survive_an_acronym(self):
172
249
  """Names are split on real word boundaries, so a `UVLayoutController`
173
250
  reads as 'UV Layout' / `uv_layout` rather than 'U V Layout'."""
174
251
  self.assertEqual(TemplateDeployer._component_key("UVLayout"), "uv_layout")
175
252
  self.assertEqual(TemplateDeployer._component_label("UVLayout"), "UV Layout")
176
- self.assertEqual(TemplateDeployer._component_key("ShotMetadata"), "shot_metadata")
253
+ self.assertEqual(
254
+ TemplateDeployer._component_key("ShotMetadata"), "shot_metadata"
255
+ )
177
256
 
178
257
  def test_selected_deploy_carries_core_and_nothing_else(self):
179
258
  """A checked subset deploys those components + the core they compile
@@ -233,7 +312,9 @@ class DeployTemplateTest(unittest.TestCase):
233
312
  TemplateDeployer.deploy_package(self.project)
234
313
  with self.assertRaises(ValueError):
235
314
  TemplateDeployer.uninstall_package(self.project, ["core"])
236
- self.assertTrue(os.path.isfile(os.path.join(self.package, "UnitytkSettings.cs")))
315
+ self.assertTrue(
316
+ os.path.isfile(os.path.join(self.package, "UnitytkSettings.cs"))
317
+ )
237
318
 
238
319
  def test_status_reports_not_installed_then_up_to_date(self):
239
320
  """The panels' Status action: absent → installed=False with the full
@@ -297,9 +378,7 @@ class DeployTemplateTest(unittest.TestCase):
297
378
  self.assertIn("[ ] Shot Metadata", text)
298
379
 
299
380
  def test_run_action_install_and_uninstall_the_checked_set(self):
300
- install = TemplateDeployer.run_action(
301
- self.project, "install", ["audio_event"]
302
- )
381
+ install = TemplateDeployer.run_action(self.project, "install", ["audio_event"])
303
382
  self.assertEqual([lvl for lvl, _ in install], ["info"])
304
383
  self.assertIn("Audio Event", install[0][1])
305
384
  self.assertTrue(
@@ -365,7 +444,7 @@ class DeployTemplateTest(unittest.TestCase):
365
444
  "AudioEventController.cs",
366
445
  "EmissiveGroupController.cs",
367
446
  "LightmapMetadataController.cs",
368
- "RenderOpacityController.cs",
447
+ "RenderEffectsController.cs",
369
448
  "ShadowPlaneController.cs",
370
449
  "ShotMetadataController.cs",
371
450
  }
@@ -374,7 +453,8 @@ class DeployTemplateTest(unittest.TestCase):
374
453
  """Deploy the set; return ``(definers, referencers)`` of *shared_class*
375
454
  across the C# templates only."""
376
455
  written = [
377
- p for p in TemplateDeployer.deploy_package(self.project)
456
+ p
457
+ for p in TemplateDeployer.deploy_package(self.project)
378
458
  if p.endswith(".cs")
379
459
  ]
380
460
  definers, referencers = set(), set()
@@ -15,8 +15,8 @@ Pipeline:
15
15
  expected mid-points, assert ramp lengths roughly match the Maya
16
16
  authoring (within tolerance for FBX time conversion).
17
17
  """
18
+
18
19
  import os
19
- import sys
20
20
  import json
21
21
  import shutil
22
22
  import unittest
@@ -49,7 +49,7 @@ def _unity_available() -> bool:
49
49
  # fade-out 91→151 (60f / 2.5s). Hold from 11 to 91.
50
50
  # ---------------------------------------------------------------------------
51
51
 
52
- MAYA_EXPORT_SCRIPT = r'''
52
+ MAYA_EXPORT_SCRIPT = r"""
53
53
  import sys, os, json
54
54
  # mayatk/pythontk repo roots arrive via PYTHONPATH set by the parent test.
55
55
 
@@ -91,7 +91,7 @@ with open(os.path.join(output_dir, "maya_results.json"), "w") as f:
91
91
  json.dump({"fbx_path": fbx_path, "fbx_exists": os.path.exists(fbx_path)}, f)
92
92
  print(f"MAYA EXPORT COMPLETE: {fbx_path}")
93
93
  maya.standalone.uninitialize()
94
- '''
94
+ """
95
95
 
96
96
 
97
97
  # ---------------------------------------------------------------------------
@@ -142,7 +142,7 @@ public class FadeWindowVerifier
142
142
  foreach (var b in AnimationUtility.GetCurveBindings(clip))
143
143
  {
144
144
  if (b.propertyName != "opacity") continue;
145
- if (b.type != typeof(RenderOpacityController)) continue;
145
+ if (b.type != typeof(RenderEffectsController)) continue;
146
146
  found = true;
147
147
 
148
148
  var curve = AnimationUtility.GetEditorCurve(clip, b);
@@ -224,7 +224,10 @@ class TestFadeWindowPreservation(unittest.TestCase):
224
224
  env["PYTHONIOENCODING"] = "utf-8"
225
225
  proc = subprocess.run(
226
226
  [MAYAPY, script, self.temp_dir],
227
- capture_output=True, text=True, env=env, timeout=180,
227
+ capture_output=True,
228
+ text=True,
229
+ env=env,
230
+ timeout=180,
228
231
  )
229
232
  if proc.returncode != 0:
230
233
  self.fail(f"mayapy failed:\n{proc.stderr[-2000:]}")
@@ -243,16 +246,19 @@ class TestFadeWindowPreservation(unittest.TestCase):
243
246
 
244
247
  assets = os.path.join(project_path, "Assets")
245
248
  unitytk.TemplateDeployer.deploy_package(project_path)
246
- shutil.copy2(self.__class__.fbx_path,
247
- os.path.join(assets, "fade_window.fbx"))
249
+ shutil.copy2(self.__class__.fbx_path, os.path.join(assets, "fade_window.fbx"))
248
250
 
249
251
  editor = os.path.join(assets, "Editor")
250
252
  os.makedirs(editor, exist_ok=True)
251
253
  results = os.path.join(self.temp_dir, "verify.json")
252
- with open(os.path.join(editor, "FadeWindowVerifier.cs"),
253
- "w", encoding="utf-8") as f:
254
- f.write(UNITY_VERIFIER_CS.replace(
255
- "__RESULTS_PATH__", results.replace("\\", "/")))
254
+ with open(
255
+ os.path.join(editor, "FadeWindowVerifier.cs"), "w", encoding="utf-8"
256
+ ) as f:
257
+ f.write(
258
+ UNITY_VERIFIER_CS.replace(
259
+ "__RESULTS_PATH__", results.replace("\\", "/")
260
+ )
261
+ )
256
262
 
257
263
  launcher.project_path = project_path
258
264
  proc = launcher.launch_editor(
@@ -268,8 +274,9 @@ class TestFadeWindowPreservation(unittest.TestCase):
268
274
  with open(results) as f:
269
275
  r = json.load(f)
270
276
 
271
- self.assertTrue(r["opacity_curve_found"],
272
- f"No opacity curve. Samples: {r.get('samples')}")
277
+ self.assertTrue(
278
+ r["opacity_curve_found"], f"No opacity curve. Samples: {r.get('samples')}"
279
+ )
273
280
 
274
281
  # Maya-authored: fade-in 10 frames, fade-out 60 frames. At 24 fps
275
282
  # that's 0.417s and 2.5s respectively. FBX/Unity may massage
@@ -280,22 +287,27 @@ class TestFadeWindowPreservation(unittest.TestCase):
280
287
  logger.info(f"fade_in={fade_in:.4f}s fade_out={fade_out:.4f}s")
281
288
 
282
289
  self.assertAlmostEqual(
283
- fade_in, 10.0 / 24.0, delta=0.05,
290
+ fade_in,
291
+ 10.0 / 24.0,
292
+ delta=0.05,
284
293
  msg=f"Short fade-in collapsed: expected ~0.417s, got {fade_in}s. "
285
- f"Samples: {r.get('samples')}",
294
+ f"Samples: {r.get('samples')}",
286
295
  )
287
296
  self.assertAlmostEqual(
288
- fade_out, 60.0 / 24.0, delta=0.05,
297
+ fade_out,
298
+ 60.0 / 24.0,
299
+ delta=0.05,
289
300
  msg=f"Long fade-out collapsed: expected ~2.5s, got {fade_out}s. "
290
- f"This is the original BoolToFadeCurve bug — every fade "
291
- f"became 0.5s regardless of artist intent. "
292
- f"Samples: {r.get('samples')}",
301
+ f"This is the original BoolToFadeCurve bug — every fade "
302
+ f"became 0.5s regardless of artist intent. "
303
+ f"Samples: {r.get('samples')}",
293
304
  )
294
305
 
295
306
  # Sanity: the two fades must differ — if BoolToFadeCurve still
296
307
  # used a hardcoded constant, both would be 0.5s.
297
308
  self.assertGreater(
298
- fade_out, fade_in * 2,
309
+ fade_out,
310
+ fade_in * 2,
299
311
  f"Fades collapsed to the same length (in={fade_in}, out={fade_out}) "
300
312
  f"— BoolToFadeCurve is not using authored key spacing.",
301
313
  )
@@ -17,8 +17,8 @@ visibility-related properties (m_IsActive, m_Enabled, Visibility) and
17
17
  reports which object paths have them, how many keys, and what values
18
18
  the visibility has at specific sample frames.
19
19
  """
20
+
20
21
  import os
21
- import sys
22
22
  import json
23
23
  import shutil
24
24
  import unittest
@@ -45,7 +45,7 @@ FBX_PATH = asset_path("fixture_keyed_visibility.fbx")
45
45
  #: fixture names resolving for one release).
46
46
  FBX_ASSET_NAME = "fixture_keyed_visibility.fbx"
47
47
 
48
- # Template CS files — need RenderOpacityController for the importer
48
+ # Template CS files — need RenderEffectsController for the importer
49
49
  TEMPLATE_DIR = os.path.join(
50
50
  os.path.dirname(__file__), os.pardir, "unitytk", "templates"
51
51
  )
@@ -393,9 +393,10 @@ def _sources_available() -> bool:
393
393
  return os.path.isfile(FBX_PATH)
394
394
 
395
395
 
396
- @EnvSkip.unless(_sources_available(),
397
- "keyed-visibility fixture FBX not accessible "
398
- "(set UNITYTK_TEST_ASSETS)")
396
+ @EnvSkip.unless(
397
+ _sources_available(),
398
+ "keyed-visibility fixture FBX not accessible (set UNITYTK_TEST_ASSETS)",
399
+ )
399
400
  @EnvSkip.unless(_find_unity_exe(), "no Unity editor found")
400
401
  class TestKeyedVisibility(unittest.TestCase):
401
402
  """Verify all keyed visibility from the fixture FBX imports correctly
@@ -431,7 +432,7 @@ class TestKeyedVisibility(unittest.TestCase):
431
432
 
432
433
  # === Step 1: Create fresh Unity project ===
433
434
  logger.info(f"Creating Unity project: {self.project_path}")
434
- proc = subprocess.run(
435
+ subprocess.run(
435
436
  [
436
437
  unity_exe,
437
438
  "-createProject",
@@ -455,7 +456,7 @@ class TestKeyedVisibility(unittest.TestCase):
455
456
  os.makedirs(scripts_dir, exist_ok=True)
456
457
  for cs_name in [
457
458
  "UnitytkSettings.cs",
458
- "RenderOpacityController.cs",
459
+ "RenderEffectsController.cs",
459
460
  "AudioEventController.cs",
460
461
  ]:
461
462
  src = os.path.join(TEMPLATE_DIR, cs_name)
@@ -496,7 +497,7 @@ class TestKeyedVisibility(unittest.TestCase):
496
497
  logger.info(
497
498
  "Launching Unity batch-mode verification (this may take a few minutes)..."
498
499
  )
499
- proc = subprocess.run(
500
+ subprocess.run(
500
501
  [
501
502
  unity_exe,
502
503
  "-projectPath",