unitytk 0.0.13__tar.gz → 0.0.14__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.13/unitytk.egg-info → unitytk-0.0.14}/PKG-INFO +5 -3
- {unitytk-0.0.13 → unitytk-0.0.14}/docs/README.md +1 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/pyproject.toml +10 -2
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_asset_delivery.py +1 -1
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_base_test.py +8 -54
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_keyed_visibility.py +6 -15
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_launcher.py +61 -25
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_multi_section_audio_integration.py +1 -4
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_shadow_plane_integration.py +13 -27
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_shared_material_standalone.py +5 -12
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_standalone_enduser.py +7 -16
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/__init__.py +1 -1
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/launcher.py +70 -35
- {unitytk-0.0.13 → unitytk-0.0.14/unitytk.egg-info}/PKG-INFO +5 -3
- unitytk-0.0.14/unitytk.egg-info/requires.txt +1 -0
- unitytk-0.0.13/unitytk.egg-info/requires.txt +0 -1
- {unitytk-0.0.13 → unitytk-0.0.14}/LICENSE +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/MANIFEST.in +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/setup.cfg +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_audio_events_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_bool_to_fade_curve.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_clip_name_contract_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_controller_runtime_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_deploy_templates.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_emissive_groups_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_fade_window_preservation.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_lightmap_metadata_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_render_effects_e2e.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_render_opacity_controller.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_render_opacity_e2e.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_render_opacity_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_run_tests.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_scene_builder.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_shadow_plane_runtime_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_shot_metadata_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_shots_audio_sidebyside_integration.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/test/test_temp_projects.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/asset_delivery.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/scene_builder.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/template_deployer.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/AudioEventController.cs +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/EmissiveGroupController.cs +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/EmissiveGroups.hlsl +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/EmissiveGroupsLit.shader +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/LightmapMetadataController.cs +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/M3trik.Unitytk.asmdef +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/README.md +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/RenderEffectsController.cs +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/ShadowPlane.shader +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/ShadowPlaneController.cs +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/ShadowPlaneHorizon.hlsl +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/ShotMetadataController.cs +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/UnitytkSettings.cs +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk/templates/__init__.py +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk.egg-info/SOURCES.txt +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/unitytk.egg-info/dependency_links.txt +0 -0
- {unitytk-0.0.13 → unitytk-0.0.14}/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.14
|
|
4
4
|
Summary: A modular Python toolkit for Unity interaction.
|
|
5
5
|
Author-email: Ryan Simpson <m3trik@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -8,14 +8,16 @@ Project-URL: Homepage, https://github.com/m3trik/unitytk
|
|
|
8
8
|
Project-URL: Repository, https://github.com/m3trik/unitytk
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System ::
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
13
|
Requires-Python: >=3.9
|
|
13
14
|
Description-Content-Type: text/markdown
|
|
14
15
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: pythontk>=0.
|
|
16
|
+
Requires-Dist: pythontk>=0.12.0
|
|
16
17
|
Dynamic: license-file
|
|
17
18
|
|
|
18
19
|
[](../test/)
|
|
20
|
+

|
|
19
21
|
|
|
20
22
|
# unitytk
|
|
21
23
|
|
|
@@ -12,9 +12,17 @@ requires-python = ">=3.9"
|
|
|
12
12
|
classifiers = [
|
|
13
13
|
"Programming Language :: Python :: 3",
|
|
14
14
|
"License :: OSI Approved :: MIT License",
|
|
15
|
-
"Operating System ::
|
|
15
|
+
"Operating System :: Microsoft :: Windows",
|
|
16
|
+
"Operating System :: POSIX :: Linux",
|
|
16
17
|
]
|
|
17
|
-
|
|
18
|
+
# The floor is HAND-RATCHETED: unitytk is off-chain from m3trik/push.ps1, so no
|
|
19
|
+
# release cascade bumps it.
|
|
20
|
+
# pythontk 0.12.0, the release this one ships and was tested with. The hard
|
|
21
|
+
# minimum is 0.8.84: UnityFinder.find_editors scans through
|
|
22
|
+
# `AppLauncher.scan_install_dirs` (new in 0.8.72) and needs its pattern order to
|
|
23
|
+
# be a priority (0.8.84), so the Hub's chosen install location wins over the
|
|
24
|
+
# default. Below 0.8.72 the scan fails, is logged, and no editor is found.
|
|
25
|
+
dependencies = ["pythontk>=0.12.0"]
|
|
18
26
|
|
|
19
27
|
[project.urls]
|
|
20
28
|
Homepage = "https://github.com/m3trik/unitytk"
|
|
@@ -356,7 +356,7 @@ class SuiteOptionsTest(unittest.TestCase):
|
|
|
356
356
|
|
|
357
357
|
|
|
358
358
|
class TestAssetsTest(unittest.TestCase):
|
|
359
|
-
"""Fixture resolution
|
|
359
|
+
"""Fixture resolution: a plain join under ``UNITYTK_TEST_ASSETS``."""
|
|
360
360
|
|
|
361
361
|
def setUp(self):
|
|
362
362
|
import tempfile
|
|
@@ -368,41 +368,15 @@ class TestAssetsTest(unittest.TestCase):
|
|
|
368
368
|
patcher.start()
|
|
369
369
|
self.addCleanup(patcher.stop)
|
|
370
370
|
|
|
371
|
-
def _touch(self, *parts):
|
|
372
|
-
path = os.path.join(self.root, *parts)
|
|
373
|
-
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
374
|
-
with open(path, "w", encoding="utf-8") as fh:
|
|
375
|
-
fh.write("x")
|
|
376
|
-
return path
|
|
377
|
-
|
|
378
371
|
def test_path_joins_under_the_root(self):
|
|
379
372
|
self.assertEqual(
|
|
380
373
|
TestAssets.path("a", "b.fbx"), os.path.join(self.root, "a", "b.fbx")
|
|
381
374
|
)
|
|
382
375
|
|
|
383
|
-
def
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
"""An already-staged asset tree keeps working for one release."""
|
|
388
|
-
for new_parts, legacy_parts in TestAssets.LEGACY_ALIASES.items():
|
|
389
|
-
with self.subTest(asset=new_parts):
|
|
390
|
-
staged = self._touch(*legacy_parts)
|
|
391
|
-
try:
|
|
392
|
-
self.assertEqual(
|
|
393
|
-
os.path.normcase(TestAssets.path(*new_parts)),
|
|
394
|
-
os.path.normcase(staged),
|
|
395
|
-
)
|
|
396
|
-
finally:
|
|
397
|
-
os.remove(staged)
|
|
398
|
-
|
|
399
|
-
def test_new_name_wins_when_both_are_staged(self):
|
|
400
|
-
new_parts, legacy_parts = next(iter(TestAssets.LEGACY_ALIASES.items()))
|
|
401
|
-
self._touch(*legacy_parts)
|
|
402
|
-
fresh = self._touch(*new_parts)
|
|
403
|
-
self.assertEqual(
|
|
404
|
-
os.path.normcase(TestAssets.path(*new_parts)), os.path.normcase(fresh)
|
|
405
|
-
)
|
|
376
|
+
def test_the_one_release_alias_map_is_gone(self):
|
|
377
|
+
"""Deleted 2026-09-23, as its own note asked: nothing maps a neutral
|
|
378
|
+
fixture name to a legacy one, so ``path`` is the plain join above."""
|
|
379
|
+
self.assertFalse(hasattr(TestAssets, "LEGACY_ALIASES"))
|
|
406
380
|
|
|
407
381
|
def test_unset_root_cannot_exist_under_either_guard(self):
|
|
408
382
|
from pathlib import Path
|
|
@@ -566,17 +540,13 @@ class PublicHygieneTest(unittest.TestCase):
|
|
|
566
540
|
The absolute studio paths, vendor name and person name went 2026-08-15; the
|
|
567
541
|
program identifiers went 2026-08-17. This is the guard that keeps them out.
|
|
568
542
|
Markdown is deliberately NOT scanned: ``CHANGELOG.md`` records what factually
|
|
569
|
-
happened
|
|
570
|
-
|
|
543
|
+
happened. Since the one-release alias map went (2026-09-23), no Python or C#
|
|
544
|
+
file may name the legacy assets -- ``base_test.py`` included.
|
|
571
545
|
"""
|
|
572
546
|
|
|
573
547
|
#: Program identifiers that must not appear as code identifiers.
|
|
574
548
|
PROGRAM_RE = re.compile(r"C-?130|C5M|FCR", re.IGNORECASE)
|
|
575
549
|
|
|
576
|
-
#: The one file allowed to name the legacy assets: the deprecation alias
|
|
577
|
-
#: map, which exists precisely so the old names keep resolving.
|
|
578
|
-
ALIAS_HOME = "base_test.py"
|
|
579
|
-
|
|
580
550
|
def test_no_module_filename_carries_a_program_identifier(self):
|
|
581
551
|
offenders = [
|
|
582
552
|
os.path.relpath(p, REPO_ROOT)
|
|
@@ -587,7 +557,7 @@ class PublicHygieneTest(unittest.TestCase):
|
|
|
587
557
|
|
|
588
558
|
def test_no_program_identifier_in_python_or_cs_source(self):
|
|
589
559
|
offenders = []
|
|
590
|
-
for path in _source_files(".py", ".cs"
|
|
560
|
+
for path in _source_files(".py", ".cs"):
|
|
591
561
|
with open(path, encoding="utf-8", errors="replace") as fh:
|
|
592
562
|
for lineno, line in enumerate(fh, 1):
|
|
593
563
|
if self.PROGRAM_RE.search(line):
|
|
@@ -596,22 +566,6 @@ class PublicHygieneTest(unittest.TestCase):
|
|
|
596
566
|
)
|
|
597
567
|
self.assertEqual(offenders, [], f"program identifier in source: {offenders}")
|
|
598
568
|
|
|
599
|
-
def test_the_alias_map_is_the_only_home_for_the_legacy_names(self):
|
|
600
|
-
"""base_test may name them -- but only inside LEGACY_ALIASES."""
|
|
601
|
-
legacy = [part for parts in TestAssets.LEGACY_ALIASES.values() for part in parts]
|
|
602
|
-
self.assertTrue(
|
|
603
|
-
any(self.PROGRAM_RE.search(p) for p in legacy),
|
|
604
|
-
"the alias map should be carrying the legacy program-identified names",
|
|
605
|
-
)
|
|
606
|
-
path = os.path.join(os.path.dirname(base_test.__file__), self.ALIAS_HOME)
|
|
607
|
-
with open(path, encoding="utf-8") as fh:
|
|
608
|
-
source = fh.read()
|
|
609
|
-
start = source.index("LEGACY_ALIASES")
|
|
610
|
-
end = source.index("@classmethod", start)
|
|
611
|
-
outside = source[:start] + source[end:]
|
|
612
|
-
hits = sorted({m.group(0) for m in self.PROGRAM_RE.finditer(outside)})
|
|
613
|
-
self.assertEqual(hits, [], f"legacy identifier outside the alias map: {hits}")
|
|
614
|
-
|
|
615
569
|
|
|
616
570
|
if __name__ == "__main__":
|
|
617
571
|
unittest.main()
|
|
@@ -40,9 +40,7 @@ FBX_PATH = asset_path("fixture_keyed_visibility.fbx")
|
|
|
40
40
|
|
|
41
41
|
#: Name the FBX is copied to inside the Unity project. Fixed rather than
|
|
42
42
|
#: derived from FBX_PATH so the C# "Assets/<name>" literal and the importer
|
|
43
|
-
#: allowlist stay true
|
|
44
|
-
#: base_test.TestAssets.LEGACY_ALIASES, which keeps the pre-2026-08-17
|
|
45
|
-
#: fixture names resolving for one release).
|
|
43
|
+
#: allowlist stay true whatever the fixture file on disk is called.
|
|
46
44
|
FBX_ASSET_NAME = "fixture_keyed_visibility.fbx"
|
|
47
45
|
|
|
48
46
|
# Template CS files — need RenderEffectsController for the importer
|
|
@@ -87,18 +85,11 @@ EXPECTED_VISIBILITY_OBJECTS = [
|
|
|
87
85
|
# Unity finder
|
|
88
86
|
# ---------------------------------------------------------------------------
|
|
89
87
|
def _find_unity_exe() -> str:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
reverse=True,
|
|
96
|
-
)
|
|
97
|
-
for v in versions:
|
|
98
|
-
exe = os.path.join(hub, v, "Editor", "Unity.exe")
|
|
99
|
-
if os.path.isfile(exe):
|
|
100
|
-
return exe
|
|
101
|
-
return ""
|
|
88
|
+
"""The newest installed Unity Editor, found the way the product finds it
|
|
89
|
+
(every Hub install location, on every OS), or ""."""
|
|
90
|
+
from unitytk.launcher import UnityLauncher
|
|
91
|
+
|
|
92
|
+
return UnityLauncher().executable_path or ""
|
|
102
93
|
|
|
103
94
|
|
|
104
95
|
# ---------------------------------------------------------------------------
|
|
@@ -15,34 +15,70 @@ from unitytk.launcher import UnityFinder, UnityLauncher
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class TestUnityLauncher(UnityTkTestCase):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
def test_find_editors_windows(self):
|
|
19
|
+
"""Windows: <Hub>/<ver>/Editor/Unity.exe, found through the shared
|
|
20
|
+
install scan (a real tree -- no listdir/exists mocks to drift from it).
|
|
21
|
+
A version folder without the binary is not an editor."""
|
|
22
|
+
import tempfile
|
|
23
|
+
|
|
24
|
+
with tempfile.TemporaryDirectory() as hub:
|
|
25
|
+
for version in ("2021.1.0f1", "2022.2.0b1"):
|
|
26
|
+
exe = os.path.join(hub, version, "Editor", "Unity.exe")
|
|
27
|
+
os.makedirs(os.path.dirname(exe))
|
|
28
|
+
open(exe, "w").close()
|
|
29
|
+
os.makedirs(os.path.join(hub, "2023.1.0f1", "Editor")) # no binary
|
|
30
|
+
with patch("platform.system", return_value="Windows"), patch(
|
|
31
|
+
"unitytk.launcher.UnityFinder.get_hub_editor_paths", return_value=[hub]
|
|
32
|
+
):
|
|
33
|
+
editors = UnityFinder.find_editors()
|
|
34
|
+
|
|
35
|
+
self.assertEqual(sorted(editors), ["2021.1.0f1", "2022.2.0b1"])
|
|
36
|
+
self.assertEqual(
|
|
37
|
+
os.path.normpath(editors["2021.1.0f1"]),
|
|
38
|
+
os.path.join(hub, "2021.1.0f1", "Editor", "Unity.exe"),
|
|
37
39
|
)
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
def test_find_editors_reads_the_hubs_install_location_on_linux(self):
|
|
42
|
+
"""Linux: ~/Unity/Hub/Editor/<ver>/Editor/Unity, and the location picked
|
|
43
|
+
in the Hub's preferences (secondaryInstallPath.json, where new installs
|
|
44
|
+
go) -- scanned first, on every OS. Only the default was ever read."""
|
|
45
|
+
import json
|
|
46
|
+
import tempfile
|
|
47
|
+
|
|
48
|
+
with tempfile.TemporaryDirectory() as home:
|
|
49
|
+
default = os.path.join(home, "Unity", "Hub", "Editor")
|
|
50
|
+
custom = os.path.join(home, "editors")
|
|
51
|
+
config = os.path.join(home, ".config", "UnityHub")
|
|
52
|
+
for root, version in ((default, "2022.3.1f1"), (custom, "6000.0.1f1")):
|
|
53
|
+
exe = os.path.join(root, version, "Editor", "Unity")
|
|
54
|
+
os.makedirs(os.path.dirname(exe))
|
|
55
|
+
open(exe, "w").close()
|
|
56
|
+
os.makedirs(config)
|
|
57
|
+
with open(os.path.join(config, "secondaryInstallPath.json"), "w") as f:
|
|
58
|
+
json.dump(custom, f)
|
|
59
|
+
env = {"HOME": home, "XDG_CONFIG_HOME": os.path.join(home, ".config")}
|
|
60
|
+
|
|
61
|
+
def expanduser(path):
|
|
62
|
+
# A LEADING "~" only, as the real one: the scan expands every
|
|
63
|
+
# pattern, and a Windows temp dir can hold one mid-path (the
|
|
64
|
+
# 8.3 name C:\Users\RUNNER~1 on GitHub's runners).
|
|
65
|
+
return home + path[1:] if path.startswith("~") else path
|
|
66
|
+
|
|
67
|
+
with patch("platform.system", return_value="Linux"), patch.dict(
|
|
68
|
+
os.environ, env
|
|
69
|
+
), patch("os.path.expanduser", expanduser):
|
|
70
|
+
self.assertEqual(
|
|
71
|
+
[os.path.normpath(r) for r in UnityFinder.get_hub_editor_paths()],
|
|
72
|
+
[custom, default],
|
|
73
|
+
)
|
|
74
|
+
editors = {
|
|
75
|
+
v: os.path.normpath(e) for v, e in UnityFinder.find_editors().items()
|
|
76
|
+
}
|
|
77
|
+
self.assertEqual(
|
|
78
|
+
editors["6000.0.1f1"], os.path.join(custom, "6000.0.1f1", "Editor", "Unity")
|
|
79
|
+
)
|
|
43
80
|
self.assertEqual(
|
|
44
|
-
editors["
|
|
45
|
-
os.path.join("C:\\Hub", "2021.1.0f1", "Editor", "Unity.exe"),
|
|
81
|
+
editors["2022.3.1f1"], os.path.join(default, "2022.3.1f1", "Editor", "Unity")
|
|
46
82
|
)
|
|
47
83
|
|
|
48
84
|
@patch("unitytk.launcher.UnityFinder.find_editors")
|
|
@@ -23,9 +23,6 @@ Source files (resolved under ``UNITYTK_TEST_ASSETS``; unset -> the suite skips):
|
|
|
23
23
|
FBX: $UNITYTK_TEST_ASSETS/audio_files/fixture_multi_section_audio.fbx
|
|
24
24
|
Audio: $UNITYTK_TEST_ASSETS/audio_files/fixture_audio_clips/ (73 MP3s)
|
|
25
25
|
|
|
26
|
-
Pre-2026-08-17 fixture names still resolve for one release --
|
|
27
|
-
``base_test.TestAssets.LEGACY_ALIASES``.
|
|
28
|
-
|
|
29
26
|
The FBX contains:
|
|
30
27
|
- ``audio_manifest`` string attributes on 3 objects (sections A, B, C)
|
|
31
28
|
with frame:eventName pairs whose names match the MP3 stems.
|
|
@@ -66,7 +63,7 @@ AUDIO_DIR = asset_path("audio_files", "fixture_audio_clips")
|
|
|
66
63
|
|
|
67
64
|
#: Name the FBX is copied to inside the Unity project. Fixed rather than
|
|
68
65
|
#: derived from FBX_PATH so the C# "Assets/<name>" literal below stays true
|
|
69
|
-
#:
|
|
66
|
+
#: whatever the fixture file on disk is called.
|
|
70
67
|
FBX_ASSET_NAME = "fixture_multi_section_audio.fbx"
|
|
71
68
|
|
|
72
69
|
# Expected counts (from binary scan 2026-02-24)
|
|
@@ -115,11 +115,10 @@ cmds.file(
|
|
|
115
115
|
)
|
|
116
116
|
mel.eval("FBXExportEmbeddedTextures -v false")
|
|
117
117
|
|
|
118
|
-
# Third scenario:
|
|
119
|
-
# against the DCC's baked keys through a real FBX import
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# Two rigs on two asymmetrically placed targets: a positional locator and a
|
|
118
|
+
# Third scenario: the v2 record the producer publishes pins the ENGINE's
|
|
119
|
+
# runtime placement against the DCC's baked keys through a real FBX import
|
|
120
|
+
# (the contract's field semantics: mayatk/docs/shadow_rig_morphing.md). Two
|
|
121
|
+
# rigs on two asymmetrically placed targets: a positional locator and a
|
|
123
122
|
# directionalLight at an asymmetric rotation (so a wrong axis cannot hide).
|
|
124
123
|
from mayatk.core_utils._core_utils import CoreUtils
|
|
125
124
|
|
|
@@ -136,33 +135,12 @@ rig_pt = ShadowRig.create([drum], source_name="pointSrc", light_pos=(-2.0, 7.0,
|
|
|
136
135
|
rig_sun.bake(1, 3)
|
|
137
136
|
rig_pt.bake(1, 3)
|
|
138
137
|
FbxUtils.publish()
|
|
138
|
+
v2_payload = ptk.SceneRecords.SHADOWS.load(DataNodes)
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def _leaf(node):
|
|
142
142
|
return CoreUtils.leaf_name(node)
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
def _record(rig, source_type):
|
|
146
|
-
plane = rig.shadow_plane
|
|
147
|
-
g = lambda attr: float(cmds.getAttr(f"{plane}.{attr}"))
|
|
148
|
-
return {
|
|
149
|
-
"name": _leaf(plane), "type": "projected",
|
|
150
|
-
"texture": os.path.basename(rig.texture_path), "intensity": 1.0,
|
|
151
|
-
"source": _leaf(rig.light), "source_type": source_type,
|
|
152
|
-
"source_size": 0.0, "source_angle": 0.0, "follow_source": True,
|
|
153
|
-
"contact": _leaf(rig.contact_locator), "ground": g("groundHeight"),
|
|
154
|
-
"radius": g("footprintRadius"), "height": g("objectHeight"),
|
|
155
|
-
"max_stretch": g("maxStretch"),
|
|
156
|
-
"canvas": [g("canvasU0"), g("canvasU1"), g("canvasW0"), g("canvasW1")],
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
v2_payload = {"version": 2, "unit_scale": 0.01,
|
|
161
|
-
"planes": [_record(rig_sun, "directional"), _record(rig_pt, "point")]}
|
|
162
|
-
DataNodes.write(
|
|
163
|
-
ptk.Scope.DELIVERABLE, ShadowRig.SHADOW_METADATA, json.dumps(v2_payload)
|
|
164
|
-
)
|
|
165
|
-
|
|
166
144
|
cmds.currentTime(1)
|
|
167
145
|
|
|
168
146
|
|
|
@@ -486,6 +464,14 @@ class TestShadowPlaneIntegration(unittest.TestCase):
|
|
|
486
464
|
recs = {p["name"]: p for p in meta.get("planes", [])}
|
|
487
465
|
self.assertIn("Box_shadow", recs, f"shadow_metadata missing: {meta}")
|
|
488
466
|
self.assertEqual(recs["Box_shadow"]["texture"], "Box_shadow.png")
|
|
467
|
+
# The producer publishes the schema ShadowPlaneController reads (v2),
|
|
468
|
+
# in Maya's centimetres; the runtime fixture below ships that record.
|
|
469
|
+
self.assertEqual(meta.get("version"), 2)
|
|
470
|
+
self.assertAlmostEqual(meta.get("unit_scale"), 0.01, places=6)
|
|
471
|
+
fixture = r["fixture"]
|
|
472
|
+
published = {p["name"] for p in (fixture["payload"] or {}).get("planes", [])}
|
|
473
|
+
for key in ("sun", "point"):
|
|
474
|
+
self.assertIn(fixture[key]["plane"], published)
|
|
489
475
|
self.assertTrue(r["embedded_fbx_exists"], "embedded FBX export failed")
|
|
490
476
|
self.assertEqual(r["embedded_texture"], "Box_keyLight_shadow.png")
|
|
491
477
|
|
|
@@ -57,18 +57,11 @@ SHARED_MAT_NAME = "shared_mat"
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _find_unity_exe() -> str:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
reverse=True,
|
|
66
|
-
)
|
|
67
|
-
for v in versions:
|
|
68
|
-
exe = os.path.join(hub, v, "Editor", "Unity.exe")
|
|
69
|
-
if os.path.isfile(exe):
|
|
70
|
-
return exe
|
|
71
|
-
return ""
|
|
60
|
+
"""The newest installed Unity Editor, found the way the product finds it
|
|
61
|
+
(every Hub install location, on every OS), or ""."""
|
|
62
|
+
from unitytk.launcher import UnityLauncher
|
|
63
|
+
|
|
64
|
+
return UnityLauncher().executable_path or ""
|
|
72
65
|
|
|
73
66
|
|
|
74
67
|
def _prerequisites_ok() -> bool:
|
|
@@ -44,9 +44,8 @@ AUDIO_DIR = asset_path("audio_files", "fixture_audio_clips")
|
|
|
44
44
|
|
|
45
45
|
#: Name the FBX is copied to inside the Unity project, and the name the
|
|
46
46
|
#: importer allowlist is written against. Fixed rather than derived from
|
|
47
|
-
#: FBX_PATH so the C# "Assets/<name>" literals stay true
|
|
48
|
-
#: fixture
|
|
49
|
-
#: pre-2026-08-17 fixture names resolving for one release).
|
|
47
|
+
#: FBX_PATH so the C# "Assets/<name>" literals stay true whatever the
|
|
48
|
+
#: fixture file on disk is called.
|
|
50
49
|
FBX_ASSET_NAME = "fixture_multi_section_audio.fbx"
|
|
51
50
|
|
|
52
51
|
#: Subfolder the all-in-one deployment scenario builds under Assets/.
|
|
@@ -87,19 +86,11 @@ MAX_KEYS_PER_CURVE = 30
|
|
|
87
86
|
|
|
88
87
|
|
|
89
88
|
def _find_unity_exe() -> str:
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
[d for d in os.listdir(hub) if os.path.isdir(os.path.join(hub, d))],
|
|
96
|
-
reverse=True,
|
|
97
|
-
)
|
|
98
|
-
for v in versions:
|
|
99
|
-
exe = os.path.join(hub, v, "Editor", "Unity.exe")
|
|
100
|
-
if os.path.isfile(exe):
|
|
101
|
-
return exe
|
|
102
|
-
return ""
|
|
89
|
+
"""The newest installed Unity Editor, found the way the product finds it
|
|
90
|
+
(every Hub install location, on every OS), or ""."""
|
|
91
|
+
from unitytk.launcher import UnityLauncher
|
|
92
|
+
|
|
93
|
+
return UnityLauncher().executable_path or ""
|
|
103
94
|
|
|
104
95
|
|
|
105
96
|
def _sources_ok() -> bool:
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"""Locate installed Unity Editor versions and launch the Editor or a Standalone Player."""
|
|
2
|
+
import glob
|
|
3
|
+
import json
|
|
2
4
|
import os
|
|
3
5
|
import re
|
|
4
6
|
import platform
|
|
@@ -7,10 +9,11 @@ from typing import Optional, List, Dict, Tuple
|
|
|
7
9
|
import subprocess
|
|
8
10
|
|
|
9
11
|
try:
|
|
10
|
-
from pythontk import AppLauncher
|
|
12
|
+
from pythontk import AppLauncher, UserConfig
|
|
11
13
|
except ImportError:
|
|
12
14
|
# Fallback for when pythontk is not fully bootstrapped or direct import needed
|
|
13
15
|
from pythontk.core_utils.app_launcher import AppLauncher
|
|
16
|
+
from pythontk.core_utils.user_config import UserConfig
|
|
14
17
|
|
|
15
18
|
logger = logging.getLogger(__name__)
|
|
16
19
|
|
|
@@ -31,62 +34,94 @@ LICENSE_FAILURE_MARKERS = (
|
|
|
31
34
|
class UnityFinder:
|
|
32
35
|
"""Helper class to locate Unity installations."""
|
|
33
36
|
|
|
37
|
+
#: Where the editor binary sits inside a Hub version folder, per OS
|
|
38
|
+
#: (``platform.system().lower()``). The version folder is that many levels
|
|
39
|
+
#: above the binary.
|
|
40
|
+
EDITOR_BINARY = {
|
|
41
|
+
"windows": ("Editor", "Unity.exe"),
|
|
42
|
+
"darwin": ("Unity.app", "Contents", "MacOS", "Unity"),
|
|
43
|
+
"linux": ("Editor", "Unity"),
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
@staticmethod
|
|
35
47
|
def get_hub_editor_path() -> Optional[str]:
|
|
36
48
|
"""Returns the default Unity Hub Editor installation directory."""
|
|
37
49
|
system = platform.system().lower()
|
|
38
50
|
if system == "windows":
|
|
39
|
-
|
|
51
|
+
program_files = os.environ.get("ProgramFiles", r"C:\Program Files")
|
|
52
|
+
return os.path.join(program_files, "Unity", "Hub", "Editor")
|
|
40
53
|
elif system == "darwin": # macOS
|
|
41
54
|
return "/Applications/Unity/Hub/Editor"
|
|
42
55
|
elif system == "linux":
|
|
43
56
|
return os.path.expanduser("~/Unity/Hub/Editor")
|
|
44
57
|
return None
|
|
45
58
|
|
|
59
|
+
@staticmethod
|
|
60
|
+
def _hub_config_dir() -> Optional[str]:
|
|
61
|
+
"""Unity Hub's own config folder (where it records a custom install path)."""
|
|
62
|
+
system = platform.system().lower()
|
|
63
|
+
if system == "windows":
|
|
64
|
+
base = os.environ.get("APPDATA")
|
|
65
|
+
return os.path.join(base, "UnityHub") if base else None
|
|
66
|
+
if system == "darwin":
|
|
67
|
+
return os.path.expanduser("~/Library/Application Support/UnityHub")
|
|
68
|
+
if system == "linux":
|
|
69
|
+
return os.path.join(UserConfig.xdg_home("config"), "UnityHub")
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def get_hub_editor_paths() -> List[str]:
|
|
74
|
+
"""Every folder the Hub installs editors into: the location chosen in the
|
|
75
|
+
Hub's preferences (``secondaryInstallPath.json``, where new installs go)
|
|
76
|
+
first, then the default -- on every OS."""
|
|
77
|
+
roots: List[str] = []
|
|
78
|
+
config = UnityFinder._hub_config_dir()
|
|
79
|
+
if config:
|
|
80
|
+
try:
|
|
81
|
+
with open(
|
|
82
|
+
os.path.join(config, "secondaryInstallPath.json"), encoding="utf-8"
|
|
83
|
+
) as f:
|
|
84
|
+
custom = json.load(f)
|
|
85
|
+
if isinstance(custom, str) and custom:
|
|
86
|
+
roots.append(custom)
|
|
87
|
+
except (OSError, ValueError):
|
|
88
|
+
pass
|
|
89
|
+
default = UnityFinder.get_hub_editor_path()
|
|
90
|
+
if default and default not in roots:
|
|
91
|
+
roots.append(default)
|
|
92
|
+
return roots
|
|
93
|
+
|
|
46
94
|
@staticmethod
|
|
47
95
|
def find_editors() -> Dict[str, str]:
|
|
48
96
|
"""
|
|
49
|
-
Scans
|
|
97
|
+
Scans every Hub install location for installed Unity Editors.
|
|
50
98
|
|
|
51
99
|
Returns:
|
|
52
100
|
Dict[str, str]: A dictionary of {version: executable_path}.
|
|
53
101
|
"""
|
|
54
|
-
|
|
55
|
-
if not
|
|
56
|
-
logger.warning(
|
|
102
|
+
roots = [r for r in UnityFinder.get_hub_editor_paths() if os.path.isdir(r)]
|
|
103
|
+
if not roots:
|
|
104
|
+
logger.warning(
|
|
105
|
+
f"Unity Hub editor folder not found: {UnityFinder.get_hub_editor_paths()}"
|
|
106
|
+
)
|
|
57
107
|
return {}
|
|
58
108
|
|
|
59
|
-
|
|
60
|
-
|
|
109
|
+
tail = UnityFinder.EDITOR_BINARY.get(platform.system().lower())
|
|
110
|
+
if tail is None:
|
|
111
|
+
return {}
|
|
61
112
|
|
|
62
|
-
#
|
|
113
|
+
# The ecosystem's install scan, not a hand-rolled listdir: one pattern
|
|
114
|
+
# per root, in root priority (the Hub's chosen location, then the
|
|
115
|
+
# default), each ranked newest first. The first root holding a version
|
|
116
|
+
# wins it.
|
|
117
|
+
found_editors: Dict[str, str] = {}
|
|
63
118
|
try:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
editor_dir = os.path.join(hub_path, version)
|
|
71
|
-
exe_path = None
|
|
72
|
-
|
|
73
|
-
if system == "windows":
|
|
74
|
-
candidate = os.path.join(editor_dir, "Editor", "Unity.exe")
|
|
75
|
-
if os.path.exists(candidate):
|
|
76
|
-
exe_path = candidate
|
|
77
|
-
elif system == "darwin":
|
|
78
|
-
candidate = os.path.join(
|
|
79
|
-
editor_dir, "Unity.app", "Contents", "MacOS", "Unity"
|
|
80
|
-
)
|
|
81
|
-
if os.path.exists(candidate):
|
|
82
|
-
exe_path = candidate
|
|
83
|
-
elif system == "linux":
|
|
84
|
-
candidate = os.path.join(editor_dir, "Editor", "Unity")
|
|
85
|
-
if os.path.exists(candidate):
|
|
86
|
-
exe_path = candidate
|
|
87
|
-
|
|
88
|
-
if exe_path:
|
|
89
|
-
found_editors[version] = exe_path
|
|
119
|
+
patterns = [os.path.join(glob.escape(root), "*", *tail) for root in roots]
|
|
120
|
+
for exe_path in AppLauncher.scan_install_dirs(patterns):
|
|
121
|
+
version_dir = exe_path
|
|
122
|
+
for _ in tail:
|
|
123
|
+
version_dir = os.path.dirname(version_dir)
|
|
124
|
+
found_editors.setdefault(os.path.basename(version_dir), exe_path)
|
|
90
125
|
except Exception as e:
|
|
91
126
|
logger.error(f"Error scanning for Unity Editors: {e}")
|
|
92
127
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unitytk
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.14
|
|
4
4
|
Summary: A modular Python toolkit for Unity interaction.
|
|
5
5
|
Author-email: Ryan Simpson <m3trik@outlook.com>
|
|
6
6
|
License: MIT
|
|
@@ -8,14 +8,16 @@ Project-URL: Homepage, https://github.com/m3trik/unitytk
|
|
|
8
8
|
Project-URL: Repository, https://github.com/m3trik/unitytk
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System ::
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
13
|
Requires-Python: >=3.9
|
|
13
14
|
Description-Content-Type: text/markdown
|
|
14
15
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: pythontk>=0.
|
|
16
|
+
Requires-Dist: pythontk>=0.12.0
|
|
16
17
|
Dynamic: license-file
|
|
17
18
|
|
|
18
19
|
[](../test/)
|
|
20
|
+

|
|
19
21
|
|
|
20
22
|
# unitytk
|
|
21
23
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pythontk>=0.12.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
pythontk>=0.7.71
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|