unitytk 0.0.15__tar.gz → 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unitytk-0.0.15 → unitytk-0.1.0}/MANIFEST.in +1 -1
- {unitytk-0.0.15/unitytk.egg-info → unitytk-0.1.0}/PKG-INFO +2 -2
- {unitytk-0.0.15 → unitytk-0.1.0}/pyproject.toml +10 -7
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_articulated_rig_runtime.py +89 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_audio_events_integration.py +11 -16
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_base_test.py +85 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_clip_name_contract_integration.py +10 -15
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_controller_runtime_integration.py +1 -16
- unitytk-0.1.0/test/test_deploy_templates.py +1141 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_emissive_groups_integration.py +5 -16
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_fade_window_preservation.py +12 -6
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_keyed_visibility.py +17 -15
- unitytk-0.1.0/test/test_lightmap_metadata_integration.py +1081 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_multi_section_audio_integration.py +16 -9
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_render_effects_e2e.py +87 -28
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_render_opacity_controller.py +12 -13
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_render_opacity_e2e.py +22 -22
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_render_opacity_integration.py +14 -14
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_shadow_plane_integration.py +5 -2
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_shadow_plane_runtime_integration.py +108 -2
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_shared_material_standalone.py +10 -13
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_shot_metadata_integration.py +345 -45
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_shots_audio_sidebyside_integration.py +12 -6
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_standalone_enduser.py +14 -28
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/__init__.py +1 -1
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/template_deployer.py +413 -135
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/ArticulatedRig}/ArticulatedRigController.cs +143 -83
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/ArticulatedRigController.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/ArticulatedRigPointerGrab.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/ArticulationRecord.g.cs +199 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/ArticulationRecord.g.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRGrab.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/XR/ArticulatedRigXRSetup.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/XR/M3trik.Unitytk.XR.asmdef.meta +7 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig/XR.meta +8 -0
- unitytk-0.1.0/unitytk/templates/ArticulatedRig.meta +8 -0
- unitytk-0.1.0/unitytk/templates/AudioEvent/AudioEventController.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/AudioEvent.meta +8 -0
- unitytk-0.1.0/unitytk/templates/EmissiveGroup/EmissiveGroupController.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/EmissiveGroup/EmissiveGroups.hlsl.meta +7 -0
- unitytk-0.1.0/unitytk/templates/EmissiveGroup/EmissiveGroupsLit.shader.meta +9 -0
- unitytk-0.1.0/unitytk/templates/EmissiveGroup.meta +8 -0
- unitytk-0.1.0/unitytk/templates/LightmapMetadata/LightmapMetadataController.cs +1127 -0
- unitytk-0.1.0/unitytk/templates/LightmapMetadata/LightmapMetadataController.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/LightmapMetadata.meta +8 -0
- unitytk-0.1.0/unitytk/templates/M3trik.Unitytk.asmdef.meta +7 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/templates/README.md +95 -26
- unitytk-0.1.0/unitytk/templates/README.md.meta +7 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/RenderEffects}/RenderEffectsController.cs +52 -20
- unitytk-0.1.0/unitytk/templates/RenderEffects/RenderEffectsController.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/RenderEffects.meta +8 -0
- unitytk-0.1.0/unitytk/templates/ShadowPlane/ShadowPlane.shader.meta +9 -0
- unitytk-0.1.0/unitytk/templates/ShadowPlane/ShadowPlaneController.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/ShadowPlane/ShadowPlaneHorizon.hlsl.meta +7 -0
- unitytk-0.1.0/unitytk/templates/ShadowPlane.meta +8 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/ShotMetadata}/ShotMetadataController.cs +135 -3
- unitytk-0.1.0/unitytk/templates/ShotMetadata/ShotMetadataController.cs.meta +11 -0
- unitytk-0.1.0/unitytk/templates/ShotMetadata.meta +8 -0
- unitytk-0.1.0/unitytk/templates/UnitytkSettings.cs.meta +11 -0
- {unitytk-0.0.15 → unitytk-0.1.0/unitytk.egg-info}/PKG-INFO +2 -2
- unitytk-0.1.0/unitytk.egg-info/SOURCES.txt +88 -0
- unitytk-0.1.0/unitytk.egg-info/requires.txt +1 -0
- unitytk-0.0.15/test/test_deploy_templates.py +0 -620
- unitytk-0.0.15/test/test_lightmap_metadata_integration.py +0 -588
- unitytk-0.0.15/unitytk/templates/LightmapMetadataController.cs +0 -551
- unitytk-0.0.15/unitytk.egg-info/SOURCES.txt +0 -60
- unitytk-0.0.15/unitytk.egg-info/requires.txt +0 -1
- {unitytk-0.0.15 → unitytk-0.1.0}/LICENSE +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/docs/README.md +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/setup.cfg +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_asset_delivery.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_bool_to_fade_curve.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_launcher.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_run_tests.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_scene_builder.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/test/test_temp_projects.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/asset_delivery.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/launcher.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/scene_builder.py +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/ArticulatedRig}/ArticulatedRigPointerGrab.cs +0 -0
- {unitytk-0.0.15/unitytk/templates/ArticulatedRigXR → unitytk-0.1.0/unitytk/templates/ArticulatedRig/XR}/ArticulatedRigXRGrab.cs +0 -0
- {unitytk-0.0.15/unitytk/templates/ArticulatedRigXR → unitytk-0.1.0/unitytk/templates/ArticulatedRig/XR}/ArticulatedRigXRSetup.cs +0 -0
- {unitytk-0.0.15/unitytk/templates/ArticulatedRigXR → unitytk-0.1.0/unitytk/templates/ArticulatedRig/XR}/M3trik.Unitytk.XR.asmdef +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/AudioEvent}/AudioEventController.cs +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/EmissiveGroup}/EmissiveGroupController.cs +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/EmissiveGroup}/EmissiveGroups.hlsl +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/EmissiveGroup}/EmissiveGroupsLit.shader +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/templates/M3trik.Unitytk.asmdef +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/ShadowPlane}/ShadowPlane.shader +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/ShadowPlane}/ShadowPlaneController.cs +0 -0
- {unitytk-0.0.15/unitytk/templates → unitytk-0.1.0/unitytk/templates/ShadowPlane}/ShadowPlaneHorizon.hlsl +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/templates/UnitytkSettings.cs +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk/templates/__init__.py +0 -0
- {unitytk-0.0.15 → unitytk-0.1.0}/unitytk.egg-info/dependency_links.txt +0 -0
- {unitytk-0.0.15 → unitytk-0.1.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.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: A modular Python toolkit for Unity interaction.
|
|
5
5
|
Author-email: Ryan Simpson <m3trik@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -13,7 +13,7 @@ Classifier: Operating System :: POSIX :: Linux
|
|
|
13
13
|
Requires-Python: >=3.9
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: pythontk>=0.
|
|
16
|
+
Requires-Dist: pythontk>=0.13.0
|
|
17
17
|
Dynamic: license-file
|
|
18
18
|
|
|
19
19
|
[](../test/)
|
|
@@ -17,12 +17,15 @@ classifiers = [
|
|
|
17
17
|
]
|
|
18
18
|
# The floor is HAND-RATCHETED: unitytk is off-chain from m3trik/push.ps1, so no
|
|
19
19
|
# release cascade bumps it.
|
|
20
|
-
# pythontk 0.
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
20
|
+
# pythontk 0.13.0, the release this one ships and was tested with: the suite
|
|
21
|
+
# holds the templates to its `Conformance` cases and its articulation solve's
|
|
22
|
+
# halving step (both new in 0.13.0). The hard minimum is 0.11.3:
|
|
23
|
+
# TemplateDeployer builds its deprecation notices at import through
|
|
24
|
+
# `Deprecation.values(..., since=)` (new in 0.11.3). UnityFinder.find_editors
|
|
25
|
+
# needs 0.8.84: it scans through `AppLauncher.scan_install_dirs` (new in
|
|
26
|
+
# 0.8.72) and needs its pattern order to be a priority (0.8.84), so the Hub's
|
|
27
|
+
# chosen install location wins over the default.
|
|
28
|
+
dependencies = ["pythontk>=0.13.0"]
|
|
26
29
|
|
|
27
30
|
[project.urls]
|
|
28
31
|
Homepage = "https://github.com/m3trik/unitytk"
|
|
@@ -37,7 +40,7 @@ where = ["."]
|
|
|
37
40
|
include = ["unitytk*"]
|
|
38
41
|
|
|
39
42
|
[tool.setuptools.package-data]
|
|
40
|
-
"*" = ["*.ui", "*.json", "*.txt", "*.md", "*.cs", "*.hlsl", "*.shader", "*.asmdef"]
|
|
43
|
+
"*" = ["*.ui", "*.json", "*.txt", "*.md", "*.cs", "*.hlsl", "*.shader", "*.asmdef", "*.meta"]
|
|
41
44
|
|
|
42
45
|
[tool.pytest.ini_options]
|
|
43
46
|
testpaths = ["test"]
|
|
@@ -69,6 +69,7 @@ from base_test import EnvSkip, TempProjects, UnityEnv
|
|
|
69
69
|
|
|
70
70
|
import pythontk as ptk
|
|
71
71
|
from unitytk import UnityLauncher, UnityFinder, TemplateDeployer
|
|
72
|
+
from unitytk.template_deployer import UPM_PACKAGE_NAME
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
def _unity_available() -> bool:
|
|
@@ -433,9 +434,17 @@ class ControllerScenario:
|
|
|
433
434
|
}
|
|
434
435
|
)
|
|
435
436
|
s_end = self.path_states()[-1]
|
|
437
|
+
|
|
438
|
+
def cut_short(**field):
|
|
439
|
+
"""The record with one joint's ``t`` or ``q`` cut short."""
|
|
440
|
+
rig = json.loads(json.dumps(self.rig))
|
|
441
|
+
rig["joints"][1].update(field)
|
|
442
|
+
return json.dumps(dict(self.payload, rigs=[rig]))
|
|
443
|
+
|
|
436
444
|
return {
|
|
437
445
|
"record": json.dumps(self.payload),
|
|
438
446
|
"record_newer": json.dumps(dict(self.payload, version=2)),
|
|
447
|
+
"records_short": [cut_short(t=[1.0]), cut_short(q=[0.0, 0.0])],
|
|
439
448
|
"root": {
|
|
440
449
|
"p": list(self.ROOT["p"]),
|
|
441
450
|
"q": list(self.ROOT["q"]),
|
|
@@ -784,6 +793,7 @@ public class ArticulatedRigVerifier
|
|
|
784
793
|
catch (Exception e) { Put("inputs_error", S(e.ToString())); }
|
|
785
794
|
if (inputs != null)
|
|
786
795
|
{
|
|
796
|
+
Run("guids", () => Guids(L(inputs["guids"])));
|
|
787
797
|
Run("conformance", () => Conformance(O(inputs["doc"])));
|
|
788
798
|
Run("invalid", () => Invalid(L(inputs["invalid"])));
|
|
789
799
|
Run("boundary", () => Boundary(O(inputs["boundary"])));
|
|
@@ -806,6 +816,15 @@ public class ArticulatedRigVerifier
|
|
|
806
816
|
catch (Exception e) { Put(section + "_error", S(e.ToString())); }
|
|
807
817
|
}
|
|
808
818
|
|
|
819
|
+
// ---- 0. fixed GUIDs ---------------------------------------------------
|
|
820
|
+
// The editor's own answer, per deployed asset path (files and folders):
|
|
821
|
+
// the GUID committed in the bundle's .meta, unless Unity re-minted it.
|
|
822
|
+
static void Guids(IList<object> paths)
|
|
823
|
+
{
|
|
824
|
+
var rows = paths.Select(p => S((string)p) + ": " + S(AssetDatabase.AssetPathToGUID((string)p)));
|
|
825
|
+
Put("guids", "{" + string.Join(", ", rows) + "}");
|
|
826
|
+
}
|
|
827
|
+
|
|
809
828
|
// ---- 1. conformance ---------------------------------------------------
|
|
810
829
|
static void Conformance(IDictionary<string, object> doc)
|
|
811
830
|
{
|
|
@@ -1113,6 +1132,20 @@ public class ArticulatedRigVerifier
|
|
|
1113
1132
|
var futureCtrl = future.AddComponent<ArticulatedRigController>();
|
|
1114
1133
|
futureCtrl.record = (string)c["record_newer"];
|
|
1115
1134
|
Put("ctrl_newer", "{\"resolved\": " + B(futureCtrl.Resolve()) + ", \"rigs\": " + futureCtrl.RigCount + "}");
|
|
1135
|
+
|
|
1136
|
+
// A joint whose t or q is cut short is refused with a warning, never
|
|
1137
|
+
// thrown out of Resolve (the importer calls it: the import would fail).
|
|
1138
|
+
var cutRows = new List<string>();
|
|
1139
|
+
foreach (var raw in L(c["records_short"]))
|
|
1140
|
+
{
|
|
1141
|
+
var cut = new GameObject("CutShortModel");
|
|
1142
|
+
cut.transform.SetParent(root, false);
|
|
1143
|
+
var cutCtrl = cut.AddComponent<ArticulatedRigController>();
|
|
1144
|
+
cutCtrl.record = (string)raw;
|
|
1145
|
+
try { cutRows.Add("{\"resolved\": " + B(cutCtrl.Resolve()) + ", \"rigs\": " + cutCtrl.RigCount + "}"); }
|
|
1146
|
+
catch (Exception thrown) { cutRows.Add("{\"threw\": " + S(thrown.GetType().Name) + "}"); }
|
|
1147
|
+
}
|
|
1148
|
+
Put("ctrl_short", "[" + string.Join(",", cutRows) + "]");
|
|
1116
1149
|
UnityEngine.Object.DestroyImmediate(root.gameObject);
|
|
1117
1150
|
}
|
|
1118
1151
|
|
|
@@ -1407,6 +1440,22 @@ def _compile_errors(log_path: str) -> str:
|
|
|
1407
1440
|
return "\n".join(errors[:20])
|
|
1408
1441
|
|
|
1409
1442
|
|
|
1443
|
+
#: The embedded package's root as an AssetDatabase path.
|
|
1444
|
+
_PACKAGE_ASSET_ROOT = f"Packages/{UPM_PACKAGE_NAME}"
|
|
1445
|
+
|
|
1446
|
+
|
|
1447
|
+
def _bundle_assets():
|
|
1448
|
+
"""Every deployed bundle file and folder, as "/"-joined package paths."""
|
|
1449
|
+
names = TemplateDeployer._bundle_names()
|
|
1450
|
+
return list(names) + TemplateDeployer._folders(names)
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
def _committed_meta(name):
|
|
1454
|
+
"""Bundle asset *name*'s committed .meta, LF (as the deployer writes it)."""
|
|
1455
|
+
ref = TemplateDeployer._bundle_ref(name + ".meta")
|
|
1456
|
+
return ref.read_bytes().replace(b"\r\n", b"\n")
|
|
1457
|
+
|
|
1458
|
+
|
|
1410
1459
|
def _run_unity(test, temp_dir, execute_method, prepare, timeout=600):
|
|
1411
1460
|
"""Create a project, let *prepare(project_path)* stage it, run the
|
|
1412
1461
|
verifier; returns the parsed results (or skips on the license wall)."""
|
|
@@ -1552,6 +1601,7 @@ class TestArticulatedRigRuntime(unittest.TestCase):
|
|
|
1552
1601
|
variants[key] = os.path.join(cls.temp_dir, f"fixture_{key}.fbx.txt")
|
|
1553
1602
|
cls.fixture.fbx(**kwargs).write(variants[key])
|
|
1554
1603
|
inputs = {
|
|
1604
|
+
"guids": [f"{_PACKAGE_ASSET_ROOT}/{n}" for n in _bundle_assets()],
|
|
1555
1605
|
"doc": cls.doc,
|
|
1556
1606
|
"invalid": _invalid_rigs(),
|
|
1557
1607
|
"boundary": cls.boundary,
|
|
@@ -1586,6 +1636,34 @@ class TestArticulatedRigRuntime(unittest.TestCase):
|
|
|
1586
1636
|
cls.unity = unity
|
|
1587
1637
|
logger.info("Unity %s", unity.get("unity_version"))
|
|
1588
1638
|
|
|
1639
|
+
@EnvSkip.unless(_unity_available(), "no Unity editor found")
|
|
1640
|
+
def test_01b_the_editor_reads_the_committed_guids(self):
|
|
1641
|
+
"""Fixed GUIDs, proven by the editor rather than by reading files:
|
|
1642
|
+
AssetDatabase names every deployed script, shader, asmdef, the README
|
|
1643
|
+
and every component folder (the XR sub-assembly's included) by the
|
|
1644
|
+
GUID committed in the bundle's .meta -- and the import left those
|
|
1645
|
+
.meta files byte-identical, so a project never churns them."""
|
|
1646
|
+
got = self._section("guids")
|
|
1647
|
+
expected = {
|
|
1648
|
+
f"{_PACKAGE_ASSET_ROOT}/{name}": re.search(
|
|
1649
|
+
rb"^guid: ([0-9a-f]{32})$", _committed_meta(name), re.M
|
|
1650
|
+
)
|
|
1651
|
+
.group(1)
|
|
1652
|
+
.decode()
|
|
1653
|
+
for name in _bundle_assets()
|
|
1654
|
+
}
|
|
1655
|
+
self.assertEqual(got, expected, "Unity named a deployed asset differently")
|
|
1656
|
+
package = TemplateDeployer.package_dir(
|
|
1657
|
+
os.path.join(self.temp_dir, "UnityProject")
|
|
1658
|
+
)
|
|
1659
|
+
rewritten = []
|
|
1660
|
+
for name in _bundle_assets():
|
|
1661
|
+
path = os.path.join(package, *name.split("/")) + ".meta"
|
|
1662
|
+
with open(path, "rb") as fh:
|
|
1663
|
+
if fh.read() != _committed_meta(name):
|
|
1664
|
+
rewritten.append(name)
|
|
1665
|
+
self.assertEqual(rewritten, [], "Unity rewrote these .meta files on import")
|
|
1666
|
+
|
|
1589
1667
|
# ---- Step 2: conformance -------------------------------------------------
|
|
1590
1668
|
|
|
1591
1669
|
def _conformance(self):
|
|
@@ -2082,6 +2160,17 @@ class TestArticulatedRigRuntime(unittest.TestCase):
|
|
|
2082
2160
|
in): nothing binds."""
|
|
2083
2161
|
self.assertEqual(self._section("ctrl_newer"), {"resolved": False, "rigs": 0})
|
|
2084
2162
|
|
|
2163
|
+
@EnvSkip.unless(_unity_available(), "no Unity editor found")
|
|
2164
|
+
def test_16c_controller_refuses_a_joint_cut_short(self):
|
|
2165
|
+
"""A joint whose ``t`` holds fewer than 3 values, or ``q`` fewer than
|
|
2166
|
+
4, is a rig the runtime refuses with a warning: nothing binds, and
|
|
2167
|
+
nothing is thrown out of ``Resolve``, which the importer calls. The
|
|
2168
|
+
reference has no such check (a short ``q`` is an IndexError there), so
|
|
2169
|
+
this is the port's own contract, not a ``_invalid_rigs`` case."""
|
|
2170
|
+
self.assertEqual(
|
|
2171
|
+
self._section("ctrl_short"), [{"resolved": False, "rigs": 0}] * 2
|
|
2172
|
+
)
|
|
2173
|
+
|
|
2085
2174
|
@EnvSkip.unless(_unity_available(), "no Unity editor found")
|
|
2086
2175
|
def test_17_xr_adapter_inert_without_xri(self):
|
|
2087
2176
|
"""Without the XR Interaction Toolkit the XR assembly is excluded by
|
|
@@ -66,8 +66,8 @@ maya.standalone.initialize(name="python")
|
|
|
66
66
|
import maya.cmds as cmds
|
|
67
67
|
import maya.mel as mel
|
|
68
68
|
|
|
69
|
-
from mayatk
|
|
70
|
-
from mayatk
|
|
69
|
+
from mayatk import AudioUtils
|
|
70
|
+
from mayatk import AudioClips
|
|
71
71
|
|
|
72
72
|
output_dir = sys.argv[1]
|
|
73
73
|
fbx_path = os.path.join(output_dir, "audio_events_test.fbx")
|
|
@@ -113,7 +113,10 @@ if not cmds.pluginInfo("fbxmaya", query=True, loaded=True):
|
|
|
113
113
|
cmds.loadPlugin("fbxmaya")
|
|
114
114
|
|
|
115
115
|
mel.eval("FBXResetExport")
|
|
116
|
-
|
|
116
|
+
# Binary, as every production writer ships: Unity's ASCII FBX reader
|
|
117
|
+
# drops the whole scene (or truncates the string) once a carrier channel
|
|
118
|
+
# passes ~6 KB, and the handoff record alone is past that.
|
|
119
|
+
mel.eval("FBXExportInAscii -v false")
|
|
117
120
|
mel.eval(f'FBXExport -f "{fbx_path.replace(os.sep, "/")}"')
|
|
118
121
|
|
|
119
122
|
assert os.path.exists(fbx_path), f"FBX was not created at {fbx_path}"
|
|
@@ -133,7 +136,10 @@ with open(os.path.join(output_dir, "maya_results.json"), "w") as f:
|
|
|
133
136
|
print("\n" + "=" * 60)
|
|
134
137
|
print("MAYA EXPORT COMPLETE")
|
|
135
138
|
print("=" * 60)
|
|
136
|
-
maya.standalone.uninitialize()
|
|
139
|
+
# Not maya.standalone.uninitialize(): Maya's static destructors fault at DLL
|
|
140
|
+
# detach, filing a crash dump and an untitled[Recovered].ma per run.
|
|
141
|
+
import pythontk as ptk
|
|
142
|
+
ptk.ProcessExit.hard_exit(0)
|
|
137
143
|
'''
|
|
138
144
|
|
|
139
145
|
|
|
@@ -561,18 +567,7 @@ class TestAudioEventsIntegration(unittest.TestCase):
|
|
|
561
567
|
else:
|
|
562
568
|
logger.info("Unity log: no AudioEvent/Warning/Error lines.")
|
|
563
569
|
|
|
564
|
-
|
|
565
|
-
# A batch-licensing failure is environmental (not a code bug) — skip.
|
|
566
|
-
if UnityLauncher.is_license_failure(returncode, full_log):
|
|
567
|
-
logger.info(
|
|
568
|
-
"Unity batch license failure (rc=%s) — see %s",
|
|
569
|
-
returncode, log_path,
|
|
570
|
-
)
|
|
571
|
-
self.skipTest(UnityEnv.license_reason())
|
|
572
|
-
self.fail(
|
|
573
|
-
f"unity_results.json not created (Unity rc={returncode}) — "
|
|
574
|
-
f"check {log_path}"
|
|
575
|
-
)
|
|
570
|
+
UnityEnv.require_results(self, results_path, returncode, log_path)
|
|
576
571
|
with open(results_path) as f:
|
|
577
572
|
self.__class__.unity_results = json.load(f)
|
|
578
573
|
|
|
@@ -20,6 +20,9 @@ green without having verified anything:
|
|
|
20
20
|
* :class:`base_test.UserPropsProbe` -- one C# user-properties postprocessor
|
|
21
21
|
shared by the five FBX-import suites, with ONE carrier-matching rule for its
|
|
22
22
|
C# and Python halves (they had drifted to ``==`` vs ``startswith``).
|
|
23
|
+
* :class:`base_test.TemplateSources` -- the suites that stage templates by hand
|
|
24
|
+
resolve them by bundle path in one place, so a moved template fails loudly
|
|
25
|
+
instead of one suite silently copying nothing.
|
|
23
26
|
|
|
24
27
|
:class:`PublicHygieneTest` is the permanent guard for the 2026-08-17 rename:
|
|
25
28
|
client program identifiers must not come back into tracked Python/C# source.
|
|
@@ -37,6 +40,7 @@ from base_test import (
|
|
|
37
40
|
EnvSkip,
|
|
38
41
|
MayaEnv,
|
|
39
42
|
SuiteOptions,
|
|
43
|
+
TemplateSources,
|
|
40
44
|
TestAssets,
|
|
41
45
|
UnityEnv,
|
|
42
46
|
UserPropsProbe,
|
|
@@ -334,6 +338,45 @@ class UnityEnvTest(unittest.TestCase):
|
|
|
334
338
|
f"{offenders}",
|
|
335
339
|
)
|
|
336
340
|
|
|
341
|
+
def test_require_results_skips_unlicensed_and_fails_a_clean_run(self):
|
|
342
|
+
"""The check every batch verifier makes: results there, nothing to do;
|
|
343
|
+
none from an unlicensed editor, an environmental skip; none from a
|
|
344
|
+
clean run, a failure. Asserting the file alone failed every such suite
|
|
345
|
+
on a machine signed out of the Hub."""
|
|
346
|
+
import pythontk as ptk
|
|
347
|
+
|
|
348
|
+
store = ptk.TempArtifacts("unitytk_require_results", policy="scoped")
|
|
349
|
+
self.addCleanup(store.cleanup)
|
|
350
|
+
root = store.dir_path()
|
|
351
|
+
present = os.path.join(root, "results.json")
|
|
352
|
+
with open(present, "w", encoding="utf-8") as fh:
|
|
353
|
+
fh.write("{}")
|
|
354
|
+
missing = os.path.join(root, "missing.json")
|
|
355
|
+
log = os.path.join(root, "unity_log.txt")
|
|
356
|
+
with open(log, "w", encoding="utf-8") as fh:
|
|
357
|
+
fh.write("Batchmode quit successfully invoked\n")
|
|
358
|
+
|
|
359
|
+
class Case(unittest.TestCase):
|
|
360
|
+
def test_present(self):
|
|
361
|
+
UnityEnv.require_results(self, present, 0, log)
|
|
362
|
+
|
|
363
|
+
def test_unlicensed(self):
|
|
364
|
+
UnityEnv.require_results(self, missing, 198, log)
|
|
365
|
+
|
|
366
|
+
def test_clean_run_wrote_nothing(self):
|
|
367
|
+
UnityEnv.require_results(self, missing, 0, log)
|
|
368
|
+
|
|
369
|
+
result = _run_case(Case)
|
|
370
|
+
self.assertEqual(
|
|
371
|
+
[reason for _t, reason in result.skipped], [UnityEnv.license_reason()]
|
|
372
|
+
)
|
|
373
|
+
self.assertEqual(
|
|
374
|
+
[t.id().rsplit(".", 1)[-1] for t, _tb in result.failures],
|
|
375
|
+
["test_clean_run_wrote_nothing"],
|
|
376
|
+
)
|
|
377
|
+
self.assertEqual(result.errors, [])
|
|
378
|
+
self.assertEqual(result.testsRun, 3)
|
|
379
|
+
|
|
337
380
|
|
|
338
381
|
class SuiteOptionsTest(unittest.TestCase):
|
|
339
382
|
"""The opt-in switches are named once, neutrally."""
|
|
@@ -395,6 +438,48 @@ class TestAssetsTest(unittest.TestCase):
|
|
|
395
438
|
)
|
|
396
439
|
|
|
397
440
|
|
|
441
|
+
class TemplateSourcesTest(unittest.TestCase):
|
|
442
|
+
"""Hand-staged templates resolve by bundle path, in one place."""
|
|
443
|
+
|
|
444
|
+
def test_every_bundle_path_resolves_in_the_checkout(self):
|
|
445
|
+
from unitytk import TemplateDeployer
|
|
446
|
+
|
|
447
|
+
names = TemplateDeployer._bundle_names()
|
|
448
|
+
self.assertTrue(TemplateSources.available(names))
|
|
449
|
+
for name in names:
|
|
450
|
+
self.assertTrue(os.path.isfile(TemplateSources.path(name)), name)
|
|
451
|
+
|
|
452
|
+
def test_copy_flat_copies_side_by_side_and_never_skips(self):
|
|
453
|
+
import pythontk as ptk
|
|
454
|
+
|
|
455
|
+
store = ptk.TempArtifacts("unitytk_template_sources", policy="scoped")
|
|
456
|
+
self.addCleanup(store.cleanup)
|
|
457
|
+
dest = os.path.join(store.dir_path(), "Scripts")
|
|
458
|
+
copied = TemplateSources.copy_flat(
|
|
459
|
+
["UnitytkSettings.cs", "RenderEffects/RenderEffectsController.cs"], dest
|
|
460
|
+
)
|
|
461
|
+
self.assertEqual(
|
|
462
|
+
copied,
|
|
463
|
+
[
|
|
464
|
+
os.path.join(dest, "UnitytkSettings.cs"),
|
|
465
|
+
os.path.join(dest, "RenderEffectsController.cs"),
|
|
466
|
+
],
|
|
467
|
+
)
|
|
468
|
+
self.assertTrue(all(os.path.isfile(p) for p in copied))
|
|
469
|
+
with self.assertRaises(FileNotFoundError): # a bare, pre-move name
|
|
470
|
+
TemplateSources.copy_flat(["RenderEffectsController.cs"], dest)
|
|
471
|
+
|
|
472
|
+
def test_no_suite_joins_the_templates_dir_itself(self):
|
|
473
|
+
"""Five suites joined their own templates dir with a bare file name;
|
|
474
|
+
one of them silently skipped a copy that no longer resolved."""
|
|
475
|
+
offenders = []
|
|
476
|
+
for path in _source_files(".py", exclude=("base_test.py",)):
|
|
477
|
+
with open(path, encoding="utf-8", errors="replace") as fh:
|
|
478
|
+
if re.search(r"""["']unitytk["'],\s*["']templates["']""", fh.read()):
|
|
479
|
+
offenders.append(os.path.relpath(path, REPO_ROOT))
|
|
480
|
+
self.assertEqual(offenders, [], f"templates dir joined by hand in: {offenders}")
|
|
481
|
+
|
|
482
|
+
|
|
398
483
|
class UserPropsProbeTest(unittest.TestCase):
|
|
399
484
|
"""One postprocessor template, one carrier-matching rule on both sides."""
|
|
400
485
|
|
|
@@ -65,7 +65,7 @@ import maya.cmds as cmds
|
|
|
65
65
|
import maya.mel as mel
|
|
66
66
|
|
|
67
67
|
import pythontk as ptk
|
|
68
|
-
from mayatk
|
|
68
|
+
from mayatk import ShotStore
|
|
69
69
|
from mayatk.env_utils.fbx_utils import FbxUtils
|
|
70
70
|
from mayatk.node_utils.data_nodes import DataNodes
|
|
71
71
|
|
|
@@ -104,7 +104,10 @@ takes = ptk.SceneRecords.declared_takes(
|
|
|
104
104
|
|
|
105
105
|
if not cmds.pluginInfo("fbxmaya", query=True, loaded=True):
|
|
106
106
|
cmds.loadPlugin("fbxmaya")
|
|
107
|
-
|
|
107
|
+
# Binary, as every production writer ships: Unity's ASCII FBX reader
|
|
108
|
+
# drops the whole scene (or truncates the string) once a carrier channel
|
|
109
|
+
# passes ~6 KB, and the handoff record alone is past that.
|
|
110
|
+
mel.eval("FBXExportInAscii -v false")
|
|
108
111
|
mel.eval('FBXExport -f "%s"' % fbx_path)
|
|
109
112
|
assert os.path.exists(fbx_path), "FBX not created: %s" % fbx_path
|
|
110
113
|
|
|
@@ -119,7 +122,10 @@ results = {
|
|
|
119
122
|
with open(os.path.join(output_dir, "maya_results.json"), "w") as f:
|
|
120
123
|
json.dump(results, f, indent=2)
|
|
121
124
|
print("MAYA CLIP-NAME CONTRACT EXPORT COMPLETE — keys:", results["clip_keys"])
|
|
122
|
-
maya.standalone.uninitialize()
|
|
125
|
+
# Not maya.standalone.uninitialize(): Maya's static destructors fault at DLL
|
|
126
|
+
# detach, filing a crash dump and an untitled[Recovered].ma per run.
|
|
127
|
+
import pythontk as ptk
|
|
128
|
+
ptk.ProcessExit.hard_exit(0)
|
|
123
129
|
'''
|
|
124
130
|
|
|
125
131
|
|
|
@@ -297,18 +303,7 @@ class TestClipNameContract(unittest.TestCase):
|
|
|
297
303
|
proc.kill()
|
|
298
304
|
raise TimeoutError("Unity timed out")
|
|
299
305
|
|
|
300
|
-
|
|
301
|
-
log_text = ""
|
|
302
|
-
if os.path.exists(log_path):
|
|
303
|
-
with open(log_path, encoding="utf-8", errors="ignore") as fh:
|
|
304
|
-
log_text = fh.read()
|
|
305
|
-
if UnityLauncher.is_license_failure(returncode, log_text):
|
|
306
|
-
logger.info(
|
|
307
|
-
"Unity batch license failure (rc=%s) — see %s",
|
|
308
|
-
returncode, log_path,
|
|
309
|
-
)
|
|
310
|
-
self.skipTest(UnityEnv.license_reason())
|
|
311
|
-
self.fail(f"unity_results.json not created (rc={returncode}) — {log_path}")
|
|
306
|
+
UnityEnv.require_results(self, results_path, returncode, log_path)
|
|
312
307
|
with open(results_path) as f:
|
|
313
308
|
self.__class__.unity_results = json.load(f)
|
|
314
309
|
logger.info(f"Unity results:\n{json.dumps(self.__class__.unity_results, indent=2)}")
|
|
@@ -215,22 +215,7 @@ class TestControllerRuntimeIntegration(unittest.TestCase):
|
|
|
215
215
|
proc.kill()
|
|
216
216
|
raise TimeoutError("Unity timed out")
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
log_text = ""
|
|
220
|
-
if os.path.exists(log_path):
|
|
221
|
-
with open(log_path, encoding="utf-8", errors="ignore") as fh:
|
|
222
|
-
log_text = fh.read()
|
|
223
|
-
if UnityLauncher.is_license_failure(returncode, log_text):
|
|
224
|
-
logger.info(
|
|
225
|
-
"Unity batch license failure (rc=%s) — see %s",
|
|
226
|
-
returncode,
|
|
227
|
-
log_path,
|
|
228
|
-
)
|
|
229
|
-
self.skipTest(UnityEnv.license_reason())
|
|
230
|
-
self.fail(
|
|
231
|
-
f"unity_results.json not created (Unity rc={returncode}) — "
|
|
232
|
-
f"check {log_path}"
|
|
233
|
-
)
|
|
218
|
+
UnityEnv.require_results(self, results_path, returncode, log_path)
|
|
234
219
|
with open(results_path) as f:
|
|
235
220
|
self.__class__.unity_results = json.load(f)
|
|
236
221
|
logger.info(
|