unitytk 0.0.4__tar.gz → 0.0.6__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.4/unitytk.egg-info → unitytk-0.0.6}/PKG-INFO +2 -2
- {unitytk-0.0.4 → unitytk-0.0.6}/docs/README.md +1 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_audio_events_integration.py +5 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_clip_name_contract_integration.py +5 -1
- unitytk-0.0.6/test/test_controller_runtime_integration.py +323 -0
- unitytk-0.0.6/test/test_emissive_groups_integration.py +438 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_lightmap_metadata_integration.py +5 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_render_opacity_controller.py +46 -2
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_render_opacity_e2e.py +5 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_render_opacity_integration.py +5 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_shadow_plane_integration.py +5 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_shot_metadata_integration.py +5 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_shots_audio_sidebyside_integration.py +5 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/__init__.py +1 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/EmissiveGroupController.cs +77 -16
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/EmissiveGroupsLit.shader +7 -1
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/README.md +12 -3
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/RenderOpacityController.cs +87 -11
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/ShadowPlaneController.cs +6 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/UnitytkSettings.cs +20 -0
- {unitytk-0.0.4 → unitytk-0.0.6/unitytk.egg-info}/PKG-INFO +2 -2
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk.egg-info/SOURCES.txt +2 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/LICENSE +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/MANIFEST.in +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/pyproject.toml +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/setup.cfg +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_asset_delivery.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_bool_to_fade_curve.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_c130_fcr_integration.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_c5m_visibility.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_deploy_templates.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_fade_window_preservation.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_launcher.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_scene_builder.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_shared_material_standalone.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/test/test_standalone_enduser.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/asset_delivery.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/launcher.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/scene_builder.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/template_deployer.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/AudioEventController.cs +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/EmissiveGroups.hlsl +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/LightmapMetadataController.cs +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/M3trik.Unitytk.asmdef +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/ShotMetadataController.cs +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk/templates/__init__.py +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk.egg-info/dependency_links.txt +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/unitytk.egg-info/requires.txt +0 -0
- {unitytk-0.0.4 → unitytk-0.0.6}/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.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: A modular Python toolkit for Unity interaction.
|
|
5
5
|
Author-email: Ryan Simpson <m3trik@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -15,7 +15,7 @@ License-File: LICENSE
|
|
|
15
15
|
Requires-Dist: pythontk>=0.7.71
|
|
16
16
|
Dynamic: license-file
|
|
17
17
|
|
|
18
|
-
[](../test/)
|
|
19
19
|
|
|
20
20
|
# unitytk
|
|
21
21
|
|
|
@@ -461,7 +461,11 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
461
461
|
|
|
462
462
|
@classmethod
|
|
463
463
|
def setUpClass(cls):
|
|
464
|
-
|
|
464
|
+
# In-repo scratch (see CLAUDE.md), not the system temp dir —
|
|
465
|
+
# these preserve a full Unity project on purpose.
|
|
466
|
+
_temp_base = os.path.join(os.path.dirname(__file__), "temp_tests")
|
|
467
|
+
os.makedirs(_temp_base, exist_ok=True)
|
|
468
|
+
cls.temp_dir = tempfile.mkdtemp(prefix="audio_events_integ_", dir=_temp_base)
|
|
465
469
|
cls.fbx_path = None
|
|
466
470
|
cls.maya_results = None
|
|
467
471
|
cls.unity_results = None
|
|
@@ -198,7 +198,11 @@ class TestClipNameContract(unittest.TestCase):
|
|
|
198
198
|
|
|
199
199
|
@classmethod
|
|
200
200
|
def setUpClass(cls):
|
|
201
|
-
|
|
201
|
+
# In-repo scratch (see CLAUDE.md), not the system temp dir —
|
|
202
|
+
# these preserve a full Unity project on purpose.
|
|
203
|
+
_temp_base = os.path.join(os.path.dirname(__file__), "temp_tests")
|
|
204
|
+
os.makedirs(_temp_base, exist_ok=True)
|
|
205
|
+
cls.temp_dir = tempfile.mkdtemp(prefix="clip_name_contract_", dir=_temp_base)
|
|
202
206
|
cls.fbx_path = None
|
|
203
207
|
cls.maya_results = None
|
|
204
208
|
cls.unity_results = None
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# !/usr/bin/python
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
"""Integration test: unitytk runtime controller behaviour inside a real Unity.
|
|
4
|
+
|
|
5
|
+
The import-side templates are well covered by the per-channel integration
|
|
6
|
+
tests, but the *runtime* half (what the MonoBehaviours actually do to a
|
|
7
|
+
Renderer) had no live coverage at all. This test closes that gap with a
|
|
8
|
+
Maya-free batch Unity run:
|
|
9
|
+
|
|
10
|
+
1. Create a Unity project and deploy the real template package.
|
|
11
|
+
2. In an Editor script, build a cube with a Standard material, drive
|
|
12
|
+
``RenderOpacityController`` / ``EmissiveGroupController`` directly, and
|
|
13
|
+
read the resulting MaterialPropertyBlock back.
|
|
14
|
+
|
|
15
|
+
Covers three behaviours that inspection alone can't settle:
|
|
16
|
+
|
|
17
|
+
a. **Built-in RP color-property resolution.** A fresh Unity project is
|
|
18
|
+
Built-in RP, whose materials expose ``_Color`` — not the ``_BaseColor``
|
|
19
|
+
the component serializes by default. When the two disagree the apply
|
|
20
|
+
returns early and the object hard-pops at zero instead of fading.
|
|
21
|
+
``Reset()`` auto-detects on the normal import path (measured in
|
|
22
|
+
``test_render_opacity_controller``), but it cannot help when the
|
|
23
|
+
material arrives after the component, when a prefab authored under one
|
|
24
|
+
pipeline is opened under another, or in a player build where ``Reset``
|
|
25
|
+
does not exist at all — so the runtime resolution is what actually
|
|
26
|
+
guarantees it. This test forces the serialized ``_BaseColor`` back after
|
|
27
|
+
``AddComponent`` to measure that path specifically.
|
|
28
|
+
b. **Auto-hide at zero**, and restoring visibility above zero.
|
|
29
|
+
c. **Property-block co-existence.** Both controllers write to the same
|
|
30
|
+
Renderer's MaterialPropertyBlock. Each must read-modify-write it; a
|
|
31
|
+
blanket ``MaterialPropertyBlock.Clear()`` on teardown would wipe the
|
|
32
|
+
other's data.
|
|
33
|
+
|
|
34
|
+
Requires:
|
|
35
|
+
- Unity Hub with at least one editor installed (skips on license wall)
|
|
36
|
+
"""
|
|
37
|
+
import os
|
|
38
|
+
import sys
|
|
39
|
+
import json
|
|
40
|
+
import tempfile
|
|
41
|
+
import unittest
|
|
42
|
+
import subprocess
|
|
43
|
+
import logging
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
scripts_dir = r"O:\Cloud\Code\_scripts"
|
|
48
|
+
if scripts_dir not in sys.path:
|
|
49
|
+
sys.path.insert(0, scripts_dir)
|
|
50
|
+
|
|
51
|
+
from unitytk import UnityLauncher, UnityFinder, TemplateDeployer
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _unity_available() -> bool:
|
|
55
|
+
return bool(UnityFinder.find_editors())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ======================================================================
|
|
59
|
+
# Unity-side verifier
|
|
60
|
+
# ======================================================================
|
|
61
|
+
|
|
62
|
+
UNITY_VERIFIER_CS = r"""
|
|
63
|
+
using UnityEngine;
|
|
64
|
+
using UnityEditor;
|
|
65
|
+
using System.Globalization;
|
|
66
|
+
using System.IO;
|
|
67
|
+
using System.Text;
|
|
68
|
+
|
|
69
|
+
public class ControllerRuntimeVerifier
|
|
70
|
+
{
|
|
71
|
+
/// <summary>Invariant-culture float, so a European locale can't emit
|
|
72
|
+
/// "0,5" and hand the Python side unparseable JSON.</summary>
|
|
73
|
+
static string F(float v) => v.ToString("R", CultureInfo.InvariantCulture);
|
|
74
|
+
|
|
75
|
+
// Called via -executeMethod ControllerRuntimeVerifier.Verify
|
|
76
|
+
public static void Verify()
|
|
77
|
+
{
|
|
78
|
+
string resultsFile = @"__RESULTS_PATH__";
|
|
79
|
+
var sb = new StringBuilder();
|
|
80
|
+
sb.AppendLine("{");
|
|
81
|
+
|
|
82
|
+
// ---- Setup: cube + Built-in RP Standard material ----------------
|
|
83
|
+
var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
|
84
|
+
var rend = go.GetComponent<Renderer>();
|
|
85
|
+
var mat = new Material(Shader.Find("Standard"));
|
|
86
|
+
mat.name = "OpacityRuntimeTestMat";
|
|
87
|
+
rend.sharedMaterial = mat;
|
|
88
|
+
|
|
89
|
+
// Documents the premise: a fresh project really is Built-in RP, so
|
|
90
|
+
// the serialized "_BaseColor" default names a property that is absent.
|
|
91
|
+
sb.AppendLine(" \"mat_has_base_color\": "
|
|
92
|
+
+ (mat.HasProperty("_BaseColor") ? "true," : "false,"));
|
|
93
|
+
sb.AppendLine(" \"mat_has_color\": "
|
|
94
|
+
+ (mat.HasProperty("_Color") ? "true," : "false,"));
|
|
95
|
+
|
|
96
|
+
// ---- (a) Partial fade must reach the property block --------------
|
|
97
|
+
var opa = go.AddComponent<RenderOpacityController>();
|
|
98
|
+
// AddComponent in the editor DOES run Reset(), which auto-detects the
|
|
99
|
+
// property — recorded here, but it is not something the import path can
|
|
100
|
+
// rely on (it only helps when a material is already assigned at that
|
|
101
|
+
// moment). Force the serialized URP default back so what follows
|
|
102
|
+
// measures the RUNTIME resolution, which always applies.
|
|
103
|
+
sb.AppendLine(" \"reset_autodetected\": "
|
|
104
|
+
+ (opa.colorProperty == "_Color" ? "true," : "false,"));
|
|
105
|
+
opa.colorProperty = "_BaseColor";
|
|
106
|
+
|
|
107
|
+
opa.opacity = 0.5f;
|
|
108
|
+
opa.ApplyNow();
|
|
109
|
+
|
|
110
|
+
var mpb = new MaterialPropertyBlock();
|
|
111
|
+
rend.GetPropertyBlock(mpb);
|
|
112
|
+
sb.AppendLine(" \"alpha_applied\": " + F(mpb.GetColor("_Color").a) + ",");
|
|
113
|
+
|
|
114
|
+
// ---- (b) Auto-hide at zero, restore above zero -------------------
|
|
115
|
+
opa.opacity = 0f;
|
|
116
|
+
opa.ApplyNow();
|
|
117
|
+
sb.AppendLine(" \"hidden_at_zero\": " + (!rend.enabled ? "true," : "false,"));
|
|
118
|
+
|
|
119
|
+
opa.opacity = 1f;
|
|
120
|
+
opa.ApplyNow();
|
|
121
|
+
sb.AppendLine(" \"restored_above_zero\": " + (rend.enabled ? "true," : "false,"));
|
|
122
|
+
|
|
123
|
+
// Back to a partial fade for the co-existence check.
|
|
124
|
+
opa.opacity = 0.5f;
|
|
125
|
+
opa.ApplyNow();
|
|
126
|
+
|
|
127
|
+
// ---- (c) Second controller on the same Renderer ------------------
|
|
128
|
+
var emi = go.AddComponent<EmissiveGroupController>();
|
|
129
|
+
emi.groups.Add(new EmissiveGroup {
|
|
130
|
+
name = "headlights", slot = 0, weight = 0.25f });
|
|
131
|
+
emi.Rebuild();
|
|
132
|
+
emi.ApplyNow();
|
|
133
|
+
|
|
134
|
+
rend.GetPropertyBlock(mpb);
|
|
135
|
+
sb.AppendLine(" \"alpha_after_emissive\": "
|
|
136
|
+
+ F(mpb.GetColor("_Color").a) + ",");
|
|
137
|
+
sb.AppendLine(" \"weight_after_emissive\": "
|
|
138
|
+
+ F(mpb.GetVector("_GroupWeights").x) + ",");
|
|
139
|
+
|
|
140
|
+
// Tearing the opacity controller down must leave the emissive weights
|
|
141
|
+
// alone. Measured: Unity does NOT call OnDisable for a plain
|
|
142
|
+
// (non-[ExecuteAlways]) MonoBehaviour in edit mode, so this does not
|
|
143
|
+
// reach ClearOverrides — the alpha readback below is what proves that,
|
|
144
|
+
// and the Python side asserts it so the day it changes we find out.
|
|
145
|
+
Object.DestroyImmediate(opa);
|
|
146
|
+
rend.GetPropertyBlock(mpb);
|
|
147
|
+
sb.AppendLine(" \"alpha_after_destroy\": "
|
|
148
|
+
+ F(mpb.GetColor("_Color").a) + ",");
|
|
149
|
+
sb.AppendLine(" \"weight_after_destroy\": "
|
|
150
|
+
+ F(mpb.GetVector("_GroupWeights").x));
|
|
151
|
+
|
|
152
|
+
Object.DestroyImmediate(go);
|
|
153
|
+
|
|
154
|
+
sb.AppendLine("}");
|
|
155
|
+
File.WriteAllText(resultsFile, sb.ToString());
|
|
156
|
+
Debug.Log("CONTROLLER RUNTIME TEST COMPLETE. Results at: " + resultsFile);
|
|
157
|
+
EditorApplication.Exit(0);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ======================================================================
|
|
164
|
+
# Test class
|
|
165
|
+
# ======================================================================
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class TestControllerRuntimeIntegration(unittest.TestCase):
|
|
169
|
+
"""unitytk MonoBehaviour runtime behaviour, read back from a live Unity."""
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def setUpClass(cls):
|
|
173
|
+
# In-repo scratch (see CLAUDE.md), not the system temp dir.
|
|
174
|
+
_temp_base = os.path.join(os.path.dirname(__file__), "temp_tests")
|
|
175
|
+
os.makedirs(_temp_base, exist_ok=True)
|
|
176
|
+
cls.temp_dir = tempfile.mkdtemp(prefix="ctrl_runtime_integ_", dir=_temp_base)
|
|
177
|
+
cls.unity_results = None
|
|
178
|
+
logger.info(f"Temp dir: {cls.temp_dir}")
|
|
179
|
+
|
|
180
|
+
@classmethod
|
|
181
|
+
def tearDownClass(cls):
|
|
182
|
+
logger.info(f"Results preserved in: {cls.temp_dir}")
|
|
183
|
+
|
|
184
|
+
@unittest.skipUnless(_unity_available(), "No Unity editor found")
|
|
185
|
+
def test_01_unity_runtime(self):
|
|
186
|
+
"""Drive both controllers in a real Unity; capture the readback."""
|
|
187
|
+
editor = os.environ.get("UNITYTK_TEST_EDITOR")
|
|
188
|
+
launcher = UnityLauncher(editor) if editor else UnityLauncher()
|
|
189
|
+
project_path = os.path.join(self.temp_dir, "UnityProject")
|
|
190
|
+
if not launcher.create_project(project_path, batch_mode=True):
|
|
191
|
+
self.skipTest(
|
|
192
|
+
"Unity batch project creation failed — typically no batch-mode "
|
|
193
|
+
"license on this machine."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Deploy the real templates -- the full embedded UPM package.
|
|
197
|
+
TemplateDeployer.deploy_package(project_path)
|
|
198
|
+
|
|
199
|
+
editor_path = os.path.join(project_path, "Assets", "Editor")
|
|
200
|
+
os.makedirs(editor_path, exist_ok=True)
|
|
201
|
+
results_path = os.path.join(self.temp_dir, "unity_results.json")
|
|
202
|
+
with open(
|
|
203
|
+
os.path.join(editor_path, "ControllerRuntimeVerifier.cs"), "w",
|
|
204
|
+
encoding="utf-8",
|
|
205
|
+
) as f:
|
|
206
|
+
f.write(UNITY_VERIFIER_CS.replace("__RESULTS_PATH__", results_path))
|
|
207
|
+
|
|
208
|
+
log_path = os.path.join(self.temp_dir, "unity_log.txt")
|
|
209
|
+
launcher.project_path = project_path
|
|
210
|
+
proc = launcher.launch_editor(
|
|
211
|
+
batch_mode=True,
|
|
212
|
+
execute_method="ControllerRuntimeVerifier.Verify",
|
|
213
|
+
log_file=log_path,
|
|
214
|
+
extra_args=["-quit"],
|
|
215
|
+
detached=False,
|
|
216
|
+
)
|
|
217
|
+
returncode = None
|
|
218
|
+
if proc:
|
|
219
|
+
try:
|
|
220
|
+
returncode = proc.wait(timeout=300)
|
|
221
|
+
except subprocess.TimeoutExpired:
|
|
222
|
+
proc.kill()
|
|
223
|
+
raise TimeoutError("Unity timed out")
|
|
224
|
+
|
|
225
|
+
if not os.path.exists(results_path):
|
|
226
|
+
log_text = ""
|
|
227
|
+
if os.path.exists(log_path):
|
|
228
|
+
with open(log_path, encoding="utf-8", errors="ignore") as fh:
|
|
229
|
+
log_text = fh.read()
|
|
230
|
+
if UnityLauncher.is_license_failure(returncode, log_text):
|
|
231
|
+
self.skipTest(
|
|
232
|
+
"Unity exited without a valid batch license "
|
|
233
|
+
f"(rc={returncode}) — see {log_path}"
|
|
234
|
+
)
|
|
235
|
+
self.fail(
|
|
236
|
+
f"unity_results.json not created (Unity rc={returncode}) — "
|
|
237
|
+
f"check {log_path}"
|
|
238
|
+
)
|
|
239
|
+
with open(results_path) as f:
|
|
240
|
+
self.__class__.unity_results = json.load(f)
|
|
241
|
+
logger.info(
|
|
242
|
+
f"Unity results:\n{json.dumps(self.__class__.unity_results, indent=2)}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@unittest.skipUnless(_unity_available(), "No Unity editor found")
|
|
246
|
+
def test_02_color_property_resolves_on_builtin(self):
|
|
247
|
+
"""The fade reaches the material even though the default names _BaseColor."""
|
|
248
|
+
unity = self.__class__.unity_results
|
|
249
|
+
if not unity:
|
|
250
|
+
self.skipTest("Unity results not available — test_01 must run first")
|
|
251
|
+
|
|
252
|
+
# Premise: a fresh batch project is Built-in RP.
|
|
253
|
+
self.assertTrue(unity.get("mat_has_color"),
|
|
254
|
+
"Standard material should expose _Color")
|
|
255
|
+
self.assertFalse(unity.get("mat_has_base_color"),
|
|
256
|
+
"premise broken: this project is no longer Built-in RP")
|
|
257
|
+
|
|
258
|
+
# The regression: colorProperty is "_BaseColor" (the URP default the
|
|
259
|
+
# component serializes) and the material has no such property, so
|
|
260
|
+
# without runtime resolution the alpha never reaches it and this is 0.0.
|
|
261
|
+
self.assertAlmostEqual(
|
|
262
|
+
unity.get("alpha_applied", 0.0), 0.5, places=3,
|
|
263
|
+
msg="opacity never reached the material — colorProperty did not "
|
|
264
|
+
"resolve to the Built-in _Color",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
@unittest.skipUnless(_unity_available(), "No Unity editor found")
|
|
268
|
+
def test_03_auto_hide_at_zero(self):
|
|
269
|
+
"""Zero opacity disables the Renderer; raising it restores visibility."""
|
|
270
|
+
unity = self.__class__.unity_results
|
|
271
|
+
if not unity:
|
|
272
|
+
self.skipTest("Unity results not available — test_01 must run first")
|
|
273
|
+
|
|
274
|
+
self.assertTrue(unity.get("hidden_at_zero"),
|
|
275
|
+
"Renderer should be disabled at zero opacity")
|
|
276
|
+
self.assertTrue(unity.get("restored_above_zero"),
|
|
277
|
+
"Renderer should be re-enabled above zero opacity")
|
|
278
|
+
|
|
279
|
+
@unittest.skipUnless(_unity_available(), "No Unity editor found")
|
|
280
|
+
def test_04_property_block_coexistence(self):
|
|
281
|
+
"""Two controllers share one Renderer's block without clobbering."""
|
|
282
|
+
unity = self.__class__.unity_results
|
|
283
|
+
if not unity:
|
|
284
|
+
self.skipTest("Unity results not available — test_01 must run first")
|
|
285
|
+
|
|
286
|
+
# Emissive writing _GroupWeights must not drop the opacity alpha...
|
|
287
|
+
self.assertAlmostEqual(
|
|
288
|
+
unity.get("alpha_after_emissive", 0.0), 0.5, places=3,
|
|
289
|
+
msg="EmissiveGroupController wiped RenderOpacityController's alpha",
|
|
290
|
+
)
|
|
291
|
+
# ...and vice versa.
|
|
292
|
+
self.assertAlmostEqual(
|
|
293
|
+
unity.get("weight_after_emissive", 0.0), 0.25, places=3,
|
|
294
|
+
msg="_GroupWeights did not reach the property block",
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Destroying the controller must leave the weights intact either way.
|
|
298
|
+
self.assertAlmostEqual(
|
|
299
|
+
unity.get("weight_after_destroy", 0.0), 0.25, places=3,
|
|
300
|
+
msg="destroying RenderOpacityController wiped the emissive weights",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# KNOWN COVERAGE GAP, asserted rather than assumed: Unity does not call
|
|
304
|
+
# OnDisable for a plain (non-[ExecuteAlways]) MonoBehaviour in edit
|
|
305
|
+
# mode, so DestroyImmediate above does NOT reach ClearOverrides — the
|
|
306
|
+
# alpha stays at the value we last applied instead of reverting to the
|
|
307
|
+
# material's own. That makes the weight assertion above necessary but
|
|
308
|
+
# not sufficient: the teardown path itself (read-modify-write of the
|
|
309
|
+
# property block, rather than a blanket Clear() that would wipe the
|
|
310
|
+
# emissive weights) is only exercised in play mode, which batch
|
|
311
|
+
# -executeMethod cannot enter synchronously. Pinned here so that if a
|
|
312
|
+
# future Unity DOES start firing OnDisable in edit mode, this test
|
|
313
|
+
# fails loudly and the assertion above becomes real coverage.
|
|
314
|
+
self.assertAlmostEqual(
|
|
315
|
+
unity.get("alpha_after_destroy", -1.0), 0.5, places=3,
|
|
316
|
+
msg="OnDisable now fires in edit mode — retarget this test at the "
|
|
317
|
+
"teardown path, which is finally reachable",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
323
|
+
unittest.main(verbosity=2)
|