-
Notifications
You must be signed in to change notification settings - Fork 84
/
characters.py
2524 lines (2058 loc) · 101 KB
/
characters.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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
#
# CC/iC Blender Tools 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.
#
# CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
import bpy
import re
import os
from . import (springbones, rigidbody, materials, modifiers, meshutils, geom, bones, physics, rigutils,
shaders, basic, imageutils, nodeutils, jsonutils, utils, vars)
from mathutils import Vector, Matrix, Quaternion
import mathutils.geometry
MANDATORY_OBJECTS = ["BODY", "TEETH", "TONGUE", "TEARLINE", "OCCLUSION", "EYE"]
def select_character(chr_cache, all=False):
if chr_cache:
rig = chr_cache.get_armature()
objects = chr_cache.get_all_objects(include_children=True)
rig_already_active = (rig and utils.get_active_object() == rig)
objects_already_selected = True
for obj in objects:
if obj not in bpy.context.selected_objects:
objects_already_selected = False
if all:
utils.try_select_objects(objects, clear_selection=True)
elif rig:
utils.try_select_object(rig, clear_selection=True)
else:
utils.try_select_objects(objects, clear_selection=True)
try:
if rig:
utils.set_active_object(rig)
if rig_already_active and (not all or objects_already_selected):
rig.show_in_front = not rig.show_in_front
else:
rig.show_in_front = False
except:
pass
def duplicate_character(chr_cache):
props = vars.props()
objects = chr_cache.get_cache_objects()
state = utils.store_object_state(objects)
rigutils.clear_all_actions(objects)
tmp = utils.force_visible_in_scene("TMP_Duplicate", *objects)
utils.try_select_objects(objects, clear_selection=True)
bpy.ops.object.duplicate()
objects = bpy.context.selected_objects.copy()
utils.restore_object_state(state)
utils.restore_visible_in_scene(tmp)
# duplicate materials
dup_mats = {}
for obj in objects:
if obj.type == "MESH":
mat: bpy.types.Material
for mat in obj.data.materials:
if mat not in dup_mats:
dup_mats[mat] = mat.copy()
for slot in obj.material_slots:
if slot.material:
slot.material = dup_mats[slot.material]
# copy chr_cache
old_cache = chr_cache
chr_cache = props.add_character_cache()
utils.copy_property_group(old_cache, chr_cache)
chr_cache.link_id = utils.generate_random_id(20)
for obj_cache in chr_cache.object_cache:
old_id = obj_cache.object_id
old_obj = obj_cache.object
new_id = utils.generate_random_id(20)
obj_cache.object_id = new_id
for obj in objects:
if utils.get_rl_object_id(obj) == old_id:
utils.set_rl_object_id(obj, new_id)
obj_cache.object = obj
if obj.type == "ARMATURE":
action = utils.safe_get_action(old_obj)
utils.safe_set_action(obj, action)
elif obj.type == "MESH" and utils.object_has_shape_keys(obj):
action = utils.safe_get_action(old_obj.data.shape_keys)
utils.safe_set_action(obj.data.shape_keys, action)
all_mat_cache = chr_cache.get_all_materials_cache(include_disabled=True)
for mat_cache in all_mat_cache:
old_id = mat_cache.material_id
new_id = utils.generate_random_id(20)
mat_cache.material_id = new_id
for obj in objects:
if obj.type == "MESH":
for mat in obj.data.materials:
if "rl_material_id" in mat and mat["rl_material_id"] == old_id:
mat["rl_material_id"] = new_id
mat_cache.material = mat
chr_rig = utils.get_armature_from_objects(objects)
character_name = utils.unique_object_name(utils.un_suffix_name(old_cache.character_name))
utils.log_info(f"Using character name: {character_name}")
chr_cache.character_name = character_name
if chr_cache.rigified:
rig_name = character_name + "_Rigify"
chr_rig["rig_id"] = utils.generate_random_id(20)
# copy the meta-rig too, if it's still there
if utils.object_exists_is_armature(chr_cache.rig_meta_rig):
tmp = utils.force_visible_in_scene("TMP_Duplicate", chr_cache.rig_meta_rig)
utils.set_active_object(chr_cache.rig_meta_rig, deselect_all=True)
bpy.ops.object.duplicate()
meta_rig = utils.get_active_object()
meta_rig.name = character_name + "_metarig"
utils.restore_visible_in_scene(tmp)
chr_cache.rig_meta_rig = meta_rig
utils.set_active_object(chr_rig)
else:
rig_name = character_name
chr_rig.name = rig_name
chr_rig.data.name = rig_name
return objects
def get_character_objects(arm):
"""Fetch all the objects in the character (or try to)"""
objects = []
if arm.type == "ARMATURE":
objects.append(arm)
for obj in arm.children:
if utils.object_exists_is_mesh(obj):
if obj not in objects:
objects.append(obj)
return objects
def get_generic_rig(objects):
props = vars.props()
arm = utils.get_armature_from_objects(objects)
if arm:
chr_cache = props.get_character_cache(arm, None)
if not chr_cache:
return arm
return None
def make_prop_armature(objects):
utils.object_mode()
# find the all the root empties and determine if there is one single root
roots = []
single_empty_root = None
for obj in objects:
# reset all transforms
#bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
# find single root
if obj.parent is None or obj.parent not in objects:
if obj.type == "EMPTY":
roots.append(obj)
single_empty_root = obj
if len(roots) > 1:
single_empty_root = False
single_empty_root = None
else:
single_empty_root = None
arm_name = "Prop_" + utils.generate_random_id(8)
if single_empty_root:
arm_name = single_empty_root.name
arm_data = bpy.data.armatures.new(arm_name)
arm = bpy.data.objects.new(arm_name, arm_data)
bpy.context.collection.objects.link(arm)
if single_empty_root:
arm.location = utils.object_world_location(single_empty_root)
utils.clear_selected_objects()
root_bone : bpy.types.EditBone = None
bone : bpy.types.EditBone = None
root_bone_name = None
tail_vector = Vector((0,0.5,0))
tail_translate = Matrix.Translation(-tail_vector)
if utils.edit_mode_to(arm):
if single_empty_root:
root_bone = arm.data.edit_bones.new(single_empty_root.name)
root_bone.head = arm.matrix_local @ utils.object_world_location(single_empty_root)
root_bone.tail = arm.matrix_local @ utils.object_world_location(single_empty_root, tail_vector)
root_bone.roll = 0
root_bone_name = root_bone.name
else:
root_bone = arm.data.edit_bones.new("Root")
root_bone.head = Vector((0,0,0))
root_bone.tail = Vector((0,0,0)) + tail_vector
root_bone.roll = 0
root_bone_name = root_bone.name
for obj in objects:
if obj.type == "EMPTY" and obj.name not in arm.data.edit_bones:
bone = arm.data.edit_bones.new(obj.name)
bone.head = arm.matrix_local @ utils.object_world_location(obj)
bone.tail = arm.matrix_local @ utils.object_world_location(obj, tail_vector)
for obj in objects:
if obj.type == "EMPTY" and obj.parent:
bone = arm.data.edit_bones[obj.name]
if obj.parent.name in arm.data.edit_bones:
parent = arm.data.edit_bones[obj.parent.name]
bone.parent = parent
elif bone != bone.parent:
bone.parent = root_bone
else:
bone.parent = None
utils.object_mode_to(arm)
obj : bpy.types.Object
for obj in objects:
if obj.type == "MESH":
if obj.parent and obj.parent.name in arm.data.bones:
parent_name = obj.parent.name
parent_bone : bpy.types.Bone = arm.data.bones[parent_name]
omw = obj.matrix_world.copy()
obj.parent = arm
obj.parent_type = 'BONE'
obj.parent_bone = parent_name
# by re-applying (a copy of) the original matrix_world, blender
# works out the correct parent inverse transforms from the bone
obj.matrix_world = omw
elif root_bone_name:
parent_bone = arm.data.bones[root_bone_name]
omw = obj.matrix_world.copy()
obj.parent = arm
obj.parent_type = 'BONE'
obj.parent_bone = root_bone_name
# by re-applying (a copy of) the original matrix_world, blender
# works out the correct parent inverse transforms from the bone
obj.matrix_world = omw
# remove the empties and move all objects into the same collection as the armature
collections = utils.get_object_scene_collections(arm)
for obj in objects:
if obj.type == "EMPTY":
bpy.data.objects.remove(obj)
else:
utils.move_object_to_scene_collections(obj, collections)
# finally force the armature name again (as it may have been taken by the original object)
arm.name = arm_name
return arm
def convert_generic_to_non_standard(objects, file_path=None, type_override=None, name_override=None, link_id=None):
props = vars.props()
prefs = vars.prefs()
utils.log_info("")
utils.log_info("Converting Generic Character:")
utils.log_info("-----------------------------")
# get generic objects
non_chr_objects = [ obj for obj in objects
if props.get_object_cache(obj) is None
and (obj.type == "MESH"
or obj.type == "EMPTY")]
active_non_chr_object = utils.get_active_object()
if active_non_chr_object not in non_chr_objects:
active_non_chr_object = non_chr_objects[0]
# select all child objects of the current selected objects
utils.try_select_objects(non_chr_objects, True)
for obj in non_chr_objects:
utils.try_select_child_objects(obj)
objects = bpy.context.selected_objects
chr_rig = utils.get_armature_from_objects(objects)
utils.log_info(f"Generic character objects:")
for obj in objects:
utils.log_info(f" - {obj.name} ({obj.type})")
# determine character type
if type_override:
chr_type = type_override
else:
if chr_rig:
chr_type = "HUMANOID"
else:
chr_type = "PROP"
utils.log_info(f"Generic character type: {chr_type}")
# determine character name
chr_name = "Unnamed"
if file_path:
dir, file = os.path.split(file_path)
name, ext = os.path.splitext(file)
chr_name = name
elif name_override:
chr_name = name_override
dir = ""
else:
if chr_type == "HUMANOID":
chr_name = "Humanoid"
elif chr_type == "CREATURE":
chr_name = "Creature"
else:
chr_name = "Prop"
chr_name = utils.unique_object_name(chr_name, chr_rig)
utils.log_info(f"Generic character name: {chr_name}")
# if no rig: generate one from the objects and empty parent transforms
if not chr_rig:
utils.log_info(f"Generating Prop Rig...")
chr_rig = make_prop_armature(objects)
# now treat the armature as any generic character
objects = get_character_objects(chr_rig)
utils.log_info(f"Creating Character Data...")
chr_rig.name = chr_name
chr_rig.data.name = chr_name
chr_cache = props.import_cache.add()
chr_cache.import_file = ""
chr_cache.character_name = chr_name
chr_cache.import_embedded = False
chr_cache.generation = "Unknown"
chr_cache.non_standard_type = chr_type
if not link_id:
link_id = utils.generate_random_id(20)
chr_cache.link_id = link_id
chr_cache.add_object_cache(chr_rig)
utils.log_info(f"Adding Character Objects:")
# add child objects to chr_cache
for obj in objects:
if utils.object_exists_is_mesh(obj):
add_object_to_character(chr_cache, obj, reparent=False,
no_materials=not prefs.auto_convert_materials)
return chr_cache
def link_override(obj: bpy.types.Object):
if obj:
collections = utils.get_object_scene_collections(obj)
override = obj.override_create(remap_local_usages=True)
coll: bpy.types.Collection
for coll in collections:
try:
coll.objects.link(override)
except: ...
def link_or_append_rl_character(op, context, blend_file, link=False):
props = vars.props()
prefs = vars.prefs()
utils.log_info("")
utils.log_info("Link/Append Reallusion Character")
utils.log_info("--------------------------------")
# if linking, reload any existing library link for this blend file
# otherwise it remembers if objects have been previously deleted.
existing_lib = None
if link:
for lib in bpy.data.libraries:
if os.path.samefile(lib.filepath, blend_file):
lib.reload()
existing_lib = lib
break
# link or append character data from blend file
with bpy.data.libraries.load(blend_file, link=link) as (src, dst):
dst.scenes = src.scenes
dst.objects = src.objects
ignore = []
keep = []
# find the add-on character data in the blend file
src_props = None
for scene in dst.scenes:
if "CC3ImportProps" in scene:
src_props = scene.CC3ImportProps
utils.log_info(f"Found Add-on Import Properties")
break
has_rigid_body = False
if src_props:
for src_cache in src_props.import_cache:
character_name = src_cache.character_name
import_file = src_cache.import_file
chr_rig = src_cache.get_armature()
chr_objects = src_cache.get_all_objects(include_armature=False,
include_children=True)
meta_rig = src_cache.rig_meta_rig
src_rig = src_cache.rig_original_rig
widgets = []
objects = []
utils.log_info(f"Character Data Found: {character_name}")
# keep the character rig
if chr_rig:
utils.log_info(f"Character rig: {chr_rig.name}")
keep.append(chr_rig)
objects.append(chr_rig)
# keep the meta rig
if meta_rig:
utils.log_info(f"Meta-rig: {meta_rig.name}")
keep.append(meta_rig)
objects.append(meta_rig)
# ignore the source rig
if src_rig:
ignore.append(src_rig)
# keep all child objects of the rigify rig
for obj in dst.objects:
if obj in chr_objects:
utils.log_info(f" - Character Object: {obj.name}")
keep.append(obj)
objects.append(obj)
# find the widgets
widget_prefix = f"WGT-{character_name}_rig"
widget_collection_name = f"WGT_{character_name}_rig"
for obj in dst.objects:
if obj.name.startswith(widget_prefix):
keep.append(obj)
widgets.append(obj)
# TODO remove all actions or keep them? or get all of them?
# link overrides
if link and False:
overrides = {}
for obj in objects:
override = obj.override_create(remap_local_usages=True)
if obj == meta_rig:
meta_rig = override
if obj == chr_rig:
chr_rig = override
if obj == src_rig:
src_rig = override
overrides[obj] = override
for obj in overrides:
override = overrides[obj]
try:
if hasattr(override, "parent"):
if override.parent and override.parent in overrides:
override.parent = overrides[override.parent]
except: ...
if override.type == "MESH":
for mod in override.modifiers:
if mod:
if hasattr(mod, "object") and mod.object in overrides:
mod.object = overrides[mod.object]
elif override.type == "EMPTY":
con = override.rigid_body_constraint
if con:
if hasattr(con, "target") and con.target in overrides:
con.target = overrides[con.target]
if hasattr(con, "object") and con.object in overrides:
con.object = overrides[con.object]
if hasattr(con, "object1") and con.object1 in overrides:
con.object1 = overrides[con.object1]
if hasattr(con, "object2") and con.object2 in overrides:
con.object2 = overrides[con.object2]
objects = list(overrides.values())
# after deciding what to keep, check the character has not already been linked
if link and (props.get_character_cache(chr_rig, None) or
props.get_character_cache_from_objects(chr_objects)):
op.report({"ERROR"}, "Character already linked!")
continue
# put all the character objects in the character collection
character_collection = utils.create_collection(character_name)
for obj in objects:
character_collection.objects.link(obj)
# put the widgets in the widget sub-collection
if widgets:
widget_collection = utils.create_collection(widget_collection_name,
existing=False,
parent_collection=character_collection)
for widget in widgets:
widget_collection.objects.link(widget)
utils.hide(widget)
# hide the widget sub-collection
lc = utils.find_layer_collection(widget_collection.name)
lc.exclude = True
# hide the meta rig
if meta_rig:
utils.hide(meta_rig)
# create the character cache and rebuild from the source data
chr_cache = props.add_character_cache(copy_from=src_cache)
rebuild_character_cache(chr_cache, chr_rig, chr_objects, src_cache)
# hide any colliders
rigidbody.hide_colliders(chr_rig)
# get rigidy body systems and hide them
if chr_rig:
parent_modes = springbones.get_all_parent_modes(chr_cache, chr_rig)
for parent_mode in parent_modes:
rig_prefix = springbones.get_spring_rig_prefix(parent_mode)
rigid_body_system = rigidbody.get_spring_rigid_body_system(chr_rig, rig_prefix)
if rigid_body_system:
if link:
rigidbody.remove_existing_rigid_body_system(chr_rig, rig_prefix, rigid_body_system.name)
else:
has_rigid_body = True
utils.hide_tree(rigid_body_system, hide=True)
# clean up unused objects
for obj in dst.objects:
if obj not in keep:
utils.delete_object(obj)
# init rigidy body world if needed
if has_rigid_body:
rigidbody.init_rigidbody_world()
def reconnect_rl_character_to_fbx(chr_rig, fbx_path):
props = vars.props()
prefs = vars.prefs()
objects = get_character_objects(chr_rig)
utils.log_info("")
utils.log_info("Re-connecting Character to Source FBX")
utils.log_info("-------------------------------------")
chr_cache = props.add_character_cache()
rig_name = chr_rig.name
character_name = rig_name
if "_Rigify" in character_name:
character_name = character_name.replace("_Rigify", "")
utils.log_info(f"Using character name: {character_name}")
if "rl_generation" in chr_rig:
generation = chr_rig["rl_generation"]
else:
generation = rigutils.get_rig_generation(chr_rig)
meta_rig_name = character_name + "_metarig"
meta_rig = None
if meta_rig_name in bpy.data.objects:
if utils.object_exists_is_armature(bpy.data.objects[meta_rig_name]):
meta_rig = bpy.data.objects[meta_rig_name]
chr_cache.import_file = fbx_path
chr_cache.character_name = character_name
chr_cache.import_embedded = False
chr_cache.generation = generation
chr_cache.non_standard_type = "HUMANOID"
chr_cache.rigified = True
chr_cache.rig_meta_rig = meta_rig
chr_cache.rigified_full_face_rig = character_has_bones(chr_rig, ["nose", "lip.T", "lip.B"])
chr_cache.add_object_cache(chr_rig)
rebuild_character_cache(chr_cache, chr_rig, objects)
return chr_cache
def reconnect_rl_character_to_blend(chr_rig, blend_file):
props = vars.props()
prefs = vars.prefs()
objects = get_character_objects(chr_rig)
utils.log_info("")
utils.log_info("Re-connecting Character to Blend File:")
utils.log_info("--------------------------------------")
rig_name = chr_rig.name
character_name = rig_name
if "_Rigify" in character_name:
character_name = character_name.replace("_Rigify", "")
utils.log_info(f"Using character name: {character_name}")
# link or append character data from blend file
with bpy.data.libraries.load(blend_file) as (src, dst):
dst.scenes = src.scenes
# find the add-on character data in the blend file
src_props = None
src_cache = None
for scene in dst.scenes:
if "CC3ImportProps" in scene:
src_props = scene.CC3ImportProps
utils.log_info(f"Found Add-on Import Properties")
break
if src_props:
# try to find the source cache by import file
if "rl_import_file" in chr_rig:
import_file = chr_rig["rl_import_file"]
for chr_cache in src_props.import_cache:
if chr_cache.import_file == import_file:
utils.log_info(f"Found matching source character fbx: {chr_cache.character_name}")
src_cache = chr_cache
break
# try to find the source cache by character name
for chr_cache in src_props.import_cache:
if chr_cache.character_name == character_name:
utils.log_info(f"Found matching source character name: {chr_cache.character_name}")
src_cache = chr_cache
break
if src_cache:
# create the character cache and rebuild from the source data
chr_cache = props.add_character_cache(copy_from=src_cache)
# can't match objects accurately, so don't try (as they are no longer the same linked objects)
rebuild_character_cache(chr_cache, chr_rig, objects, src_cache=src_cache)
return chr_cache
return None
def rebuild_character_cache(chr_cache, chr_rig, objects, src_cache=None):
props = vars.props()
prefs = vars.prefs()
if chr_rig:
chr_cache.add_object_cache(chr_rig)
utils.log_info("")
utils.log_info("Re-building Character Cache:")
utils.log_info("----------------------------")
errors = []
json_data = jsonutils.read_json(chr_cache.import_file, errors)
chr_json = jsonutils.get_character_json(json_data, chr_cache.get_character_id())
# add child objects to chr_cache
processed = []
defaults = []
for obj in objects:
obj_id = utils.get_rl_object_id(obj)
if utils.object_exists_is_mesh(obj) and obj not in processed:
processed.append(obj)
src_obj_cache = src_cache.get_object_cache(obj, by_id=obj_id) if src_cache else None
utils.log_info(f"Object: {obj.name} {obj_id} {src_obj_cache}")
obj_json = jsonutils.get_object_json(chr_json, obj)
obj_cache = chr_cache.add_object_cache(obj, copy_from=src_obj_cache)
for mat in obj.data.materials:
if mat and mat.node_tree is not None:
mat_id = mat["rl_material_id"] if "rl_material_id" in mat else None
src_mat_cache = src_cache.get_material_cache(mat, by_id=mat_id) if src_cache else None
utils.log_info(f"Material: {mat.name} {mat_id} {src_mat_cache}")
if src_obj_cache and src_mat_cache:
object_type = src_obj_cache.object_type
material_type = src_mat_cache.material_type
elif "rl_object_type" in obj and "rl_material_type" in mat:
object_type = obj["rl_object_type"]
material_type = mat["rl_material_type"]
else:
object_type, material_type = materials.detect_materials(chr_cache, obj, mat, obj_json)
if obj_cache.object_type != "BODY":
obj_cache.set_object_type(object_type)
if mat not in processed:
mat_cache = chr_cache.add_material_cache(mat, material_type, copy_from=src_mat_cache)
mat_cache.dir = imageutils.get_material_tex_dir(chr_cache, obj, mat)
physics.detect_physics(chr_cache, obj, obj_cache, mat, mat_cache, chr_json)
processed.append(mat)
if not src_obj_cache or not src_mat_cache:
defaults.append(mat)
# re-initialize the shader parameters (if not copied over)
if defaults:
shaders.init_character_property_defaults(chr_cache, chr_json, only=defaults)
if not src_cache:
basic.init_basic_default(chr_cache)
return chr_cache
def parent_to_rig(rig, obj):
"""For if the object is not parented to the rig and/or does not have an armature modifier set to the rig.
"""
if rig and obj and rig.type == "ARMATURE" and obj.type == "MESH":
if obj.parent != rig:
# clear any parenting
if obj.parent:
if utils.set_active_object(obj):
bpy.ops.object.parent_clear(type = "CLEAR_KEEP_TRANSFORM")
# parent to rig
if rig:
if utils.try_select_objects([rig, obj]):
if utils.set_active_object(rig):
bpy.ops.object.parent_set(type = "OBJECT", keep_transform = True)
# add or update armature modifier
arm_mod = modifiers.get_object_modifier(obj, "ARMATURE")
if not arm_mod:
arm_mod: bpy.types.ArmatureModifier = modifiers.get_armature_modifier(obj, create=True, armature=rig)
modifiers.move_mod_first(obj, arm_mod)
# update armature modifier rig
if arm_mod and arm_mod.object != rig:
arm_mod.object = rig
utils.clear_selected_objects()
utils.set_active_object(obj)
def add_object_to_character(chr_cache, obj : bpy.types.Object, reparent=True, no_materials=False):
props = vars.props()
if chr_cache and utils.object_exists_is_mesh(obj):
obj_cache = chr_cache.get_object_cache(obj)
if not obj_cache:
# convert the object name to remove any duplicate suffixes:
obj_name = utils.unique_object_name(obj.name, obj)
if obj.name != obj_name:
obj.name = obj_name
# add the object into the object cache
obj_cache = chr_cache.add_object_cache(obj)
if "rl_object_type" in obj:
obj_cache.set_object_type(obj["rl_object_type"])
else:
obj_cache.set_object_type("DEFAULT")
obj_cache.user_added = True
obj_cache.disabled = False
if not no_materials:
add_missing_materials_to_character(chr_cache, obj, obj_cache)
utils.clear_selected_objects()
if reparent:
arm = chr_cache.get_armature()
if arm:
parent_to_rig(arm, obj)
def remove_object_from_character(chr_cache, obj):
props = vars.props()
if utils.object_exists_is_mesh(obj):
obj_cache = chr_cache.get_object_cache(obj)
if obj_cache and obj_cache.object_type not in MANDATORY_OBJECTS:
obj_cache.disabled = True
# unparent from character
arm = chr_cache.get_armature()
if arm:
if utils.try_select_objects([arm, obj]):
if utils.set_active_object(arm):
bpy.ops.object.parent_clear(type = "CLEAR_KEEP_TRANSFORM")
# remove armature modifier
arm_mod : bpy.types.ArmatureModifier = modifiers.get_object_modifier(obj, "ARMATURE")
if arm_mod:
obj.modifiers.remove(arm_mod)
#utils.hide(obj)
utils.clear_selected_objects()
# don't reselect the removed object as this may cause
# onfusion when using checking function immediately after...
#utils.set_active_object(obj)
def copy_objects_character_to_character(context_obj, chr_cache, objects, reparent = True):
props = vars.props()
arm = chr_cache.get_armature()
if not arm:
return
context_collections = utils.get_object_scene_collections(context_obj)
to_copy = {}
for obj in objects:
if utils.object_exists_is_mesh(obj):
cc = props.get_character_cache(obj, None)
if cc != chr_cache:
if cc not in to_copy:
to_copy[cc] = []
to_copy[cc].append(obj)
copied_objects = []
for cc in to_copy:
for o in to_copy[cc]:
oc = cc.get_object_cache(o)
# copy object
obj = utils.duplicate_object(o)
utils.move_object_to_scene_collections(obj, context_collections)
copied_objects.append(obj)
# convert the object name to remove any duplicate suffixes:
obj_name = utils.unique_object_name(obj.name, obj)
if obj.name != obj_name:
obj.name = obj_name
# add the object into the object cache
obj_cache = chr_cache.add_object_cache(obj, copy_from=oc, user=True)
obj_cache.user_added = True
obj_cache.disabled = False
add_missing_materials_to_character(chr_cache, obj, obj_cache)
if reparent:
parent_to_rig(arm, obj)
utils.clear_selected_objects()
utils.try_select_objects(copied_objects, make_active=True)
def get_accessory_root(chr_cache, object):
"""Accessories can be identified by them having only vertex groups not listed in the bone mappings for this generation."""
if not chr_cache or not object:
return None
# none of this works if rigified...
if chr_cache.rigified:
return None
if not chr_cache or not object or not utils.object_exists_is_mesh(object):
return None
rig = chr_cache.get_armature()
bone_mapping = chr_cache.get_rig_bone_mapping()
if not rig or not bone_mapping:
return None
accessory_root = None
# accessories can be identified by them having only vertex groups not listed in the bone mappings for this generation.
for vg in object.vertex_groups:
# if even one vertex groups belongs to the character bones, it will not import into cc4 as an accessory
if bones.bone_mapping_contains_bone(bone_mapping, vg.name):
return None
else:
bone = bones.get_bone(rig, vg.name)
if bone:
root = bones.get_accessory_root_bone(bone_mapping, bone)
if root:
accessory_root = root
return accessory_root
def make_accessory(chr_cache, objects):
prefs = vars.prefs()
rig = chr_cache.get_armature()
# store parent objects (as the parenting is destroyed when adding objects to character)
obj_data = {}
for obj in objects:
if obj.type == "MESH":
obj_data[obj] = {
"parent_object": obj.parent,
"matrix_world": obj.matrix_world.copy()
}
# add any non character objects to character
for obj in objects:
obj_cache = chr_cache.get_object_cache(obj)
if not obj_cache:
utils.log_info(f"Adding {obj.name} to character.")
add_object_to_character(chr_cache, obj, True,
no_materials=not prefs.auto_convert_materials)
obj_cache = chr_cache.get_object_cache(obj)
else:
parent_to_rig(rig, obj)
cursor_pos = bpy.context.scene.cursor.location
if utils.try_select_objects(objects, True, "MESH", True):
if utils.set_mode("EDIT"):
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.view3d.snap_cursor_to_selected()
if rig and utils.edit_mode_to(rig, only_this = True):
# add accessory named bone to rig
accessory_root = rig.data.edit_bones.new("Accessory")
root_head = rig.matrix_world.inverted() @ bpy.context.scene.cursor.location
root_tail = rig.matrix_world.inverted() @ (bpy.context.scene.cursor.location + Vector((0, 1/4, 0)))
piv_tail = rig.matrix_world.inverted() @ (bpy.context.scene.cursor.location + Vector((0, 1/10000, 0)))
utils.log_info(f"Adding accessory root bone: {accessory_root.name}/({root_head})")
accessory_root.head = root_head
accessory_root.tail = root_tail
default_parent = bones.get_rl_edit_bone(rig, chr_cache.accessory_parent_bone)
accessory_root.parent = default_parent
for obj in objects:
if obj.type == "MESH":
# add object bone to rig
obj_bone = rig.data.edit_bones.new(obj.name)
obj_head = rig.matrix_world.inverted() @ (obj.matrix_world @ Vector((0, 0, 0)))
obj_tail = rig.matrix_world.inverted() @ ((obj.matrix_world @ Vector((0, 0, 0))) + Vector((0, 1/8, 0)))
utils.log_info(f"Adding object bone: {obj_bone.name}/({obj_head})")
obj_bone.head = obj_head
obj_bone.tail = obj_tail
# add pivot bone to rig
#piv_bone = rig.data.edit_bones.new("CC_Base_Pivot")
#utils.log_info(f"Adding pivot bone: {piv_bone.name}/({root_head})")
#piv_bone.head = root_head + Vector((0, 1/100, 0))
#piv_bone.tail = piv_tail + Vector((0, 1/100, 0))
#piv_bone.parent = obj_bone
# add deformation bone to rig
def_bone = rig.data.edit_bones.new(obj.name)
utils.log_info(f"Adding deformation bone: {def_bone.name}/({obj_head})")
def_head = rig.matrix_world.inverted() @ ((obj.matrix_world @ Vector((0, 0, 0))) + Vector((0, 1/32, 0)))
def_tail = rig.matrix_world.inverted() @ ((obj.matrix_world @ Vector((0, 0, 0))) + Vector((0, 1/32 + 1/16, 0)))
def_bone.head = def_head
def_bone.tail = def_tail
def_bone.parent = obj_bone
# remove all vertex groups from object
obj.vertex_groups.clear()
# add vertex groups for object bone
vg = meshutils.add_vertex_group(obj, def_bone.name)
meshutils.set_vertex_group(obj, vg, 1.0)
obj_data[obj]["bone"] = obj_bone
obj_data[obj]["def_bone"] = def_bone
# parent the object bone to the accessory bone (or object transform parent bone)
for obj in objects:
if obj.type == "MESH" and obj in obj_data.keys():
# fetch the object's bone
obj_bone = obj_data[obj]["bone"]
# find the parent bone to the object (if exists)
parent_bone = None
obj_parent = obj_data[obj]["parent_object"]
if obj_parent and obj_parent in obj_data.keys():
parent_bone = obj_data[obj_parent]["bone"]
# parent the bone