node_to_json 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: node_to_json
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Blender 5.2 Serialize Node Groups to JSON files.Load Node Groups from JSON files.
5
5
  Author: Demingo Hill (Noizirom) (C)
6
6
  License-Expression: GPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "node_to_json"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "Blender 5.2 Serialize Node Groups to JSON files.Load Node Groups from JSON files."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "node_to_json"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "Blender 5.2 Serialize Node Groups to JSON files.Load Node Groups from JSON files."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,237 @@
1
+ """
2
+ Blender 5.2
3
+ Serialize Node Groups to JSON files.
4
+ Load Node Groups from JSON files.
5
+ Author: Demingo Hill (Noizirom) (C)
6
+ """
7
+ from .asset_funcs import (
8
+ get_asset_nodes,
9
+ asset_node_group,
10
+ nodes_dir,
11
+ get_asset_name_dict,
12
+ )
13
+ from .func_util import (
14
+ nested_dict,
15
+ grouped_by_dict,
16
+ convert_attr,
17
+ convert_default_value,
18
+ get_node_group_data_type,
19
+ get_node_groups_by_type,
20
+ get_reversed_node_groups_by_type,
21
+ get_dict_keys,
22
+ get_dict_values,
23
+ get_basename_keys,
24
+ get_basenames,
25
+ match_keys_basenames,
26
+ immutable_dict,
27
+ hash_dict,
28
+ hash_list,
29
+ is_string_blank,
30
+ string_has_space,
31
+ string_startswith_space,
32
+ string_has_escape_character,
33
+ string_startswith,
34
+ string_endswith,
35
+ string_char_count,
36
+ concat_gen,
37
+ gen_len,
38
+ get_max_series_num,
39
+ get_match_series_highest,
40
+ get_all_related_node_groups,
41
+ get_node_group_names_,
42
+ node_group_name_dict,
43
+ get_all_related_node_groups_wo_assests,
44
+ get_node_group_names_wo_assests,
45
+ node_group_name_dict_wo_assests,
46
+ delete_full_node_tree,
47
+ delete_geo_node_modifier,
48
+ get_modifier_stack_geo_nodes,
49
+ get_items,
50
+ get_attrs,
51
+ split_list_of_dicts,
52
+ string_point_check,
53
+ CallAfterFunc,
54
+ CallBeforeFunc,
55
+ benchmark,
56
+ get_profile,
57
+ )
58
+ from .json_io import dict_to_json, json_to_dict
59
+ from .node_getters import (
60
+ serialize_node_group,
61
+ serialize_mat_group,
62
+ serialize_material,
63
+ serialize_comp_group,
64
+ serialize_compositor,
65
+ get_node_build_data,
66
+ get_node_group_interface,
67
+ get_node_group_names,
68
+ get_node_presets,
69
+ get_group_nodes_presets,
70
+ get_special_node_presets,
71
+ get_geometry_node_input_presets,
72
+ get_geometry_node_presets,
73
+ get_node_group_input_presets,
74
+ get_node_group_presets,
75
+ special_nodes,
76
+ )
77
+ from .node_registry import (
78
+ bpy_types_funcs,
79
+ register_node_setter,
80
+ ng_getter_funcs,
81
+ register_ng_getter,
82
+ ng_setter_funcs,
83
+ register_ng_setter,
84
+ )
85
+ from .node_setters import (
86
+ create_node_group_from_data,
87
+ add_node_group,
88
+ create_shader_group_from_data,
89
+ create_material,
90
+ add_material_to_ob,
91
+ create_comp_group,
92
+ add_comp_group,
93
+ create_texture_group_from_data,
94
+ process_node,
95
+ get_name_dict,
96
+ get_group_dict,
97
+ set_special_node_presets,
98
+ set_geometry_node_input_presets,
99
+ set_geometry_node_presets,
100
+ set_node_group_input_presets,
101
+ set_node_group_presets,
102
+ )
103
+ from .file_util import (
104
+ is_file_accessible,
105
+ read_file,
106
+ write_file,
107
+ get_dir_files,
108
+ get_dir_file_stems,
109
+ copy_file_by_chunks,
110
+ read_file_by_chunks,
111
+ write_bytesio,
112
+ write_stringio,
113
+ inject_detect,
114
+ is_file_suspicious,
115
+ get_from_zip,
116
+ get_zip_file_list,
117
+ zip_from_folder,
118
+ zip_append,
119
+ read_from_zip,
120
+ create_filler_zip,
121
+ zip_data_file,
122
+ zip_data_files,
123
+ append_file_to_zip,
124
+ )
125
+
126
+ __all__ = [
127
+ "get_asset_nodes",
128
+ "asset_node_group",
129
+ "nodes_dir",
130
+ "get_asset_name_dict",
131
+ "nested_dict",
132
+ "grouped_by_dict",
133
+ "convert_attr",
134
+ "convert_default_value",
135
+ "get_node_group_data_type",
136
+ "get_node_groups_by_type",
137
+ "get_reversed_node_groups_by_type",
138
+ "dict_to_json",
139
+ "json_to_dict",
140
+ "serialize_node_group",
141
+ "serialize_mat_group",
142
+ "serialize_material",
143
+ "serialize_comp_group",
144
+ "serialize_compositor",
145
+ "get_node_build_data",
146
+ "get_node_group_interface",
147
+ "get_node_group_names",
148
+ "bpy_types_funcs",
149
+ "register_node_setter",
150
+ "ng_getter_funcs",
151
+ "register_ng_getter",
152
+ "ng_setter_funcs",
153
+ "register_ng_setter",
154
+ "create_node_group_from_data",
155
+ "add_node_group",
156
+ "create_shader_group_from_data",
157
+ "create_material",
158
+ "add_material_to_ob",
159
+ "create_comp_group",
160
+ "add_comp_group",
161
+ "create_texture_group_from_data",
162
+ "process_node",
163
+ "get_name_dict",
164
+ "get_group_dict",
165
+ "set_special_node_presets",
166
+ "set_geometry_node_input_presets",
167
+ "set_geometry_node_presets",
168
+ "set_node_group_input_presets",
169
+ "set_node_group_presets",
170
+ "get_node_presets",
171
+ "get_group_nodes_presets",
172
+ "get_special_node_presets",
173
+ "get_geometry_node_input_presets",
174
+ "get_geometry_node_presets",
175
+ "get_node_group_input_presets",
176
+ "get_node_group_presets",
177
+ "special_nodes",
178
+ "get_dict_keys",
179
+ "get_dict_values",
180
+ "get_basename_keys",
181
+ "get_basenames",
182
+ "match_keys_basenames",
183
+ "immutable_dict",
184
+ "hash_dict",
185
+ "hash_list",
186
+ "is_string_blank",
187
+ "string_has_space",
188
+ "string_startswith_space",
189
+ "string_has_escape_character",
190
+ "string_startswith",
191
+ "string_endswith",
192
+ "string_char_count",
193
+ "concat_gen",
194
+ "gen_len",
195
+ "get_max_series_num",
196
+ "get_match_series_highest",
197
+ "get_all_related_node_groups",
198
+ "get_node_group_names_",
199
+ "node_group_name_dict",
200
+ "get_all_related_node_groups_wo_assests",
201
+ "get_node_group_names_wo_assests",
202
+ "node_group_name_dict_wo_assests",
203
+ "delete_full_node_tree",
204
+ "delete_geo_node_modifier",
205
+ "get_modifier_stack_geo_nodes",
206
+ "is_file_accessible",
207
+ "read_file",
208
+ "write_file",
209
+ "get_dir_files",
210
+ "get_dir_file_stems",
211
+ "copy_file_by_chunks",
212
+ "read_file_by_chunks",
213
+ "write_bytesio",
214
+ "write_stringio",
215
+ "inject_detect",
216
+ "is_file_suspicious",
217
+ "get_from_zip",
218
+ "get_zip_file_list",
219
+ "zip_from_folder",
220
+ "zip_append",
221
+ "read_from_zip",
222
+ "create_filler_zip",
223
+ "zip_data_file",
224
+ "zip_data_files",
225
+ "append_file_to_zip",
226
+ "get_items",
227
+ "get_attrs",
228
+ "split_list_of_dicts",
229
+ "string_point_check",
230
+ "CallAfterFunc",
231
+ "CallBeforeFunc",
232
+ "benchmark",
233
+ "get_profile",
234
+ ]
235
+
236
+ def __dir__():
237
+ return __all__
@@ -21,10 +21,19 @@ def get_asset_nodes_helper(file: str) -> Generator[str, None, None]:
21
21
 
22
22
 
23
23
  def get_asset_nodes() -> Generator[str, None, None]:
24
+ """Retrieve the name of all factory asset node groups.
25
+ """
24
26
  for file in nodes_dir.rglob('*blend'):
25
27
  yield from get_asset_nodes_helper(file)
26
28
 
27
29
 
30
+ def get_asset_name_dict() -> Generator[tuple[str, str], None, None]:
31
+ """Retrieve the name and file path of all factory asset node groups.
32
+ """
33
+ for file in nodes_dir.rglob('*blend'):
34
+ for name in get_asset_nodes_helper(file):
35
+ yield name, file
36
+
28
37
 
29
38
  def get_asset_file(name: str) -> str | None:
30
39
  for file in nodes_dir.rglob('*blend'):
@@ -0,0 +1,344 @@
1
+ from pathlib import Path
2
+ from re import findall
3
+ from numpy import where, char
4
+ from zipfile import ZipFile, is_zipfile, ZIP_LZMA
5
+ from typing import Any, Generator, Callable
6
+ from io import BytesIO, StringIO
7
+ from os import access, F_OK, R_OK, W_OK
8
+
9
+
10
+ class SuspectFileError(Exception):
11
+ def __init__(self, message):
12
+ self.message = message
13
+ super().__init__(self.message)
14
+
15
+
16
+ def _is_file_accessible(file_path: str) -> list[bool]:
17
+ return [access(file_path, F_OK), access(file_path, R_OK), access(file_path, W_OK)]
18
+
19
+
20
+ def _is_file_accessible_message(data: list[bool]) -> list[str]:
21
+ msg = "File Path {} {}. "
22
+ if not all(data):
23
+ return [msg.format('is not', 'accessible')]
24
+ exists_ = (msg.format('does', 'exist') if data[0] else msg.format('does not', 'exist'))
25
+ readable_ = (msg.format('is', 'readable') if data[1] else msg.format('is not', 'readable'))
26
+ writeable_ = (msg.format('is', 'writeable') if data[2] else msg.format('is not', 'writeable'))
27
+ return [exists_, readable_, writeable_]
28
+
29
+
30
+ def is_file_accessible(file_path: str) -> dict[str, list[bool] | str]:
31
+ """Retrieve data dict of a file's existance, readability, and writeability.
32
+
33
+ :param file_path: File path to run check on.
34
+ :type file_path: str
35
+ :return: Dictionary of a files accesibility and a message stating the results
36
+ :rtype: dict[str, list[bool] | str]"""
37
+ acc = _is_file_accessible(file_path)
38
+ return {'access': acc, 'message': "".join(_ for _ in _is_file_accessible_message(acc))}
39
+
40
+
41
+ def read_file(file: str) -> Any:
42
+ """Read data from a file.
43
+
44
+ :param file: Path of a file to read.
45
+ :type file: str
46
+ :return: Data read from the file.
47
+ :rtype: Any"""
48
+ with open(file, 'r') as f:
49
+ data = f.read()
50
+ return data
51
+
52
+
53
+ def write_file(file: str, data: Any) -> None:
54
+ """Write data to a file.
55
+
56
+ :param file: Path of a file to write to.
57
+ :type file: str
58
+ :param data: Data to write to file.
59
+ :type data: Any"""
60
+ with open(file, 'w') as f:
61
+ f.write(data)
62
+
63
+
64
+ def get_dir_files(dir_path: str, filter: str='*py') -> Generator[Path, None, None]:
65
+ """Retrieve a generator object of the files in a directory the match the filter.
66
+
67
+ :param dir_path: Path of the directory to retrieve files from.
68
+ :type dir_path: str
69
+ :param filter: Filter to use for file retrieval.
70
+ :type filter: str
71
+ :return: Generator of the files in the directory that match the filter.
72
+ :rtype: Generator[Path, None, None]"""
73
+ return Path(dir_path).glob(filter)
74
+
75
+
76
+ def get_dir_file_stems(dir_path: str, filter: str='*py', ignore_files: list[str] | None=None) -> Generator[str, None, None]:
77
+ """Retrieve a generator object of the file name stripped of the prefix path.
78
+
79
+ :param dir_path: Path of the directory to retrieve files from.
80
+ :type dir_path: str
81
+ :param filter: Filter to use for file retrieval.
82
+ :type filter: str
83
+ :param ignore_files: File names to ignore.
84
+ :type ignore_files: list[str] | None
85
+ :return: Description
86
+ :rtype: Generator[str, None, None]"""
87
+ if not ignore_files:
88
+ ignore_files = ['__init__']
89
+ return (file.stem for file in get_dir_files(dir_path, filter=filter) if file.stem not in ignore_files)
90
+
91
+
92
+ def copy_file_by_chunks(src_file: str, target_file: str) -> None:
93
+ """Copy a file in byte chunks.
94
+
95
+ :param src_file: Source file path to copy data from.
96
+ :type src_file: str
97
+ :param target_file: Target file path to copy data to.
98
+ :type target_file: str"""
99
+ with open(src_file, 'rb') as src:
100
+ with open(target_file, 'wb') as tgt:
101
+ while True:
102
+ chunk = src.read(1024 * 1024)
103
+ if not chunk:
104
+ break
105
+ tgt.write(chunk)
106
+
107
+
108
+ def read_file_by_chunks(src_file: str) -> Generator[Any, None, None]:
109
+ """Read a file in byte chunks.
110
+
111
+ :param src_file: Source file path to read data from.
112
+ :type src_file: str"""
113
+ with open(src_file, 'rb') as src:
114
+ while True:
115
+ chunk = src.read(1024 * 1024)
116
+ if not chunk:
117
+ break
118
+ yield chunk
119
+
120
+
121
+ def write_bytesio(data: bytes) -> BytesIO:
122
+ """Convert bytes data into a BytesIo object.
123
+
124
+ :param data: Bytes data.
125
+ :type data: bytes
126
+ :return: Bytes data converted to a BytesIO object.
127
+ :rtype: BytesIO"""
128
+ buffer = BytesIO()
129
+ buffer.write(data)
130
+ buffer.seek(0)
131
+ return buffer
132
+
133
+
134
+ def write_stringio(data: str) -> StringIO:
135
+ """Convert string data into a StringIO object.
136
+
137
+ :param data: String data.
138
+ :type data: str
139
+ :return: String data converted to a StringIO object.
140
+ :rtype: StringIO"""
141
+ buffer = StringIO()
142
+ buffer.write(data)
143
+ buffer.seek(0)
144
+ return buffer
145
+
146
+
147
+ def inject_detect(data: str) -> bool:
148
+ """Check a text string for suspicious characters and function calls.
149
+
150
+ :param data: Large text string usually read from a file.
151
+ :type data: str
152
+ :return: Result of string check.
153
+ :rtype: bool"""
154
+ s = findall(r'\b(?:os|sys|subprocess|asyncio|pathlib|marshal|ast|cmd|ord|chr)\b', data)
155
+ f = findall(r' eval\(| var\(| exec\(| ord\(| chr\(| -c ', data)
156
+ i = findall(r' os\.| sys\.| subprocess\.| asyncio\.| pathlib\.| marshal\.| ast\.| cmd\.', data)
157
+ check_ = [len(s) > 0, len(f) > 0, len(i) > 0]
158
+ if any(check_):
159
+ print(f"[SUSPICIOUS]: {[_ for idx, _ in enumerate([s, f, i]) if check_[idx]]}")
160
+ return True
161
+ return False
162
+
163
+
164
+ def is_file_suspicious(file: str) -> bool | None:
165
+ """Check a file for suspicious characters and function calls.
166
+
167
+ :param file: Path of file to check.
168
+ :type file: str
169
+ :return: Return False if checks return False else raise SuspectFileError.
170
+ :rtype: bool | None"""
171
+ data = read_file(file)
172
+ check_ = inject_detect(data)
173
+ if check_:
174
+ raise SuspectFileError(f"Suspect File!!! {Path(file.name)}")
175
+ return check_
176
+
177
+
178
+ def get_from_zip(zip_file: str, file_name: str, is_gen: bool, func: Callable, *args: Any, **kwargs: Any) -> Generator[Any, None, None] | Any | None:
179
+ """Retrieve data from a zip archive.
180
+
181
+ :param zip_file: Path of a compressed zip archive.
182
+ :type zip_file: str
183
+ :param file_name: Name of file in zip archive.
184
+ :type file_name: str
185
+ :param is_gen: Return a generator object if True else the result of a function func.
186
+ :type is_gen: bool
187
+ :param func: Function to run on file data.
188
+ :type func: Callable
189
+ :param args: Arguments for function func.
190
+ :type args: Any
191
+ :param kwargs: Keyword arguments for function func.
192
+ :type kwargs: Any
193
+ :return: Generator object if is_gen is True else the result of function func or None if the operations fail.
194
+ :rtype: Generator[Any, None, None] | Any"""
195
+ try:
196
+ with ZipFile(zip_file, 'r') as zf:
197
+ with zf.open(file_name) as hf:
198
+ if is_gen:
199
+ return (_ for _ in list(func(hf, *args, **kwargs)))
200
+ return func(hf, *args, **kwargs)
201
+ except:
202
+ pass
203
+
204
+
205
+ def get_zip_file_list(zip_file: str) -> Generator[str, None, None]:
206
+ """Retrieve the name of the files contained in a zip archive.
207
+
208
+ :param zip_file: Path of a compressed zip archive.
209
+ :type zip_file: str
210
+ :return: A generator object of the file names
211
+ :rtype: Generator[str, None, None]"""
212
+ with ZipFile(zip_file, 'r') as zf:
213
+ name_list = (f for f in zf.namelist())
214
+ return name_list
215
+
216
+
217
+ def zip_from_folder(src_dir_path: str, dest_dir_path: str) -> None:
218
+ """Copy files from folder into a zip archive.
219
+
220
+ :param src_dir_path: Source path of files to move to zip archive.
221
+ :type src_dir_path: str
222
+ :param dest_dir_path: Path of the zip archive.
223
+ :type dest_dir_path: str"""
224
+ name = Path(src_dir_path).stem
225
+ zip_name = f"{name}.zip"
226
+ zip_file = Path(dest_dir_path).joinpath(zip_name)
227
+ files_list = Path(src_dir_path).rglob("*")
228
+ with ZipFile(file=zip_file, mode='w', compression=ZIP_LZMA, compresslevel=9) as zf:
229
+ for file in files_list:
230
+ zf.write(filename=file, arcname=file.name)
231
+
232
+
233
+ def zip_append(zip_file: str, file: str, arcname: str | None=None) -> None:
234
+ """Append a file to a zip archive.
235
+
236
+ :param zip_file: Path of a compressed zip archive.
237
+ :type zip_file: str
238
+ :param file: File to append to zip archive.
239
+ :type file: str
240
+ :param arcname: Name to assign to appended file. If None, the file name is used instead.
241
+ :type arcname: str | None"""
242
+ with ZipFile(zip_file, 'a') as zf:
243
+ name = (Path(file).name if isinstance(arcname, type(None)) else arcname)
244
+ zf.write(file, arcname=name)
245
+
246
+
247
+ def read_from_zip(zip_file: str, file_name: str) -> Any:
248
+ """Read from a file in a zip archive.
249
+
250
+ :param zip_file: Path of a compressed zip archive.
251
+ :type zip_file: str
252
+ :param file_name: Name of file from zip archive to read.
253
+ :type file_name: str
254
+ :return: Data read from the file.
255
+ :rtype: Any"""
256
+ with ZipFile(zip_file, 'r') as zf:
257
+ with zf.open(zf.namelist()[where(char.find(zf.namelist(), file_name) > -1)[0][0]]) as f:
258
+ data = f.read()
259
+ return data
260
+
261
+
262
+ def create_filler_zip(zip_file: str, file_name: str='USER.txt') -> None:
263
+ """Create a filler file for a zip archive. Useful for creating an empty zip archive since zip archives can not be empty.
264
+
265
+ :param zip_file: Path of a compressed zip archive.
266
+ :type zip_file: str
267
+ :param file_name: Name for the filler file
268
+ :type file_name: str"""
269
+ with ZipFile(zip_file, 'w', compression=ZIP_LZMA, compresslevel=9, allowZip64=True) as zf:
270
+ zf.writestr(file_name, "")
271
+
272
+
273
+ def write_zip_file_data(file: str, zip_file: str, arcname: str | None=None) -> None:
274
+ """Copy file data to a zip archive.
275
+
276
+ :param file: Path of file to copy to zip archive.
277
+ :type file: str
278
+ :param zip_file: Path of a compressed zip archive.
279
+ :type zip_file: str
280
+ :param arcname: Name to save file in zip archive. If None use the file name instead.
281
+ :type arcname: str | None"""
282
+ file = Path(file)
283
+ temp_file = Path(f"temp{file.suffix}")
284
+ temp_file.touch(exist_ok=True)
285
+ copy_file_by_chunks(file, temp_file)
286
+ if not arcname:
287
+ arcname = file.name
288
+ zip_file.write(filename=temp_file, arcname=arcname)
289
+ temp_file.unlink()
290
+
291
+
292
+ def zip_data_file(file: str, zip_file: str, arcname: str | None=None) -> None:
293
+ """Write file data to zip archive.
294
+
295
+ :param file: Path of file to copy to zip archive.
296
+ :type file: str
297
+ :param zip_file: Path of a compressed zip archive.
298
+ :type zip_file: str
299
+ :param arcname: Name to save file in zip archive. If None use the file name instead.
300
+ :type arcname: str | None"""
301
+ with ZipFile(file=zip_file, mode='w', compression=ZIP_LZMA, compresslevel=9) as zf:
302
+ write_zip_file_data(file, zf, arcname=arcname)
303
+
304
+
305
+ def zip_data_files(src_dir_path: str, dest_dir_path: str, filter: str="*py") -> None:
306
+ """Write file data from directory to zip archive.
307
+
308
+ :param src_dir_path: Source directory to copy files from.
309
+ :type src_dir_path: str
310
+ :param dest_dir_path: Directory to write zip archive to.
311
+ :type dest_dir_path: str
312
+ :param filter: Filter for files in source directory.
313
+ :type filter: str"""
314
+ name = Path(src_dir_path).stem
315
+ zip_name = f"{name}.zip"
316
+ zip_file = Path(dest_dir_path).joinpath(zip_name)
317
+ files_list = Path(src_dir_path).rglob(filter)
318
+ with ZipFile(file=zip_file, mode='w', compression=ZIP_LZMA, compresslevel=9) as zf:
319
+ for file in files_list:
320
+ write_zip_file_data(file, zf)
321
+
322
+
323
+ def append_file_to_zip(src_file: str, zip_file: str, arcname: str | None=None) -> None:
324
+ """Append a file to a zip archive.
325
+
326
+ :param src_file: Path of file to copy to zip archive.
327
+ :type src_file: str
328
+ :param zip_file: Path of a compressed zip archive.
329
+ :type zip_file: str
330
+ :param arcname: Name to save file in zip archive. If None use the file name instead.
331
+ :type arcname: str | None"""
332
+ if not is_zipfile(zip_file):
333
+ raise ValueError("Destination is not a zip archive!")
334
+ src_file = Path(src_file)
335
+ file_names = set(Path(file).name for file in get_zip_file_list(zip_file))
336
+ if arcname:
337
+ if arcname in file_names:
338
+ raise FileExistsError("File already exists!")
339
+ else:
340
+ if str(src_file.stem) in file_names:
341
+ raise FileExistsError("File already exists!")
342
+ with ZipFile(file=zip_file, mode='a', compression=ZIP_LZMA, compresslevel=9) as zf:
343
+ write_zip_file_data(src_file, zf, arcname=arcname)
344
+