-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathpost_import_model.gd
1117 lines (1033 loc) · 54.5 KB
/
post_import_model.gd
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
# This file is part of Unidot Importer. See LICENSE.txt for full MIT license.
# Copyright (c) 2021-present Lyuma <[email protected]> and contributors
# SPDX-License-Identifier: MIT
@tool
extends EditorScenePostImport
const asset_database_class := preload("./asset_database.gd")
const asset_meta_class := preload("./asset_meta.gd")
const object_adapter_class := preload("./object_adapter.gd")
const unidot_utils_class = preload("./unidot_utils.gd")
# Use this as an example script for writing your own custom post-import scripts. The function requires you pass a table
# of valid animation names and parameters
# Todo: Secondary UV Sets.
# Note: bakery has its own data for this:
# https://forum.unity.com/threads/bakery-gpu-lightmapper-v1-8-rtpreview-released.536008/page-39#post-4077463
# animations:
# extraUserProperties:
# - '#BAKERY{"meshName":["Mesh1","Mesh2","Mesh3","Mesh4","Mesh5","Mesh6","Mesh7","Mesh8","Mesh9","Mesh10"],
# "padding":[239,59,202,202,202,202,94,94,94,94],"unwrapper":[0,0,0,0,0,0,0,0,0,0]}'
var object_adapter = object_adapter_class.new()
var unidot_utils = unidot_utils_class.new()
var default_material: Material = null
const ROOT_NODE_NAME = "//RootNode"
static func get_extracted_assets_dir(model_pathname: String):
return model_pathname.get_base_dir().path_join("extracted")
class ParseState:
var object_adapter: Object
var scene: Node
var toplevel_node: Node
var root_bone_idx: int = -1
var metaobj: Resource
var source_file_path: String
var extracted_assets_basename: String
var use_new_names: bool = false
var preserve_hierarchy: bool = false
var preserve_hierarchy_orig_root_node_name: String = ""
var skinned_parent_to_node: Dictionary = {}.duplicate()
var godot_sanitized_to_orig_remap: Dictionary = {}.duplicate()
var bone_map_dict: Dictionary
var external_objects_by_id: Dictionary = {}.duplicate() # fileId -> UnidotRef Array
var external_objects_by_type_name: Dictionary = {}.duplicate() # type -> name -> UnidotRef Array
var material_to_texture_name: Dictionary = {}.duplicate() # for Extract Legacy Materials / By Base Texture Name
var animation_to_take_name: Dictionary = {}.duplicate()
var material_order_by_mesh_rev: Dictionary
var saved_materials_by_name: Dictionary = {}.duplicate()
var saved_meshes_by_name: Dictionary = {}.duplicate()
var saved_mesh_fileids_by_name: Dictionary = {}.duplicate()
var saved_skins_by_name: Dictionary = {}.duplicate()
var saved_animations_by_name: Dictionary = {}.duplicate()
var nodes_by_name: Dictionary = {}.duplicate()
var skeleton_bones_by_name: Dictionary = {}.duplicate()
var objtype_to_name_to_id: Dictionary = {}.duplicate()
var objtype_to_next_id: Dictionary = {}.duplicate()
var used_ids: Dictionary = {}.duplicate()
var new_name_dupe_map: Dictionary = {}.duplicate()
var fileid_to_nodepath: Dictionary = {}.duplicate()
var fileid_to_skeleton_bone: Dictionary = {}.duplicate()
var fileid_to_utype: Dictionary = {}.duplicate()
var fileid_to_gameobject_fileid: Dictionary = {}.duplicate()
var type_to_fileids: Dictionary = {}.duplicate()
var transform_fileid_to_rotation_delta: Dictionary = {}.duplicate()
var transform_fileid_to_parent_fileid: Dictionary = {}.duplicate()
var humanoid_original_transforms: Dictionary
var humanoid_skeleton_hip_position: Vector3 = Vector3(0.0, 1.0, 0.0)
var transform_fileid_to_scale_signs: Dictionary
var all_name_map: Dictionary = {}.duplicate()
var root_rotation_delta: Transform3D = Transform3D.IDENTITY
var is_obj: bool = false
var is_dae: bool = false
var default_obj_mesh_name: String = "default"
var node_is_toplevel: bool = false
var extractLegacyMaterials: bool = false
var importMaterials: bool = true
var materialSearch: int = 1
var legacy_material_name_setting: int = 1
var default_material: Material = null
var asset_database: Resource = null
var unidot_utils = unidot_utils_class.new()
# Uh... they... forgot a function?????
func pop_back(arr: PackedStringArray):
arr.remove_at(len(arr) - 1)
# Do we actually need this? Ordering?
#var materials = [].duplicate()
#var meshes = [].duplicate()
#var animations = [].duplicate()
#var nodes = [].duplicate()
#func has_obj_id(type: String, name: String) -> bool:
# return objtype_to_name_to_id.get(type, {}).has(name)
func generate_object_hash(dupe_map: Dictionary, type: String, obj_path: String) -> int:
var ret: int = HashGenerator.generate_object_hash(dupe_map, type, obj_path)
var t: String = "Type:" + type + "->" + obj_path
t += str(dupe_map[t])
metaobj.log_debug(ret, "Hash " + t + " => " + str(ret))
return ret
func get_obj_id(type: String, path: PackedStringArray, name: String) -> int:
if type == "PrefabInstance":
# May be based on hash in gltf docs, not sure. Anyway for fbx 100100000 seems ok
return 100100000 # I think we'll just hardcode this.
if type == "AnimationClip":
name = animation_to_take_name.get(name, name)
if self.is_dae and path == PackedStringArray():
name = name.replace(" ", "_")
var name_key = type + "|" + name
var old_style_name = name
if new_name_dupe_map.has(name_key):
old_style_name = old_style_name + " " + str(new_name_dupe_map[name_key])
new_name_dupe_map[name_key] = new_name_dupe_map.get(name_key, 0) + 1
if objtype_to_name_to_id.get(type, {}).has(old_style_name): # Pre-version 2019
return objtype_to_name_to_id.get(type, {}).get(old_style_name, 0)
elif use_new_names: # Post version 2019
var pathstr = name
if len(path) > 0:
pathstr = ""
for i in range(len(path)):
if i != 0:
pathstr += "/"
pathstr += path[i]
return generate_object_hash(new_name_dupe_map, type, pathstr)
else: # Pre version 2019, not in map
if type == "Animator":
return 9500000
var next_obj_id: int = objtype_to_next_id.get(type, object_adapter.to_utype(type) * 100000)
while used_ids.has(next_obj_id):
next_obj_id += 2
objtype_to_next_id[type] = next_obj_id + 2
used_ids[next_obj_id] = true
if type != "Material":
metaobj.log_warn(next_obj_id, "Generating id " + str(next_obj_id) + " for " + str(name) + " type " + str(type))
return next_obj_id
func get_orig_name(obj_gltf_type: String, p_obj_name: String) -> String:
var obj_name = p_obj_name
if obj_gltf_type == "nodes" and p_obj_name == "Skeleton3D" and not bone_map_dict.is_empty():
obj_name = "GeneralSkeleton"
if obj_gltf_type == "bone_name" and not bone_map_dict.is_empty():
var bone_mapped = bone_map_dict.get(p_obj_name, "")
if bone_mapped != "":
obj_name = bone_mapped
# metaobj.log_debug(0, "Lookup bone name " + str(p_obj_name) + " -> " + str(obj_name))
return self.godot_sanitized_to_orig_remap.get(obj_gltf_type, {}).get(obj_name, obj_name)
func get_orig_bone_or_node(p_obj_name: String) -> String:
return self.godot_sanitized_to_orig_remap.get("nodes", {}).get(p_obj_name,
self.godot_sanitized_to_orig_remap.get("bone_name", {}).get(p_obj_name, p_obj_name))
func build_skinned_name_to_node_map(node: Node, p_name_to_node_dict: Dictionary) -> Dictionary:
var name_to_node_dict: Dictionary = p_name_to_node_dict
var node_name = get_orig_bone_or_node(node.name)
for child in node.get_children():
name_to_node_dict = build_skinned_name_to_node_map(child, name_to_node_dict)
metaobj.log_debug(0, "node.name " + str(node_name) + ": " + str(name_to_node_dict))
if node is MeshInstance3D:
if node.skin != null:
name_to_node_dict[node_name] = node
metaobj.log_debug(0, "adding " + str(node_name) + ": " + str(name_to_node_dict))
return name_to_node_dict
func get_resource_path(sanitized_name: String, extension: String) -> String:
# return source_file_path.get_basename() + "." + str(fileId) + extension
return extracted_assets_basename + "." + sanitize_filename(sanitized_name) + extension
func get_parent_materials_paths(material_name: String) -> Array:
# return source_file_path.get_basename() + "." + str(fileId) + extension
var retlist: Array = []
var basedir: String = source_file_path.get_base_dir()
while basedir != "res://" and basedir != "/" and not basedir.is_empty() and basedir != ".":
retlist.append(get_materials_path_base(material_name, basedir, ".mat.tres"))
retlist.append(get_materials_path_base(material_name, basedir, ".material"))
basedir = basedir.get_base_dir()
retlist.append(get_materials_path_base(material_name, "res://", ".mat.tres"))
retlist.append(get_materials_path_base(material_name, "res://", ".material"))
metaobj.log_debug(0, "Looking in directories " + str(retlist))
return retlist
func get_materials_path_base(material_name: String, base_dir: String, ext: String) -> String:
# return source_file_path.get_basename() + "." + str(fileId) + extension
return base_dir + "/Materials/" + str(material_name) + ext
func get_materials_path(material_name: String, ext: String=".material") -> String:
return get_materials_path_base(material_name, source_file_path.get_base_dir(), ext)
func sanitize_filename(sanitized_name: String, repl: String="", include_dot: bool=true) -> String:
return sanitized_name.replace("/", repl).replace(":", repl).replace(".", repl).replace("@", repl).replace('"', repl).replace("<", repl).replace(">", repl).replace("*", repl).replace("|", repl).replace("?", repl)
func fold_root_transforms_into_only_child() -> bool:
# FIXME: Animations targeting the parent or the child might need to be adjusted.
var root_node: Node3D = toplevel_node
var is_foldable: bool = root_node.get_child_count() == 1
var wanted_child: int = 0
if root_node.get_child_count() == 2 and root_node.get_child(0) is AnimationPlayer:
wanted_child = 1
is_foldable = true
elif root_node.get_child_count() == 2 and root_node.get_child(1) is AnimationPlayer:
is_foldable = true
if not is_foldable:
return false
var child_node = root_node.get_child(wanted_child)
if child_node is Skeleton3D:
if len(child_node.get_parentless_bones()) > 1:
return false
root_bone_idx = child_node.get_parentless_bones()[0]
root_rotation_delta = root_rotation_delta * child_node.transform * child_node.get_bone_pose(root_bone_idx)
toplevel_node = child_node
return true
if child_node is Node3D:
root_rotation_delta = root_rotation_delta * child_node.transform
toplevel_node = child_node
return true
return false
func register_component(node: Node, p_path: PackedStringArray, p_component: String, fileId_go: int = 0, p_bone_idx: int = -1, parent_transform_id: int = 0) -> int:
#???
#if node == toplevel_node:
# return # GameObject nodes always point to the toplevel node.
var nodepath: NodePath = scene.get_path_to(node)
var gltf_type: String = "nodes"
var orig_name: String
if node is Skeleton3D:
gltf_type = "bone_name"
orig_name = get_orig_name(gltf_type, node.get_bone_name(p_bone_idx))
elif node is AnimationPlayer:
orig_name = get_orig_name(gltf_type, node.get_parent().name)
else:
orig_name = get_orig_name(gltf_type, node.name)
p_path.push_back(p_component)
if node == self.toplevel_node:
orig_name = ""
var fileId_comp: int = get_obj_id(p_component, p_path, orig_name)
pop_back(p_path) # Must happen first: "GameObject" does not exist in the path
if p_component == "Transform":
fileId_go = get_obj_id("GameObject", p_path, orig_name)
if not all_name_map.has(fileId_go):
all_name_map[fileId_go] = {}.duplicate()
all_name_map[fileId_go][object_adapter.to_utype(p_component)] = fileId_comp
if p_component == "Transform":
transform_fileid_to_parent_fileid[fileId_comp] = parent_transform_id
all_name_map[fileId_go][1] = fileId_go # Redundant...
fileid_to_nodepath[fileId_go] = nodepath
fileid_to_gameobject_fileid[fileId_go] = fileId_go
fileid_to_utype[fileId_go] = 1
if not type_to_fileids.has("GameObject"):
type_to_fileids["GameObject"] = PackedInt64Array()
type_to_fileids["GameObject"].push_back(fileId_go)
fileid_to_nodepath[fileId_comp] = nodepath
#if fileId in fileid_to_skeleton_bone:
# fileid_to_skeleton_bone.erase(fileId)
fileid_to_gameobject_fileid[fileId_comp] = fileId_go
fileid_to_utype[fileId_comp] = object_adapter.to_utype(p_component)
if not type_to_fileids.has(p_component):
type_to_fileids[p_component] = PackedInt64Array()
type_to_fileids[p_component].push_back(fileId_comp)
if node is Skeleton3D:
var og_bone_name: String = node.get_bone_name(p_bone_idx)
fileid_to_skeleton_bone[fileId_comp] = og_bone_name
if p_component == "Transform":
fileid_to_skeleton_bone[fileId_go] = og_bone_name
#metaobj.log_debug(0, "fileid_go:" + str(fileId_go) + '/ ' + str(all_name_map[fileId_go]))
if p_component == "Transform":
return fileId_go
return fileId_comp
func register_resource(p_resource: Resource, p_name: String, p_type: String, fileId_object: int, p_aux_resource: Variant = null):
# Using : Variant for argument 5 to workaround the following GDScript bug:
# SCRIPT ERROR: Invalid type in function 'register_resource' in base 'RefCounted (ParseState)'.
# The Object-derived class of argument 5 (null instance) is not a subclass of the expected argument class. (Resource)
var gltf_type = "meshes"
if p_type == "Material":
gltf_type = "materials"
if p_type == "AnimationClip":
gltf_type = "animations"
metaobj.insert_resource(fileId_object, p_resource)
metaobj.log_debug(0, "Register " + str(metaobj.guid) + ":" + str(fileId_object) + ": " + str(p_type) + " '" + str(p_name) + "' " + str(p_resource))
if p_aux_resource != null:
metaobj.insert_resource(-fileId_object, p_aux_resource) # Used for skin object.
metaobj.log_debug(0, "Register aux " + str(metaobj.guid) + ":" + str(-fileId_object) + ": '" + str(p_name) + "' " + str(p_aux_resource))
return fileId_object
func iterate_skeleton(node: Skeleton3D, p_path: PackedStringArray, p_skel_bone: int, p_attachments_by_bone_name: Dictionary, p_parent_transform_id: int, p_global_rest:= Transform3D(), p_pre_retarget_global_rest:= Transform3D()):
#metaobj.log_debug(0, "Skeleton iterate_skeleton " + str(node.get_class()) + ", " + str(p_path) + ", " + str(node.name))
assert(p_skel_bone != -1)
var fileId_go: int = register_component(node, p_path, "Transform", 0, p_skel_bone, p_parent_transform_id)
var fileId_transform: int = all_name_map[fileId_go][4]
var bone_name: String = node.get_bone_name(p_skel_bone)
if p_skel_bone == root_bone_idx:
p_pre_retarget_global_rest *= (node.transform * node.get_bone_pose(root_bone_idx)).affine_inverse()
# ParOrigT * ChildOrigT = GodotHumanT * X * ChildOrigT
# GodotHumanT = ParOrigT * GodotCorrectionT
# We want to solve for X = GodotCorrectionT.inv
# GodotCorrectionT = ParOrigT.inv * GodotHumanT
if humanoid_original_transforms.has(bone_name):
p_pre_retarget_global_rest *= Transform3D(humanoid_original_transforms.get(bone_name).basis, p_pre_retarget_global_rest.basis.inverse() * p_global_rest.basis * node.get_bone_pose(p_skel_bone).origin)
metaobj.log_debug(0, "Humanoid Bone " + str(bone_name) + ": " + str(humanoid_original_transforms.get(bone_name).basis.get_rotation_quaternion()) + " origin: " + str(humanoid_original_transforms.get(bone_name).origin))
if bone_name == "Hips":
humanoid_skeleton_hip_position = node.get_bone_pose(p_skel_bone).origin
humanoid_skeleton_hip_position.y = node.motion_scale
else:
p_pre_retarget_global_rest *= node.get_bone_pose(p_skel_bone)
# metaobj.log_debug(0, "Non-humanoid Bone " + str(bone_name) + ": " + str(node.get_bone_pose(p_skel_bone).basis.get_rotation_quaternion()))
p_global_rest *= node.get_bone_pose(p_skel_bone)
# metaobj.log_debug(0, "global rest " + str(bone_name) + ": " + str(node.get_bone_pose(p_skel_bone).basis.get_rotation_quaternion()))
if not p_global_rest.is_equal_approx(p_pre_retarget_global_rest):
# metaobj.log_debug(0, "bone " + bone_name + " rest " + str(p_global_rest) + " pre ret " + str(p_pre_retarget_global_rest))
transform_fileid_to_rotation_delta[fileId_transform] = p_global_rest.affine_inverse() * p_pre_retarget_global_rest
if not node.has_meta("humanoid_rotation_delta"):
node.set_meta("humanoid_rotation_delta", {})
node.get_meta("humanoid_rotation_delta")[node.get_bone_name(p_skel_bone)] = transform_fileid_to_rotation_delta[fileId_transform]
for child_bone in node.get_bone_children(p_skel_bone):
var orig_child_name: String = get_orig_name("bone_name", node.get_bone_name(child_bone))
p_path.push_back(orig_child_name)
var new_id = self.iterate_skeleton(node, p_path, child_bone, p_attachments_by_bone_name, fileId_transform, p_global_rest, p_pre_retarget_global_rest)
pop_back(p_path)
if new_id != 0:
self.all_name_map[fileId_go][orig_child_name] = new_id
for p_attachment in p_attachments_by_bone_name.get(node.get_bone_name(p_skel_bone), []):
var attachment_node: Node = p_attachment
for child in attachment_node.get_children():
if child is MeshInstance3D:
var renderer_fileId: int
if child.get_blend_shape_count() > 0: # if skin != null
renderer_fileId = register_component(child, p_path, "SkinnedMeshRenderer", fileId_go, p_skel_bone)
else:
register_component(child, p_path, "MeshFilter", fileId_go, p_skel_bone)
renderer_fileId = register_component(child, p_path, "MeshRenderer", fileId_go, p_skel_bone)
var mesh_fileId = process_mesh_instance(child)
metaobj.fileid_to_material_order_rev[renderer_fileId] = metaobj.fileid_to_material_order_rev.get(mesh_fileId, PackedInt32Array())
elif child is Camera3D:
register_component(child, p_path, "Camera", fileId_go, p_skel_bone)
elif child is Light3D:
register_component(child, p_path, "Light", fileId_go, p_skel_bone)
elif child is Skeleton3D:
var new_attachments_by_bone_name: Dictionary = {}.duplicate()
for possible_attach in child.get_children():
if possible_attach is BoneAttachment3D:
var bn = possible_attach.bone_name
if not new_attachments_by_bone_name.has(bn):
new_attachments_by_bone_name[bn] = [].duplicate()
new_attachments_by_bone_name[bn].append(possible_attach)
for child_child_bone in child.get_parentless_bones():
var orig_child_name: String = get_orig_name("bone_name", child.get_bone_name(child_child_bone))
p_path.push_back(orig_child_name)
var new_id = self.iterate_skeleton(child, p_path, child_child_bone, new_attachments_by_bone_name, fileId_transform, p_global_rest, p_pre_retarget_global_rest)
pop_back(p_path)
if new_id != 0:
self.all_name_map[fileId_go][orig_child_name] = new_id
else:
var orig_child_name: String = get_orig_name("nodes", child.name)
p_path.push_back(orig_child_name)
var new_id = self.iterate_node(child, p_path, false, fileId_transform, p_global_rest, p_pre_retarget_global_rest)
pop_back(p_path)
if new_id != 0:
self.all_name_map[fileId_go][orig_child_name] = new_id
var key = p_path[len(p_path) - 1] # node.name
# node.get_parent() == null or
if len(p_path) == 2 and str(p_path[1]) == "root":
key = preserve_hierarchy_orig_root_node_name
if key != "":
# Some transformed gltf files will have skinned meshes as direct root nodes in "scenes"
# We want to handle these at the same time we handle the root node.
for child in skinned_parent_to_node.get("", {}):
skinned_parent_to_node[preserve_hierarchy_orig_root_node_name].append(child)
skinned_parent_to_node.erase("")
var skinned_children = skinned_parent_to_node.get(key, [])
skinned_parent_to_node.erase(key)
for child in skinned_children:
metaobj.log_debug(0, "Skinned parent " + str(node.name) + ": " + str(child.name))
var orig_child_name: String = get_orig_bone_or_node(child.name)
var new_id: int = 0
if len(p_path) == 1:
# If not preserve_hierarchy and we determined we can fold root node, that means skinned parents is empty.
metaobj.log_fail(0, "Root node shouldn't have any skinned meshes when root is folded")
p_path.push_back(orig_child_name)
new_id = self.iterate_node(child, p_path, true, fileId_transform)
pop_back(p_path)
if new_id != 0:
self.all_name_map[fileId_go][orig_child_name] = new_id
return fileId_go
func iterate_node(node: Node, p_path: PackedStringArray, from_skinned_parent: bool, p_parent_transform_id: int, p_global_rest := Transform3D(), p_pre_retarget_global_rest := Transform3D()):
metaobj.log_debug(0, "Conventional iterate_node " + str(node.get_class()) + ", " + str(p_path) + ", " + str(node.name))
#for child in node.get_children():
# iterate_node(child, p_path, false)
var fileId_go: int = 0
var fileId_transform: int = 0
if len(p_path) != 1 or node.get_parent() != null:
if not (node is AnimationPlayer):
fileId_go = register_component(node, p_path, "Transform", 0, -1, p_parent_transform_id)
fileId_transform = all_name_map[fileId_go][4]
if node is AnimationPlayer:
#var parent_node: Node3D = node.get_parent()
#if scene.get_path_to(parent_node) == NodePath("."):
# parent_node = parent_node.get_child(0)
#node_name = str(parent_node.name)
register_component(node, p_path, "Animator", fileId_go)
process_animation_player(node)
elif node is MeshInstance3D:
if node.skin != null and not skinned_parent_to_node.is_empty() and not from_skinned_parent:
metaobj.log_debug(0, "Already recursed " + str(node.name))
return 0 # We already recursed into this skinned mesh.
var renderer_fileId: int
if from_skinned_parent or node.get_blend_shape_count() > 0: # has_obj_id("SkinnedMeshRenderer", node_name):
renderer_fileId = register_component(node, p_path, "SkinnedMeshRenderer", fileId_go)
else:
register_component(node, p_path, "MeshFilter", fileId_go)
renderer_fileId = register_component(node, p_path, "MeshRenderer", fileId_go)
var mesh_fileId = process_mesh_instance(node)
metaobj.fileid_to_material_order_rev[renderer_fileId] = metaobj.fileid_to_material_order_rev.get(mesh_fileId, PackedInt32Array())
elif node is Camera3D:
register_component(node, p_path, "Camera", fileId_go)
elif node is Light3D:
register_component(node, p_path, "Light", fileId_go)
var animplayer: AnimationPlayer = null
for child in node.get_children():
if child is AnimationPlayer:
animplayer = child
break
# Scene root may have the same node.name as another node, so we must ignore it!
if node is Node3D and node != scene:
# The only known case of non-Node3D is AnimationPlayer
if humanoid_original_transforms.has(node.name):
metaobj.log_warn(0, "Humanoid Node " + str(node.name) + ": " + str(humanoid_original_transforms.get(node.name).basis.get_rotation_quaternion()))
p_pre_retarget_global_rest *= Transform3D(humanoid_original_transforms.get(node.name).basis, p_pre_retarget_global_rest.basis.inverse() * p_global_rest.basis * node.transform.origin)
else:
p_pre_retarget_global_rest *= node.transform
# metaobj.log_debug(0, "Non-humanoid Bone " + str(node.name) + ": " + str(node.transform.basis.get_rotation_quaternion()))
p_global_rest *= node.transform
# metaobj.log_debug(0, "global rest " + str(node.name) + ": " + str(node.transform.basis.get_rotation_quaternion()))
if not p_global_rest.is_equal_approx(p_pre_retarget_global_rest):
# For the purpose of determining which coordinate are negative, treat 0 as positive
# Though Godot probably malfunctions in other ways if scale axes are 0, since it will lose the rotation.
var signs: Vector3 = (node.scale.sign() + Vector3(0.5,0.5,0.5)).sign()
if not signs.is_equal_approx(Vector3.ONE) and not signs.is_equal_approx(-Vector3.ONE):
transform_fileid_to_scale_signs[fileId_transform] = signs
if not p_global_rest.is_equal_approx(p_pre_retarget_global_rest):
# metaobj.log_debug(0, "node " + node.name + " rest " + str(p_global_rest) + " pre ret " + str(p_pre_retarget_global_rest))
transform_fileid_to_rotation_delta[fileId_transform] = p_global_rest.affine_inverse() * p_pre_retarget_global_rest
if len(p_path) == 1:
p_pre_retarget_global_rest *= root_rotation_delta
if node.get_child_count() >= 1 and node.get_child(0).name == "RootNode":
node = node.get_child(0)
for child in node.get_children():
if child is Skeleton3D:
var new_attachments_by_bone_name = {}.duplicate()
for possible_attach in child.get_children():
if possible_attach is BoneAttachment3D:
var bn = possible_attach.bone_name
if not new_attachments_by_bone_name.has(bn):
new_attachments_by_bone_name[bn] = [].duplicate()
new_attachments_by_bone_name[bn].append(possible_attach)
for child_child_bone in child.get_parentless_bones():
var orig_child_name: String = get_orig_name("bone_name", child.get_bone_name(child_child_bone))
if len(p_path) == 1 and node.get_parent() == null:
preserve_hierarchy_orig_root_node_name = orig_child_name
p_path.push_back("root")
else:
p_path.push_back(orig_child_name)
var new_id = self.iterate_skeleton(child, p_path, child_child_bone, new_attachments_by_bone_name, fileId_transform, p_global_rest, p_pre_retarget_global_rest)
pop_back(p_path)
if len(p_path) == 1 and node.get_parent() == null:
# HACK: If we are above the root node (due to preserve_hierarchy=false), pretend we are the child.
fileId_go = new_id
fileId_transform = all_name_map[fileId_go][4]
elif new_id != 0:
self.all_name_map[fileId_go][orig_child_name] = new_id
else:
if not (child is AnimationPlayer):
var child_name: String = child.name
if child is MeshInstance3D:
if is_obj and child.mesh != null:
#node_name = "default"
child_name = default_obj_mesh_name # Does this make sense?? For compatibility?
var orig_child_name: String = get_orig_name("nodes", child_name)
if len(p_path) == 1 and node.get_parent() == null:
preserve_hierarchy_orig_root_node_name = orig_child_name
p_path.push_back("root")
else:
p_path.push_back(orig_child_name)
var new_id = self.iterate_node(child, p_path, false, fileId_transform, p_global_rest, p_pre_retarget_global_rest)
pop_back(p_path)
if len(p_path) == 1 and node.get_parent() == null:
# HACK: If we are above the root node (due to preserve_hierarchy=false), pretend we are the child.
fileId_go = new_id
fileId_transform = all_name_map[fileId_go][4]
elif new_id != 0:
self.all_name_map[fileId_go][orig_child_name] = new_id
var key = p_path[len(p_path) - 1] # node.name
# node.get_parent() == null or
if len(p_path) == 2 and str(p_path[1]) == "root":
key = preserve_hierarchy_orig_root_node_name
if key != "":
# Some transformed gltf files will have skinned meshes as direct root nodes in "scenes"
# We want to handle these at the same time we handle the root node.
for child in skinned_parent_to_node.get("", {}):
skinned_parent_to_node[preserve_hierarchy_orig_root_node_name].append(child)
skinned_parent_to_node.erase("")
if node is Node3D:
var skinned_children = skinned_parent_to_node.get(key, [])
skinned_parent_to_node.erase(key)
for child in skinned_children:
metaobj.log_debug(0, "Skinned parent from non-bone " + str(node.name) + ": " + str(child.name))
var orig_child_name: String = get_orig_bone_or_node(child.name)
var new_id: int = 0
if len(p_path) == 1:
# If not preserve_hierarchy and we determined we can fold root node, that means skinned parents is empty.
metaobj.log_fail(0, "Root node shouldn't have any skinned meshes when root is folded")
p_path.push_back(orig_child_name)
new_id = self.iterate_node(child, p_path, true, fileId_transform)
pop_back(p_path)
if new_id != 0:
self.all_name_map[fileId_go][orig_child_name] = new_id
if animplayer != null:
self.iterate_node(animplayer, p_path, false, fileId_transform)
return fileId_go
func process_animation_player(node: AnimationPlayer):
var i = 0
var anim_lib = node.get_animation_library(node.get_animation_library_list()[0])
var anim_list = anim_lib.get_animation_list()
var anim_count: int = 0
for godot_anim_name in anim_list:
if godot_anim_name == "RESET" or godot_anim_name == "_T-Pose_":
continue
anim_count += 1
for godot_anim_name in anim_list:
if godot_anim_name == "RESET" or godot_anim_name == "_T-Pose_":
continue
var anim: Animation = anim_lib.get_animation(godot_anim_name)
var anim_name: String = get_orig_name("animations", godot_anim_name)
if saved_animations_by_name.has(godot_anim_name):
anim = saved_animations_by_name.get(godot_anim_name)
if anim != null:
anim_lib.remove_animation(godot_anim_name)
anim_lib.add_animation(godot_anim_name, anim)
continue
saved_animations_by_name[godot_anim_name] = null
metaobj.log_debug(0, "Process ANIM " + str(anim_name) + " (" + str(godot_anim_name) + ")")
#if not has_obj_id("AnimationClip", get_orig_name("animations", anim_name))
#if fileId == 0:
# metaobj.log_fail(0, "Missing fileId for Animation " + str(anim_name))
#else:
var fileId = get_obj_id("AnimationClip", PackedStringArray(), anim_name)
if fileId != 0:
if external_objects_by_id.has(fileId):
anim = metaobj.get_godot_resource(external_objects_by_id.get(fileId))
else:
if anim != null:
var respath: String
if source_file_path.get_basename() == godot_anim_name or anim_count <= 1:
if asset_database.use_text_resources:
respath = get_resource_path("animation", ".tres")
else:
respath = get_resource_path("", "anim")
else:
respath = get_resource_path(godot_anim_name, ".tres" if asset_database.use_text_resources else ".anim")
unidot_utils.save_resource(anim, respath)
anim = load(respath)
if anim != null:
anim_lib.remove_animation(godot_anim_name)
anim_lib.add_animation(godot_anim_name, anim)
saved_animations_by_name[godot_anim_name] = anim
metaobj.imported_animation_paths[godot_anim_name] = str(anim.resource_path)
self.register_resource(anim, anim_name, "AnimationClip", fileId)
# metaobj.log_debug(0, "AnimationPlayer " + str(scene.get_path_to(node)) + " / Anim " + str(i) + " anim_name: " + anim_name + " resource_name: " + str(anim.resource_name))
i += 1
func process_mesh_instance(node: MeshInstance3D) -> int:
metaobj.log_debug(0, "Process mesh instance: " + str(node.name))
if node.skin == null and node.skeleton != NodePath():
metaobj.log_fail(0, "A Skeleton exists for MeshRenderer " + str(node.name))
if node.skin != null and node.skeleton == NodePath():
metaobj.log_fail(0, "No Skeleton exists for SkinnedMeshRenderer " + str(node.name))
var mesh: Mesh = node.mesh
if mesh == null:
return 0
# FIXME: mesh_name is broken on master branch, maybe 3.2 as well.
var godot_mesh_name: String = str(mesh.resource_name)
if godot_mesh_name.begins_with("Root Scene_"):
godot_mesh_name = godot_mesh_name.substr(11)
if is_obj:
godot_mesh_name = default_obj_mesh_name
var mesh_name: String = get_orig_name("meshes", godot_mesh_name)
var mesh_fileId: int = 0
if saved_meshes_by_name.has(godot_mesh_name):
mesh = saved_meshes_by_name.get(godot_mesh_name)
mesh_fileId = saved_mesh_fileids_by_name[godot_mesh_name]
if mesh != null:
node.mesh = mesh
if node.skin != null:
node.skin = saved_skins_by_name.get(godot_mesh_name)
else:
saved_meshes_by_name[godot_mesh_name] = null
for i in range(mesh.get_surface_count()):
var mat: Material = mesh.surface_get_material(i)
if mat == null:
continue
var godot_mat_name: String = mat.resource_name
var mat_name: String = get_orig_name("materials", godot_mat_name)
if mat_name == "DefaultMaterial" or mat_name == "":
mat_name = "No Name"
if is_obj:
mat_name = default_obj_mesh_name + "Mat" # obj files seem to use this rule
if saved_materials_by_name.has(godot_mat_name):
mat = saved_materials_by_name.get(godot_mat_name)
if mat != null:
mesh.surface_set_material(i, mat)
continue
saved_materials_by_name[godot_mat_name] = null
var fileId = get_obj_id("Material", PackedStringArray(), mat_name)
metaobj.log_debug(fileId, "Material " + str(mat_name) + " (" + str(godot_mat_name) + ") import " + str(importMaterials) + " legacy " + str(extractLegacyMaterials) + " fileId " + str(fileId))
if importMaterials and not extractLegacyMaterials and fileId == 0 and not use_new_names:
metaobj.log_fail(0, "Missing fileId for Material " + str(mat_name))
else:
var new_mat: Material = null
if not importMaterials:
godot_mat_name = "default"
mat = default_material
elif external_objects_by_id.has(fileId):
new_mat = metaobj.get_godot_resource(external_objects_by_id.get(fileId))
elif external_objects_by_type_name.get("Material", {}).has(mat_name):
new_mat = metaobj.get_godot_resource(external_objects_by_type_name.get("Material").get(mat_name))
if new_mat != null:
mat = new_mat
metaobj.log_debug(fileId, "External material object " + str(fileId) + "/" + str(mat_name) + " " + str(new_mat.resource_name) + "@" + str(new_mat.resource_path))
elif importMaterials and extractLegacyMaterials:
# Exmaple [S*S]kitsune_men.material -> [S_S]kitsune_men.material
var legacy_material_name: String = sanitize_filename(godot_mat_name, "_") # It's unclear what should happen if this contains a "." character.
if legacy_material_name.is_empty():
legacy_material_name = "No Name"
if legacy_material_name_setting == 0:
legacy_material_name = sanitize_filename(material_to_texture_name.get(godot_mat_name, godot_mat_name), "_")
if legacy_material_name_setting == 2:
legacy_material_name = sanitize_filename(source_file_path.get_file().get_basename() + "-" + godot_mat_name, "_")
metaobj.log_debug(fileId, "Extract legacy material " + mat_name + ": " + get_materials_path(legacy_material_name))
var d = DirAccess.open("res://")
mat = null
if materialSearch == 0:
# only current dir
legacy_material_name = get_materials_path(legacy_material_name, ".material")
if d.file_exists(legacy_material_name):
mat = load(legacy_material_name)
else:
legacy_material_name = get_materials_path(legacy_material_name, ".mat.tres")
mat = load(legacy_material_name)
elif materialSearch >= 1:
# same dir and parents
var mat_paths: Array = get_parent_materials_paths(legacy_material_name)
for mp in mat_paths:
if d.file_exists(mp):
legacy_material_name = mp
mat = load(mp)
if mat != null:
break
if mat == null and materialSearch >= 2:
# and material in the whole project with this name!!
for pathname in asset_database.path_to_meta:
if pathname.get_file() == legacy_material_name + ".material" or pathname.get_file() == godot_mat_name + ".mat.tres" or pathname.get_file() == godot_mat_name + ".mat.res":
legacy_material_name = pathname
mat = load(pathname)
break
if mat != null:
new_mat = mat
else:
metaobj.log_fail(fileId, "Material " + str(legacy_material_name) + " was not found. using default")
if new_mat == null and mat != null:
var respath: String
if godot_mat_name.is_empty():
respath = get_resource_path("DefaultMaterial", ".material")
else:
respath = get_resource_path(godot_mat_name, ".material")
metaobj.log_debug(fileId, "Before save " + str(mat_name) + " " + str(mat.resource_name) + "@" + str(respath) + " from " + str(mat.resource_path))
if mat.albedo_texture != null:
metaobj.log_debug(fileId, " albedo = " + str(mat.albedo_texture.resource_name) + " / " + str(mat.albedo_texture.resource_path))
if mat.normal_texture != null:
metaobj.log_debug(fileId, " normal = " + str(mat.normal_texture.resource_name) + " / " + str(mat.normal_texture.resource_path))
for prop in mat.get_property_list():
var prop_name: String = prop["name"]
if (prop["usage"] & PROPERTY_USAGE_STORAGE) != 0 and prop["hint_string"].contains("Texture"):
var tex: Texture2D = mat.get(prop_name) as Texture2D
# metaobj.log_debug(fileId, "Tex " + str(tex) + " for prop " + str(prop_name))
if tex != null and tex.has_meta(&"src_tex"):
# metaobj.log_debug(fileId, "tmp tex " + str(tex.resource_path) + " meta list " + str(tex.get_meta_list()) + " " + str(tex.get_meta(&"src_tex")))
var src_tex: Texture2D = tex.get_meta(&"src_tex") as Texture2D
if src_tex != null:
metaobj.log_debug(fileId, " " + str(prop_name) + " = " + str(tex.resource_path) + " => " + str(src_tex.resource_path))
mat.set(prop_name, src_tex)
unidot_utils.save_resource(mat, respath)
mat = load(respath)
metaobj.log_debug(fileId, "Save-and-load material object " + str(mat_name) + " " + str(mat.resource_name) + "@" + str(mat.resource_path))
if mat.albedo_texture != null:
metaobj.log_debug(fileId, " albedo = " + str(mat.albedo_texture.resource_name) + " / " + str(mat.albedo_texture.resource_path))
if mat.normal_texture != null:
metaobj.log_debug(fileId, " normal = " + str(mat.normal_texture.resource_name) + " / " + str(mat.normal_texture.resource_path))
metaobj.log_debug(fileId, "Mat for " + str(i) + " is " + str(mat))
if mat != null:
mesh.surface_set_material(i, mat)
saved_materials_by_name[godot_mat_name] = mat
if mat != default_material:
if mat.resource_path.is_empty():
metaobj.log_fail(fileId, "Unable to asssign material path for " + str(godot_mat_name) + " due to empty resource_path")
metaobj.imported_material_paths[godot_mat_name] = str(mat.resource_path)
register_resource(mat, mat_name, "Material", fileId)
# metaobj.log_debug(0, "MeshInstance " + str(scene.get_path_to(node)) + " / Mesh " + str(mesh.resource_name if mesh != null else "NULL")+ " Material " + str(i) + " name " + str(mat.resource_name if mat != null else "NULL"))
# metaobj.log_debug(0, "Looking up " + str(mesh_name) + " in " + str(objtype_to_name_to_id.get("Mesh", {})))
var fileId: int = get_obj_id("Mesh", PackedStringArray(), mesh_name)
if fileId == 0:
metaobj.log_fail(0, "Missing fileId for Mesh " + str(mesh_name) + " (" + str(godot_mesh_name) + ")")
else:
metaobj.fileid_to_material_order_rev[fileId] = material_order_by_mesh_rev.get(godot_mesh_name, PackedInt32Array())
var skin: Skin = node.skin
if external_objects_by_id.has(fileId):
mesh = metaobj.get_godot_resource(external_objects_by_id.get(fileId))
if skin != null:
skin = metaobj.get_godot_resource(external_objects_by_id.get(-fileId))
else:
if mesh != null:
var respath: String = get_resource_path(godot_mesh_name, ".mesh")
unidot_utils.save_resource(mesh, respath)
mesh = load(respath)
if skin != null:
skin = skin.duplicate()
var skel: Skeleton3D = node.get_parent() as Skeleton3D
if skel != null and skel.has_meta("humanoid_rotation_delta"):
skin.set_meta("humanoid_rotation_delta", skel.get_meta("humanoid_rotation_delta").duplicate())
var respath: String = get_resource_path(godot_mesh_name, ".skin.tres")
unidot_utils.save_resource(skin, respath)
skin = load(respath)
if mesh != null:
node.mesh = mesh
saved_meshes_by_name[godot_mesh_name] = mesh
mesh_fileId = fileId
saved_mesh_fileids_by_name[godot_mesh_name] = mesh_fileId
metaobj.imported_mesh_paths[godot_mesh_name] = str(mesh.resource_path)
metaobj.imported_mesh_paths["Root Scene_" + godot_mesh_name] = str(mesh.resource_path)
register_resource(mesh, mesh_name, "Mesh", fileId, skin)
if skin != null:
node.skin = skin
saved_skins_by_name[godot_mesh_name] = skin
is_obj = false
return mesh_fileId
func _post_import(p_scene: Node) -> Object:
var source_file_path: String = get_source_file()
var godot_import_config: ConfigFile = ConfigFile.new()
if godot_import_config.load(source_file_path + ".import") != OK:
push_error("Running _post_import script for " + str(source_file_path) + " but cannot load .import")
var rel_path = source_file_path.replace("res://", "")
print("Unidot Post-import " + source_file_path)
var asset_database: asset_database_class = asset_database_class.new().get_singleton()
default_material = asset_database.default_material_reference
var metaobj: asset_meta_class = asset_database.get_meta_at_path(rel_path)
var f: FileAccess
if metaobj == null:
push_warning("Asset database missing entry for " + str(source_file_path))
assert(not asset_database.in_package_import)
f = FileAccess.open(source_file_path + ".meta", FileAccess.READ)
if f:
metaobj = asset_database.parse_meta(f, rel_path)
f = null
else:
metaobj = asset_database.create_dummy_meta(rel_path)
asset_database.insert_meta(metaobj)
metaobj.initialize(asset_database)
metaobj.log_debug(0, str(metaobj.importer))
# For now, we assume all data is available in the asset database resource.
# var metafile = source_file_path + ".meta"
var ps: ParseState = ParseState.new()
ps.object_adapter = object_adapter
ps.scene = p_scene
ps.source_file_path = source_file_path
ps.extracted_assets_basename = get_extracted_assets_dir(source_file_path).path_join(source_file_path.get_basename().get_file())
ps.metaobj = metaobj
ps.asset_database = asset_database
ps.material_order_by_mesh_rev = metaobj.internal_data.get("material_order_by_mesh_rev", {})
ps.material_to_texture_name = metaobj.internal_data.get("material_to_texture_name", {})
ps.godot_sanitized_to_orig_remap = metaobj.internal_data.get("godot_sanitized_to_orig_remap", {})
ps.humanoid_original_transforms = metaobj.internal_data.get("humanoid_original_transforms", {})
if metaobj.is_force_humanoid() or metaobj.importer.keys.get("animationType", 2) == 3:
var bone_map: BoneMap = metaobj.importer.generate_bone_map_from_human()
var bone_map_dict: Dictionary = {}
for prop in bone_map.get_property_list():
if prop["name"].begins_with("bone_map/"):
var prof_name: String = prop["name"].trim_prefix("bone_map/")
bone_map_dict[prof_name] = bone_map.get_skeleton_bone_name(prof_name)
ps.bone_map_dict = bone_map_dict
ps.extractLegacyMaterials = metaobj.importer.keys.get("materials", {}).get("materialLocation", 0) == 0
ps.importMaterials = (metaobj.importer.keys.get("materials", {}).get("materialImportMode", metaobj.importer.keys.get("materials", {}).get("importMaterials", 1)) != 0)
ps.materialSearch = metaobj.importer.keys.get("materials", {}).get("materialSearch", 1)
ps.legacy_material_name_setting = metaobj.importer.keys.get("materials", {}).get("materialName", 0)
ps.preserve_hierarchy = false
if typeof(metaobj.importer.get("preserveHierarchy")) != TYPE_NIL:
ps.preserve_hierarchy = metaobj.importer.preserveHierarchy
ps.default_material = default_material
ps.is_obj = source_file_path.ends_with(".obj")
ps.is_dae = source_file_path.ends_with(".dae")
metaobj.log_debug(0, "Path " + str(source_file_path))
var external_objects: Dictionary = metaobj.importer.get_external_objects()
ps.external_objects_by_type_name = external_objects
var skinned_name_to_node: Dictionary = ps.build_skinned_name_to_node_map(ps.scene, {}.duplicate())
var skinned_parents: Dictionary = metaobj.internal_data.get("skinned_parents", {})
var skinned_parent_to_node: Dictionary = {}.duplicate()
metaobj.log_debug(0, "Now skinning " + str(skinned_name_to_node) + " from parents " + str(skinned_parents))
for par in skinned_parents:
var node_list = []
for skinned_name in skinned_parents[par]:
if skinned_name_to_node.has(skinned_name):
metaobj.log_debug(0, "Do skinned " + str(skinned_name) + " to " + str(skinned_name_to_node[skinned_name]))
node_list.append(skinned_name_to_node[skinned_name])
else:
metaobj.log_debug(0, "Missing skinned " + str(skinned_name) + " parent " + str(par))
skinned_parent_to_node[par] = node_list
ps.skinned_parent_to_node = skinned_parent_to_node
ps.default_obj_mesh_name = "default"
if ps.is_obj:
var objf: FileAccess = FileAccess.open(source_file_path, FileAccess.READ)
if objf:
var textstr = objf.get_as_text()
objf = null
# Find the name of the first mesh (first g before first f).
# Note: Godot does not support splitting .obj into multiple meshes
# So we will only use the name of the first mesh for now.
var fidx = textstr.find("\nf ")
var gidx = textstr.rfind("\ng ", fidx)
if gidx == -1:
if textstr.begins_with("g "):
gidx = 2
else:
gidx += 3
var gendidx = textstr.find("\n", gidx)
if gendidx != -1 and gidx != -1:
ps.default_obj_mesh_name = textstr.substr(gidx, gendidx - gidx).strip_edges()
if ps.default_obj_mesh_name.is_empty():
ps.default_obj_mesh_name = "default"
var internalIdMapping: Array = []
ps.use_new_names = false
if metaobj.importer != null and typeof(metaobj.importer.keys.get("internalIDToNameTable")) != TYPE_NIL:
internalIdMapping = metaobj.importer.get("internalIDToNameTable")
ps.use_new_names = true # FIXME: Should this only be if empty?
metaobj.log_debug(0, "Setting new names to true")
if metaobj.importer != null and typeof(metaobj.importer.keys.get("fileIDToRecycleName")) != TYPE_NIL:
var recycles: Dictionary = metaobj.importer.fileIDToRecycleName
for fileIdStr in recycles:
var obj_name: String = recycles[fileIdStr]
var fileId: int = int(str(fileIdStr).to_int())
var utype: int = fileId / 100000
internalIdMapping.append({"first": {utype: fileId}, "second": obj_name})
# fileIDToRecycleName:
# 100000: //RootNode
# 100002: Box023
# internalIDToNameTable:
# - first:
# 1: 100000
# second: //RootNode
# - first:
# 1: 100002
# second: Armature
var used_names_by_type: Dictionary = {}.duplicate()
# defaults:
metaobj.prefab_main_gameobject_id = 100000
metaobj.prefab_main_transform_id = 400000
for id_mapping in internalIdMapping:
var og_obj_name: String = id_mapping.get("second")
for utypestr in id_mapping.get("first"):
var fIdMaybeString: Variant = id_mapping.get("first").get(utypestr)
# Casting to int became complicated... This could be string or int depending on yaml parser.
if typeof(fIdMaybeString) == TYPE_STRING:
fIdMaybeString = fIdMaybeString.to_int()
var fileId: int = int(fIdMaybeString)
var utype: int
if typeof(utypestr) == TYPE_STRING:
utype = int(utypestr.to_int())
else:
utype = int(utypestr)
var obj_name: String = og_obj_name
var type: String = str(object_adapter.to_classname(fileId / 100000))
if obj_name.begins_with("//"):
# Not sure why, but the root object always begin with //RootNode
# Maybe it indicates that the node will be hidden???
obj_name = ""
elif ps.is_obj:
obj_name = ps.default_obj_mesh_name # Technically wrong in version 2019+. Should read the last "g objName" line before "f"
if not ps.objtype_to_name_to_id.has(type):
ps.objtype_to_name_to_id[type] = {}.duplicate()
used_names_by_type[type] = {}.duplicate()
var orig_obj_name: String = obj_name
var next_num: int = used_names_by_type.get(type).get(orig_obj_name, 1)
while used_names_by_type[type].has(obj_name):
obj_name = "%s %d" % [orig_obj_name, next_num] # No space is deliberate, from sanitization rules.
next_num += 1
used_names_by_type[type][orig_obj_name] = next_num
used_names_by_type[type][obj_name] = 1
#metaobj.log_debug(0, "Adding recycle id " + str(fileId) + " and type " + str(type) + " and utype " + str(fileId / 100000) + ": " + str(obj_name))
ps.objtype_to_name_to_id[type][obj_name] = fileId
ps.used_ids[fileId] = true
ps.objtype_to_next_id[type] = utype * 100000
if external_objects.get(type, {}).has(og_obj_name):
ps.external_objects_by_id[fileId] = external_objects.get(type).get(og_obj_name)
var animation_clips: Array[Dictionary] = metaobj.importer.get_animation_clips()
for key in animation_clips:
ps.animation_to_take_name[key["name"]] = key["take_name"]
#metaobj.log_debug(0, "Ext objs by id: "+ str(ps.external_objects_by_id))
#metaobj.log_debug(0, "objtype name by id: "+ str(ps.objtype_to_name_to_id))
ps.toplevel_node = p_scene
p_scene.name = source_file_path.get_file().get_basename()
if ps.is_dae:
# Basically, Godot implements up_axis by transforming mesh data. However, assets expect only the root node
# to be transformed, so we rewrote the up_axis in the .dae in BaseModelHandler, and here we re-apply
# the up-axis to the root node. This workflow will break if user wishes to change this in Blender after import.
var up_axis: String = metaobj.internal_data.get("up_axis", "Y_UP")
# * ps.toplevel_node.transform)
if up_axis.to_upper() == "X_UP":
ps.root_rotation_delta = Transform3D(Basis.from_euler(Vector3(0, 0, PI / -2.0)), Vector3.ZERO)
if up_axis.to_upper() == "Z_UP":
ps.root_rotation_delta = Transform3D(Basis.from_euler(Vector3(PI / -2.0, 0, 0)), Vector3.ZERO)
var toplevel_path: PackedStringArray = PackedStringArray().duplicate()
toplevel_path.push_back("//RootNode")
var did_fold_root_transforms: bool = false
var tmp_old_toplevel: Node3D = ps.toplevel_node
if not ps.preserve_hierarchy and skinned_parents.get("", {}).is_empty():
# I determined this is done before hash calculation in version 2019+
# I did not test this on older versions, so I'm keeping the old order for those.
did_fold_root_transforms = ps.fold_root_transforms_into_only_child()
if not did_fold_root_transforms:
toplevel_path.push_back("root")
var tmp_root_transform_fileid = 0
var root_go_id = ps.iterate_node(tmp_old_toplevel, toplevel_path, false, tmp_root_transform_fileid)
if not did_fold_root_transforms:
ps.pop_back(toplevel_path)
var prefab_instance = ps.get_obj_id("PrefabInstance", toplevel_path, "")
# GameObject references always point to the toplevel node:
metaobj.prefab_main_gameobject_id = root_go_id
metaobj.prefab_main_transform_id = ps.all_name_map[root_go_id][4]
if !ps.root_rotation_delta.is_equal_approx(Transform3D.IDENTITY):
metaobj.transform_fileid_to_rotation_delta[metaobj.prefab_main_transform_id] = ps.root_rotation_delta
ps.fileid_to_nodepath[metaobj.prefab_main_gameobject_id] = NodePath(".") # Prefab name always toplevel.
ps.fileid_to_nodepath[metaobj.prefab_main_transform_id] = NodePath(".")
ps.fileid_to_skeleton_bone.erase(metaobj.prefab_main_gameobject_id)
ps.fileid_to_skeleton_bone.erase(metaobj.prefab_main_transform_id)
metaobj.type_to_fileids = ps.type_to_fileids
metaobj.fileid_to_nodepath = ps.fileid_to_nodepath
metaobj.fileid_to_skeleton_bone = ps.fileid_to_skeleton_bone
metaobj.fileid_to_utype = ps.fileid_to_utype
metaobj.fileid_to_gameobject_fileid = ps.fileid_to_gameobject_fileid
metaobj.transform_fileid_to_rotation_delta = ps.transform_fileid_to_rotation_delta
metaobj.transform_fileid_to_parent_fileid = ps.transform_fileid_to_parent_fileid
metaobj.gameobject_name_to_fileid_and_children = ps.all_name_map
metaobj.prefab_gameobject_name_to_fileid_and_children = ps.all_name_map
metaobj.humanoid_skeleton_hip_position = ps.humanoid_skeleton_hip_position
metaobj.transform_fileid_to_scale_signs = ps.transform_fileid_to_scale_signs