forked from animate1978/MB-Lab
-
Notifications
You must be signed in to change notification settings - Fork 0
/
creation_tools_ops.py
399 lines (347 loc) · 14.3 KB
/
creation_tools_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
# 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 #####
#
# Teto, for MB-Lab
import logging
import json
import os
import bpy
import numpy
from . import algorithms
from . import file_ops
from . import numpy_ops
from . import utils
#from . import
logger = logging.getLogger(__name__)
forbidden_directories = ["__pycache__", "data", "mb-lab_updater", "animations", "anthropometry",
"bboxes", "expressions_comb", "expressions_morphs", "face_rig", "Hair_Data", "joints",
"measures", "morphs", "Particle_Hair", "pgroups", "phenotypes", "poses", "presets",
"textures", "transformations", "vertices", "vgroups", "hu_f_anthropometry",
"hu_m_anthropometry", "anime_expressions", "human_expressions", "female_poses",
"male_poses", "rest_poses"]
forbidden_names = ["human", "humans", "anime", "male", "female", "anthropometry", "bbox", "expressions",
"exprs", "Expression", "morphs", "hair", "joints", "offset", "measures", "extra", "polygs", "ptypes",
"poses", "rest", "specialtype", "anyme", "style", "type", "base", "transf", "verts",
"vgroups", "muscles", "none"]
needed_directories = ["animations", "anthropometry", "bboxes", "expressions_comb",
"expressions_morphs", "face_rig", "Hair_Data", "joints", "measures", "morphs",
"Particle_Hair", "pgroups", "phenotypes", "poses", "presets", "textures", "transformations",
"vertices", "vgroups"]
static_names = {
"human": ["human", "hu"],
"anime": ["anime", "an"],
"male": ["male", "m_"],
"female": ["female", "f_"],
#"": [],
}
#with complete name, short name.
static_genders = [("male", "male", "m_"),
("female", "female", "f_"),
("undefined", "undefined", "u_")]
# The content of the config file
config_content = {"templates_list": [], "character_list": [], "data_directory": ""}
# The content of blend file associated to project.
blend_file_content = None
blend_file_content_loaded = False
loaded_project = False
#--------------------------------------
def get_forbidden_directories():
return forbidden_directories
def is_forbidden_directory(dir):
return dir.lower() in forbidden_directories
def get_forbidden_names():
return forbidden_names
def is_forbidden_name(name):
return name.lower() in forbidden_names
def get_static_names():
return static_names
def get_static_genders():
return static_genders
#--------------------------------------
def create_needed_directories(name=""):
if name == None or name == "":
logger.critical("!WARNING! Name doesn't exist.")
return
elif is_forbidden_name(name):
logger.critical("!WARNING! Name is forbidden.")
return
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), name)
if os.path.exists(path):
logger.critical("!WARNING! Directory already exists.")
for sub_dir in needed_directories:
try:
os.makedirs(os.path.join(path, sub_dir), mode=0o777)
except FileExistsError:
logger.warning("Directory " + sub_dir + " already exists. Skipped.")
def set_data_directory(dir):
global config_content
config_content["data_directory"] = dir
def get_data_directory():
global config_content
return config_content["data_directory"]
def get_project_directory():
global config_content
addon_directory = os.path.dirname(os.path.realpath(__file__))
return os.path.join(addon_directory, config_content["data_directory"])
def save_config():
global config_content
if config_content["data_directory"] == "" or config_content["data_directory"] == "data":
return
addon_directory = os.path.dirname(os.path.realpath(__file__))
file_name = os.path.join(addon_directory, config_content["data_directory"], config_content["data_directory"] + "_config.json")
# Save of the content, except data directory
temp = config_content.copy()
del temp["data_directory"]
with open(file_name, "w") as j_file:
json.dump(temp, j_file, indent=2)
def load_config(config_name):
global loaded_project
global config_content
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), config_name)
if os.path.exists(path):
file_name = os.path.join(path, config_name + "_config.json")
config_content = file_ops.load_json_data(file_name, "Load config file")
config_content["data_directory"] = config_name
loaded_project = True
# User never add a complete content. He always add a single value led
# by a key.
# Important : Before adding content in a base (like "human_female_base")
# or an body type (like "f_ca01"), the key must be added in
# "templates_list" or "character_list" before.
def add_content(key, key_in, content):
global config_content
if key == "":
return
# Now we figure out what we're talking about.
if key == "data_directory":
pass
elif key == "templates_list" or key == "character_list":
if not content in config_content[key]:
config_content[key].append(content)
elif key in config_content["templates_list"]:
if not key in config_content:
config_content[key] = {
"description": "", "template_model": "",
"template_polygons": "", "name": key,
"vertices": 0, "faces": 0, "label": ""}
if key_in != None:
config_content[key][key_in] = content
elif key in config_content["character_list"]:
if not key in config_content:
config_content[key] = {
"description": "", "template_model": "", "name": key,
"label": "", "texture_albedo": "", "texture_bump": "",
"texture_displacement": "", "texture_eyes": "",
"texture_tongue_albedo": "", "texture_teeth_albedo": "",
"texture_nails_albedo": "", "texture_eyelash_albedo": "",
"texture_frecklemask": "", "texture_blush": "",
"texture_sebum": "", "texture_lipmap": "", "texture_subdermal": "",
"texture_thickness": "", "texture_iris_color": "",
"texture_iris_bump": "", "texture_sclera_color": "",
"texture_translucent_mask": "", "texture_sclera_mask": "",
"morphs_extra_file": "", "shared_morphs_file": "",
"shared_morphs_extra_file": "", "bounding_boxes_file": "",
"proportions_folder": "", "joints_base_file": "",
"joints_offset_file": "", "measures_file": "",
"presets_folder": "", "transformations_file": "",
"vertexgroup_base_file": "", "vertexgroup_muscle_file": ""}
if not key_in in config_content[key]:
config_content[key][key_in] = content
elif key_in != None:
config_content[key][key_in] = content
def set_content(key, content):
global config_content
if key == None or key == "":
return
config_content[key] = content
def delete_content(key):
global config_content
if key == None or key == "":
return
del config_content[key]
def get_content(key, key_in):
global config_content
if key == None:
return ""
if key in config_content["templates_list"] or key in config_content["character_list"]:
if not key in config_content:
add_content(key, None, "")
if key in config_content:
content = config_content[key]
if type(content) is dict and key_in != None:
return content[key_in]
else:
return content
return ""
def init_config():
global config_content
global loaded_project
global blend_file_content_loaded
# init collection
c = bpy.data.collections.get('MB_LAB_Character')
if c is not None and blend_file_content != None:
for name in blend_file_content[1]:
obj = algorithms.get_object_by_name(name)
bpy.data.objects.remove(obj)
bpy.data.collections.remove(c)
# Init variables
config_content = {"templates_list": [], "character_list": [], "data_directory": ""}
loaded_project = False
blend_file_content_loaded = False
def is_project_loaded():
global loaded_project
return loaded_project
def is_directories_created():
global config_content
addon_directory = os.path.dirname(os.path.realpath(__file__))
dirpath = os.path.join(addon_directory, config_content["data_directory"])
if os.path.isdir(dirpath):
return True
return False
def is_config_created():
global config_content
dirpath = os.path.join(get_project_directory(), config_content["data_directory"] + "_config.json")
if os.path.isfile(dirpath):
return True
return False
def delete_template(name):
global config_content
if name in config_content:
del config_content[name]
if name in config_content["templates_list"]:
config_content["templates_list"].remove(name)
def delete_character(name):
global config_content
if name in config_content:
del config_content[name]
if name in config_content["character_list"]:
config_content["character_list"].remove(name)
def get_file_list(dir, file_type="json"):
path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
get_data_directory(), dir)
return [('NONE', 'Unknown', 'Unknown file for the moment')] + file_ops.get_items_list(path, file_type, with_type=True)
def get_presets_folder_list():
dir_list = get_content("templates_list", None)
if len(dir_list) < 1:
dir_list["Please create templates"]
return [('NONE', 'Unknown', 'Unknown folder for the moment')] + algorithms.create_enum_property_items(dir_list, tip_length=20)
# ------------------------------------------------------------------------
# All methods dedicated to the content of blend file
# ------------------------------------------------------------------------
def is_blend_file_exist():
global config_content
if len(config_content["data_directory"]) < 1:
return False
dirpath = os.path.join(get_project_directory(), "humanoid_library.blend")
if os.path.isfile(dirpath):
return True
return False
def get_blend_file_pathname():
return os.path.join(get_project_directory(), "humanoid_library.blend")
def get_blend_file_name():
return "humanoid_library.blend"
def load_blend_file():
global blend_file_content
global blend_file_content_loaded
if blend_file_content_loaded:
return blend_file_content
if is_blend_file_exist():
lib_filepath = get_blend_file_pathname()
else:
logger.critical("Blend file does not exist or is not under /" + get_project_directory() + "/")
return []
# Import objects name from library
with bpy.data.libraries.load(lib_filepath) as (data_from, data_to):
blend_file_content = [data_from, data_from.objects, data_from.meshes]
# Import objects name from library
file_ops.append_object_from_library(lib_filepath, data_from.objects)
blend_file_content_loaded = True
return blend_file_content
def blend_is_loaded():
global blend_file_content_loaded
return blend_file_content_loaded
def get_meshes_names():
global blend_file_content
global blend_file_content_loaded
if blend_file_content_loaded:
return blend_file_content[2]
return []
def get_objects_names():
global blend_file_content
global blend_file_content_loaded
if blend_file_content_loaded:
return blend_file_content[1]
return []
def get_vertices_faces_count(model_name):
global blend_file_content
global blend_file_content_loaded
if not blend_file_content_loaded:
return 0, 0
obj = bpy.data.meshes[model_name]
return len(obj.vertices.values()), len(obj.polygons.values())
# ------------------------------------------------------------------------
# All methods to return tuples for drop-down lists
# ------------------------------------------------------------------------
def get_templates_list():
global config_content
return_list = [("NEW", "New template...", "Create a new template")]
if len(config_content["templates_list"]) < 1:
return return_list
for tl in config_content["templates_list"]:
return_list.append((tl, tl, tl))
return return_list
def get_character_list(with_new=True):
global config_content
return_list = []
if with_new:
return_list = [("NEW", "New character...", "Create a new character")]
elif len(config_content["character_list"]) < 1:
return [('NONE', "No character", "No character created")]
for cl in config_content["character_list"]:
return_list.append((cl, cl, cl))
return return_list
def get_meshes_list():
global blend_file_content
global blend_file_content_loaded
if not blend_file_content_loaded:
return [("NONE", "No blend file", "Must load a blend file to work")]
return_list = []
for mesh in blend_file_content[2]:
return_list.append((mesh, mesh, "mesh : " + mesh))
return return_list
def is_mesh_compatible(mesh, chara_name="", model_name=""):
if mesh == None:
return False
length = len(mesh.data.vertices)
final_name = model_name
if len(chara_name) > 0:
content = get_content(chara_name, "template_model")
for temp in get_content("templates_list", None):
sub_content = get_content(temp, "template_model")
if sub_content == content:
final_name = temp
break
if len(final_name) > 0:
content = get_content(final_name, "vertices")
if content == length:
return True
return False
return False