scratchattach 2.1.9__py3-none-any.whl → 2.1.10a1__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.
- scratchattach/__init__.py +28 -25
- scratchattach/cloud/__init__.py +2 -0
- scratchattach/cloud/_base.py +454 -282
- scratchattach/cloud/cloud.py +171 -168
- scratchattach/editor/__init__.py +21 -0
- scratchattach/editor/asset.py +199 -0
- scratchattach/editor/backpack_json.py +117 -0
- scratchattach/editor/base.py +142 -0
- scratchattach/editor/block.py +507 -0
- scratchattach/editor/blockshape.py +353 -0
- scratchattach/editor/build_defaulting.py +47 -0
- scratchattach/editor/comment.py +74 -0
- scratchattach/editor/commons.py +243 -0
- scratchattach/editor/extension.py +43 -0
- scratchattach/editor/field.py +90 -0
- scratchattach/editor/inputs.py +132 -0
- scratchattach/editor/meta.py +106 -0
- scratchattach/editor/monitor.py +175 -0
- scratchattach/editor/mutation.py +317 -0
- scratchattach/editor/pallete.py +91 -0
- scratchattach/editor/prim.py +170 -0
- scratchattach/editor/project.py +273 -0
- scratchattach/editor/sbuild.py +2837 -0
- scratchattach/editor/sprite.py +586 -0
- scratchattach/editor/twconfig.py +113 -0
- scratchattach/editor/vlb.py +134 -0
- scratchattach/eventhandlers/_base.py +99 -92
- scratchattach/eventhandlers/cloud_events.py +110 -103
- scratchattach/eventhandlers/cloud_recorder.py +26 -21
- scratchattach/eventhandlers/cloud_requests.py +460 -452
- scratchattach/eventhandlers/cloud_server.py +246 -244
- scratchattach/eventhandlers/cloud_storage.py +135 -134
- scratchattach/eventhandlers/combine.py +29 -27
- scratchattach/eventhandlers/filterbot.py +160 -159
- scratchattach/eventhandlers/message_events.py +41 -40
- scratchattach/other/other_apis.py +284 -212
- scratchattach/other/project_json_capabilities.py +475 -546
- scratchattach/site/_base.py +64 -46
- scratchattach/site/activity.py +414 -122
- scratchattach/site/backpack_asset.py +118 -84
- scratchattach/site/classroom.py +430 -142
- scratchattach/site/cloud_activity.py +107 -103
- scratchattach/site/comment.py +220 -190
- scratchattach/site/forum.py +400 -399
- scratchattach/site/project.py +806 -787
- scratchattach/site/session.py +1134 -867
- scratchattach/site/studio.py +611 -609
- scratchattach/site/user.py +835 -837
- scratchattach/utils/commons.py +243 -148
- scratchattach/utils/encoder.py +157 -156
- scratchattach/utils/enums.py +197 -190
- scratchattach/utils/exceptions.py +233 -206
- scratchattach/utils/requests.py +67 -59
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/METADATA +155 -146
- scratchattach-2.1.10a1.dist-info/RECORD +62 -0
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/WHEEL +1 -1
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info/licenses}/LICENSE +21 -21
- scratchattach-2.1.9.dist-info/RECORD +0 -40
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Optional, Iterable, Self
|
|
5
|
+
|
|
6
|
+
from . import base, sprite, mutation, field, inputs, commons, vlb, blockshape, prim, comment, build_defaulting
|
|
7
|
+
from ..utils import exceptions
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Block(base.SpriteSubComponent):
|
|
11
|
+
def __init__(self, _opcode: str, _shadow: bool = False, _top_level: Optional[bool] = None,
|
|
12
|
+
_mutation: Optional[mutation.Mutation] = None, _fields: Optional[dict[str, field.Field]] = None,
|
|
13
|
+
_inputs: Optional[dict[str, inputs.Input]] = None, x: int = 0, y: int = 0, pos: Optional[tuple[int, int]] = None,
|
|
14
|
+
|
|
15
|
+
_next: Optional[Block] = None, _parent: Optional[Block] = None,
|
|
16
|
+
*, _next_id: Optional[str] = None, _parent_id: Optional[str] = None, _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
|
|
17
|
+
# Defaulting for args
|
|
18
|
+
if _fields is None:
|
|
19
|
+
_fields = {}
|
|
20
|
+
if _inputs is None:
|
|
21
|
+
_inputs = {}
|
|
22
|
+
|
|
23
|
+
if pos is not None:
|
|
24
|
+
x, y = pos
|
|
25
|
+
|
|
26
|
+
self.opcode = _opcode
|
|
27
|
+
self.is_shadow = _shadow
|
|
28
|
+
self.is_top_level = _top_level
|
|
29
|
+
|
|
30
|
+
self.x, self.y = x, y
|
|
31
|
+
|
|
32
|
+
self.mutation = _mutation
|
|
33
|
+
self.fields = _fields
|
|
34
|
+
self.inputs = _inputs
|
|
35
|
+
|
|
36
|
+
self._next_id = _next_id
|
|
37
|
+
"""
|
|
38
|
+
Temporarily stores id of next block. Will be used later during project instantiation to find the next block object
|
|
39
|
+
"""
|
|
40
|
+
self._parent_id = _parent_id
|
|
41
|
+
"""
|
|
42
|
+
Temporarily stores id of parent block. Will be used later during project instantiation to find the parent block object
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
self.next = _next
|
|
46
|
+
self.parent = _parent
|
|
47
|
+
|
|
48
|
+
self.check_toplevel()
|
|
49
|
+
|
|
50
|
+
super().__init__(_sprite)
|
|
51
|
+
self.link_subcomponents()
|
|
52
|
+
|
|
53
|
+
def __repr__(self):
|
|
54
|
+
return f"Block<{self.opcode!r}>"
|
|
55
|
+
|
|
56
|
+
def link_subcomponents(self):
|
|
57
|
+
if self.mutation:
|
|
58
|
+
self.mutation.block = self
|
|
59
|
+
|
|
60
|
+
for iterable in (self.fields.values(), self.inputs.values()):
|
|
61
|
+
for subcomponent in iterable:
|
|
62
|
+
subcomponent.block = self
|
|
63
|
+
|
|
64
|
+
def add_input(self, name: str, _input: inputs.Input) -> Self:
|
|
65
|
+
self.inputs[name] = _input
|
|
66
|
+
for val in (_input.value, _input.obscurer):
|
|
67
|
+
if isinstance(val, Block):
|
|
68
|
+
val.parent = self
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def add_field(self, name: str, _field: field.Field) -> Self:
|
|
72
|
+
self.fields[name] = _field
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
def set_mutation(self, _mutation: mutation.Mutation) -> Self:
|
|
76
|
+
self.mutation = _mutation
|
|
77
|
+
_mutation.block = self
|
|
78
|
+
_mutation.link_arguments()
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def set_comment(self, _comment: comment.Comment) -> Self:
|
|
82
|
+
_comment.block = self
|
|
83
|
+
self.sprite.add_comment(_comment)
|
|
84
|
+
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def check_toplevel(self):
|
|
88
|
+
self.is_top_level = self.parent is None
|
|
89
|
+
|
|
90
|
+
if not self.is_top_level:
|
|
91
|
+
self.x, self.y = None, None
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def target(self):
|
|
95
|
+
"""
|
|
96
|
+
Alias for sprite
|
|
97
|
+
"""
|
|
98
|
+
return self.sprite
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def block_shape(self) -> blockshape.BlockShape:
|
|
102
|
+
"""
|
|
103
|
+
Search for the blockshape stored in blockshape.py
|
|
104
|
+
:return: The block's block shape (by opcode)
|
|
105
|
+
"""
|
|
106
|
+
_shape = blockshape.BlockShapes.find(self.opcode, "opcode")
|
|
107
|
+
if _shape is None:
|
|
108
|
+
warnings.warn(f"No blockshape {self.opcode!r} exists! Defaulting to {blockshape.BlockShapes.UNDEFINED}")
|
|
109
|
+
return blockshape.BlockShapes.UNDEFINED
|
|
110
|
+
return _shape
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def can_next(self):
|
|
114
|
+
"""
|
|
115
|
+
:return: Whether the block *can* have a next block (basically checks if it's not a cap block, also considering the behaviour of control_stop)
|
|
116
|
+
"""
|
|
117
|
+
_shape = self.block_shape
|
|
118
|
+
if _shape.is_cap is not blockshape.MUTATION_DEPENDENT:
|
|
119
|
+
return _shape.is_attachable
|
|
120
|
+
else:
|
|
121
|
+
if self.mutation is None:
|
|
122
|
+
# If there's no mutation, let's just assume yes
|
|
123
|
+
warnings.warn(f"{self} has no mutation! Assuming we can add block ;-;")
|
|
124
|
+
return True
|
|
125
|
+
|
|
126
|
+
return self.mutation.has_next
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def id(self) -> str | None:
|
|
130
|
+
"""
|
|
131
|
+
Work out the id of this block by searching through the sprite dictionary
|
|
132
|
+
"""
|
|
133
|
+
# warnings.warn(f"Using block IDs can cause consistency issues and is not recommended")
|
|
134
|
+
# This property is used when converting comments to JSON (we don't want random warning when exporting a project)
|
|
135
|
+
for _block_id, _block in self.sprite.blocks.items():
|
|
136
|
+
if _block is self:
|
|
137
|
+
return _block_id
|
|
138
|
+
|
|
139
|
+
# Let's just automatically assign ourselves an id
|
|
140
|
+
self.sprite.add_block(self)
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def parent_id(self):
|
|
144
|
+
if self.parent is not None:
|
|
145
|
+
return self.parent.id
|
|
146
|
+
else:
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def next_id(self):
|
|
151
|
+
if self.next is not None:
|
|
152
|
+
return self.next.id
|
|
153
|
+
else:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def relatives(self) -> list[Block]:
|
|
158
|
+
"""
|
|
159
|
+
:return: A list of blocks which are related to this block (e.g. parent, next, inputs)
|
|
160
|
+
"""
|
|
161
|
+
_ret = []
|
|
162
|
+
|
|
163
|
+
def yield_block(_block: Block | None):
|
|
164
|
+
if isinstance(_block, Block):
|
|
165
|
+
_ret.append(_block)
|
|
166
|
+
|
|
167
|
+
yield_block(self.next)
|
|
168
|
+
yield_block(self.parent)
|
|
169
|
+
|
|
170
|
+
return _ret
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def children(self) -> list[Block | prim.Prim]:
|
|
174
|
+
"""
|
|
175
|
+
:return: A list of blocks that are inside of this block, **NOT INCLUDING THE ATTACHED BLOCK**
|
|
176
|
+
"""
|
|
177
|
+
_children = []
|
|
178
|
+
for _input in self.inputs.values():
|
|
179
|
+
if isinstance(_input.value, Block) or isinstance(_input.value, prim.Prim):
|
|
180
|
+
_children.append(_input.value)
|
|
181
|
+
|
|
182
|
+
if _input.obscurer is not None:
|
|
183
|
+
_children.append(_input.obscurer)
|
|
184
|
+
return _children
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def previous_chain(self):
|
|
188
|
+
if self.parent is None:
|
|
189
|
+
return [self]
|
|
190
|
+
|
|
191
|
+
return [self] + self.parent.previous_chain
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def attached_chain(self):
|
|
195
|
+
if self.next is None:
|
|
196
|
+
return [self]
|
|
197
|
+
|
|
198
|
+
return [self] + self.next.attached_chain
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def complete_chain(self):
|
|
202
|
+
# Both previous and attached chains start with self
|
|
203
|
+
return self.previous_chain[:1:-1] + self.attached_chain
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def top_level_block(self):
|
|
207
|
+
"""
|
|
208
|
+
same as the old stack_parent property from sbedtior v1
|
|
209
|
+
"""
|
|
210
|
+
return self.previous_chain[-1]
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def bottom_level_block(self):
|
|
214
|
+
return self.attached_chain[-1]
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def stack_tree(self):
|
|
218
|
+
"""
|
|
219
|
+
:return: A tree-like nested list structure representing the stack of blocks, including inputs, starting at this block
|
|
220
|
+
"""
|
|
221
|
+
_tree = [self]
|
|
222
|
+
for child in self.children:
|
|
223
|
+
if isinstance(child, prim.Prim):
|
|
224
|
+
_tree.append(child)
|
|
225
|
+
elif isinstance(child, Block):
|
|
226
|
+
_tree.append(child.stack_tree)
|
|
227
|
+
|
|
228
|
+
if self.next:
|
|
229
|
+
_tree += self.next.stack_tree
|
|
230
|
+
|
|
231
|
+
return _tree
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def category(self):
|
|
235
|
+
"""
|
|
236
|
+
Works out what category of block this is using the opcode. Does not perform validation
|
|
237
|
+
"""
|
|
238
|
+
return self.opcode.split('_')[0]
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def is_input(self):
|
|
242
|
+
"""
|
|
243
|
+
:return: Whether this block is an input obscurer or value
|
|
244
|
+
"""
|
|
245
|
+
return self.parent_input is not None
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def is_next_block(self):
|
|
249
|
+
"""
|
|
250
|
+
:return: Whether this block is attached (as next block) to a previous block and not an input
|
|
251
|
+
"""
|
|
252
|
+
return self.parent and not self.is_input
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def parent_input(self):
|
|
256
|
+
if not self.parent:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
for _input in self.parent.inputs.values():
|
|
260
|
+
if _input.obscurer is self or _input.value is self:
|
|
261
|
+
return _input
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def new_id(self):
|
|
266
|
+
return self.sprite.new_id
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def comment(self) -> comment.Comment | None:
|
|
270
|
+
for _comment in self.sprite.comments:
|
|
271
|
+
if _comment.block is self:
|
|
272
|
+
return _comment
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def turbowarp_block_opcode(self):
|
|
277
|
+
"""
|
|
278
|
+
:return: The 'opcode' if this is a turbowarp block: e.g.
|
|
279
|
+
- log
|
|
280
|
+
- breakpoint
|
|
281
|
+
- error
|
|
282
|
+
- warn
|
|
283
|
+
- is compiled?
|
|
284
|
+
- is turbowarp?
|
|
285
|
+
- is forkphorus?
|
|
286
|
+
If it's not one, just returns None
|
|
287
|
+
"""
|
|
288
|
+
if self.opcode == "procedures_call":
|
|
289
|
+
if self.mutation:
|
|
290
|
+
if self.mutation.proc_code:
|
|
291
|
+
# \u200B is a zero-width space
|
|
292
|
+
if self.mutation.proc_code == "\u200B\u200Bbreakpoint\u200B\u200B":
|
|
293
|
+
return "breakpoint"
|
|
294
|
+
elif self.mutation.proc_code == "\u200B\u200Blog\u200B\u200B %s":
|
|
295
|
+
return "log"
|
|
296
|
+
elif self.mutation.proc_code == "\u200B\u200Berror\u200B\u200B %s":
|
|
297
|
+
return "error"
|
|
298
|
+
elif self.mutation.proc_code == "\u200B\u200Bwarn\u200B\u200B %s":
|
|
299
|
+
return "warn"
|
|
300
|
+
|
|
301
|
+
elif self.opcode == "argument_reporter_boolean":
|
|
302
|
+
arg = self.fields.get("VALUE")
|
|
303
|
+
|
|
304
|
+
if arg is not None:
|
|
305
|
+
arg = arg.value
|
|
306
|
+
if isinstance(arg, str):
|
|
307
|
+
arg = arg.lower()
|
|
308
|
+
|
|
309
|
+
if arg == "is turbowarp?":
|
|
310
|
+
return "is_turbowarp?"
|
|
311
|
+
|
|
312
|
+
elif arg == "is compiled?":
|
|
313
|
+
return "is_compiled?"
|
|
314
|
+
|
|
315
|
+
elif arg == "is forkphorus?":
|
|
316
|
+
return "is_forkphorus?"
|
|
317
|
+
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def is_turbowarp_block(self):
|
|
322
|
+
return self.turbowarp_block_opcode is not None
|
|
323
|
+
|
|
324
|
+
@staticmethod
|
|
325
|
+
def from_json(data: dict) -> Block:
|
|
326
|
+
"""
|
|
327
|
+
Load a block from the JSON dictionary.
|
|
328
|
+
:param data: a dictionary (not list)
|
|
329
|
+
:return: The new Block object
|
|
330
|
+
"""
|
|
331
|
+
_opcode = data["opcode"]
|
|
332
|
+
|
|
333
|
+
_x, _y = data.get("x"), data.get("y")
|
|
334
|
+
|
|
335
|
+
_next_id = data.get("next")
|
|
336
|
+
_parent_id = data.get("parent")
|
|
337
|
+
|
|
338
|
+
_shadow = data.get("shadow", False)
|
|
339
|
+
_top_level = data.get("topLevel", _parent_id is None)
|
|
340
|
+
|
|
341
|
+
_inputs = {}
|
|
342
|
+
for _input_code, _input_data in data.get("inputs", {}).items():
|
|
343
|
+
_inputs[_input_code] = inputs.Input.from_json(_input_data)
|
|
344
|
+
|
|
345
|
+
_fields = {}
|
|
346
|
+
for _field_code, _field_data in data.get("fields", {}).items():
|
|
347
|
+
_fields[_field_code] = field.Field.from_json(_field_data)
|
|
348
|
+
|
|
349
|
+
if "mutation" in data:
|
|
350
|
+
_mutation = mutation.Mutation.from_json(data["mutation"])
|
|
351
|
+
else:
|
|
352
|
+
_mutation = None
|
|
353
|
+
|
|
354
|
+
return Block(_opcode, _shadow, _top_level, _mutation, _fields, _inputs, _x, _y, _next_id=_next_id,
|
|
355
|
+
_parent_id=_parent_id)
|
|
356
|
+
|
|
357
|
+
def to_json(self) -> dict:
|
|
358
|
+
self.check_toplevel()
|
|
359
|
+
|
|
360
|
+
_json = {
|
|
361
|
+
"opcode": self.opcode,
|
|
362
|
+
"next": self.next_id,
|
|
363
|
+
"parent": self.parent_id,
|
|
364
|
+
"inputs": {_id: _input.to_json() for _id, _input in self.inputs.items()},
|
|
365
|
+
"fields": {_id: _field.to_json() for _id, _field in self.fields.items()},
|
|
366
|
+
"shadow": self.is_shadow,
|
|
367
|
+
"topLevel": self.is_top_level,
|
|
368
|
+
}
|
|
369
|
+
_comment = self.comment
|
|
370
|
+
if _comment:
|
|
371
|
+
commons.noneless_update(_json, {
|
|
372
|
+
"comment": _comment.id
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
if self.is_top_level:
|
|
376
|
+
commons.noneless_update(_json, {
|
|
377
|
+
"x": self.x,
|
|
378
|
+
"y": self.y,
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
if self.mutation is not None:
|
|
382
|
+
commons.noneless_update(_json, {
|
|
383
|
+
"mutation": self.mutation.to_json(),
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
return _json
|
|
387
|
+
|
|
388
|
+
def link_using_sprite(self, link_subs: bool = True):
|
|
389
|
+
if link_subs:
|
|
390
|
+
self.link_subcomponents()
|
|
391
|
+
|
|
392
|
+
if self.mutation:
|
|
393
|
+
self.mutation.link_arguments()
|
|
394
|
+
|
|
395
|
+
if self._parent_id is not None:
|
|
396
|
+
self.parent = self.sprite.find_block(self._parent_id, "id")
|
|
397
|
+
if self.parent is not None:
|
|
398
|
+
self._parent_id = None
|
|
399
|
+
|
|
400
|
+
if self._next_id is not None:
|
|
401
|
+
self.next = self.sprite.find_block(self._next_id, "id")
|
|
402
|
+
if self.next is not None:
|
|
403
|
+
self._next_id = None
|
|
404
|
+
|
|
405
|
+
for _block in self.relatives:
|
|
406
|
+
_block.sprite = self.sprite
|
|
407
|
+
|
|
408
|
+
for _field in self.fields.values():
|
|
409
|
+
if _field.id is not None:
|
|
410
|
+
new_value = self.sprite.find_vlb(_field.id, "id")
|
|
411
|
+
if new_value is None:
|
|
412
|
+
# We probably need to add a local global variable
|
|
413
|
+
_type = _field.type
|
|
414
|
+
|
|
415
|
+
if _type == field.Types.VARIABLE:
|
|
416
|
+
# Create a new variable
|
|
417
|
+
new_value = vlb.Variable(commons.gen_id(),
|
|
418
|
+
_field.value)
|
|
419
|
+
elif _type == field.Types.LIST:
|
|
420
|
+
# Create a list
|
|
421
|
+
new_value = vlb.List(commons.gen_id(),
|
|
422
|
+
_field.value)
|
|
423
|
+
elif _type == field.Types.BROADCAST:
|
|
424
|
+
# Create a broadcast
|
|
425
|
+
new_value = vlb.Broadcast(commons.gen_id(),
|
|
426
|
+
_field.value)
|
|
427
|
+
else:
|
|
428
|
+
# Something probably went wrong
|
|
429
|
+
warnings.warn(
|
|
430
|
+
f"Could not find {_field.id!r} in {self.sprite}. Can't create a new {_type} so we gave a warning")
|
|
431
|
+
|
|
432
|
+
if new_value is not None:
|
|
433
|
+
self.sprite.add_local_global(new_value)
|
|
434
|
+
|
|
435
|
+
# Check again since there may have been a newly created VLB
|
|
436
|
+
if new_value is not None:
|
|
437
|
+
_field.value = new_value
|
|
438
|
+
_field.id = None
|
|
439
|
+
|
|
440
|
+
for _input in self.inputs.values():
|
|
441
|
+
_input.link_using_block()
|
|
442
|
+
|
|
443
|
+
# Adding/removing block
|
|
444
|
+
def attach_block(self, new: Block) -> Block:
|
|
445
|
+
if not self.can_next:
|
|
446
|
+
raise exceptions.BadBlockShape(f"{self.block_shape} cannot be stacked onto")
|
|
447
|
+
elif new.block_shape.is_hat or not new.block_shape.is_stack:
|
|
448
|
+
raise exceptions.BadBlockShape(f"{new.block_shape} is not stackable")
|
|
449
|
+
|
|
450
|
+
new.parent = self
|
|
451
|
+
new.next = self.next
|
|
452
|
+
|
|
453
|
+
self.next = new
|
|
454
|
+
|
|
455
|
+
new.check_toplevel()
|
|
456
|
+
self.sprite.add_block(new)
|
|
457
|
+
|
|
458
|
+
return new
|
|
459
|
+
|
|
460
|
+
def duplicate_single_block(self) -> Block:
|
|
461
|
+
return self.attach_block(self.dcopy())
|
|
462
|
+
|
|
463
|
+
def attach_chain(self, *chain: Iterable[Block]) -> Block:
|
|
464
|
+
attaching_block = self
|
|
465
|
+
for _block in chain:
|
|
466
|
+
attaching_block = attaching_block.attach_block(_block)
|
|
467
|
+
|
|
468
|
+
return attaching_block
|
|
469
|
+
|
|
470
|
+
def duplicate_chain(self) -> Block:
|
|
471
|
+
return self.bottom_level_block.attach_chain(
|
|
472
|
+
*map(Block.dcopy, self.attached_chain)
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
def slot_above(self, new: Block) -> Block:
|
|
476
|
+
if not new.can_next:
|
|
477
|
+
raise exceptions.BadBlockShape(f"{new.block_shape} cannot be stacked onto")
|
|
478
|
+
|
|
479
|
+
elif self.block_shape.is_hat or not self.block_shape.is_stack:
|
|
480
|
+
raise exceptions.BadBlockShape(f"{self.block_shape} is not stackable")
|
|
481
|
+
|
|
482
|
+
new.parent, new.next = self.parent, self
|
|
483
|
+
|
|
484
|
+
self.parent = new
|
|
485
|
+
|
|
486
|
+
if new.parent:
|
|
487
|
+
new.parent.next = new
|
|
488
|
+
|
|
489
|
+
return self.sprite.add_block(new)
|
|
490
|
+
|
|
491
|
+
def delete_single_block(self):
|
|
492
|
+
if self.is_next_block:
|
|
493
|
+
self.parent.next = self.next
|
|
494
|
+
|
|
495
|
+
if self.next:
|
|
496
|
+
self.next.parent = self.parent
|
|
497
|
+
|
|
498
|
+
if self.is_top_level:
|
|
499
|
+
self.next.is_top_level = True
|
|
500
|
+
self.next.x, self.next.y = self.next.x, self.next.y
|
|
501
|
+
|
|
502
|
+
self.sprite.remove_block(self)
|
|
503
|
+
|
|
504
|
+
def delete_chain(self):
|
|
505
|
+
for _block in self.attached_chain:
|
|
506
|
+
_block.delete_single_block()
|
|
507
|
+
|