azurpaint 1.0.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.
- azurpaint-1.0.0/LICENSE +21 -0
- azurpaint-1.0.0/PKG-INFO +116 -0
- azurpaint-1.0.0/README.md +71 -0
- azurpaint-1.0.0/azurpaint/__init__.py +6 -0
- azurpaint-1.0.0/azurpaint/classes/AABB.py +26 -0
- azurpaint-1.0.0/azurpaint/classes/AssetReader.py +259 -0
- azurpaint-1.0.0/azurpaint/classes/GameObject.py +284 -0
- azurpaint-1.0.0/azurpaint/classes/MeshImage.py +196 -0
- azurpaint-1.0.0/azurpaint/classes/RectTransform.py +32 -0
- azurpaint-1.0.0/azurpaint/classes/Vector2.py +228 -0
- azurpaint-1.0.0/azurpaint/classes/__init__.py +6 -0
- azurpaint-1.0.0/azurpaint/exception.py +28 -0
- azurpaint-1.0.0/azurpaint/lib/Inspector.py +282 -0
- azurpaint-1.0.0/azurpaint/lib/Tree.py +253 -0
- azurpaint-1.0.0/azurpaint/lib/__init__.py +2 -0
- azurpaint-1.0.0/azurpaint/main.py +134 -0
- azurpaint-1.0.0/azurpaint/types.py +13 -0
- azurpaint-1.0.0/azurpaint.egg-info/PKG-INFO +116 -0
- azurpaint-1.0.0/azurpaint.egg-info/SOURCES.txt +24 -0
- azurpaint-1.0.0/azurpaint.egg-info/dependency_links.txt +1 -0
- azurpaint-1.0.0/azurpaint.egg-info/requires.txt +2 -0
- azurpaint-1.0.0/azurpaint.egg-info/top_level.txt +1 -0
- azurpaint-1.0.0/pyproject.toml +41 -0
- azurpaint-1.0.0/setup.cfg +4 -0
- azurpaint-1.0.0/setup.py +9 -0
- azurpaint-1.0.0/tests/test.py +43 -0
azurpaint-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Fernando
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
azurpaint-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: azurpaint
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Azur Lane painting reconstructor/extractor.
|
|
5
|
+
Author-email: Fernando <81207411+Fernando2603@users.noreply.github.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 Fernando
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/Fernando2603/azurpaint
|
|
29
|
+
Project-URL: Bug Tracker, https://github.com/Fernando2603/azurpaint/issues
|
|
30
|
+
Keywords: python,unity,azurlane,azur-lane,extractor
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: Programming Language :: Python
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Topic :: Games/Entertainment
|
|
39
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
40
|
+
Requires-Python: >=3.9
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: Pillow
|
|
44
|
+
Requires-Dist: UnityPy==1.10.6
|
|
45
|
+
|
|
46
|
+
# azurpaint
|
|
47
|
+
|
|
48
|
+
[](https://pypi.python.org/pypi/azurpaint)
|
|
49
|
+
[](https://github.com/Fernando2603/azurpaint/blob/main/LICENSE)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Azur Lane painting reconstructor/extractor.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
**Python 3.9.0 or higher is required**
|
|
56
|
+
```cmd
|
|
57
|
+
pip install azurpaint
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
Simple example to extract painting
|
|
63
|
+
|
|
64
|
+
- create new folder
|
|
65
|
+
- get and extract `/Android/obb/com.YoStarEN.AzurLane/*.obb` to new folder
|
|
66
|
+
- copy `AssetBundles` from `/Android/data/com.YoStarEN.AzurLane/files/AssetBundles`
|
|
67
|
+
|
|
68
|
+
> [!NOTE]
|
|
69
|
+
> only `painting`, `paintings` and `paintingface` folder are required from `AssetBundles`.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
```Python
|
|
73
|
+
from pathlib import Path
|
|
74
|
+
from azurpaint import Azurpaint
|
|
75
|
+
from azurpaint.exception import PrefabNotFound
|
|
76
|
+
|
|
77
|
+
# extract painting with default expression/face
|
|
78
|
+
def extract(asset_bundle_path, prefab_path):
|
|
79
|
+
try:
|
|
80
|
+
azurpaint = Azurpaint(asset_bundle_path, prefab_path)
|
|
81
|
+
|
|
82
|
+
# search dependencies automatically within AssetBundles (root)
|
|
83
|
+
# this is still in experimental mode so far on testing the result is good
|
|
84
|
+
# load_dependencies only searching in local file and it doesn't know
|
|
85
|
+
# if there any missing dependency it doesn't know what file it is
|
|
86
|
+
# so you should provide complete asset include extracted OBB before running this
|
|
87
|
+
azurpaint.load_dependencies()
|
|
88
|
+
|
|
89
|
+
# PIL.Image.Image
|
|
90
|
+
return azurpaint.create()
|
|
91
|
+
except PrefabNotFound:
|
|
92
|
+
print(f"{prefab_path} is not an prefab.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# extract painting with all face/expression into output_dir
|
|
96
|
+
def extract_all_face(asset_bundle_path, prefab_path, output_dir):
|
|
97
|
+
try:
|
|
98
|
+
azurpaint = Azurpaint(asset_bundle_path, prefab_path)
|
|
99
|
+
azurpaint.load_dependencies()
|
|
100
|
+
|
|
101
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
102
|
+
azurpaint.create().save(Path(output_dir, f"{azurpaint.prefab.name}-default.png"))
|
|
103
|
+
|
|
104
|
+
for face in azurpaint.face_list:
|
|
105
|
+
azurpaint.change_face(face)
|
|
106
|
+
azurpaint.create().save(Path(output_dir, f"{azurpaint.prefab.name}-{face}.png"))
|
|
107
|
+
|
|
108
|
+
except PrefabNotFound:
|
|
109
|
+
print(f"{prefab_path} is not an prefab.")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == '__main__':
|
|
113
|
+
# azurpaint require asset that have .prefab
|
|
114
|
+
# will raise PrefabNotFound if file is not an prefab
|
|
115
|
+
extract('path_to/AssetBundles', 'painting/tashigan')
|
|
116
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# azurpaint
|
|
2
|
+
|
|
3
|
+
[](https://pypi.python.org/pypi/azurpaint)
|
|
4
|
+
[](https://github.com/Fernando2603/azurpaint/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Azur Lane painting reconstructor/extractor.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
**Python 3.9.0 or higher is required**
|
|
11
|
+
```cmd
|
|
12
|
+
pip install azurpaint
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Simple example to extract painting
|
|
18
|
+
|
|
19
|
+
- create new folder
|
|
20
|
+
- get and extract `/Android/obb/com.YoStarEN.AzurLane/*.obb` to new folder
|
|
21
|
+
- copy `AssetBundles` from `/Android/data/com.YoStarEN.AzurLane/files/AssetBundles`
|
|
22
|
+
|
|
23
|
+
> [!NOTE]
|
|
24
|
+
> only `painting`, `paintings` and `paintingface` folder are required from `AssetBundles`.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
```Python
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from azurpaint import Azurpaint
|
|
30
|
+
from azurpaint.exception import PrefabNotFound
|
|
31
|
+
|
|
32
|
+
# extract painting with default expression/face
|
|
33
|
+
def extract(asset_bundle_path, prefab_path):
|
|
34
|
+
try:
|
|
35
|
+
azurpaint = Azurpaint(asset_bundle_path, prefab_path)
|
|
36
|
+
|
|
37
|
+
# search dependencies automatically within AssetBundles (root)
|
|
38
|
+
# this is still in experimental mode so far on testing the result is good
|
|
39
|
+
# load_dependencies only searching in local file and it doesn't know
|
|
40
|
+
# if there any missing dependency it doesn't know what file it is
|
|
41
|
+
# so you should provide complete asset include extracted OBB before running this
|
|
42
|
+
azurpaint.load_dependencies()
|
|
43
|
+
|
|
44
|
+
# PIL.Image.Image
|
|
45
|
+
return azurpaint.create()
|
|
46
|
+
except PrefabNotFound:
|
|
47
|
+
print(f"{prefab_path} is not an prefab.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# extract painting with all face/expression into output_dir
|
|
51
|
+
def extract_all_face(asset_bundle_path, prefab_path, output_dir):
|
|
52
|
+
try:
|
|
53
|
+
azurpaint = Azurpaint(asset_bundle_path, prefab_path)
|
|
54
|
+
azurpaint.load_dependencies()
|
|
55
|
+
|
|
56
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
57
|
+
azurpaint.create().save(Path(output_dir, f"{azurpaint.prefab.name}-default.png"))
|
|
58
|
+
|
|
59
|
+
for face in azurpaint.face_list:
|
|
60
|
+
azurpaint.change_face(face)
|
|
61
|
+
azurpaint.create().save(Path(output_dir, f"{azurpaint.prefab.name}-{face}.png"))
|
|
62
|
+
|
|
63
|
+
except PrefabNotFound:
|
|
64
|
+
print(f"{prefab_path} is not an prefab.")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == '__main__':
|
|
68
|
+
# azurpaint require asset that have .prefab
|
|
69
|
+
# will raise PrefabNotFound if file is not an prefab
|
|
70
|
+
extract('path_to/AssetBundles', 'painting/tashigan')
|
|
71
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .Vector2 import Vector2
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AABB:
|
|
7
|
+
center: Vector2
|
|
8
|
+
extend: Vector2
|
|
9
|
+
|
|
10
|
+
def __init__(self, center: Vector2, extend: Vector2) -> None:
|
|
11
|
+
self.center = center
|
|
12
|
+
self.extend = extend
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __repr__(self) -> str:
|
|
16
|
+
return f"<{self.__class__.__name__} center={self.center.values()} extend={self.extend.values()}>"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def padding(self) -> Vector2:
|
|
21
|
+
return self.center - self.extend
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def size(self) -> Vector2:
|
|
26
|
+
return self.center + self.extend
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union, cast
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from UnityPy import Environment, classes
|
|
6
|
+
from UnityPy.enums import ClassIDType
|
|
7
|
+
from UnityPy.files import ObjectReader
|
|
8
|
+
|
|
9
|
+
from ..types import PathLike
|
|
10
|
+
from ..exception import PrefabNotFound
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AssetReader:
|
|
14
|
+
"""
|
|
15
|
+
Read UnityAsset using UnityPy.
|
|
16
|
+
"""
|
|
17
|
+
path: Path
|
|
18
|
+
prefab: Path
|
|
19
|
+
environment: Environment
|
|
20
|
+
files: List[str] = []
|
|
21
|
+
face: Dict[str, int]
|
|
22
|
+
|
|
23
|
+
def __init__(self, path: PathLike, prefab: PathLike) -> None:
|
|
24
|
+
self.face = {}
|
|
25
|
+
self.path = Path(path)
|
|
26
|
+
self.prefab = Path(prefab)
|
|
27
|
+
|
|
28
|
+
if not self.filepath.exists():
|
|
29
|
+
raise FileNotFoundError(f"File {self.filepath.as_posix()!r} not exists.")
|
|
30
|
+
|
|
31
|
+
self.environment = Environment(self.filepath.as_posix())
|
|
32
|
+
self.files.append(self.prefab.as_posix())
|
|
33
|
+
|
|
34
|
+
if not self.has_prefab:
|
|
35
|
+
raise PrefabNotFound(f"Prefab {self.prefab.as_posix()!r} not exists.")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def __repr__(self) -> str:
|
|
39
|
+
return f"<{self.__class__.__name__} prefab={self.prefab.as_posix()}>"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def root(self) -> classes.GameObject:
|
|
44
|
+
return self.get_object_by_path_id(next(iter(self.environment.container.values())).path_id)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def filepath(self) -> Path:
|
|
49
|
+
return Path(self.path, self.prefab)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def has_prefab(self) -> bool:
|
|
54
|
+
try:
|
|
55
|
+
return any(key.endswith('.prefab') for key in self.environment.container.keys())
|
|
56
|
+
except:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def cabs(self) -> List[str]:
|
|
62
|
+
return list(k for k in self.environment.cabs.keys() if not k.endswith('.ress'))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def dependencies(self) -> List[str]:
|
|
67
|
+
assets: classes.AssetBundle = self.get_object_by_path_id(1)
|
|
68
|
+
|
|
69
|
+
if not hasattr(assets, 'm_Dependencies'):
|
|
70
|
+
raise ValueError(f"Prefab {self.prefab.as_posix()} is missing 'AssetBundle.m_Dependencies'.")
|
|
71
|
+
|
|
72
|
+
return cast(List[str], assets.m_Dependencies)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_cabs(self, name: PathLike) -> List[str]:
|
|
76
|
+
environment = Environment(Path(name).as_posix())
|
|
77
|
+
return list(k for k in environment.cabs.keys() if not k.endswith('.ress'))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load(self, path: PathLike, is_face: bool = False) -> Path:
|
|
81
|
+
path = Path(path)
|
|
82
|
+
|
|
83
|
+
if not path.is_relative_to(self.path):
|
|
84
|
+
path = Path(self.path, path)
|
|
85
|
+
|
|
86
|
+
# is_dependencies is not used, it's a bit complicated with azurlane assets
|
|
87
|
+
# since some asset like paintingface is not an direct dependency for a lot ship
|
|
88
|
+
self.environment.load_file(path.as_posix())
|
|
89
|
+
self.files.append(path.relative_to(self.path).as_posix())
|
|
90
|
+
|
|
91
|
+
if is_face or path.is_relative_to(Path(self.path, 'paintingface')):
|
|
92
|
+
self.face = self._get_face(path)
|
|
93
|
+
|
|
94
|
+
return path
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def loads(self, path: Union[PathLike, Iterable[PathLike]]) -> List[Path]:
|
|
98
|
+
if not isinstance(path, Iterable) or isinstance(path, str):
|
|
99
|
+
path = [path]
|
|
100
|
+
|
|
101
|
+
_loaded = []
|
|
102
|
+
|
|
103
|
+
for link in path:
|
|
104
|
+
_loaded.append(self.load(link))
|
|
105
|
+
|
|
106
|
+
return _loaded
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _gather_files(self, folders: List[str] = ['painting', 'paintings', 'paintingface']) -> List[str]:
|
|
110
|
+
"""
|
|
111
|
+
Get all files list that have same name as prefab in painting, paintings and paintingface
|
|
112
|
+
"""
|
|
113
|
+
files: List[str] = []
|
|
114
|
+
|
|
115
|
+
for folder in folders:
|
|
116
|
+
glob = Path(self.path, folder).glob(f"{self.prefab.name.split('_')[0]}*")
|
|
117
|
+
files.extend([file.relative_to(self.path).as_posix() for file in glob])
|
|
118
|
+
|
|
119
|
+
return files
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _get_face(self, path: Path) -> Dict[str, int]:
|
|
123
|
+
face = {}
|
|
124
|
+
|
|
125
|
+
for obj in cast(List[ObjectReader], self.environment.files[path.as_posix()].get_objects()):
|
|
126
|
+
if obj.type != ClassIDType.Sprite:
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
obj = cast(classes.Sprite, obj.read())
|
|
130
|
+
face[obj.name] = obj.path_id
|
|
131
|
+
|
|
132
|
+
return {key: face[key] for key in sorted(face.keys())}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _load_face(self, force: bool = True) -> None:
|
|
137
|
+
if len(self.face):
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
files = self._gather_files(['paintingface'])
|
|
141
|
+
prefab_face = Path('paintingface', self.prefab.name).as_posix()
|
|
142
|
+
|
|
143
|
+
if prefab_face in files:
|
|
144
|
+
self.load(prefab_face, is_face=True)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if not force:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# face not found or not same as prefab name
|
|
151
|
+
# sometimes face have different name with underscore extension
|
|
152
|
+
# but still have same basename eg like _hx extension
|
|
153
|
+
# this is not perfect it may load wrong face
|
|
154
|
+
possible_name: List[str] = [prefab_face]
|
|
155
|
+
|
|
156
|
+
while '_' in prefab_face:
|
|
157
|
+
prefab_face = prefab_face.rsplit('_', 1)[0]
|
|
158
|
+
possible_name.append(prefab_face)
|
|
159
|
+
|
|
160
|
+
matches: List[str] = [file for file in files if file in possible_name]
|
|
161
|
+
|
|
162
|
+
if len(matches) == 0:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
# when matches is more than one. probably the longest is more desired one
|
|
166
|
+
# since it splitted with underscore so we're prefer longer string
|
|
167
|
+
self.load(max(matches, key=len), is_face=True)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def find_dependencies(self) -> Tuple[Set[str], Set[str]]:
|
|
171
|
+
"""
|
|
172
|
+
Find all dependencies in loaded path
|
|
173
|
+
|
|
174
|
+
:returns: tuple[`result`, `missing_dependencies`]
|
|
175
|
+
"""
|
|
176
|
+
result: Set[str] = set()
|
|
177
|
+
dependencies: Set[str] = set(self.dependencies)
|
|
178
|
+
|
|
179
|
+
if not len(dependencies):
|
|
180
|
+
return result, dependencies
|
|
181
|
+
|
|
182
|
+
files: List[str] = self._gather_files()
|
|
183
|
+
|
|
184
|
+
# blank image used by asset to create canvas
|
|
185
|
+
files.append('painting/touming_tex')
|
|
186
|
+
|
|
187
|
+
for file in files:
|
|
188
|
+
if not len(dependencies):
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
for cab in self.get_cabs(Path(self.path, file)):
|
|
192
|
+
if cab in dependencies:
|
|
193
|
+
result.add(file)
|
|
194
|
+
dependencies.remove(cab)
|
|
195
|
+
|
|
196
|
+
return result, dependencies
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def load_dependencies(self, force_face_load: bool = False) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Automatically search for dependencies by name.
|
|
202
|
+
|
|
203
|
+
:params force_face_load: force load face with matching name
|
|
204
|
+
|
|
205
|
+
Warning
|
|
206
|
+
-------
|
|
207
|
+
This is still in experimental state, undefined behaviour may occurs.
|
|
208
|
+
"""
|
|
209
|
+
dependencies, _ = self.find_dependencies()
|
|
210
|
+
|
|
211
|
+
if not len(self.face):
|
|
212
|
+
self._load_face(force=force_face_load)
|
|
213
|
+
|
|
214
|
+
self.loads(dependencies)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_object_by_path_id(self, path_id: int) -> Any:
|
|
218
|
+
for obj in self.environment.objects:
|
|
219
|
+
if obj.path_id == path_id:
|
|
220
|
+
return cast(Any, obj.read(return_typetree_on_error=False))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def get_object_by_name(self, name: str, type: ClassIDType) -> Any:
|
|
224
|
+
for obj in self.environment.objects:
|
|
225
|
+
try:
|
|
226
|
+
if not hasattr(obj, 'name') or (obj.type != type):
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
obj = cast(Any, obj.read(return_typetree_on_error=False))
|
|
230
|
+
|
|
231
|
+
if hasattr(obj, 'name') and (str(obj.name) == str(name)):
|
|
232
|
+
return obj
|
|
233
|
+
|
|
234
|
+
except:
|
|
235
|
+
continue
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_component_from_object(
|
|
239
|
+
self,
|
|
240
|
+
gameobject: classes.GameObject,
|
|
241
|
+
types: Optional[Iterable[ClassIDType]] = None,
|
|
242
|
+
names: Optional[Set[str]] = None,
|
|
243
|
+
attributes: Optional[Set[str]] = None
|
|
244
|
+
) -> Any:
|
|
245
|
+
for component_pptr in cast(List[classes.PPtr], gameobject.m_Components):
|
|
246
|
+
if types and (not component_pptr.type in types):
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
component = self.get_object_by_path_id(component_pptr.path_id)
|
|
250
|
+
|
|
251
|
+
if names and (not component.name in names):
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
if attributes:
|
|
255
|
+
for attribute in attributes:
|
|
256
|
+
if hasattr(component, attribute):
|
|
257
|
+
return component
|
|
258
|
+
|
|
259
|
+
return component
|