scratchattach 2.1.14__py3-none-any.whl → 3.0.0b0__py3-none-any.whl

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.
Files changed (69) hide show
  1. scratchattach/__init__.py +14 -6
  2. scratchattach/__main__.py +93 -0
  3. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
  4. scratchattach-3.0.0b0.dist-info/RECORD +8 -0
  5. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
  6. scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
  7. scratchattach/cloud/__init__.py +0 -2
  8. scratchattach/cloud/_base.py +0 -458
  9. scratchattach/cloud/cloud.py +0 -183
  10. scratchattach/editor/__init__.py +0 -21
  11. scratchattach/editor/asset.py +0 -253
  12. scratchattach/editor/backpack_json.py +0 -117
  13. scratchattach/editor/base.py +0 -193
  14. scratchattach/editor/block.py +0 -579
  15. scratchattach/editor/blockshape.py +0 -357
  16. scratchattach/editor/build_defaulting.py +0 -51
  17. scratchattach/editor/code_translation/__init__.py +0 -0
  18. scratchattach/editor/code_translation/parse.py +0 -177
  19. scratchattach/editor/comment.py +0 -80
  20. scratchattach/editor/commons.py +0 -306
  21. scratchattach/editor/extension.py +0 -50
  22. scratchattach/editor/field.py +0 -99
  23. scratchattach/editor/inputs.py +0 -135
  24. scratchattach/editor/meta.py +0 -114
  25. scratchattach/editor/monitor.py +0 -183
  26. scratchattach/editor/mutation.py +0 -324
  27. scratchattach/editor/pallete.py +0 -90
  28. scratchattach/editor/prim.py +0 -170
  29. scratchattach/editor/project.py +0 -279
  30. scratchattach/editor/sprite.py +0 -599
  31. scratchattach/editor/twconfig.py +0 -114
  32. scratchattach/editor/vlb.py +0 -134
  33. scratchattach/eventhandlers/__init__.py +0 -0
  34. scratchattach/eventhandlers/_base.py +0 -100
  35. scratchattach/eventhandlers/cloud_events.py +0 -110
  36. scratchattach/eventhandlers/cloud_recorder.py +0 -26
  37. scratchattach/eventhandlers/cloud_requests.py +0 -459
  38. scratchattach/eventhandlers/cloud_server.py +0 -246
  39. scratchattach/eventhandlers/cloud_storage.py +0 -136
  40. scratchattach/eventhandlers/combine.py +0 -30
  41. scratchattach/eventhandlers/filterbot.py +0 -161
  42. scratchattach/eventhandlers/message_events.py +0 -42
  43. scratchattach/other/__init__.py +0 -0
  44. scratchattach/other/other_apis.py +0 -284
  45. scratchattach/other/project_json_capabilities.py +0 -475
  46. scratchattach/site/__init__.py +0 -0
  47. scratchattach/site/_base.py +0 -66
  48. scratchattach/site/activity.py +0 -382
  49. scratchattach/site/alert.py +0 -227
  50. scratchattach/site/backpack_asset.py +0 -118
  51. scratchattach/site/browser_cookie3_stub.py +0 -17
  52. scratchattach/site/browser_cookies.py +0 -61
  53. scratchattach/site/classroom.py +0 -447
  54. scratchattach/site/cloud_activity.py +0 -107
  55. scratchattach/site/comment.py +0 -242
  56. scratchattach/site/forum.py +0 -432
  57. scratchattach/site/project.py +0 -825
  58. scratchattach/site/session.py +0 -1238
  59. scratchattach/site/studio.py +0 -611
  60. scratchattach/site/user.py +0 -956
  61. scratchattach/utils/__init__.py +0 -0
  62. scratchattach/utils/commons.py +0 -255
  63. scratchattach/utils/encoder.py +0 -158
  64. scratchattach/utils/enums.py +0 -236
  65. scratchattach/utils/exceptions.py +0 -243
  66. scratchattach/utils/requests.py +0 -93
  67. scratchattach-2.1.14.dist-info/RECORD +0 -66
  68. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
  69. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
@@ -1,599 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import warnings
5
- from io import BytesIO, TextIOWrapper
6
- from typing import Optional, Any, BinaryIO
7
- from zipfile import ZipFile
8
- from typing import Iterable, TYPE_CHECKING
9
- from . import base, project, vlb, asset, comment, prim, block, commons, build_defaulting
10
- if TYPE_CHECKING:
11
- from . import asset
12
-
13
- class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
14
- _local_globals: list[base.NamedIDComponent]
15
- asset_data: list[asset.AssetFile]
16
- def __init__(self, is_stage: bool = False, name: str = '', _current_costume: int = 1, _layer_order: Optional[int] = None,
17
- _volume: int = 100,
18
- _broadcasts: Optional[list[vlb.Broadcast]] = None,
19
- _variables: Optional[list[vlb.Variable]] = None, _lists: Optional[list[vlb.List]] = None,
20
- _costumes: Optional[list[asset.Costume]] = None, _sounds: Optional[list[asset.Sound]] = None,
21
- _comments: Optional[list[comment.Comment]] = None, _prims: Optional[dict[str, prim.Prim]] = None,
22
- _blocks: Optional[dict[str, block.Block]] = None,
23
- # Stage only:
24
- _tempo: int | float = 60, _video_state: str = "off", _video_transparency: int | float = 50,
25
- _text_to_speech_language: str = "en", _visible: bool = True,
26
- # Sprite only:
27
- _x: int | float = 0, _y: int | float = 0, _size: int | float = 100, _direction: int | float = 90,
28
- _draggable: bool = False, _rotation_style: str = "all around",
29
-
30
- *, _project: Optional[project.Project] = None):
31
- """
32
- Represents a sprite or the stage (known internally as a Target)
33
- https://en.scratch-wiki.info/wiki/Scratch_File_Format#Targets
34
- """
35
- # Defaulting for list parameters
36
- if _broadcasts is None:
37
- _broadcasts = []
38
- if _variables is None:
39
- _variables = []
40
- if _lists is None:
41
- _lists = []
42
- if _costumes is None:
43
- _costumes = []
44
- if _sounds is None:
45
- _sounds = []
46
- if _comments is None:
47
- _comments = []
48
- if _prims is None:
49
- _prims = {}
50
- if _blocks is None:
51
- _blocks = {}
52
-
53
- self.is_stage = is_stage
54
- self.name = name
55
- self.current_costume = _current_costume
56
- self.layer_order = _layer_order
57
- self.volume = _volume
58
-
59
- self.broadcasts = _broadcasts
60
- self.variables = _variables
61
- self.lists = _lists
62
- self._local_globals = []
63
-
64
- self.costumes = _costumes
65
- self.sounds = _sounds
66
-
67
- self.comments = _comments
68
- self.prims = _prims
69
- self.blocks = _blocks
70
-
71
- self.tempo = _tempo
72
- self.video_state = _video_state
73
- self.video_transparency = _video_transparency
74
- self.text_to_speech_language = _text_to_speech_language
75
- self.visible = _visible
76
- self.x, self.y = _x, _y
77
- self.size = _size
78
- self.direction = _direction
79
- self.draggable = _draggable
80
- self.rotation_style = _rotation_style
81
-
82
- self.asset_data = []
83
-
84
- super().__init__(_project)
85
-
86
- # Assign sprite
87
- for iterable in (self.vlbs, self.comments, self.assets, self.prims.values(), self.blocks.values()):
88
- for sub_component in iterable:
89
- sub_component.sprite = self
90
-
91
- def __repr__(self):
92
- return f"Sprite<{self.name}>"
93
-
94
- def __enter__(self):
95
- build_defaulting.stack_add_sprite(self)
96
- return self
97
-
98
- def __exit__(self, exc_type, exc_val, exc_tb):
99
- build_defaulting.pop_sprite(self)
100
-
101
- def link_subcomponents(self):
102
- self.link_prims()
103
- self.link_blocks()
104
- self.link_comments()
105
-
106
- def link_prims(self):
107
- """
108
- Link primitives to corresponding VLB objects (requires project attribute)
109
- """
110
- for _prim in self.prims.values():
111
- _prim.link_using_sprite()
112
-
113
- def link_blocks(self):
114
- """
115
- Link blocks to sprite/to other blocks
116
- """
117
- for _block_id, _block in self.blocks.items():
118
- _block.link_using_sprite()
119
-
120
- def link_comments(self):
121
- for _comment in self.comments:
122
- _comment.link_using_sprite()
123
-
124
- def add_local_global(self, _vlb: base.NamedIDComponent):
125
- """
126
- Add a global variable/list to this sprite (for when an overarching project/stage is not available)
127
- """
128
- self._local_globals.append(_vlb)
129
- _vlb.sprite = self
130
-
131
- def add_variable(self, _variable: vlb.Variable):
132
- self.variables.append(_variable)
133
- _variable.sprite = self
134
-
135
- def add_list(self, _list: vlb.List):
136
- self.lists.append(_list)
137
- _list.sprite = self
138
-
139
- def add_broadcast(self, _broadcast: vlb.Broadcast):
140
- self.broadcasts.append(_broadcast)
141
- _broadcast.sprite = self
142
-
143
- def add_vlb(self, _vlb: base.NamedIDComponent):
144
- if isinstance(_vlb, vlb.Variable):
145
- self.add_variable(_vlb)
146
- elif isinstance(_vlb, vlb.List):
147
- self.add_list(_vlb)
148
- elif isinstance(_vlb, vlb.Broadcast):
149
- self.add_broadcast(_vlb)
150
- else:
151
- warnings.warn(f"Invalid 'VLB' {_vlb} of type: {type(_vlb)}")
152
-
153
- def add_block(self, _block: block.Block | prim.Prim) -> block.Block | prim.Prim:
154
- if _block.sprite is self:
155
- if _block in self.blocks.values():
156
- return _block
157
-
158
- _block.sprite = self
159
- new_id = self.new_id
160
-
161
- if isinstance(_block, block.Block):
162
- self.blocks[new_id] = _block
163
- _block.id = new_id
164
- _block.link_using_sprite()
165
-
166
- elif isinstance(_block, prim.Prim):
167
- self.prims[new_id] = _block
168
- _block.link_using_sprite()
169
-
170
- return _block
171
-
172
- def add_chain(self, *chain: block.Block | prim.Prim) -> block.Block | prim.Prim:
173
- """
174
- Adds a list of blocks to the sprite **AND RETURNS THE FIRST BLOCK**
175
- :param chain:
176
- :return:
177
- """
178
- chain = tuple(chain)
179
-
180
- _prev = self.add_block(chain[0])
181
-
182
- for _block in chain[1:]:
183
- if not isinstance(_prev, block.Block) or not isinstance(_block, block.Block):
184
- continue
185
- _prev = _prev.attach_block(_block)
186
-
187
- return chain[0]
188
-
189
- def add_comment(self, _comment: comment.Comment) -> comment.Comment:
190
- _comment.sprite = self
191
- if _comment.id is None:
192
- _comment.id = self.new_id
193
-
194
- self.comments.append(_comment)
195
- _comment.link_using_sprite()
196
-
197
- return _comment
198
-
199
- def remove_block(self, _block: block.Block):
200
- for key, val in self.blocks.items():
201
- if val is _block:
202
- del self.blocks[key]
203
- return
204
-
205
- @property
206
- def folder(self):
207
- return commons.get_folder_name(self.name)
208
-
209
- @property
210
- def name_nfldr(self):
211
- return commons.get_name_nofldr(self.name)
212
-
213
- @property
214
- def vlbs(self) -> list[base.NamedIDComponent]:
215
- """
216
- :return: All vlbs associated with the sprite. No local globals are added
217
- """
218
- vlbs: list[base.NamedIDComponent] = []
219
- vlbs.extend(self.variables)
220
- vlbs.extend(self.lists)
221
- vlbs.extend(self.broadcasts)
222
- return vlbs
223
-
224
- @property
225
- def assets(self) -> list[asset.Costume | asset.Sound]:
226
- return self.costumes + self.sounds
227
-
228
- @property
229
- def stage(self) -> Sprite:
230
- return self.project.stage
231
-
232
- @property
233
- def new_id(self):
234
- return commons.gen_id()
235
-
236
- @staticmethod
237
- def from_json(data: dict):
238
- _is_stage = data["isStage"]
239
- _name = data["name"]
240
- _current_costume = data.get("currentCostume", 1)
241
- _layer_order = data.get("layerOrder", 1)
242
- _volume = data.get("volume", 100)
243
-
244
- # Read VLB
245
- def read_idcomponent(attr_name: str, cls: type[base.IDComponent]):
246
- _vlbs = []
247
- for _vlb_id, _vlb_data in data.get(attr_name, {}).items():
248
- _vlbs.append(cls.from_json((_vlb_id, _vlb_data)))
249
- return _vlbs
250
-
251
- _variables = read_idcomponent("variables", vlb.Variable)
252
- _lists = read_idcomponent("lists", vlb.List)
253
- _broadcasts = read_idcomponent("broadcasts", vlb.Broadcast)
254
-
255
- # Read assets
256
- _costumes = []
257
- for _costume_data in data.get("costumes", []):
258
- _costumes.append(asset.Costume.from_json(_costume_data))
259
- _sounds = []
260
- for _sound_data in data.get("sounds", []):
261
- _sounds.append(asset.Sound.from_json(_sound_data))
262
-
263
- # Read comments
264
- _comments = read_idcomponent("comments", comment.Comment)
265
-
266
- # Read blocks/prims
267
- _prims = {}
268
- _blocks = {}
269
- for _block_id, _block_data in data.get("blocks", {}).items():
270
- if isinstance(_block_data, list):
271
- # Prim
272
- _prims[_block_id] = prim.Prim.from_json(_block_data)
273
- else:
274
- # Block
275
- _blocks[_block_id] = block.Block.from_json(_block_data)
276
-
277
- # Stage/sprite specific vars
278
- _tempo, _video_state, _video_transparency, _text_to_speech_language = (None,) * 4
279
- _visible, _x, _y, _size, _direction, _draggable, _rotation_style = (None,) * 7
280
- if _is_stage:
281
- _tempo = data["tempo"]
282
- _video_state = data["videoState"]
283
- _video_transparency = data["videoTransparency"]
284
- _text_to_speech_language = data["textToSpeechLanguage"]
285
- else:
286
- _visible = data["visible"]
287
- _x = data["x"]
288
- _y = data["y"]
289
- _size = data["size"]
290
- _direction = data["direction"]
291
- _draggable = data["draggable"]
292
- _rotation_style = data["rotationStyle"]
293
-
294
- return Sprite(_is_stage, _name, _current_costume, _layer_order, _volume, _broadcasts, _variables, _lists,
295
- _costumes,
296
- _sounds, _comments, _prims, _blocks,
297
-
298
- _tempo, _video_state, _video_transparency, _text_to_speech_language,
299
- _visible, _x, _y, _size, _direction, _draggable, _rotation_style
300
- )
301
-
302
- def to_json(self) -> dict:
303
- _json = {
304
- "isStage": self.is_stage,
305
- "name": self.name,
306
- "currentCostume": self.current_costume,
307
- "volume": self.volume,
308
- "layerOrder": self.layer_order,
309
-
310
- "variables": {_variable.id: _variable.to_json() for _variable in self.variables},
311
- "lists": {_list.id: _list.to_json() for _list in self.lists},
312
- "broadcasts": {_broadcast.id: _broadcast.to_json() for _broadcast in self.broadcasts},
313
-
314
- "blocks": {_block_id: _block.to_json() for _block_id, _block in (self.blocks | self.prims).items()},
315
- "comments": {_comment.id: _comment.to_json() for _comment in self.comments},
316
-
317
- "costumes": [_costume.to_json() for _costume in self.costumes],
318
- "sounds": [_sound.to_json() for _sound in self.sounds]
319
- }
320
-
321
- if self.is_stage:
322
- _json.update({
323
- "tempo": self.tempo,
324
- "videoTransparency": self.video_transparency,
325
- "videoState": self.video_state,
326
- "textToSpeechLanguage": self.text_to_speech_language
327
- })
328
- else:
329
- _json.update({
330
- "visible": self.visible,
331
-
332
- "x": self.x, "y": self.y,
333
- "size": self.size,
334
- "direction": self.direction,
335
-
336
- "draggable": self.draggable,
337
- "rotationStyle": self.rotation_style
338
- })
339
-
340
- return _json
341
-
342
- # Finding/getting from list/dict attributes
343
- def find_asset(self, value: str, by: str = "name", multiple: bool = False, a_type: Optional[type]=None) -> asset.Asset | asset.Sound | asset.Costume | list[asset.Asset | asset.Sound | asset.Costume]:
344
- if a_type is None:
345
- a_type = asset.Asset
346
-
347
- _ret = []
348
- by = by.lower()
349
- for _asset in self.assets:
350
- if not isinstance(_asset, a_type):
351
- continue
352
-
353
- # Defaulting
354
- compare = getattr(_asset, by)
355
-
356
- if compare == value:
357
- if multiple:
358
- _ret.append(_asset)
359
- else:
360
- return _asset
361
-
362
- if multiple:
363
- return _ret
364
-
365
- def find_variable(self, value: str, by: str = "name", multiple: bool = False) -> vlb.Variable | list[vlb.Variable]:
366
- _ret = []
367
- by = by.lower()
368
- for _variable in self.variables + self._local_globals:
369
- if not isinstance(_variable, vlb.Variable):
370
- continue
371
-
372
- if by == "id":
373
- compare = _variable.id
374
- else:
375
- # Defaulting
376
- compare = _variable.name
377
- if compare == value:
378
- if multiple:
379
- _ret.append(_variable)
380
- else:
381
- return _variable
382
- # Search in stage for global variables
383
- if self.project:
384
- if not self.is_stage:
385
- if multiple:
386
- _ret += self.stage.find_variable(value, by, True)
387
- else:
388
- return self.stage.find_variable(value, by)
389
-
390
- if multiple:
391
- return _ret
392
-
393
- def find_list(self, value: str, by: str = "name", multiple: bool = False) -> vlb.List | list[vlb.List]:
394
- _ret = []
395
- by = by.lower()
396
- for _list in self.lists + self._local_globals:
397
- if not isinstance(_list, vlb.List):
398
- continue
399
- if by == "id":
400
- compare = _list.id
401
- else:
402
- # Defaulting
403
- compare = _list.name
404
- if compare == value:
405
- if multiple:
406
- _ret.append(_list)
407
- else:
408
- return _list
409
- # Search in stage for global lists
410
- if self.project:
411
- if not self.is_stage:
412
- if multiple:
413
- _ret += self.stage.find_list(value, by, True)
414
- else:
415
- return self.stage.find_list(value, by)
416
-
417
- if multiple:
418
- return _ret
419
-
420
- def find_broadcast(self, value: str, by: str = "name", multiple: bool = False) -> vlb.Broadcast | list[
421
- vlb.Broadcast]:
422
- _ret = []
423
- by = by.lower()
424
- for _broadcast in self.broadcasts + self._local_globals:
425
- if not isinstance(_broadcast, vlb.Broadcast):
426
- continue
427
- if by == "id":
428
- compare = _broadcast.id
429
- else:
430
- # Defaulting
431
- compare = _broadcast.name
432
- if compare == value:
433
- if multiple:
434
- _ret.append(_broadcast)
435
- else:
436
- return _broadcast
437
- # Search in stage for global broadcasts
438
- if self.project:
439
- if not self.is_stage:
440
- if multiple:
441
- _ret += self.stage.find_broadcast(value, by, True)
442
- else:
443
- return self.stage.find_broadcast(value, by)
444
-
445
- if multiple:
446
- return _ret
447
-
448
- def find_vlb(self, value: str, by: str = "name",
449
- multiple: bool = False) -> vlb.Variable | vlb.List | vlb.Broadcast | list[
450
- vlb.Variable | vlb.List | vlb.Broadcast]:
451
- if multiple:
452
- return self.find_variable(value, by, True) + \
453
- self.find_list(value, by, True) + \
454
- self.find_broadcast(value, by, True)
455
- else:
456
- _ret = self.find_variable(value, by)
457
- if _ret is not None:
458
- return _ret
459
- _ret = self.find_list(value, by)
460
- if _ret is not None:
461
- return _ret
462
- return self.find_broadcast(value, by)
463
-
464
- def find_block(self, value: str | Any, by: str, multiple: bool = False) -> block.Block | prim.Prim | list[
465
- block.Block | prim.Prim]:
466
- _ret = []
467
- by = by.lower()
468
- for _block_id, _block in (self.blocks | self.prims).items():
469
- _block: block.Block | prim.Prim
470
-
471
- is_block = isinstance(_block, block.Block)
472
- is_prim = isinstance(_block, prim.Prim)
473
-
474
- compare = None
475
- if by == "id":
476
- compare = _block_id
477
- elif by == "argument ids":
478
- if is_prim:
479
- continue
480
-
481
- if _block.mutation is not None:
482
- compare = _block.mutation.argument_ids
483
- elif by == "opcode":
484
- if is_prim:
485
- continue
486
-
487
- # Defaulting
488
- compare = _block.opcode
489
- else:
490
- if is_block:
491
- compare = _block.opcode
492
- else:
493
- compare = _block.value
494
-
495
- if compare == value:
496
- if multiple:
497
- _ret.append(_block)
498
- else:
499
- return _block
500
- # Search in stage for global variables
501
- if self.project:
502
- if not self.is_stage:
503
- if multiple:
504
- _ret += self.stage.find_block(value, by, True)
505
- else:
506
- return self.stage.find_block(value, by)
507
-
508
- if multiple:
509
- return _ret
510
-
511
- def export(self, fp: Optional[str] = None, *, export_as_zip: bool = True):
512
- if fp is None:
513
- fp = commons.sanitize_fn(f"{self.name}.sprite3")
514
-
515
- data = self.to_json()
516
-
517
- if export_as_zip:
518
- with ZipFile(fp, "w") as archive:
519
- for _asset in self.assets:
520
- asset_file = _asset.asset_file
521
- if asset_file.filename not in archive.namelist():
522
- archive.writestr(asset_file.filename, asset_file.data)
523
-
524
- archive.writestr("sprite.json", json.dumps(data))
525
- else:
526
- with open(fp, "w") as json_file:
527
- json.dump(data, json_file)
528
-
529
- @property
530
- def all_ids(self):
531
- ret = []
532
- for _vlb in self.vlbs + self._local_globals:
533
- ret.append(_vlb.id)
534
- for iterator in self.blocks.keys(), self.prims.keys():
535
- ret += list(iterator)
536
-
537
- return ret
538
- @staticmethod
539
- def load_json(data: str | bytes | TextIOWrapper | BinaryIO, load_assets: bool = True, _name: Optional[str] = None):
540
- _dir_for_name = None
541
-
542
- if _name is None:
543
- if hasattr(data, "name"):
544
- _dir_for_name = data.name
545
-
546
- if not isinstance(_name, str) and _name is not None:
547
- _name = str(_name)
548
-
549
- if isinstance(data, bytes):
550
- data = BytesIO(data)
551
-
552
- elif isinstance(data, str):
553
- _dir_for_name = data
554
- data = open(data, "rb")
555
-
556
- if _name is None and _dir_for_name is not None:
557
- # Remove any directory names and the file extension
558
- _name = _dir_for_name.split('/')[-1]
559
- _name = '.'.join(_name.split('.')[:-1])
560
-
561
- asset_data = []
562
- with data:
563
- # For if the sprite3 is just JSON (e.g. if it's exported from scratchattach)
564
- if commons.is_valid_json(data):
565
- json_str = data
566
-
567
- else:
568
- with ZipFile(data) as archive:
569
- json_str = archive.read("sprite.json")
570
-
571
- # Also load assets
572
- if load_assets:
573
-
574
- for filename in archive.namelist():
575
- if filename != "sprite.json":
576
- md5_hash = filename.split('.')[0]
577
-
578
- asset_data.append(
579
- asset.AssetFile(filename, archive.read(filename), md5_hash)
580
- )
581
- else:
582
- warnings.warn(
583
- "Loading sb3 without loading assets. When exporting the project, there may be errors due to assets not being uploaded to the Scratch website")
584
-
585
- return _name, asset_data, json_str
586
-
587
- @classmethod
588
- def from_sprite3(cls, data: str | bytes | TextIOWrapper | BinaryIO, load_assets: bool = True, _name: Optional[str] = None):
589
- """
590
- Load a project from an .sb3 file/bytes/file path
591
- """
592
- _name, asset_data, json_str = cls.load_json(data, load_assets, _name)
593
- data = json.loads(json_str)
594
-
595
- sprite = cls.from_json(data)
596
- # Sprites already have names, so we probably don't want to set it
597
- # sprite.name = _name
598
- sprite.asset_data = asset_data
599
- return sprite
@@ -1,114 +0,0 @@
1
- """
2
- Parser for TurboWarp settings configuration
3
- """
4
-
5
- from __future__ import annotations
6
-
7
- import json
8
- import math
9
- from dataclasses import dataclass
10
- from typing import Any
11
-
12
- from . import commons, base
13
-
14
- _START = """Configuration for https://turbowarp.org/
15
- You can move, resize, and minimize this comment, but don't edit it by hand. This comment can be deleted to remove the stored settings.
16
- """
17
- _END = " // _twconfig_"
18
-
19
-
20
- @dataclass
21
- class TWConfig(base.JSONSerializable):
22
- framerate: int = None,
23
- interpolation: bool = False,
24
- hq_pen: bool = False,
25
- max_clones: float | int | None = None,
26
- misc_limits: bool = True,
27
- fencing: bool = True
28
- width: int = None
29
- height: int = None
30
-
31
- @staticmethod
32
- def from_json(data: dict) -> TWConfig:
33
- # Non-runtime options
34
- _framerate = data.get("framerate")
35
- _interpolation = data.get("interpolation", False)
36
- _hq_pen = data.get("hq", False)
37
-
38
- # Runtime options
39
- _runtime_options = data.get("runtimeOptions", {})
40
-
41
- # Luckily for us, the JSON module actually accepts the 'Infinity' literal. Otherwise, it would be a right pain
42
- _max_clones = _runtime_options.get("maxClones")
43
- _misc_limits = _runtime_options.get("miscLimits", True)
44
- _fencing = _runtime_options.get("fencing", True)
45
-
46
- # Custom stage size
47
- _width = data.get("width")
48
- _height = data.get("height")
49
-
50
- return TWConfig(_framerate, _interpolation, _hq_pen, _max_clones, _misc_limits, _fencing, _width, _height)
51
-
52
- def to_json(self) -> dict:
53
- runtime_options = {}
54
- commons.noneless_update(
55
- runtime_options,
56
- {
57
- "maxClones": self.max_clones,
58
- "miscLimits": none_if_eq(self.misc_limits, True),
59
- "fencing": none_if_eq(self.fencing, True)
60
- })
61
-
62
- data = {}
63
- commons.noneless_update(data, {
64
- "framerate": self.framerate,
65
- "runtimeOptions": runtime_options,
66
- "interpolation": none_if_eq(self.interpolation, False),
67
- "hq": none_if_eq(self.hq_pen, False),
68
- "width": self.width,
69
- "height": self.height
70
- })
71
- return data
72
-
73
- @property
74
- def infinite_clones(self):
75
- return self.max_clones == math.inf
76
-
77
- @staticmethod
78
- def from_str(string: str):
79
- return TWConfig.from_json(get_twconfig_data(string))
80
-
81
-
82
- def is_valid_twconfig(string: str) -> bool:
83
- """
84
- Checks if some text is TWConfig (does not check the JSON itself)
85
- :param string: text (from a comment)
86
- :return: Boolean whether it is TWConfig
87
- """
88
-
89
- if string.startswith(_START) and string.endswith(_END):
90
- json_part = string[len(_START):-len(_END)]
91
- if commons.is_valid_json(json_part):
92
- return True
93
- return False
94
-
95
-
96
- def get_twconfig_data(string: str) -> dict | None:
97
- try:
98
- return json.loads(string[len(_START):-len(_END)])
99
- except ValueError:
100
- return None
101
-
102
-
103
- # todo: move this to commons.py?
104
- def none_if_eq(data, compare) -> Any | None:
105
- """
106
- Returns None if data and compare are the same
107
- :param data: Original data
108
- :param compare: Data to compare
109
- :return: Either the original data or None
110
- """
111
- if data == compare:
112
- return None
113
- else:
114
- return data