scratchattach 2.1.13__tar.gz → 2.1.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.
- scratchattach-2.1.14/MANIFEST.in +1 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/PKG-INFO +8 -3
- scratchattach-2.1.13/scratchattach.egg-info/requires.txt → scratchattach-2.1.14/requirements.txt +1 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/cloud/_base.py +12 -8
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/cloud/cloud.py +19 -7
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/asset.py +59 -5
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/base.py +82 -31
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/block.py +86 -15
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/blockshape.py +8 -4
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/build_defaulting.py +6 -2
- scratchattach-2.1.14/scratchattach/editor/code_translation/parse.py +177 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/comment.py +6 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/commons.py +82 -19
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/extension.py +10 -3
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/field.py +9 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/inputs.py +4 -1
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/meta.py +11 -3
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/monitor.py +46 -38
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/mutation.py +11 -4
- scratchattach-2.1.14/scratchattach/editor/pallete.py +90 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/prim.py +2 -2
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/project.py +9 -3
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/sprite.py +19 -6
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/twconfig.py +2 -1
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/vlb.py +1 -1
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/_base.py +2 -2
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/cloud_events.py +2 -2
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/cloud_requests.py +3 -3
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/cloud_server.py +3 -3
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/message_events.py +1 -1
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/other/other_apis.py +4 -4
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/other/project_json_capabilities.py +3 -3
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/_base.py +13 -12
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/activity.py +11 -43
- scratchattach-2.1.14/scratchattach/site/alert.py +227 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/backpack_asset.py +2 -2
- scratchattach-2.1.14/scratchattach/site/browser_cookie3_stub.py +17 -0
- scratchattach-2.1.14/scratchattach/site/browser_cookies.py +61 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/classroom.py +51 -34
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/cloud_activity.py +4 -4
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/comment.py +30 -8
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/forum.py +101 -69
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/project.py +37 -17
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/session.py +169 -79
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/studio.py +4 -4
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/site/user.py +179 -64
- scratchattach-2.1.14/scratchattach/utils/__init__.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/utils/commons.py +35 -23
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/utils/enums.py +44 -5
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/utils/exceptions.py +10 -0
- scratchattach-2.1.14/scratchattach/utils/requests.py +93 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach.egg-info/PKG-INFO +8 -3
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach.egg-info/SOURCES.txt +19 -3
- scratchattach-2.1.14/scratchattach.egg-info/requires.txt +9 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/setup.py +16 -6
- scratchattach-2.1.14/tests/test_activity.py +32 -0
- scratchattach-2.1.14/tests/test_auth.py +15 -0
- scratchattach-2.1.14/tests/test_comment.py +70 -0
- scratchattach-2.1.14/tests/test_editor_integration.py +22 -0
- scratchattach-2.1.14/tests/test_editor_obfuscation.py +28 -0
- scratchattach-2.1.14/tests/test_editor_project.py +9 -0
- scratchattach-2.1.14/tests/test_import.py +4 -0
- scratchattach-2.1.14/tests/test_other_apis.py +66 -0
- scratchattach-2.1.14/tests/test_project.py +72 -0
- scratchattach-2.1.14/tests/test_search.py +19 -0
- scratchattach-2.1.14/tests/test_studio.py +69 -0
- scratchattach-2.1.14/tests/test_teacher_activity.py +31 -0
- scratchattach-2.1.14/tests/test_user.py +98 -0
- scratchattach-2.1.13/scratchattach/editor/pallete.py +0 -91
- scratchattach-2.1.13/scratchattach/editor/sbuild.py +0 -2837
- scratchattach-2.1.13/scratchattach/site/browser_cookies.py +0 -55
- scratchattach-2.1.13/scratchattach/utils/requests.py +0 -67
- scratchattach-2.1.13/tests/test1.py +0 -20
- scratchattach-2.1.13/tests/test_import.py +0 -4
- {scratchattach-2.1.13 → scratchattach-2.1.14}/LICENSE +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/README.md +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/__init__.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/cloud/__init__.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/__init__.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/editor/backpack_json.py +0 -0
- {scratchattach-2.1.13/scratchattach/eventhandlers → scratchattach-2.1.14/scratchattach/editor/code_translation}/__init__.py +0 -0
- {scratchattach-2.1.13/scratchattach/other → scratchattach-2.1.14/scratchattach/eventhandlers}/__init__.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/cloud_recorder.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/cloud_storage.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/combine.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/eventhandlers/filterbot.py +0 -0
- {scratchattach-2.1.13/scratchattach/site → scratchattach-2.1.14/scratchattach/other}/__init__.py +0 -0
- {scratchattach-2.1.13/scratchattach/utils → scratchattach-2.1.14/scratchattach/site}/__init__.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach/utils/encoder.py +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach.egg-info/dependency_links.txt +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/scratchattach.egg-info/top_level.txt +0 -0
- {scratchattach-2.1.13 → scratchattach-2.1.14}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include requirements.txt
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scratchattach
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.14
|
|
4
4
|
Summary: A Scratch API Wrapper
|
|
5
|
-
Home-page: https://scratchattach.tim1de.net
|
|
6
5
|
Author: TimMcCool
|
|
7
6
|
Author-email:
|
|
7
|
+
Project-URL: Source, https://github.com/timmccool/scratchattach
|
|
8
|
+
Project-URL: Homepage, https://scratchattach.tim1de.net
|
|
8
9
|
Keywords: scratch api,scratchattach,scratch api python,scratch python,scratch for python,scratch,scratch cloud,scratch cloud variables,scratch bot
|
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -19,13 +20,17 @@ Requires-Dist: requests
|
|
|
19
20
|
Requires-Dist: bs4
|
|
20
21
|
Requires-Dist: SimpleWebSocketServer
|
|
21
22
|
Requires-Dist: typing-extensions
|
|
23
|
+
Requires-Dist: browser_cookie3
|
|
24
|
+
Provides-Extra: lark
|
|
25
|
+
Requires-Dist: lark; extra == "lark"
|
|
22
26
|
Dynamic: author
|
|
23
27
|
Dynamic: classifier
|
|
24
28
|
Dynamic: description
|
|
25
29
|
Dynamic: description-content-type
|
|
26
|
-
Dynamic: home-page
|
|
27
30
|
Dynamic: keywords
|
|
28
31
|
Dynamic: license-file
|
|
32
|
+
Dynamic: project-url
|
|
33
|
+
Dynamic: provides-extra
|
|
29
34
|
Dynamic: requires-dist
|
|
30
35
|
Dynamic: summary
|
|
31
36
|
|
|
@@ -7,6 +7,7 @@ from typing import Optional, Union, TypeVar, Generic, TYPE_CHECKING, Any
|
|
|
7
7
|
from abc import ABC, abstractmethod, ABCMeta
|
|
8
8
|
from threading import Lock
|
|
9
9
|
from collections.abc import Iterator
|
|
10
|
+
import warnings
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from _typeshed import SupportsRead
|
|
@@ -24,13 +25,13 @@ class SupportsClose(ABC):
|
|
|
24
25
|
|
|
25
26
|
import websocket
|
|
26
27
|
|
|
27
|
-
from
|
|
28
|
-
from
|
|
29
|
-
from
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
from
|
|
28
|
+
from scratchattach.site import session
|
|
29
|
+
from scratchattach.eventhandlers import cloud_recorder
|
|
30
|
+
from scratchattach.utils import exceptions
|
|
31
|
+
from scratchattach.eventhandlers.cloud_requests import CloudRequests
|
|
32
|
+
from scratchattach.eventhandlers.cloud_events import CloudEvents
|
|
33
|
+
from scratchattach.eventhandlers.cloud_storage import CloudStorage
|
|
34
|
+
from scratchattach.site import cloud_activity
|
|
34
35
|
|
|
35
36
|
T = TypeVar("T")
|
|
36
37
|
|
|
@@ -122,7 +123,10 @@ class WebSocketEventStream(EventStream):
|
|
|
122
123
|
self.source_cloud.username = cloud.username
|
|
123
124
|
self.source_cloud.ws_timeout = None # No timeout -> allows continous listening
|
|
124
125
|
self.reading = Lock()
|
|
125
|
-
|
|
126
|
+
try:
|
|
127
|
+
self.source_cloud.connect()
|
|
128
|
+
except exceptions.CloudConnectionError:
|
|
129
|
+
warnings.warn("Initial cloud connection attempt failed, retrying...", exceptions.UnexpectedWebsocketEventWarning)
|
|
126
130
|
self.packets_left = []
|
|
127
131
|
|
|
128
132
|
def receive_new(self, non_blocking: bool = False):
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"""v2 ready: ScratchCloud, TwCloud and CustomCloud classes"""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from websocket import WebSocketBadStatusException
|
|
4
7
|
|
|
5
8
|
from ._base import BaseCloud
|
|
6
9
|
from typing import Type
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
+
from scratchattach.utils.requests import requests
|
|
11
|
+
from scratchattach.utils import exceptions, commons
|
|
12
|
+
from scratchattach.site import cloud_activity
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class ScratchCloud(BaseCloud):
|
|
@@ -26,7 +29,10 @@ class ScratchCloud(BaseCloud):
|
|
|
26
29
|
|
|
27
30
|
def connect(self):
|
|
28
31
|
self._assert_auth() # Connecting to Scratch's cloud websocket requires a login to the Scratch website
|
|
29
|
-
|
|
32
|
+
try:
|
|
33
|
+
super().connect()
|
|
34
|
+
except WebSocketBadStatusException as e:
|
|
35
|
+
raise exceptions.CloudConnectionError(f"Error: Scratch's Cloud system may be down. Please try again later.") from e
|
|
30
36
|
|
|
31
37
|
def set_var(self, variable, value):
|
|
32
38
|
self._assert_auth() # Setting a cloud var requires a login to the Scratch website
|
|
@@ -88,7 +94,7 @@ class ScratchCloud(BaseCloud):
|
|
|
88
94
|
|
|
89
95
|
def events(self, *, use_logs=False):
|
|
90
96
|
if self._session is None or use_logs:
|
|
91
|
-
from
|
|
97
|
+
from scratchattach.eventhandlers.cloud_events import CloudLogEvents
|
|
92
98
|
return CloudLogEvents(self)
|
|
93
99
|
else:
|
|
94
100
|
return super().events()
|
|
@@ -146,7 +152,10 @@ def get_cloud(project_id, *, CloudClass:Type[BaseCloud]=ScratchCloud) -> BaseClo
|
|
|
146
152
|
Returns:
|
|
147
153
|
Type[scratchattach.cloud._base.BaseCloud]: An object representing the cloud of a project. Can be of any class inheriting from BaseCloud.
|
|
148
154
|
"""
|
|
149
|
-
|
|
155
|
+
warnings.warn(
|
|
156
|
+
"Warning: To set Scratch cloud variables, use session.connect_cloud instead of get_cloud",
|
|
157
|
+
exceptions.AnonymousSiteComponentWarning
|
|
158
|
+
)
|
|
150
159
|
return CloudClass(project_id=project_id)
|
|
151
160
|
|
|
152
161
|
def get_scratch_cloud(project_id):
|
|
@@ -160,7 +169,10 @@ def get_scratch_cloud(project_id):
|
|
|
160
169
|
Returns:
|
|
161
170
|
scratchattach.cloud.ScratchCloud: An object representing the Scratch cloud of a project.
|
|
162
171
|
"""
|
|
163
|
-
|
|
172
|
+
warnings.warn(
|
|
173
|
+
"To set Scratch cloud variables, use session.connect_scratch_cloud instead of get_scratch_cloud",
|
|
174
|
+
exceptions.AnonymousSiteComponentWarning
|
|
175
|
+
)
|
|
164
176
|
return ScratchCloud(project_id=project_id)
|
|
165
177
|
|
|
166
178
|
def get_tw_cloud(project_id, *, purpose="", contact="", cloud_host="wss://clouddata.turbowarp.org"):
|
|
@@ -10,12 +10,19 @@ from typing import Optional
|
|
|
10
10
|
|
|
11
11
|
@dataclass(init=True, repr=True)
|
|
12
12
|
class AssetFile:
|
|
13
|
+
"""
|
|
14
|
+
Represents the file information for an asset
|
|
15
|
+
- stores the filename, data, and md5 hash
|
|
16
|
+
"""
|
|
13
17
|
filename: str
|
|
14
|
-
_data: bytes = field(repr=False,
|
|
15
|
-
_md5: str = field(repr=False,
|
|
18
|
+
_data: bytes = field(repr=False, default_factory=bytes)
|
|
19
|
+
_md5: str = field(repr=False, default_factory=str)
|
|
16
20
|
|
|
17
21
|
@property
|
|
18
22
|
def data(self):
|
|
23
|
+
"""
|
|
24
|
+
Return the contents of the asset file, as bytes
|
|
25
|
+
"""
|
|
19
26
|
if self._data is None:
|
|
20
27
|
# Download and cache
|
|
21
28
|
rq = requests.get(f"https://assets.scratch.mit.edu/internalapi/asset/{self.filename}/get/")
|
|
@@ -28,6 +35,9 @@ class AssetFile:
|
|
|
28
35
|
|
|
29
36
|
@property
|
|
30
37
|
def md5(self):
|
|
38
|
+
"""
|
|
39
|
+
Compute/retrieve the md5 hash value of the asset file data
|
|
40
|
+
"""
|
|
31
41
|
if self._md5 is None:
|
|
32
42
|
self._md5 = md5(self.data).hexdigest()
|
|
33
43
|
|
|
@@ -38,7 +48,7 @@ class Asset(base.SpriteSubComponent):
|
|
|
38
48
|
def __init__(self,
|
|
39
49
|
name: str = "costume1",
|
|
40
50
|
file_name: str = "b7853f557e4426412e64bb3da6531a99.svg",
|
|
41
|
-
_sprite:
|
|
51
|
+
_sprite: commons.SpriteInput = build_defaulting.SPRITE_DEFAULT):
|
|
42
52
|
"""
|
|
43
53
|
Represents a generic asset. Can be a sound or an image.
|
|
44
54
|
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Assets
|
|
@@ -60,22 +70,40 @@ class Asset(base.SpriteSubComponent):
|
|
|
60
70
|
|
|
61
71
|
@property
|
|
62
72
|
def folder(self):
|
|
73
|
+
"""
|
|
74
|
+
Get the folder name of this asset, based on the asset name. Uses the turbowarp syntax
|
|
75
|
+
"""
|
|
63
76
|
return commons.get_folder_name(self.name)
|
|
64
77
|
|
|
65
78
|
@property
|
|
66
79
|
def name_nfldr(self):
|
|
80
|
+
"""
|
|
81
|
+
Get the asset name after removing the folder name
|
|
82
|
+
"""
|
|
67
83
|
return commons.get_name_nofldr(self.name)
|
|
68
84
|
|
|
69
85
|
@property
|
|
70
86
|
def file_name(self):
|
|
87
|
+
"""
|
|
88
|
+
Get the exact file name, as it would be within an sb3 file
|
|
89
|
+
equivalent to the md5ext value using in scratch project JSON
|
|
90
|
+
"""
|
|
71
91
|
return f"{self.id}.{self.data_format}"
|
|
72
92
|
|
|
73
93
|
@property
|
|
74
94
|
def md5ext(self):
|
|
95
|
+
"""
|
|
96
|
+
Get the exact file name, as it would be within an sb3 file
|
|
97
|
+
equivalent to the md5ext value using in scratch project JSON
|
|
98
|
+
"""
|
|
75
99
|
return self.file_name
|
|
76
100
|
|
|
77
101
|
@property
|
|
78
102
|
def parent(self):
|
|
103
|
+
"""
|
|
104
|
+
Return the project that this asset is attached to. If there is no attached project,
|
|
105
|
+
try returning the attached sprite
|
|
106
|
+
"""
|
|
79
107
|
if self.project is None:
|
|
80
108
|
return self.sprite
|
|
81
109
|
else:
|
|
@@ -83,6 +111,9 @@ class Asset(base.SpriteSubComponent):
|
|
|
83
111
|
|
|
84
112
|
@property
|
|
85
113
|
def asset_file(self) -> AssetFile:
|
|
114
|
+
"""
|
|
115
|
+
Get the associated asset file object for this asset object
|
|
116
|
+
"""
|
|
86
117
|
for asset_file in self.parent.asset_data:
|
|
87
118
|
if asset_file.filename == self.file_name:
|
|
88
119
|
return asset_file
|
|
@@ -94,17 +125,27 @@ class Asset(base.SpriteSubComponent):
|
|
|
94
125
|
|
|
95
126
|
@staticmethod
|
|
96
127
|
def from_json(data: dict):
|
|
128
|
+
"""
|
|
129
|
+
Load asset data from project.json
|
|
130
|
+
"""
|
|
97
131
|
_name = data.get("name")
|
|
132
|
+
assert isinstance(_name, str)
|
|
98
133
|
_file_name = data.get("md5ext")
|
|
99
134
|
if _file_name is None:
|
|
100
135
|
if "dataFormat" in data and "assetId" in data:
|
|
101
136
|
_id = data["assetId"]
|
|
102
137
|
_data_format = data["dataFormat"]
|
|
103
138
|
_file_name = f"{_id}.{_data_format}"
|
|
139
|
+
else:
|
|
140
|
+
_file_name = ""
|
|
141
|
+
assert isinstance(_file_name, str)
|
|
104
142
|
|
|
105
143
|
return Asset(_name, _file_name)
|
|
106
144
|
|
|
107
145
|
def to_json(self) -> dict:
|
|
146
|
+
"""
|
|
147
|
+
Convert asset data to project.json format
|
|
148
|
+
"""
|
|
108
149
|
return {
|
|
109
150
|
"name": self.name,
|
|
110
151
|
|
|
@@ -113,6 +154,7 @@ class Asset(base.SpriteSubComponent):
|
|
|
113
154
|
"dataFormat": self.data_format,
|
|
114
155
|
}
|
|
115
156
|
|
|
157
|
+
# todo: implement below:
|
|
116
158
|
"""
|
|
117
159
|
@staticmethod
|
|
118
160
|
def from_file(fp: str, name: str = None):
|
|
@@ -132,9 +174,9 @@ class Costume(Asset):
|
|
|
132
174
|
bitmap_resolution=None,
|
|
133
175
|
rotation_center_x: int | float = 48,
|
|
134
176
|
rotation_center_y: int | float = 50,
|
|
135
|
-
_sprite:
|
|
177
|
+
_sprite: commons.SpriteInput = build_defaulting.SPRITE_DEFAULT):
|
|
136
178
|
"""
|
|
137
|
-
A costume. An asset with additional properties
|
|
179
|
+
A costume (image). An asset with additional properties
|
|
138
180
|
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Costumes
|
|
139
181
|
"""
|
|
140
182
|
super().__init__(name, file_name, _sprite)
|
|
@@ -145,6 +187,9 @@ class Costume(Asset):
|
|
|
145
187
|
|
|
146
188
|
@staticmethod
|
|
147
189
|
def from_json(data):
|
|
190
|
+
"""
|
|
191
|
+
Load costume data from project.json
|
|
192
|
+
"""
|
|
148
193
|
_asset_load = Asset.from_json(data)
|
|
149
194
|
|
|
150
195
|
bitmap_resolution = data.get("bitmapResolution")
|
|
@@ -156,6 +201,9 @@ class Costume(Asset):
|
|
|
156
201
|
bitmap_resolution, rotation_center_x, rotation_center_y)
|
|
157
202
|
|
|
158
203
|
def to_json(self) -> dict:
|
|
204
|
+
"""
|
|
205
|
+
Convert costume to project.json format
|
|
206
|
+
"""
|
|
159
207
|
_json = super().to_json()
|
|
160
208
|
_json.update({
|
|
161
209
|
"bitmapResolution": self.bitmap_resolution,
|
|
@@ -184,6 +232,9 @@ class Sound(Asset):
|
|
|
184
232
|
|
|
185
233
|
@staticmethod
|
|
186
234
|
def from_json(data):
|
|
235
|
+
"""
|
|
236
|
+
Load sound from project.json
|
|
237
|
+
"""
|
|
187
238
|
_asset_load = Asset.from_json(data)
|
|
188
239
|
|
|
189
240
|
rate = data.get("rate")
|
|
@@ -191,6 +242,9 @@ class Sound(Asset):
|
|
|
191
242
|
return Sound(_asset_load.name, _asset_load.file_name, rate, sample_count)
|
|
192
243
|
|
|
193
244
|
def to_json(self) -> dict:
|
|
245
|
+
"""
|
|
246
|
+
Convert Sound to project.json format
|
|
247
|
+
"""
|
|
194
248
|
_json = super().to_json()
|
|
195
249
|
commons.noneless_update(_json, {
|
|
196
250
|
"rate": self.rate,
|
|
@@ -8,15 +8,21 @@ import copy
|
|
|
8
8
|
import json
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
10
|
from io import TextIOWrapper
|
|
11
|
-
from typing import Optional, Any, TYPE_CHECKING, BinaryIO
|
|
11
|
+
from typing import Optional, Any, TYPE_CHECKING, BinaryIO, Union
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
-
from . import project,
|
|
14
|
+
from . import project, block, asset
|
|
15
|
+
from . import mutation as module_mutation
|
|
16
|
+
from . import sprite as module_sprite
|
|
17
|
+
from . import commons
|
|
15
18
|
|
|
16
19
|
from . import build_defaulting
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
class Base(ABC):
|
|
23
|
+
"""
|
|
24
|
+
Abstract base class for most sa.editor classes. Implements copy functions
|
|
25
|
+
"""
|
|
20
26
|
def dcopy(self):
|
|
21
27
|
"""
|
|
22
28
|
:return: A **deep** copy of self
|
|
@@ -31,22 +37,33 @@ class Base(ABC):
|
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
class JSONSerializable(Base, ABC):
|
|
40
|
+
"""
|
|
41
|
+
'Interface' for to_json() and from_json() methods
|
|
42
|
+
Also implements save_json() using to_json()
|
|
43
|
+
"""
|
|
34
44
|
@staticmethod
|
|
35
45
|
@abstractmethod
|
|
36
|
-
def from_json(data
|
|
46
|
+
def from_json(data):
|
|
37
47
|
pass
|
|
38
48
|
|
|
39
49
|
@abstractmethod
|
|
40
|
-
def to_json(self)
|
|
50
|
+
def to_json(self):
|
|
41
51
|
pass
|
|
42
52
|
|
|
43
53
|
def save_json(self, name: str = ''):
|
|
54
|
+
"""
|
|
55
|
+
Save a json file
|
|
56
|
+
"""
|
|
44
57
|
data = self.to_json()
|
|
45
58
|
with open(f"{self.__class__.__name__.lower()}{name}.json", "w") as f:
|
|
46
59
|
json.dump(data, f)
|
|
47
60
|
|
|
48
61
|
|
|
49
62
|
class JSONExtractable(JSONSerializable, ABC):
|
|
63
|
+
"""
|
|
64
|
+
Interface for objects that can be loaded from zip archives containing json files (sprite/project)
|
|
65
|
+
Only has one method - load_json
|
|
66
|
+
"""
|
|
50
67
|
@staticmethod
|
|
51
68
|
@abstractmethod
|
|
52
69
|
def load_json(data: str | bytes | TextIOWrapper | BinaryIO, load_assets: bool = True, _name: Optional[str] = None) -> tuple[
|
|
@@ -58,40 +75,43 @@ class JSONExtractable(JSONSerializable, ABC):
|
|
|
58
75
|
:param _name: Any provided name (will automatically find one otherwise)
|
|
59
76
|
:return: tuple of the name, asset data & json as a string
|
|
60
77
|
"""
|
|
61
|
-
...
|
|
62
78
|
|
|
63
79
|
|
|
64
80
|
class ProjectSubcomponent(JSONSerializable, ABC):
|
|
81
|
+
"""
|
|
82
|
+
Base class for any class with an associated project
|
|
83
|
+
"""
|
|
65
84
|
def __init__(self, _project: Optional[project.Project] = None):
|
|
66
85
|
self.project = _project
|
|
67
86
|
|
|
68
87
|
|
|
69
88
|
class SpriteSubComponent(JSONSerializable, ABC):
|
|
70
|
-
|
|
89
|
+
"""
|
|
90
|
+
Base class for any class with an associated sprite
|
|
91
|
+
"""
|
|
92
|
+
sprite: module_sprite.Sprite
|
|
93
|
+
def __init__(self, _sprite: "commons.SpriteInput" = build_defaulting.SPRITE_DEFAULT):
|
|
71
94
|
if _sprite is build_defaulting.SPRITE_DEFAULT:
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
retrieved_sprite = build_defaulting.current_sprite()
|
|
96
|
+
assert retrieved_sprite is not None, "You don't have any sprites."
|
|
97
|
+
_sprite = retrieved_sprite
|
|
74
98
|
self.sprite = _sprite
|
|
75
99
|
|
|
76
|
-
# @property
|
|
77
|
-
# def sprite(self):
|
|
78
|
-
# if self._sprite is None:
|
|
79
|
-
# print("ok, ", build_defaulting.current_sprite())
|
|
80
|
-
# return build_defaulting.current_sprite()
|
|
81
|
-
# else:
|
|
82
|
-
# return self._sprite
|
|
83
|
-
|
|
84
|
-
# @sprite.setter
|
|
85
|
-
# def sprite(self, value):
|
|
86
|
-
# self._sprite = value
|
|
87
|
-
|
|
88
100
|
@property
|
|
89
101
|
def project(self) -> project.Project:
|
|
90
|
-
|
|
102
|
+
"""
|
|
103
|
+
Get associated project by proxy of the associated sprite
|
|
104
|
+
"""
|
|
105
|
+
p = self.sprite.project
|
|
106
|
+
assert p is not None
|
|
107
|
+
return p
|
|
91
108
|
|
|
92
109
|
|
|
93
110
|
class IDComponent(SpriteSubComponent, ABC):
|
|
94
|
-
|
|
111
|
+
"""
|
|
112
|
+
Base class for classes with an id attribute
|
|
113
|
+
"""
|
|
114
|
+
def __init__(self, _id: str, _sprite: "commons.SpriteInput" = build_defaulting.SPRITE_DEFAULT):
|
|
95
115
|
self.id = _id
|
|
96
116
|
super().__init__(_sprite)
|
|
97
117
|
|
|
@@ -103,8 +123,7 @@ class NamedIDComponent(IDComponent, ABC):
|
|
|
103
123
|
"""
|
|
104
124
|
Base class for Variables, Lists and Broadcasts (Name + ID + sprite)
|
|
105
125
|
"""
|
|
106
|
-
|
|
107
|
-
def __init__(self, _id: str, name: str, _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
|
|
126
|
+
def __init__(self, _id: str, name: str, _sprite: "commons.SpriteInput" = build_defaulting.SPRITE_DEFAULT):
|
|
108
127
|
self.name = name
|
|
109
128
|
super().__init__(_id, _sprite)
|
|
110
129
|
|
|
@@ -113,30 +132,62 @@ class NamedIDComponent(IDComponent, ABC):
|
|
|
113
132
|
|
|
114
133
|
|
|
115
134
|
class BlockSubComponent(JSONSerializable, ABC):
|
|
135
|
+
"""
|
|
136
|
+
Base class for classes with associated blocks
|
|
137
|
+
"""
|
|
116
138
|
def __init__(self, _block: Optional[block.Block] = None):
|
|
117
139
|
self.block = _block
|
|
118
140
|
|
|
119
141
|
@property
|
|
120
|
-
def sprite(self) ->
|
|
121
|
-
|
|
142
|
+
def sprite(self) -> module_sprite.Sprite:
|
|
143
|
+
"""
|
|
144
|
+
Fetch sprite by proxy of the block
|
|
145
|
+
"""
|
|
146
|
+
b = self.block
|
|
147
|
+
assert b is not None
|
|
148
|
+
return b.sprite
|
|
122
149
|
|
|
123
150
|
@property
|
|
124
151
|
def project(self) -> project.Project:
|
|
125
|
-
|
|
152
|
+
"""
|
|
153
|
+
Fetch project by proxy of the sprite (by proxy of the block)
|
|
154
|
+
"""
|
|
155
|
+
p = self.sprite.project
|
|
156
|
+
assert p is not None
|
|
157
|
+
return p
|
|
126
158
|
|
|
127
159
|
|
|
128
160
|
class MutationSubComponent(JSONSerializable, ABC):
|
|
129
|
-
|
|
161
|
+
"""
|
|
162
|
+
Base class for classes with associated mutations
|
|
163
|
+
"""
|
|
164
|
+
mutation: Optional[module_mutation.Mutation]
|
|
165
|
+
def __init__(self, _mutation: Optional[module_mutation.Mutation] = None):
|
|
130
166
|
self.mutation = _mutation
|
|
131
167
|
|
|
132
168
|
@property
|
|
133
169
|
def block(self) -> block.Block:
|
|
134
|
-
|
|
170
|
+
"""
|
|
171
|
+
Fetch block by proxy of mutation
|
|
172
|
+
"""
|
|
173
|
+
m = self.mutation
|
|
174
|
+
assert m is not None
|
|
175
|
+
b = m.block
|
|
176
|
+
assert b is not None
|
|
177
|
+
return b
|
|
135
178
|
|
|
136
179
|
@property
|
|
137
|
-
def sprite(self) ->
|
|
180
|
+
def sprite(self) -> module_sprite.Sprite:
|
|
181
|
+
"""
|
|
182
|
+
Fetch sprite by proxy of block (by proxy of mutation)
|
|
183
|
+
"""
|
|
138
184
|
return self.block.sprite
|
|
139
185
|
|
|
140
186
|
@property
|
|
141
187
|
def project(self) -> project.Project:
|
|
142
|
-
|
|
188
|
+
"""
|
|
189
|
+
Fetch project by proxy of sprite (by proxy of block (by proxy of mutation))
|
|
190
|
+
"""
|
|
191
|
+
p = self.sprite.project
|
|
192
|
+
assert p is not None
|
|
193
|
+
return p
|