This repository has been archived by the owner on Jul 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 322
/
Copy pathfile_ops.py
408 lines (347 loc) · 14.1 KB
/
file_ops.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# MB-Lab
#
# MB-Lab fork website : https://github.com/animate1978/MB-Lab
#
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
#
# ManuelbastioniLAB - Copyright (C) 2015-2018 Manuel Bastioni
import logging
import os
import json
import array
import time
import mathutils
import bpy
from . import utils
from .utils import get_object_parent
logger = logging.getLogger(__name__)
data_directory = "data"
# Below is a trick. For unknown reason, get_configuration()
# is invoked multiple times in some occasions like expressions tool
# after finalization. That's why the tool is so long.
# As the configuration need to be loaded a single time by session,
# this variable was created.
configuration_done = None
root_directories = None
def is_writeable(filepath):
try:
with open(filepath, 'w'):
return True
except IOError:
logger.warning("Writing permission denied for %s", filepath)
return False
def get_root_directories():
global root_directories
if root_directories != None:
return root_directories
rd = []
fd = [".git", ".github", "__pycache__", "mb-lab_updater"]
addon_directory = os.path.dirname(os.path.realpath(__file__))
for root, dirs, files in os.walk(addon_directory):
if root == addon_directory:
rd = dirs
break
root_directories = []
for dir in rd:
if dir not in fd:
logger.debug("found root dir %s", dir)
root_directories.append(
(dir,
"MB_Lab" if dir == "data" else dir,
dir))
return root_directories
def set_data_path(path):
global data_directory
global configuration_done
data_directory = path
configuration_done = None
def get_data_path():
global data_directory
addon_directory = os.path.dirname(os.path.realpath(__file__))
root_dir = os.path.join(addon_directory, data_directory)
logger.info("Looking for the retarget data in the folder %s...", simple_path(root_dir))
if not os.path.isdir(root_dir):
logger.critical("Tools data not found. Please check your Blender addons directory.")
return None
return root_dir
def get_configuration():
global configuration_done
# For a reason, after finalization, some functions use this
# but they don't need it. As a configuration doesn't change
# during a session, no need to recalculate it each time.
# what's why the variable below exists.
if configuration_done != None:
return configuration_done
addon_directory = os.path.dirname(os.path.realpath(__file__))
configuration_done = {}
for tup in get_root_directories():
data_path = os.path.join(addon_directory, tup[0])
if not os.path.isdir(data_path):
logger.error("get_configuration: %s is not a directury!", data_path)
continue
tmp = {}
logger.debug("Loading config dir %s", data_path)
for list_dir in os.listdir(data_path):
configuration_path = os.path.join(data_path, list_dir)
if os.path.isfile(configuration_path) and configuration_path.endswith("_config.json"):
logger.debug("Loading config json %s", configuration_path)
tmp = load_json_data(configuration_path, "Characters definition")
for prop in tmp:
if prop == 'data_directory':
pass
elif not prop in configuration_done:
configuration_done[prop] = tmp[prop]
elif prop == "templates_list" or prop == "character_list":
configuration_done[prop] += tmp[prop]
else:
configuration_done[prop] = tmp[prop]
if configuration_done:
return configuration_done
logger.critical("Configuration database not found. Please check your Blender addons directory.")
return None
def get_blendlibrary_path():
data_path = get_data_path()
if data_path:
return os.path.join(data_path, "humanoid_library.blend")
logger.critical("Models library not found. Please check your Blender addons directory.")
return None
def simple_path(input_path, use_basename=True, max_len=50):
"""
Return the last part of long paths
"""
if use_basename:
return os.path.basename(input_path)
if len(input_path) > max_len:
return f"[Trunked]..{input_path[len(input_path)-max_len:]}"
return input_path
def exists_database(lib_path):
result = False
if simple_path(lib_path) != "":
if os.path.isdir(lib_path):
if os.listdir(lib_path):
for database_file in os.listdir(lib_path):
_, extension = os.path.splitext(database_file)
if "json" in extension or "bvh" in extension:
result = True
else:
logger.warning("Unknow file extension in %s", simple_path(lib_path))
else:
logger.warning("data path %s not found", simple_path(lib_path))
return result
#Teto
def save_json_data(json_path, char_data):
try:
with open(json_path, "w") as j_file:
json.dump(char_data, j_file)
j_file.close()
except IOError:
if simple_path(json_path) != "":
logger.warning("File can not be saved: %s", simple_path(json_path))
except Exception:
logger.warning("The data are not serializable: %s", simple_path(json_path))
#End Teto
def load_json_data(json_path, description=None):
try:
time1 = time.time()
with open(json_path, "r") as j_file:
j_database = json.load(j_file)
if not description:
logger.info("Json database %s loaded in %s secs",
simple_path(json_path), time.time()-time1)
else:
logger.info("%s loaded from %s in %s secs",
description, simple_path(json_path), time.time()-time1)
return j_database
except IOError:
if simple_path(json_path) != "":
logger.warning("File not found: %s", simple_path(json_path))
except json.JSONDecodeError:
logger.warning("Errors in json file: %s", simple_path(json_path))
return None
def load_vertices_database(vertices_path):
vertices = []
verts = load_json_data(vertices_path, "Vertices data")
if verts:
for vert_co in verts:
vertices.append(mathutils.Vector(vert_co))
return vertices
def set_verts_coords_from_file(obj, vertices_path):
new_vertices = load_vertices_database(vertices_path)
if obj:
if len(new_vertices) == len(obj.data.vertices):
for i, vert in enumerate(obj.data.vertices):
vert.co = new_vertices[i]
def generate_items_list(folderpath, file_type="json", with_type = False):
items_list = []
if os.path.isdir(folderpath):
for database_file in os.listdir(folderpath):
the_item, extension = os.path.splitext(database_file)
if file_type in extension:
final_name = the_item if not with_type else database_file
if final_name not in items_list:
the_descr = "Load and apply {0} from lab library".format(the_item)
items_list.append((final_name, final_name, the_descr))
items_list.sort()
return items_list
# A convenient way to not create the items list all the time
items_dict = {}
def get_items_list(folderpath, file_type="json", with_type=False, reset=False):
global items_dict
if folderpath in items_dict and not reset:
return items_dict[folderpath]
items_list = generate_items_list(folderpath, file_type, with_type)
if len(items_list) > 0:
items_dict[folderpath] = items_list
return items_list
# Append humanoid objects
def import_object_from_lib(lib_filepath, name, final_name=None, stop_import=True):
if name != "":
if stop_import:
logger.info("Appending object %s from %s", name, simple_path(lib_filepath))
if name in bpy.data.objects:
logger.warning("Object %s already in the scene. Import stopped", name)
return None
if final_name:
if final_name in bpy.data.objects:
logger.warning("Object %s already in the scene. Import stopped", final_name)
return None
append_object_from_library(lib_filepath, [name])
obj = get_object_by_name(name)
if obj:
logger.info("Object '%s' imported", name)
if final_name:
obj.name = final_name
logger.info("Object '%s' renamed as '%s'", name, final_name)
return obj
logger.warning("Object %s not found in library %s", name, simple_path(lib_filepath))
return None
def append_object_from_library(lib_filepath, obj_names, suffix=None):
try:
with bpy.data.libraries.load(lib_filepath) as (data_from, data_to):
if suffix:
names_to_append = [name for name in data_from.objects if suffix in name]
data_to.objects = names_to_append
else:
names_to_append = obj_names
data_to.objects = [name for name in names_to_append if name in data_from.objects]
except OSError:
logger.critical("lib %s not found", lib_filepath)
return
for obj in data_to.objects:
link_to_collection(obj)
obj_parent = utils.get_object_parent(obj)
if obj_parent:
link_to_collection(obj_parent)
def append_mesh_from_library(lib_filepath, mesh_names, suffix=None):
try:
with bpy.data.libraries.load(lib_filepath) as (data_from, data_to):
if suffix:
names_to_append = [name for name in data_from.meshes if suffix in name]
data_to.meshes = names_to_append
else:
names_to_append = mesh_names
data_to.meshes = [name for name in names_to_append if name in data_from.meshes]
except OSError:
logger.critical("lib %s not found", lib_filepath)
def read_object_names_from_library(lib_filepath):
try:
with bpy.data.libraries.load(lib_filepath) as (data_from, data_to):
for name in data_from.objects:
print("OBJ_LIB: ", name)
except OSError:
logger.critical("lib %s not found", lib_filepath)
def link_to_collection(obj):
# sanity check
if obj.name not in bpy.data.objects:
logger.error("Cannot link obj %s because it's not in bpy.data.objects", obj.name)
return
collection_name = 'MB_LAB_Character'
c = bpy.data.collections.get(collection_name)
scene = bpy.context.scene
# collection is already created
if c is not None:
if obj.name not in c.objects:
c.objects.link(obj)
else:
logger.warning("The object %s is already linked to the scene", obj.name)
else:
# create the collection, link collection to scene and link obj to collection
c = bpy.data.collections.new(collection_name)
scene.collection.children.link(c)
c.objects.link(obj)
def is_armature_linked(obj, armat):
if obj.type == 'MESH':
for modfr in obj.modifiers:
if modfr.type == 'ARMATURE' and modfr.object == armat:
return True
return False
def get_object_by_name(name):
return bpy.data.objects.get(name)
def select_object_by_name(name):
obj = get_object_by_name(name)
if obj:
obj.select_set(True)
def get_newest_object(existing_obj_names):
for obj in bpy.data.objects:
name = obj.name
if name not in existing_obj_names:
return get_object_by_name(name)
return None
# This has been commented out, it doesn't seem to be used anywhere
#def json_booleans_to_python(value):
# return value != 0
def load_image(filepath):
if os.path.isfile(filepath):
logger.info("Loading image %s", os.path.basename(filepath))
img = bpy.data.images.load(filepath, check_existing=True)
img.reload()
else:
logger.info("Image %s not found", os.path.basename(filepath))
def get_image(name):
if name:
if name in bpy.data.images:
# Some check for log
if bpy.data.images[name].source == "FILE":
if os.path.basename(bpy.data.images[name].filepath) != name:
logger.warning("Image named %s is from file: %s",
name, os.path.basename(bpy.data.images[name].filepath))
return bpy.data.images[name]
logger.warning("Getting image failed. Image %s not found in bpy.data.images", name)
return None
logger.warning("Getting image failed. Image name is %s", name)
return None
def save_image(name, filepath, fileformat='PNG'):
img = get_image(name)
scn = bpy.context.scene
if img:
current_format = scn.render.image_settings.file_format
scn.render.image_settings.file_format = fileformat
img.save_render(filepath)
scn.render.image_settings.file_format = current_format
else:
logger.warning(
"The image %s cannot be saved because it's not present in bpy.data.images.", name)
def new_texture(name, image=None):
if name not in bpy.data.textures:
_new_texture = bpy.data.textures.new(name, type='IMAGE')
else:
_new_texture = bpy.data.textures[name]
if image:
_new_texture.image = image
return _new_texture