anycubic-cloud-api 0.1.0__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 (33) hide show
  1. anycubic_cloud_api/__init__.py +62 -0
  2. anycubic_cloud_api/anycubic_api.py +7 -0
  3. anycubic_cloud_api/api/base.py +400 -0
  4. anycubic_cloud_api/api/functions.py +2055 -0
  5. anycubic_cloud_api/api/mqtt.py +449 -0
  6. anycubic_cloud_api/const/api_endpoints.py +106 -0
  7. anycubic_cloud_api/const/const.py +47 -0
  8. anycubic_cloud_api/const/enums.py +97 -0
  9. anycubic_cloud_api/const/mqtt.py +13 -0
  10. anycubic_cloud_api/data_models/consumable.py +116 -0
  11. anycubic_cloud_api/data_models/files.py +429 -0
  12. anycubic_cloud_api/data_models/gcode_file.py +107 -0
  13. anycubic_cloud_api/data_models/orders.py +369 -0
  14. anycubic_cloud_api/data_models/print_response.py +71 -0
  15. anycubic_cloud_api/data_models/print_speed_mode.py +61 -0
  16. anycubic_cloud_api/data_models/printer.py +2753 -0
  17. anycubic_cloud_api/data_models/printer_properties.py +1107 -0
  18. anycubic_cloud_api/data_models/printing_settings.py +130 -0
  19. anycubic_cloud_api/data_models/project.py +1234 -0
  20. anycubic_cloud_api/exceptions/error_strings.py +340 -0
  21. anycubic_cloud_api/exceptions/exceptions.py +91 -0
  22. anycubic_cloud_api/helpers/helpers.py +240 -0
  23. anycubic_cloud_api/models/auth.py +396 -0
  24. anycubic_cloud_api/models/cloud_upload.py +249 -0
  25. anycubic_cloud_api/models/http.py +29 -0
  26. anycubic_cloud_api/resources/anycubic_mqqt_tls_ca.crt +24 -0
  27. anycubic_cloud_api/resources/anycubic_mqqt_tls_client.crt +24 -0
  28. anycubic_cloud_api/resources/anycubic_mqqt_tls_client.key +28 -0
  29. anycubic_cloud_api-0.1.0.dist-info/METADATA +79 -0
  30. anycubic_cloud_api-0.1.0.dist-info/RECORD +33 -0
  31. anycubic_cloud_api-0.1.0.dist-info/WHEEL +5 -0
  32. anycubic_cloud_api-0.1.0.dist-info/licenses/LICENSE +674 -0
  33. anycubic_cloud_api-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,13 @@
1
+ MQTT_HOST = "mqtt-universe.anycubic.com"
2
+ MQTT_PORT = 8883
3
+
4
+ MQTT_TOPIC_PREFIX = "anycubic/anycubicCloud/v1"
5
+ MQTT_ROOT_TOPIC_PLUS = f"{MQTT_TOPIC_PREFIX}/+/public/"
6
+ MQTT_ROOT_TOPIC_PRINTER = f"{MQTT_TOPIC_PREFIX}/printer/app/"
7
+ MQTT_ROOT_TOPIC_PUBLISH = f"{MQTT_TOPIC_PREFIX}/app/"
8
+ MQTT_ROOT_TOPIC_PUBLISH_PRINTER = f"{MQTT_TOPIC_PREFIX}/printer/public/"
9
+ MQTT_ROOT_TOPIC_SERVER = f"{MQTT_TOPIC_PREFIX}/server/app/"
10
+ MQTT_ROOT_TOPIC_SLICER = f"{MQTT_TOPIC_PREFIX}/pc/printer/"
11
+ # Slicer MQTT topics seem to use /pc/ instead of /app/
12
+
13
+ MQTT_TIMEOUT = 60 * 20
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import UserDict
4
+ from typing import Any
5
+
6
+
7
+ class AnycubicConsumableData(UserDict[Any, Any]):
8
+ __slots__ = (
9
+ "data",
10
+ "_consumed_data",
11
+ )
12
+
13
+ def __init__(
14
+ self,
15
+ *args: Any,
16
+ **kwargs: Any,
17
+ ) -> None:
18
+ super().__init__(*args, **kwargs)
19
+ self.data: dict[Any, Any] = self._encode_items()
20
+ self._consumed_data: dict[Any, Any] = dict()
21
+
22
+ @property
23
+ def is_empty(self) -> bool:
24
+ return len(self.data) == 0
25
+
26
+ @property
27
+ def remaining_data(self) -> dict[Any, Any]:
28
+ return self.data
29
+
30
+ def force_empty(self) -> None:
31
+ for key in list(self.data.keys()):
32
+ self._consumed_data[key] = self.data.pop(key)
33
+
34
+ def _encode_list(
35
+ self,
36
+ data: list[Any],
37
+ ) -> list[None | str | int | float | bool | AnycubicConsumableData | list[Any]]:
38
+ new_data: list[Any] = list()
39
+ for v in data:
40
+ if isinstance(v, AnycubicConsumableData):
41
+ new_data.append(v)
42
+ elif isinstance(v, dict):
43
+ new_data.append(AnycubicConsumableData(v))
44
+ elif isinstance(v, list):
45
+ new_data.append(self._encode_list(v))
46
+ elif (
47
+ isinstance(v, str) or
48
+ isinstance(v, int) or
49
+ isinstance(v, float) or
50
+ isinstance(v, bool) or
51
+ v is None
52
+ ):
53
+ new_data.append(v)
54
+ else:
55
+ raise TypeError(
56
+ f"Invalid type for AnycubicConsumableData: {type(v)}"
57
+ )
58
+
59
+ return new_data
60
+
61
+ def _encode_items(
62
+ self,
63
+ ) -> dict[Any, None | str | int | float | bool | AnycubicConsumableData | list[Any]]:
64
+ new_data: dict[Any, Any] = dict()
65
+ for k, v in self.data.items():
66
+ if isinstance(v, AnycubicConsumableData):
67
+ new_data[k] = v
68
+ elif isinstance(v, dict):
69
+ new_data[k] = AnycubicConsumableData(v)
70
+ elif isinstance(v, list):
71
+ new_data[k] = self._encode_list(v)
72
+ elif (
73
+ isinstance(v, str) or
74
+ isinstance(v, int) or
75
+ isinstance(v, float) or
76
+ isinstance(v, bool) or
77
+ v is None
78
+ ):
79
+ new_data[k] = v
80
+ else:
81
+ raise TypeError(
82
+ f"Invalid type for AnycubicConsumableData: {type(v)}"
83
+ )
84
+
85
+ return new_data
86
+
87
+ def __getitem__(
88
+ self,
89
+ key: Any,
90
+ ) -> Any:
91
+ try:
92
+ return self._consumed_data[key]
93
+ except KeyError:
94
+ pass
95
+
96
+ try:
97
+ value = self.data[key]
98
+ if (
99
+ not isinstance(value, AnycubicConsumableData) or
100
+ value.is_empty
101
+ ):
102
+ self._consumed_data[key] = self.data.pop(key)
103
+ except KeyError:
104
+ raise
105
+
106
+ return value
107
+
108
+ def get(
109
+ self,
110
+ key: Any,
111
+ default: Any = None,
112
+ ) -> Any:
113
+ try:
114
+ return self[key]
115
+ except KeyError:
116
+ return default
@@ -0,0 +1,429 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+
6
+ class AnycubicCloudFile:
7
+ __slots__ = (
8
+ "_id",
9
+ "_user_id",
10
+ "_post_id",
11
+ "_filename",
12
+ "_time",
13
+ "_size",
14
+ "_status",
15
+ "_ip",
16
+ "_old_filename",
17
+ "_img_status",
18
+ "_device_type",
19
+ "_file_type",
20
+ "_md5",
21
+ "_url",
22
+ "_thumbnail",
23
+ "_is_delete",
24
+ "_update_time",
25
+ "_uuid",
26
+ "_store_type",
27
+ "_bucket",
28
+ "_region",
29
+ "_path",
30
+ "_thumbnail_nonce",
31
+ "_sliceparse_nonce",
32
+ "_file_extension",
33
+ "_name_counts",
34
+ "_source_user_upload_id",
35
+ "_origin_post_id",
36
+ "_stl_user_upload_id",
37
+ "_is_official_slice",
38
+ "_triangles_count",
39
+ "_is_parse",
40
+ "_source_type",
41
+ "_file_source",
42
+ "_user_lock_space_id",
43
+ "_origin_file_md5",
44
+ "_is_temp_file",
45
+ "_official_file_key",
46
+ "_official_file_id",
47
+ "_simplify_model",
48
+ "_size_x",
49
+ "_size_y",
50
+ "_size_z",
51
+ "_estimate",
52
+ "_material_name",
53
+ "_layer_height",
54
+ "_supplies_usage",
55
+ "_gcode_id",
56
+ "_printer_names",
57
+ "_slice_param",
58
+ )
59
+
60
+ def __init__(
61
+ self,
62
+ id: int,
63
+ user_id: Any = None,
64
+ post_id: Any = None,
65
+ filename: Any = None,
66
+ time: Any = None,
67
+ size: int | None = None,
68
+ status: Any = None,
69
+ ip: Any = None,
70
+ old_filename: str | None = None,
71
+ img_status: Any = None,
72
+ device_type: Any = None,
73
+ file_type: Any = None,
74
+ md5: Any = None,
75
+ url: Any = None,
76
+ thumbnail: Any = None,
77
+ is_delete: Any = None,
78
+ update_time: Any = None,
79
+ uuid: Any = None,
80
+ store_type: Any = None,
81
+ bucket: Any = None,
82
+ region: Any = None,
83
+ path: Any = None,
84
+ thumbnail_nonce: Any = None,
85
+ sliceparse_nonce: Any = None,
86
+ file_extension: Any = None,
87
+ name_counts: Any = None,
88
+ source_user_upload_id: Any = None,
89
+ origin_post_id: Any = None,
90
+ stl_user_upload_id: Any = None,
91
+ is_official_slice: Any = None,
92
+ triangles_count: Any = None,
93
+ is_parse: Any = None,
94
+ source_type: Any = None,
95
+ file_source: Any = None,
96
+ user_lock_space_id: Any = None,
97
+ origin_file_md5: Any = None,
98
+ is_temp_file: Any = None,
99
+ official_file_key: Any = None,
100
+ official_file_id: Any = None,
101
+ simplify_model: Any = None,
102
+ size_x: Any = None,
103
+ size_y: Any = None,
104
+ size_z: Any = None,
105
+ estimate: Any = None,
106
+ material_name: Any = None,
107
+ layer_height: Any = None,
108
+ supplies_usage: Any = None,
109
+ gcode_id: int | None = None,
110
+ printer_names: Any = None,
111
+ slice_param: Any = None,
112
+ ) -> None:
113
+ self._id: int = int(id)
114
+ self._user_id = user_id
115
+ self._post_id = post_id
116
+ self._filename = filename
117
+ self._time = time
118
+ self._size: int = int(size) if size is not None else 0
119
+ self._status = status
120
+ self._ip = ip
121
+ self._old_filename: str | None = str(old_filename) if old_filename is not None else None
122
+ self._img_status = img_status
123
+ self._device_type = device_type
124
+ self._file_type = file_type
125
+ self._md5 = md5
126
+ self._url = url
127
+ self._thumbnail = thumbnail
128
+ self._is_delete = is_delete
129
+ self._update_time = update_time
130
+ self._uuid = uuid
131
+ self._store_type = store_type
132
+ self._bucket = bucket
133
+ self._region = region
134
+ self._path = path
135
+ self._thumbnail_nonce = thumbnail_nonce
136
+ self._sliceparse_nonce = sliceparse_nonce
137
+ self._file_extension = file_extension
138
+ self._name_counts = name_counts
139
+ self._source_user_upload_id = source_user_upload_id
140
+ self._origin_post_id = origin_post_id
141
+ self._stl_user_upload_id = stl_user_upload_id
142
+ self._is_official_slice = is_official_slice
143
+ self._triangles_count = triangles_count
144
+ self._is_parse = is_parse
145
+ self._source_type = source_type
146
+ self._file_source = file_source
147
+ self._user_lock_space_id = user_lock_space_id
148
+ self._origin_file_md5 = origin_file_md5
149
+ self._is_temp_file = is_temp_file
150
+ self._official_file_key = official_file_key
151
+ self._official_file_id = official_file_id
152
+ self._simplify_model = simplify_model
153
+ self._size_x = size_x
154
+ self._size_y = size_y
155
+ self._size_z = size_z
156
+ self._estimate = estimate
157
+ self._material_name = material_name
158
+ self._layer_height = layer_height
159
+ self._supplies_usage = supplies_usage
160
+ self._gcode_id: int | None = int(gcode_id) if gcode_id is not None else None
161
+ self._printer_names = printer_names
162
+ self._slice_param = slice_param
163
+
164
+ @classmethod
165
+ def from_json(cls, data: dict[str, Any] | None) -> AnycubicCloudFile | None:
166
+ if data is None:
167
+ return None
168
+
169
+ return cls(
170
+ id=data.get('id', -999),
171
+ user_id=data.get('user_id'),
172
+ post_id=data.get('post_id'),
173
+ filename=data.get('filename'),
174
+ time=data.get('time'),
175
+ size=data.get('size'),
176
+ status=data.get('status'),
177
+ ip=data.get('ip'),
178
+ old_filename=data.get('old_filename'),
179
+ img_status=data.get('img_status'),
180
+ device_type=data.get('device_type'),
181
+ file_type=data.get('file_type'),
182
+ md5=data.get('md5'),
183
+ url=data.get('url'),
184
+ thumbnail=data.get('thumbnail'),
185
+ is_delete=data.get('is_delete'),
186
+ update_time=data.get('update_time'),
187
+ uuid=data.get('uuid'),
188
+ store_type=data.get('store_type'),
189
+ bucket=data.get('bucket'),
190
+ region=data.get('region'),
191
+ path=data.get('path'),
192
+ thumbnail_nonce=data.get('thumbnail_nonce'),
193
+ sliceparse_nonce=data.get('sliceparse_nonce'),
194
+ file_extension=data.get('file_extension'),
195
+ name_counts=data.get('name_counts'),
196
+ source_user_upload_id=data.get('source_user_upload_id'),
197
+ origin_post_id=data.get('origin_post_id'),
198
+ stl_user_upload_id=data.get('stl_user_upload_id'),
199
+ is_official_slice=data.get('is_official_slice'),
200
+ triangles_count=data.get('triangles_count'),
201
+ is_parse=data.get('is_parse'),
202
+ source_type=data.get('source_type'),
203
+ file_source=data.get('file_source'),
204
+ user_lock_space_id=data.get('user_lock_space_id'),
205
+ origin_file_md5=data.get('origin_file_md5'),
206
+ is_temp_file=data.get('is_temp_file'),
207
+ official_file_key=data.get('official_file_key'),
208
+ official_file_id=data.get('official_file_id'),
209
+ simplify_model=data.get('simplify_model'),
210
+ size_x=data.get('size_x'),
211
+ size_y=data.get('size_y'),
212
+ size_z=data.get('size_z'),
213
+ estimate=data.get('estimate'),
214
+ material_name=data.get('material_name'),
215
+ layer_height=data.get('layer_height'),
216
+ supplies_usage=data.get('supplies_usage'),
217
+ gcode_id=data.get('gcode_id'),
218
+ printer_names=data.get('printer_names'),
219
+ slice_param=data.get('slice_param'),
220
+ )
221
+
222
+ @property
223
+ def id(self) -> int:
224
+ return self._id
225
+
226
+ @property
227
+ def gcode_id(self) -> int | None:
228
+ return self._gcode_id
229
+
230
+ @property
231
+ def old_filename(self) -> str | None:
232
+ return self._old_filename
233
+
234
+ @property
235
+ def size(self) -> int:
236
+ return self._size
237
+
238
+ @property
239
+ def size_mb(self) -> float:
240
+ return self._size / 1e6
241
+
242
+ @property
243
+ def data_object(self) -> dict[str, Any]:
244
+ return {
245
+ 'id': self.id,
246
+ 'name': self.old_filename,
247
+ 'size_mb': self.size_mb,
248
+ }
249
+
250
+ def __repr__(self) -> str:
251
+ return (
252
+ f"AnycubicCloudFile("
253
+ f"id={self._id}, "
254
+ f"user_id={self._user_id}, "
255
+ f"post_id={self._post_id}, "
256
+ f"filename={self._filename}, "
257
+ f"time={self._time}, "
258
+ f"size={self._size}, "
259
+ f"status={self._status}, "
260
+ f"ip={self._ip}, "
261
+ f"old_filename={self._old_filename}, "
262
+ f"img_status={self._img_status}, "
263
+ f"device_type={self._device_type}, "
264
+ f"file_type={self._file_type}, "
265
+ f"md5={self._md5}, "
266
+ f"url={self._url}, "
267
+ f"thumbnail={self._thumbnail}, "
268
+ f"is_delete={self._is_delete}, "
269
+ f"update_time={self._update_time}, "
270
+ f"uuid={self._uuid}, "
271
+ f"store_type={self._store_type}, "
272
+ f"bucket={self._bucket}, "
273
+ f"region={self._region}, "
274
+ f"path={self._path}, "
275
+ f"thumbnail_nonce={self._thumbnail_nonce}, "
276
+ f"sliceparse_nonce={self._sliceparse_nonce}, "
277
+ f"file_extension={self._file_extension}, "
278
+ f"name_counts={self._name_counts}, "
279
+ f"source_user_upload_id={self._source_user_upload_id}, "
280
+ f"origin_post_id={self._origin_post_id}, "
281
+ f"stl_user_upload_id={self._stl_user_upload_id}, "
282
+ f"is_official_slice={self._is_official_slice}, "
283
+ f"triangles_count={self._triangles_count}, "
284
+ f"is_parse={self._is_parse}, "
285
+ f"source_type={self._source_type}, "
286
+ f"file_source={self._file_source}, "
287
+ f"user_lock_space_id={self._user_lock_space_id}, "
288
+ f"origin_file_md5={self._origin_file_md5}, "
289
+ f"is_temp_file={self._is_temp_file}, "
290
+ f"official_file_key={self._official_file_key}, "
291
+ f"official_file_id={self._official_file_id}, "
292
+ f"simplify_model={self._simplify_model}, "
293
+ f"size_x={self._size_x}, "
294
+ f"size_y={self._size_y}, "
295
+ f"size_z={self._size_z}, "
296
+ f"estimate={self._estimate}, "
297
+ f"material_name={self._material_name}, "
298
+ f"layer_height={self._layer_height}, "
299
+ f"supplies_usage={self._supplies_usage}, "
300
+ f"gcode_id={self._gcode_id}, "
301
+ f"printer_names={self._printer_names}, "
302
+ f"slice_param={self._slice_param})"
303
+ )
304
+
305
+
306
+ class AnycubicCloudStore:
307
+ __slots__ = (
308
+ "_used_bytes",
309
+ "_total_bytes",
310
+ "_used_str",
311
+ "_total_str",
312
+ "_user_file_exists",
313
+ )
314
+
315
+ def __init__(
316
+ self,
317
+ used_bytes: int = 0,
318
+ total_bytes: int = 0,
319
+ used_str: str = "",
320
+ total_str: str = "",
321
+ user_file_exists: bool = False,
322
+ ) -> None:
323
+ self._used_bytes: int = int(used_bytes)
324
+ self._total_bytes: int = int(total_bytes)
325
+ self._used_str: str = used_str
326
+ self._total_str: str = total_str
327
+ self._user_file_exists = bool(user_file_exists)
328
+
329
+ @classmethod
330
+ def from_json(cls, data: dict[str, Any] | None) -> AnycubicCloudStore | None:
331
+ if data is None:
332
+ return None
333
+
334
+ return cls(
335
+ used_bytes=data['used_bytes'],
336
+ total_bytes=data['total_bytes'],
337
+ used_str=data['used'],
338
+ total_str=data['total'],
339
+ user_file_exists=data['user_file_exists'],
340
+ )
341
+
342
+ @property
343
+ def used_bytes(self) -> int:
344
+ return self._used_bytes
345
+
346
+ @property
347
+ def total_bytes(self) -> int:
348
+ return self._total_bytes
349
+
350
+ @property
351
+ def available_bytes(self) -> int:
352
+ return self._total_bytes - self._used_bytes
353
+
354
+ def __repr__(self) -> str:
355
+ return (
356
+ f"AnycubicCloudStore("
357
+ f"used={self._used_str}, "
358
+ f"total={self._total_str}, "
359
+ f"user_file_exists={self._user_file_exists})"
360
+ )
361
+
362
+
363
+ class AnycubicFile:
364
+ __slots__ = (
365
+ "_filename",
366
+ "_timestamp",
367
+ "_size",
368
+ "_is_dir",
369
+ )
370
+
371
+ def __init__(
372
+ self,
373
+ filename: str,
374
+ timestamp: int | None,
375
+ size: int | None,
376
+ is_dir: bool,
377
+ ) -> None:
378
+ self._filename: str = str(filename)
379
+ self._timestamp: int = int(timestamp) if timestamp is not None else 0
380
+ self._size: int = int(size) if size is not None else 0
381
+ self._is_dir: bool = bool(is_dir)
382
+
383
+ @classmethod
384
+ def from_json(cls, data: dict[str, Any] | None) -> AnycubicFile | None:
385
+ if data is None:
386
+ return None
387
+
388
+ return cls(
389
+ filename=data['filename'],
390
+ timestamp=data.get('timestamp'),
391
+ size=data.get('size'),
392
+ is_dir=data['is_dir'],
393
+ )
394
+
395
+ @property
396
+ def filename(self) -> str:
397
+ return self._filename
398
+
399
+ @property
400
+ def timestamp(self) -> int:
401
+ return self._timestamp
402
+
403
+ @property
404
+ def size(self) -> int:
405
+ return self._size
406
+
407
+ @property
408
+ def size_mb(self) -> float:
409
+ return self._size / 1e6 if self._size else self._size
410
+
411
+ @property
412
+ def is_dir(self) -> bool:
413
+ return self._is_dir
414
+
415
+ @property
416
+ def data_object(self) -> dict[str, str | float]:
417
+ return {
418
+ 'name': self._filename,
419
+ 'size_mb': self.size_mb,
420
+ }
421
+
422
+ def __repr__(self) -> str:
423
+ return (
424
+ f"AnycubicFile("
425
+ f"filename={self._filename}, "
426
+ f"timestamp={self._timestamp}, "
427
+ f"size={self._size}, "
428
+ f"is_dir={self._is_dir})"
429
+ )
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import UserDict
4
+ from typing import Any
5
+
6
+ from aiofiles import open as aio_file_open
7
+
8
+ from ..exceptions.error_strings import ErrorsGcodeParsing
9
+ from ..exceptions.exceptions import AnycubicGcodeParsingError
10
+ from ..helpers.helpers import (
11
+ GCODE_STRING_FIRST_ATTR_LINE,
12
+ REX_GCODE_DATA_KEY_VALUE,
13
+ gcode_key_value_pair_to_dict,
14
+ )
15
+
16
+
17
+ class AnycubicGcodeFile(UserDict[str, Any]):
18
+ __slots__ = (
19
+ "_material_list",
20
+ )
21
+
22
+ def __init__(
23
+ self,
24
+ *args: Any,
25
+ **kwargs: Any,
26
+ ) -> None:
27
+ super().__init__(*args, **kwargs)
28
+ self._material_list: list[dict[str, Any]] | None = None
29
+
30
+ try:
31
+ self.material_list
32
+ except AnycubicGcodeParsingError:
33
+ pass
34
+
35
+ @classmethod
36
+ async def async_read_from_file(
37
+ cls,
38
+ full_file_path: str | None = None,
39
+ file_bytes: bytes | None = None,
40
+ ) -> AnycubicGcodeFile:
41
+ data_found = False
42
+ file_lines = list()
43
+ slicer_data = dict()
44
+
45
+ if full_file_path is not None:
46
+ try:
47
+ async with aio_file_open(full_file_path, mode='r') as f:
48
+ file_lines = await f.readlines()
49
+ except Exception as error:
50
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.read_fail.format(error))
51
+ elif file_bytes is not None:
52
+ try:
53
+ file_lines = file_bytes.decode('utf-8').split('\n')
54
+ except Exception as error:
55
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.byte_decode_fail.format(error))
56
+ else:
57
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.invalid_path_and_bytes)
58
+
59
+ for line in file_lines:
60
+ if not data_found and line.startswith(GCODE_STRING_FIRST_ATTR_LINE):
61
+ data_found = True
62
+ if not data_found:
63
+ continue
64
+ try:
65
+ slicer_data.update(gcode_key_value_pair_to_dict(REX_GCODE_DATA_KEY_VALUE, line))
66
+ except Exception as error:
67
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.parse_meta_fail.format(error))
68
+
69
+ return cls(slicer_data)
70
+
71
+ @property
72
+ def material_list(self) -> list[dict[str, Any]]:
73
+ if self._material_list is not None:
74
+ return self._material_list
75
+
76
+ filament_used_g = self.data.get('filament_used_g')
77
+ filament_used_mm = self.data.get('filament_used_mm')
78
+ filament_used_cm3 = self.data.get('filament_used_cm3')
79
+ ams_data = self.data.get('paint_info')
80
+
81
+ if not ams_data:
82
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.empty_paint_info)
83
+
84
+ if not filament_used_g or len(filament_used_g) < 1:
85
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.empty_used_filament)
86
+
87
+ if len(filament_used_g) < len(ams_data):
88
+ raise AnycubicGcodeParsingError(ErrorsGcodeParsing.invalid_used_filament)
89
+
90
+ if not filament_used_mm or len(filament_used_mm) < len(ams_data):
91
+ filament_used_mm = list([None for x in range(len(ams_data))])
92
+
93
+ if not filament_used_cm3 or len(filament_used_cm3) < len(ams_data):
94
+ filament_used_cm3 = list([None for x in range(len(ams_data))])
95
+
96
+ self._material_list = list([
97
+ {
98
+ **paint_info,
99
+ 'filament_used': filament_used_g[paint_info['paint_index']],
100
+ 'filament_used_mm': filament_used_mm[paint_info['paint_index']],
101
+ 'filament_used_cm3': filament_used_cm3[paint_info['paint_index']],
102
+ } for paint_info in ams_data
103
+ ])
104
+
105
+ self.data['material_list'] = self._material_list
106
+
107
+ return self._material_list