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.
- anycubic_cloud_api/__init__.py +62 -0
- anycubic_cloud_api/anycubic_api.py +7 -0
- anycubic_cloud_api/api/base.py +400 -0
- anycubic_cloud_api/api/functions.py +2055 -0
- anycubic_cloud_api/api/mqtt.py +449 -0
- anycubic_cloud_api/const/api_endpoints.py +106 -0
- anycubic_cloud_api/const/const.py +47 -0
- anycubic_cloud_api/const/enums.py +97 -0
- anycubic_cloud_api/const/mqtt.py +13 -0
- anycubic_cloud_api/data_models/consumable.py +116 -0
- anycubic_cloud_api/data_models/files.py +429 -0
- anycubic_cloud_api/data_models/gcode_file.py +107 -0
- anycubic_cloud_api/data_models/orders.py +369 -0
- anycubic_cloud_api/data_models/print_response.py +71 -0
- anycubic_cloud_api/data_models/print_speed_mode.py +61 -0
- anycubic_cloud_api/data_models/printer.py +2753 -0
- anycubic_cloud_api/data_models/printer_properties.py +1107 -0
- anycubic_cloud_api/data_models/printing_settings.py +130 -0
- anycubic_cloud_api/data_models/project.py +1234 -0
- anycubic_cloud_api/exceptions/error_strings.py +340 -0
- anycubic_cloud_api/exceptions/exceptions.py +91 -0
- anycubic_cloud_api/helpers/helpers.py +240 -0
- anycubic_cloud_api/models/auth.py +396 -0
- anycubic_cloud_api/models/cloud_upload.py +249 -0
- anycubic_cloud_api/models/http.py +29 -0
- anycubic_cloud_api/resources/anycubic_mqqt_tls_ca.crt +24 -0
- anycubic_cloud_api/resources/anycubic_mqqt_tls_client.crt +24 -0
- anycubic_cloud_api/resources/anycubic_mqqt_tls_client.key +28 -0
- anycubic_cloud_api-0.1.0.dist-info/METADATA +79 -0
- anycubic_cloud_api-0.1.0.dist-info/RECORD +33 -0
- anycubic_cloud_api-0.1.0.dist-info/WHEEL +5 -0
- anycubic_cloud_api-0.1.0.dist-info/licenses/LICENSE +674 -0
- 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
|