unitytk 0.0.9__tar.gz → 0.0.10__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.0.9/unitytk.egg-info → unitytk-0.0.10}/PKG-INFO +1 -1
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_bool_to_fade_curve.py +16 -19
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_controller_runtime_integration.py +40 -22
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_deploy_templates.py +92 -12
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_fade_window_preservation.py +32 -20
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_keyed_visibility.py +9 -8
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_multi_section_audio_integration.py +194 -130
- unitytk-0.0.10/test/test_render_effects_e2e.py +413 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_render_opacity_controller.py +17 -16
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_render_opacity_e2e.py +50 -33
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_run_tests.py +106 -1
- unitytk-0.0.10/test/test_shadow_plane_integration.py +754 -0
- unitytk-0.0.10/test/test_shadow_plane_runtime_integration.py +1192 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_shared_material_standalone.py +11 -12
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_standalone_enduser.py +151 -98
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_temp_projects.py +67 -1
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/__init__.py +1 -1
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/template_deployer.py +81 -22
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/README.md +18 -11
- unitytk-0.0.10/unitytk/templates/RenderEffectsController.cs +733 -0
- unitytk-0.0.10/unitytk/templates/ShadowPlane.shader +230 -0
- unitytk-0.0.10/unitytk/templates/ShadowPlaneController.cs +1209 -0
- unitytk-0.0.10/unitytk/templates/ShadowPlaneHorizon.hlsl +471 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/UnitytkSettings.cs +40 -3
- {unitytk-0.0.9 → unitytk-0.0.10/unitytk.egg-info}/PKG-INFO +1 -1
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk.egg-info/SOURCES.txt +5 -1
- unitytk-0.0.9/test/test_shadow_plane_integration.py +0 -404
- unitytk-0.0.9/unitytk/templates/RenderOpacityController.cs +0 -611
- unitytk-0.0.9/unitytk/templates/ShadowPlaneController.cs +0 -307
- {unitytk-0.0.9 → unitytk-0.0.10}/LICENSE +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/MANIFEST.in +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/docs/README.md +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/pyproject.toml +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/setup.cfg +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_asset_delivery.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_audio_events_integration.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_base_test.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_clip_name_contract_integration.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_emissive_groups_integration.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_launcher.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_lightmap_metadata_integration.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_render_opacity_integration.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_scene_builder.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_shot_metadata_integration.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/test/test_shots_audio_sidebyside_integration.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/asset_delivery.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/launcher.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/scene_builder.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/AudioEventController.cs +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/EmissiveGroupController.cs +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/EmissiveGroups.hlsl +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/EmissiveGroupsLit.shader +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/LightmapMetadataController.cs +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/M3trik.Unitytk.asmdef +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/ShotMetadataController.cs +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk/templates/__init__.py +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk.egg-info/dependency_links.txt +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk.egg-info/requires.txt +0 -0
- {unitytk-0.0.9 → unitytk-0.0.10}/unitytk.egg-info/top_level.txt +0 -0
|
@@ -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 ``
|
|
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
|
-
#
|
|
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
|
-
|
|
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 = [
|
|
271
|
-
|
|
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
|
-
``
|
|
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<
|
|
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"),
|
|
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,
|
|
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(
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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),
|
|
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
|
-
|
|
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(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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),
|
|
282
|
-
|
|
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),
|
|
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),
|
|
293
|
-
|
|
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),
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
139
|
+
comps = {c.key: c for c in TemplateDeployer.components()}
|
|
136
140
|
self.assertEqual(
|
|
137
|
-
sorted(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
"
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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(
|
|
272
|
-
|
|
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,
|
|
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
|
-
|
|
294
|
+
f"Samples: {r.get('samples')}",
|
|
286
295
|
)
|
|
287
296
|
self.assertAlmostEqual(
|
|
288
|
-
fade_out,
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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,
|
|
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
|
|
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(
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
500
|
+
subprocess.run(
|
|
500
501
|
[
|
|
501
502
|
unity_exe,
|
|
502
503
|
"-projectPath",
|