This repository was archived by the owner on Apr 7, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy path__init__.py
More file actions
855 lines (737 loc) · 32.3 KB
/
__init__.py
File metadata and controls
855 lines (737 loc) · 32.3 KB
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
bl_info = {
"name": "RE Asset Library",
"author": "NSA Cloud",
"version": (0, 25),
"blender": (4, 3, 0),
"location": "Asset Browser > RE Assets",
"description": "Quickly search through and import RE Engine meshes.",
"wiki_url": "https://github.com/NSACloud/RE-Asset-Library",
"tracker_url": "",
"category": "Import-Export"}
import bpy
from . import addon_updater_ops
from bpy.app.handlers import persistent
import os
import json
import queue
import shutil
import subprocess
import glob
from .modules.gen_functions import formatByteSize,resolvePath,wildCardFileSearch,openFolder
from .modules.blender_utils import showErrorMessageBox
from bpy_extras.io_utils import ImportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty, CollectionProperty,PointerProperty
from bpy.types import Operator,Panel,AddonPreferences
from .modules.pak.re_pak_operators import (
WM_OT_PromptSetExtractInfo,
WM_OT_SetExtractInfo,
WM_OT_ExtractGameFiles,
WM_OT_OpenExtractFolder,
WM_OT_ReloadPakCache,
WM_OT_CreatePakPatch,
WM_OT_UnpackModPak,
PAK_FH_drag_import,
)
from .modules.pak.re_pak_utils import (
extractFilesFromPakCache,
)
from .modules.asset.re_asset_utils import (
buildNativesPathFromObj,
)
from .modules.asset.blender_re_asset import (
importREMeshAsset,
importREChainAsset,
importREChain2Asset,
importREFBXSkelAsset,
)
from .modules.asset.re_asset_operators import (
getGameNameFromAssetBrowser,
getAssetLibrary,
unzipLibrary,
loadGameInfo,
downloadREAssetLibDirectory,
downloadFileContent,
getFileCRC,
download_file_from_google_drive,
getChunkPathList,
WM_OT_RenderREAssets,
WM_OT_FetchREAssetThumbnails,
WM_OT_ImportREAssetLibraryFromCatalog,
WM_OT_SaveREAssetLibraryToCatalog,
WM_OT_ExportCatalogDiff,
WM_OT_ImportCatalogDiff,
WM_OT_PackageREAssetLibrary,
WM_OT_InitializeREAssetLibrary,
WM_OT_CheckForREAssetLibraryUpdate,
WM_OT_OpenLibraryFolder,
WM_OT_GenerateMaterialCompendium,
WM_OT_GenerateRSZCRCCompendium,
REToolListFileToREAssetCatalogAndGameInfo,
)
from .modules.mdf.re_mdf_updater_operators import (
WM_OT_BatchMDFUpdater,
WM_OT_BlenderMDFUpdater,
)
from .modules.rszmini.re_rsz_updater_operators import (
WM_OT_BatchRSZUpdater,
)
from .modules.asset.re_asset_propertyGroups import (
REAssetWhiteListEntryPropertyGroup,
ASSET_UL_FileTypeWhiteList,
REAssetLibEntryPropertyGroup,
ASSET_UL_REAssetLibList,
)
from .modules.asset.ui_re_asset_panels import (
OBJECT_PT_REAssetLibraryPanel,
)
from .modules.pak.re_pak_propertyGroups import (
ToggleStringPropertyGroup,
ASSET_UL_StringCheckList,
)
from .modules.pak.ui_re_pak_panels import (
OBJECT_PT_ExtractGameFilesPanel,
)
class LIST_OT_NewWhiteListItem(Operator):
bl_idname = "re_asset.new_whitelist_item"
bl_label = "Add File Type"
bl_description = "Add file type to whitelist.\nWhen creating a new asset library, choose which file types will be imported into the library.\nNOTE: Allowing too many file types may cause the asset browser to slow down."
bl_options ={"INTERNAL"}
def execute(self, context):
bpy.context.preferences.addons[__name__].preferences.fileTypeWhiteList_items.add()
return{'FINISHED'}
class LIST_OT_DeleteWhiteListItem(Operator):
bl_idname = "re_asset.delete_whitelist_item"
bl_label = "Remove File Type"
bl_options ={"INTERNAL"}
bl_description = "Remove file type from the whitelist"
def execute(self, context):
whiteList = bpy.context.preferences.addons[__name__].preferences.fileTypeWhiteList_items
index = bpy.context.preferences.addons[__name__].preferences.fileTypeWhiteList_index
whiteList.remove(index)
bpy.context.preferences.addons[__name__].preferences.fileTypeWhiteList_index = min(max(0, index - 1), len(whiteList) - 1)
return{'FINISHED'}
class LIST_OT_ResetWhiteListItems(Operator):
bl_idname = "re_asset.reset_whitelist_items"
bl_label = "Reset to Default"
bl_options ={"INTERNAL"}
bl_description = "Resets the filetype whitelist to it's default values."
def execute(self, context):
whiteList = bpy.context.preferences.addons[__name__].preferences.fileTypeWhiteList_items
whiteList.clear()
item = whiteList.add()
item.fileType = "mesh"
item = whiteList.add()
item.fileType = "chain"
item = whiteList.add()
item.fileType = "chain2"
item = whiteList.add()
item.fileType = "fbxskel"
return{'FINISHED'}
class REAssetPreferences(AddonPreferences):
bl_idname = __name__
# addon updater preferences
assetLibraryPath: StringProperty(
name="Asset Library Path",
subtype='DIR_PATH',
description = "Location to store downloaded/created RE Asset libraries",
default = os.path.join(os.path.dirname(os.path.realpath(__file__)),"REAssetLibrary")
)
showMeshImportOptions : BoolProperty(
name = "Show Mesh Import Options",
description = "When dragging an RE Asset onto the 3D View, prompt with import options",
default = True)
placeAtCursor : BoolProperty(
name = "Place At Cursor",
description = "When dragging an RE Asset, it will be imported at the location that it was dragged to.\nIf this is disabled, it will be imported at the world origin.\nNote that if you are creating mesh mods, do not check this option. Having objects not imported at the world origin may cause issues when exporting",
default = False)
instanceDuplicates : BoolProperty(
name = "Instance Duplicates",
description = "If a mesh is imported more than once, create an instance of previously imported mesh.\nNOTE: The Create Collections import option must be enabled",
default = True)
forceExtract : BoolProperty(
name = "Force Extract Files",
description = "When dragging an asset from the browser, force files to be extracted from the game files.\nUse this option when there's a game update and you need the latest version of a file.\nAlso enable this if you're getting missing/red textures when importing models.\nThis makes importing files slower but will ensure that nothing is missing or outdated",
default = True)
fileTypeWhiteList_items: bpy.props.CollectionProperty(type=REAssetWhiteListEntryPropertyGroup)
fileTypeWhiteList_index: bpy.props.IntProperty(name="")
auto_check_update: bpy.props.BoolProperty(
name = "Auto-check for Update",
description = "If enabled, auto-check for updates using an interval",
default = False,
)
updater_interval_months: bpy.props.IntProperty(
name='Months',
description = "Number of months between checking for updates",
default=0,
min=0
)
updater_interval_days: bpy.props.IntProperty(
name='Days',
description = "Number of days between checking for updates",
default=7,
min=0,
)
updater_interval_hours: bpy.props.IntProperty(
name='Hours',
description = "Number of hours between checking for updates",
default=0,
min=0,
max=23
)
updater_interval_minutes: bpy.props.IntProperty(
name='Minutes',
description = "Number of minutes between checking for updates",
default=0,
min=0,
max=59
)
def draw(self, context):
layout = self.layout
op = self.layout.operator(
'wm.url_open',
text='Donate on Ko-fi',
icon='FUND'
)
op.url = 'https://ko-fi.com/nsacloud'
layout.label(text="RE Asset Libraries")
layout.prop(self,"assetLibraryPath")
layout.operator("re_asset.open_re_asset_library_folder",icon = "FILE_FOLDER")
layout.row()
layout.operator("re_asset.download_re_asset_library",icon = "INTERNET")
layout.operator("re_asset.detect_re_asset_library",icon = "FILE_REFRESH")
layout.operator("re_asset.create_re_asset_library",icon = "NEWFOLDER")
layout.row()
layout.label(text="New Asset Library File Type Whitelist")
layout.template_list("ASSET_UL_FileTypeWhiteList", "", self, "fileTypeWhiteList_items", self, "fileTypeWhiteList_index",rows = 4)
row = layout.row()
col = row.column()
col.operator("re_asset.new_whitelist_item")
col = row.column()
col.operator("re_asset.delete_whitelist_item")
layout.operator("re_asset.reset_whitelist_items")
layout.label(text="Import Options")
layout.prop(self,"showMeshImportOptions")
layout.prop(self,"placeAtCursor")
#layout.prop(self,"instanceDuplicates")
layout.prop(self,"forceExtract")
addon_updater_ops.update_settings_ui(self,context)
class ImportREAssetLib(bpy.types.Operator, ImportHelper):
'''Import RE Engine Asset Library (.reassetlib) File'''
bl_idname = "re_asset.importlibrary"
bl_label = "Import RE Asset Library (.reassetlib)"
bl_options = {'PRESET', "REGISTER", "UNDO"}
filename_ext = ".reassetlib"
filter_glob: StringProperty(default="*.reassetlib", options = {"HIDDEN"})
currentBlendPath: bpy.props.StringProperty(default="", options = {"HIDDEN"})
def execute(self, context):
if os.path.isfile(self.filepath):
assetLibraryDir = bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.assetLibraryPath)
gameName = unzipLibrary(assetLibraryDir, self.filepath)
if gameName != None:
print(f"Attempting to install {gameName} library...")
newLibraryDir = os.path.join(assetLibraryDir,gameName)
os.makedirs(newLibraryDir,exist_ok=True)
outputCatalogPath = os.path.join(newLibraryDir,f"REAssetCatalog_{gameName}.tsv")
outputGameInfoPath = os.path.join(newLibraryDir,f"GameInfo_{gameName}.json")
addonDir = os.path.split(__file__)[0]
sourceBlendPath = os.path.join(addonDir,"Resources","Blend","libraryBase.blend")
outputBlendPath = os.path.join(newLibraryDir,f"REAssetLibrary_{gameName}.blend")
scriptPath = os.path.join(addonDir,"Resources","Scripts","initializeLibrary.py")
if not os.path.isfile(outputBlendPath):
shutil.copy(sourceBlendPath,outputBlendPath)
if os.path.isfile(outputCatalogPath) and os.path.isfile(outputGameInfoPath) and os.path.isfile(outputBlendPath) and os.path.isfile(scriptPath):
if outputBlendPath == self.currentBlendPath:
bpy.ops.re_asset.initialize_library()
else:
with subprocess.Popen([bpy.app.binary_path, outputBlendPath, "--python", scriptPath]):
pass#Wait for install to finish
self.report({"INFO"},f"Installed {gameName} library.")
else:
self.report({"ERROR"},"Missing files, cannot create library.")
return {'CANCELLED'}
#Update asset library list
bpy.ops.re_asset.detect_re_asset_library(silent = True)
return {"FINISHED"}
self.report({"INFO"},"Failed to import RE Asset Library. See Window > Toggle System Console for details")
return {"CANCELLED"}
def getAssetDirectoryItems(self,context):
entryList = []
for index,entry in enumerate(self.assetLibList_items):
entryList.append((str(index),entry.displayName,""))
return entryList
class WM_OT_DownloadREAssetLibrary(Operator):
bl_label = "Download RE Asset Libraries"
bl_description = "Download RE Asset Libaries"
bl_idname = "re_asset.download_re_asset_library"
bl_options = {'INTERNAL'}
libraryListEntry: EnumProperty(
name="RE Asset Library",
description="Choose which asset library to download from the repository",
items= getAssetDirectoryItems
)
assetLibList_items: bpy.props.CollectionProperty(type = REAssetLibEntryPropertyGroup)
assetLibList_index: bpy.props.IntProperty(name="")
def execute(self, context):
if len(self.assetLibList_items) != 0:
bpy.ops.wm.save_userpref()#Save preferences to prevent asset library path from being lost
entry = self.assetLibList_items[int(self.libraryListEntry)]
libraryDir = bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.assetLibraryPath)
os.makedirs(libraryDir,exist_ok = True)
outFilePath = os.path.join(libraryDir,f"{entry.gameName}.reassetlib")
if os.path.isfile(outFilePath):#If the file has been downloaded before, remove it before starting the download again.
try:
os.remove(outFilePath)
except Exception as err:
print(f"Failed to delete existing .reassetlib file: {str(err)}")
libCRC = int(entry.CRC)
"""
content = downloadFileContent(entry.URL)
if content != None:
with open(outFilePath,"wb") as outFile:
outFile.write(content)
"""
for _,_ in download_file_from_google_drive(file_id=entry.URL,destination=outFilePath):
pass
if os.path.isfile(outFilePath):
if libCRC == getFileCRC(outFilePath):
print("CRC Check Passed")
bpy.ops.re_asset.importlibrary(filepath = outFilePath)
try:
os.remove(outFilePath)
except:
pass
self.report({"INFO"},f"Downloaded {entry.gameName} library. You can open the Asset Browser by going to File > New > RE Assets.")
else:
print("CRC Check failed, aborting install.")
self.report({"ERROR"},"CRC Check on the downloaded file failed. Try downloading the library again.")
return {'FINISHED'}
def invoke(self, context, event):
self.assetLibList_items.clear()
directoryDict = downloadREAssetLibDirectory()
"""
TESTPATH = r"D:\Modding\Monster Hunter Wilds\AssetLibrary\REAssetLib_directory.json"
with open(TESTPATH,"r", encoding ="utf-8") as file:
directoryDict = json.load(file)
"""
#directoryDict = None
if directoryDict != None:
#print(directoryDict)
if directoryDict.get("libraryList"):
libraryList = directoryDict.get("libraryList")
for entry in libraryList:
item = self.assetLibList_items.add()
item.gameName = entry["gameName"]
item.displayName = entry["displayName"]
item.releaseDescription = entry.get("releaseDescription","")
item.timestamp = entry["timestamp"]
item.CRC = str(entry["CRC"])
item.compressedSize = str(entry["compressedSize"])
item.uncompressedSize = str(entry["uncompressedSize"])
item.URL = entry["URL"]
return context.window_manager.invoke_props_dialog(self,width = 400,confirm_text = "Download Asset Library")
else:
return context.window_manager.invoke_popup(self)
def draw(self, context):
layout = self.layout
if len(self.assetLibList_items) != 0:
layout.prop(self,"libraryListEntry")
layout.separator()
layout.label(text="Info")
box = layout.box()
if len(self.assetLibList_items) != 0:
entry = self.assetLibList_items[int(self.libraryListEntry)]
box.label(text = f"Library Name: {entry.gameName}")
box.separator()
box.label(text = entry.releaseDescription)
box.label(text = f"Last Update: {entry.timestamp}")
box.label(text = f"Download Size: {formatByteSize(int(entry.compressedSize))}")
box.label(text = f"Installed Size: {formatByteSize(int(entry.uncompressedSize))}")
layout.label(text="Blender will become unresponsive while downloading the library.",icon = "ERROR")
else:
box.label(text = "Failed to retrieve available asset libraries.")
box.label(text = "Check your internet connection.")
class WM_OT_CreateNewREAssetLibrary(Operator):
bl_label = "Create New RE Asset Library"
bl_description = "Create a new asset library using an RETool .list file"
bl_idname = "re_asset.create_re_asset_library"
bl_options = {'INTERNAL'}
gameName: EnumProperty(
name="",
description="Set which game to create the library for",
items= [
("DMC5", "Devil May Cry 5", ""),
("RE2", "Resident Evil 2", ""),
("RE3", "Resident Evil 3", ""),
("RE8", "Resident Evil 8", ""),
("RE2RT", "Resident Evil 2 Ray Tracing", ""),
("RE3RT", "Resident Evil 3 Ray Tracing", ""),
("RE7RT", "Resident Evil 7 Ray Tracing", ""),
("MHRSB", "Monster Hunter Rise", ""),
("SF6", "Street Fighter 6", ""),
("RE4", "Resident Evil 4", ""),
("DD2", "Dragon's Dogma 2", ""),
("KG", "Kunitsu-Gami", ""),
("DR", "Dead Rising", ""),
("ONI2", "Onimusha 2", ""),
("PRAG", "Pragmata", ""),
("MHWILDS", "Monster Hunter Wilds", ""),
("MHS3", "Monster Hunter Stories 3", ""),
("RE9", "Resident Evil 9", ""),#TODO Update this to pull the active game enum directly from the mesh editor mdf property group
]
)
listPath: StringProperty(
name="List Path",
subtype='FILE_PATH',
description = "Location of RE Tool .list file. Make sure to use the correct one for the game you set.\nIf you don't have a .list file, download one from the GitHub repo by pressing the Download List Files button below\nTip: You can shift right click the list file and click Copy as path, then paste it into this field",
)
def draw(self,context):
layout = self.layout
layout.prop(self,"gameName")
layout.prop(self,"listPath")
op = self.layout.operator(
'wm.url_open',
text='Download List Files',
icon='TEXT'
)
op.url = 'https://github.com/Ekey/REE.PAK.Tool/tree/main/Projects'
@classmethod
def poll(self,context):
return context is not None
def execute(self, context):
newListPath = self.listPath.replace("\"","")
if os.path.isfile(newListPath):
#if os.path.isdir(bpy.context.preferences.addons[__name__].preferences.assetLibraryPath):
newLibraryDir = os.path.join(bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.assetLibraryPath),self.gameName)
bpy.ops.wm.save_userpref()#Save preferences to prevent asset library path from being lost
os.makedirs(newLibraryDir,exist_ok=True)
outputCatalogPath = os.path.join(newLibraryDir,f"REAssetCatalog_{self.gameName}.tsv")
outputGameInfoPath = os.path.join(newLibraryDir,f"GameInfo_{self.gameName}.json")
addonDir = os.path.split(__file__)[0]
sourceBlendPath = os.path.join(addonDir,"Resources","Blend","libraryBase.blend")
outputBlendPath = os.path.join(newLibraryDir,f"REAssetLibrary_{self.gameName}.blend")
scriptPath = os.path.join(addonDir,"Resources","Scripts","initializeLibrary.py")
whiteListSet = set()
for item in bpy.context.preferences.addons[__name__].preferences.fileTypeWhiteList_items:
whiteListSet.add(item.fileType.lower())
REToolListFileToREAssetCatalogAndGameInfo(newListPath, outputCatalogPath, outputGameInfoPath,list(whiteListSet))
shutil.copy(sourceBlendPath,outputBlendPath)
if os.path.isfile(outputCatalogPath) and os.path.isfile(outputGameInfoPath) and os.path.isfile(outputBlendPath) and os.path.isfile(scriptPath):
subprocess.Popen([bpy.app.binary_path, outputBlendPath, "--python", scriptPath])
else:
self.report({"ERROR"},"Missing files, cannot create library.")
return {'CANCELLED'}
self.report({"INFO"},"Created new RE Asset Library.")
return {'FINISHED'}
self.report({"INFO"},"Created new RE Asset Library.")
#else:
#self.report({"ERROR"},"Invalid asset library path.")
else:
self.report({"ERROR"},"Invalid list path.")
return {'FINISHED'}
def invoke(self,context,event):
return context.window_manager.invoke_props_dialog(self)
class WM_OT_DetectREAssetLibrary(Operator):
bl_label = "Refresh RE Asset Libraries"
bl_description = "Check for any libraries that are not listed and add them to the list."
bl_idname = "re_asset.detect_re_asset_library"
bl_options = {'INTERNAL'}
silent : BoolProperty(
name = "Silent",
description = "Don't report status after running",
default = False)
@classmethod
def poll(self,context):
return context is not None
def execute(self, context):
assetLibraryPath = bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.assetLibraryPath)
if not os.path.isdir(assetLibraryPath):
if not self.silent:
self.report({"ERROR"},"Invalid RE asset library path.")
return {'CANCELLED'}
subDirectoryList = [ f.name for f in os.scandir(assetLibraryPath) if f.is_dir() ]
for directory in subDirectoryList:
if os.path.isfile(os.path.join(assetLibraryPath,directory,f"REAssetLibrary_{directory.upper()}.blend")):
#Get existing library entry or make a new one
libName = f"RE Assets - {directory.upper()}"
library = getAssetLibrary(libName)
library.name = libName
library.path = os.path.join(assetLibraryPath,directory)
for lib in bpy.context.preferences.filepaths.asset_libraries:#Remove any paths that don't exist
if "RE Assets - " in lib.name and not os.path.isdir(lib.path):
bpy.context.preferences.filepaths.asset_libraries.remove(lib)
bpy.ops.wm.save_userpref()
if not self.silent:
self.report({"INFO"},"Refreshed RE Asset Library list.")
return {'FINISHED'}
class WM_OT_OpenREAssetLibraryFolder(Operator):
bl_label = "Open RE Asset Library Folder"
bl_description = "Opens the folder containing RE Asset Libraries in File Explorer"
bl_idname = "re_asset.open_re_asset_library_folder"
def execute(self, context):
try:
openFolder(bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.assetLibraryPath))
except:
pass
return {'FINISHED'}
class ASSETBROWSER_PT_REAssetToolPanel(Panel):
bl_label = "RE Asset Library"
bl_idname = "ASSETBROWSER_PT_REAssetToolPanel"
bl_options = {"INSTANCED"}
bl_space_type = "VIEW_3D"
bl_region_type = "WINDOW"
def draw(self, context):
preferences = bpy.context.preferences.addons[__name__].preferences
layout = self.layout
libVersion = str(bl_info["version"][0])+"."+str(bl_info["version"][1])
layout.label(text=f"RE Asset Library V{libVersion}")
gameName = getGameNameFromAssetBrowser()
if gameName != None:
layout.label(text = f"Library: {gameName}")
layout.operator("re_asset.set_game_extract_paths",icon = "CURRENT_FILE")
layout.operator("re_asset.extract_game_files",icon = "DOCUMENTS")
layout.operator("re_asset.open_chunk_extract_folder",icon = "FOLDER_REDIRECT")
layout.operator("re_asset.reload_pak_cache",icon = "FILE_REFRESH")
layout.operator("re_asset.check_for_library_update",icon="IMPORT")
else:
layout.separator()
layout.label(text = f"No RE Asset library is selected.")
layout.operator("re_asset.download_re_asset_library",icon = "INTERNET")
layout.separator(type="LINE")
layout.label(text = "RE Asset Settings")
layout.prop(preferences,"showMeshImportOptions")
layout.prop(preferences,"placeAtCursor")
#layout.prop(self,"instanceDuplicates")#TODO
layout.prop(preferences,"forceExtract")
class WM_OT_OpenFileLocation(Operator):
bl_label = "Open Asset Location"
bl_description = "Open the location the selected RE Asset is saved to.\nNote that the file has to be extracted to be able to find it's location"
bl_idname = "re_asset_library.open_file_location"
bl_context = "objectmode"
bl_options = {'INTERNAL'}
@classmethod
def poll(self,context):
return context.asset
def execute(self, context):
if context.asset:
asset = context.asset
#print(f"{asset.name=}")
#print(f"{asset.metadata.description}")
realPath = None
filePath = asset.metadata.description.replace("/",os.sep).replace("\\",os.sep)
libPath = asset.full_library_path
if libPath == "":#current file
libPath = bpy.context.blend_data.filepath
#print(libPath)
split = os.path.split(libPath)[1].split("REAssetLibrary_")
if len(split) == 2:
gameName = split[1].split(".blend")[0].upper()
chunkList = getChunkPathList(gameName)
if len(chunkList) != 0:
for chunkPath in chunkList:
realPath = wildCardFileSearch(glob.escape(os.path.join(chunkPath,filePath))+".*")
if realPath != None:
openFolder(os.path.split(realPath)[0])
self.report({"INFO"},"Opened file location.")
break
if realPath == None:
self.report({"ERROR"},"File not found. It might not be extracted.\nDrag it from the library into the 3D view to extract it.")
else:
self.report({"ERROR"},f"No chunk paths for {gameName} are present.")
else:
self.report({"ERROR"},"Asset is not an RE Asset.")
return {'FINISHED'}
def re_asset_settings_button(self, context):
self.layout.popover("ASSETBROWSER_PT_REAssetToolPanel",icon = "SETTINGS")
#self.layout.operator(WM_OT_SetREAssetSettings.bl_idname,icon = "SETTINGS")
def re_asset_open_file_location_button(self, context):
self.layout.operator(WM_OT_OpenFileLocation.bl_idname,icon = "FOLDER_REDIRECT")
execution_queue = queue.Queue()
def run_in_main_thread(function):
execution_queue.put(function)
def execute_queued_functions():
#print("Queue Check Ran")
while not execution_queue.empty():
function = execution_queue.get()
function()
else:
#print("Timer Stop")
return None
return 0.5
def deleteLastREAsset():
#print("Delete function run")
if bpy.context.scene.get("lastREAsset"):#Object pointer stored in scene
objName = bpy.context.scene["lastREAsset"].name
del bpy.context.scene["lastREAsset"]#Clear reference
if objName in bpy.data.objects:
bpy.data.objects.remove(bpy.data.objects[objName], do_unlink=True)#Remove asset placement object
#print(bpy.context.scene.get("lastREAsset") +" Deleted")
@persistent
def REAssetPostHandler(lapp_context):
gameInfoPath = None
gameInfo = None
assetPath = None
if len(lapp_context.import_items) == 1:#Make sure it's an asset drag and drop
item = lapp_context.import_items[0]
if item.id.get("~TYPE") == "RE_ASSET_LIBRARY_ASSET":
addonPreferences = bpy.context.preferences.addons[__name__].preferences
gameInfoPath = os.path.join(os.path.split(bpy.path.abspath(item.source_library.filepath))[0],"GameInfo_"+item.id.get("~GAME","UNKN")+".json")
#print(gameInfoPath)
if os.path.isfile(gameInfoPath):
gameInfo = loadGameInfo(gameInfoPath)
else:
print(f"RE Asset Library - Missing GameInfo:{gameInfoPath}")
assetType = item.id.get("assetType","UNKN")
promptSetExtractInfo = False
if gameInfo != None:
#Find asset path
chunkPathList = getChunkPathList(item.id.get("~GAME"))
#if len(chunkPathList) == 0:
# promptSetExtractInfo = True
#showErrorMessageBox("No chunk paths found for "+obj["~GAME"]+ " in RE Mesh Editor preferences.")
#else:
if len(chunkPathList) != 0:
for chunkPath in chunkPathList:
newPath = os.path.join(bpy.path.abspath(chunkPath),item.id.get("assetPath","MISSING_ASSET_PATH")+"."+gameInfo["fileVersionDict"].get(f"{assetType}_VERSION","UNKNOWNVERSION")).replace("/",os.sep).replace("\\",os.sep)
print(f"Checking for file at: {newPath}")
if os.path.isfile(newPath):
assetPath = newPath
print(f"Found asset path")
break
else:
promptSetExtractInfo = True
if assetPath == None or addonPreferences.forceExtract:
extractInfoPath = os.path.join(os.path.split(bpy.path.abspath(item.source_library.filepath))[0],"ExtractInfo_"+item.id.get("~GAME","UNKN")+".json")
pakCachePath = os.path.join(os.path.split(bpy.path.abspath(item.source_library.filepath))[0],"PakCache_"+item.id.get("~GAME","UNKN")+".pakcache")
if not os.path.isfile(extractInfoPath):#File extraction is not set up
promptSetExtractInfo = True
else:
if not promptSetExtractInfo and item.id.get("assetPath"):
extractFilesFromPakCache(gameInfoPath,[],extractInfoPath,pakCachePath,extractDependencies = True,blenderAssetObj = item.id)
for chunkPath in chunkPathList:
newPath = os.path.join(bpy.path.abspath(chunkPath),item.id.get("assetPath","MISSING_ASSET_PATH")+"."+gameInfo["fileVersionDict"].get(f"{assetType}_VERSION","UNKNOWNVERSION")).replace("/",os.sep).replace("\\",os.sep)
print(f"Checking for file at: {newPath}")
if os.path.isfile(newPath):
assetPath = newPath
print(f"Found asset path")
break
if assetPath == None:
showErrorMessageBox(item.id.get("assetPath",item.id.name)+" - File not found at any chunk paths. See console for details on how to fix this. (Window > Toggle System Console)")
print("\nIf this issue persists, try the following:")
print("1: Check for updates to the asset library addon in Edit > Preferences > Addons > RE Asset Library > Check now for re_asset_library update.")
print("2: Uninstall any mods installed with Fluffy Manager and validate game files on Steam.")
print("3: In the RE Asset Library menu in the asset browser, click Set Game Extract Paths and set the paths again.")
print("4: In the RE Asset Library menu in the asset browser, click Reload Pak Cache, then enable Force Extract Files.")
print("5: Drag the file onto the 3D view again.")
print("If it still does not work again after doing all of the above steps, please screenshot the output of the console when importing something from the asset browser and send it to me.\nAlso make sure you're not trying to extract DLC you don't have installed.\n")
if assetPath != None:
match assetType:
case "MESH":
importREMeshAsset(item.id,assetPath,addonPreferences)
case "CHAIN":
importREChainAsset(item.id,assetPath,addonPreferences)
case "CHAIN2":
importREChain2Asset(item.id,assetPath,addonPreferences)
case "FBXSKEL":
importREFBXSkelAsset(item.id,assetPath,addonPreferences)
case "REFSKEL":
importREFBXSkelAsset(item.id,assetPath,addonPreferences)
case "SKELETON":
importREFBXSkelAsset(item.id,assetPath,addonPreferences)
case _:
print(f"RE Asset Library - Unsupported Asset Type, cannot import. {item.id.name} - {assetType} ")
print("Make sure all RE addons are up to date.")
bpy.context.scene["lastREAsset"] = item.id
if promptSetExtractInfo:
bpy.ops.re_asset.prompt_extract_info("INVOKE_DEFAULT",libraryPath = bpy.path.abspath(item.source_library.filepath))
if not bpy.app.timers.is_registered(execute_queued_functions()):
bpy.app.timers.register(execute_queued_functions)
#Run every .5 seconds until object is deleted after linking, can't do this in the post handler or blender will complain
run_in_main_thread(deleteLastREAsset)
# Registration
classes = [
REAssetWhiteListEntryPropertyGroup,
ASSET_UL_FileTypeWhiteList,
REAssetLibEntryPropertyGroup,
ASSET_UL_REAssetLibList,
LIST_OT_NewWhiteListItem,
LIST_OT_DeleteWhiteListItem,
LIST_OT_ResetWhiteListItems,
REAssetPreferences,
ToggleStringPropertyGroup,
ASSET_UL_StringCheckList,
WM_OT_PromptSetExtractInfo,
WM_OT_SetExtractInfo,
WM_OT_ExtractGameFiles,
WM_OT_OpenExtractFolder,
WM_OT_ReloadPakCache,
WM_OT_CreatePakPatch,
WM_OT_UnpackModPak,
PAK_FH_drag_import,
WM_OT_BatchMDFUpdater,
WM_OT_BlenderMDFUpdater,
WM_OT_BatchRSZUpdater,
WM_OT_InitializeREAssetLibrary,
WM_OT_DownloadREAssetLibrary,
WM_OT_CreateNewREAssetLibrary,
WM_OT_DetectREAssetLibrary,
WM_OT_OpenREAssetLibraryFolder,
WM_OT_CheckForREAssetLibraryUpdate,
WM_OT_OpenLibraryFolder,
WM_OT_GenerateMaterialCompendium,
WM_OT_GenerateRSZCRCCompendium,
ImportREAssetLib,
WM_OT_OpenFileLocation,
WM_OT_RenderREAssets,
WM_OT_FetchREAssetThumbnails,
WM_OT_ImportREAssetLibraryFromCatalog,
WM_OT_SaveREAssetLibraryToCatalog,
WM_OT_ExportCatalogDiff,
WM_OT_ImportCatalogDiff,
WM_OT_PackageREAssetLibrary,
OBJECT_PT_ExtractGameFilesPanel,
OBJECT_PT_REAssetLibraryPanel,
ASSETBROWSER_PT_REAssetToolPanel,
]
def on_register():
if len(bpy.context.preferences.addons[__name__].preferences.fileTypeWhiteList_items) == 0:
bpy.ops.re_asset.reset_whitelist_items()
bpy.ops.re_asset.detect_re_asset_library(silent = True)#Save preferences on register, otherwise when a new blend file is opened, the addon might not be registered on the new instance.
def register():
addon_updater_ops.register(bl_info)
for classEntry in classes:
bpy.utils.register_class(classEntry)
bpy.types.ASSETBROWSER_MT_editor_menus.append(re_asset_settings_button)
bpy.types.ASSETBROWSER_MT_editor_menus.append(re_asset_open_file_location_button)
bpy.app.handlers.blend_import_post.append(REAssetPostHandler)
bpy.app.timers.register(on_register, first_interval=.01)
#Add RE Assets workspace under File > New
addonDir = os.path.split(bpy.path.abspath(__file__))[0]
WORKSPACE_SRC = os.path.join(addonDir,"Resources","Blend","REAssetWorkspace.blend")
WORKSPACE_DEST = os.path.join(bpy.path.abspath(bpy.utils.script_path_user()), "startup","bl_app_templates_user","RE_Assets","startup.blend")
try:
os.makedirs(os.path.split(WORKSPACE_DEST)[0],exist_ok = True)
except Exception as err:
print(f"Failed to create RE Asset workspace folder {str(err)}")
try:
shutil.copy(WORKSPACE_SRC,WORKSPACE_DEST)
except Exception as err:
print(f"Failed to copy RE Asset workspace blend file {str(err)}")
def unregister():
addon_updater_ops.unregister()
for classEntry in classes:
bpy.utils.unregister_class(classEntry)
bpy.types.ASSETBROWSER_MT_editor_menus.remove(re_asset_settings_button)
bpy.types.ASSETBROWSER_MT_editor_menus.remove(re_asset_open_file_location_button)
if REAssetPostHandler in bpy.app.handlers.blend_import_post:
bpy.app.handlers.blend_import_post.remove(REAssetPostHandler)
#Removing on unregister causes issues when loading the workspace
"""
WORKSPACE_DEST = os.path.join(bpy.path.abspath(bpy.utils.script_path_user()), "startup","bl_app_templates_user","RE_Assets","startup.blend")
try:
os.remove(WORKSPACE_DEST)
#pass
except:
pass
"""
if __name__ == '__main__':
register()