diliupg

Export ONLY the currently selected asset(s) to T3D (SLIM, with dialogs)

Sep 13th, 2026
250
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.09 KB | Gaming | 0 0
  1. """
  2. UE 4.27.2 - Export ONLY the currently selected asset(s) to T3D (SLIM, with dialogs)
  3.  
  4. Exports only assets highlighted in the Content Browser (single or multi-select).
  5. Mirrors the /Game/... hierarchy under DEST_ROOT. Existing files at the destination
  6. that are NOT the selected asset are left untouched. Shows a popup at start (if
  7. nothing exportable was selected) and at completion.
  8.  
  9. Setup:
  10.    Requires "Python Editor Script Plugin" (Edit > Plugins > Scripting).
  11.  
  12. Usage:
  13.    1. In the Content Browser, select one or more assets (Ctrl/Shift-click).
  14.    2. File > Execute Python Script...  OR  right-click > Scripted Asset Actions.
  15. """
  16.  
  17. import os
  18. import re
  19. import unreal
  20.  
  21. # ============================================================
  22. # CONFIG
  23. # ============================================================
  24. DEST_ROOT = r"F:/AI-ALIENS-T3D-slim"
  25.  
  26. WHITELIST = {
  27.     "Blueprint",
  28.     "WidgetBlueprint",
  29.     "BehaviorTree",
  30.     "BlackboardData",
  31.     "EnvQuery",
  32.     "UserDefinedEnum",
  33.     "UserDefinedStruct",
  34.     "AnimBlueprint",
  35.     "Material",
  36.     "MaterialFunction",
  37.     "MaterialInstanceConstant",
  38. }
  39. # ============================================================
  40.  
  41.  
  42. # ---- UI helpers ----------------------------------------------
  43.  
  44. def format_bytes(n):
  45.     if n < 1024:
  46.         return "%d B" % n
  47.     if n < 1024 * 1024:
  48.         return "%.1f KB" % (n / 1024.0)
  49.     return "%.1f MB" % (n / (1024.0 * 1024.0))
  50.  
  51.  
  52. def show_dialog(title, message):
  53.     """Modal OK dialog. Falls back to Output Log if the API is missing."""
  54.     try:
  55.         unreal.EditorDialog.show_message(title, message, unreal.AppMsgType.OK)
  56.     except Exception as e:
  57.         unreal.log_warning("[Dialog fallback: %s] %s | %s" % (e, title, message))
  58.  
  59.  
  60. # ---- Optimizer -----------------------------------------------
  61.  
  62. _DROP_LINE = re.compile(
  63.     r"^\s*("
  64.     r"NodePosX|NodePosY|NodeHeight|NodeWidth|"
  65.     r"NodeGuid|BlueprintGuid|GraphGuid|VarGuid|"
  66.     r"bLegacyNeedToPurgeSkelRefs|"
  67.     r"bCommentBubblePinned|bCommentBubbleVisible|"
  68.     r"bUserSetEnabledState|"
  69.     r"PinPreviewIndex|"
  70.     r"ThumbnailInfo"
  71.     r")\s*="
  72. )
  73. _DROP_INDEXED = re.compile(r"^\s*(LastEditedDocuments|CategorySorting)\(\d+\)\s*=")
  74.  
  75. _SKIP_OBJECT_HINTS = ("SceneThumbnailInfo",)
  76.  
  77. _INLINE_STRIP = [
  78.     re.compile(r"PersistentGuid=00000000000000000000000000000000,"),
  79.     re.compile(r"bIsReference=False,"),
  80.     re.compile(r"bIsConst=False,"),
  81.     re.compile(r"bIsWeakPointer=False,"),
  82.     re.compile(r"bIsUObjectWrapper=False,"),
  83.     re.compile(r"bHidden=False,"),
  84.     re.compile(r"bNotConnectable=False,"),
  85.     re.compile(r"bDefaultValueIsReadOnly=False,"),
  86.     re.compile(r"bDefaultValueIsIgnored=False,"),
  87.     re.compile(r"bAdvancedView=False,"),
  88.     re.compile(r"bOrphanedPin=False,"),
  89.     re.compile(r'PinType\.PinSubCategory="",'),
  90.     re.compile(r"PinType\.PinSubCategoryObject=None,"),
  91.     re.compile(r"PinType\.PinSubCategoryMemberReference=\(\),"),
  92.     re.compile(r"PinType\.PinValueType=\(\),"),
  93.     re.compile(r"PinType\.ContainerType=None,"),
  94.     re.compile(r'PinToolTip="[^"]*",'),
  95.     re.compile(r"PinFriendlyName=NSLOCTEXT\([^)]*\),"),
  96.     re.compile(r'AutogeneratedDefaultValue="[^"]*",'),
  97. ]
  98.  
  99.  
  100. def optimize_t3d_text(text):
  101.     try:
  102.         lines = text.split("\n")
  103.         out = []
  104.         skip_depth = 0
  105.         for raw in lines:
  106.             line = raw.rstrip("\r")
  107.             stripped = line.strip()
  108.             if skip_depth > 0:
  109.                 if stripped.startswith("Begin Object"):
  110.                     skip_depth += 1
  111.                 elif stripped.startswith("End Object"):
  112.                     skip_depth -= 1
  113.                 continue
  114.             if stripped.startswith("Begin Object") and any(
  115.                 h in stripped for h in _SKIP_OBJECT_HINTS
  116.             ):
  117.                 skip_depth = 1
  118.                 continue
  119.             if _DROP_LINE.match(line) or _DROP_INDEXED.match(line):
  120.                 continue
  121.             for pat in _INLINE_STRIP:
  122.                 line = pat.sub("", line)
  123.             out.append(line)
  124.         return "\r\n".join(out)
  125.     except Exception as e:
  126.         unreal.log_warning("optimize_t3d_text failed (%s); keeping raw." % e)
  127.         return text
  128.  
  129.  
  130. # ---- Export helpers ------------------------------------------
  131.  
  132. def content_path_to_relative(content_path):
  133.     if content_path.startswith("/Game/"):
  134.         return content_path[len("/Game/"):]
  135.     if content_path == "/Game":
  136.         return ""
  137.     return content_path.lstrip("/")
  138.  
  139.  
  140. def export_asset(asset, out_file):
  141.     task = unreal.AssetExportTask()
  142.     task.object = asset
  143.     task.filename = out_file
  144.     task.automated = True
  145.     task.prompt = False
  146.     task.replace_identical = True
  147.     task.use_file_archive = False
  148.     task.write_empty_files = False
  149.     return unreal.Exporter.run_asset_export_task(task)
  150.  
  151.  
  152. def slim_file_in_place(path):
  153.     with open(path, "rb") as f:
  154.         raw = f.read()
  155.     orig = len(raw)
  156.     try:
  157.         text = raw.decode("utf-8")
  158.     except UnicodeDecodeError:
  159.         text = raw.decode("utf-8", errors="replace")
  160.     new_bytes = optimize_t3d_text(text).encode("utf-8")
  161.     with open(path, "wb") as f:
  162.         f.write(new_bytes)
  163.     return orig, len(new_bytes)
  164.  
  165.  
  166. # ---- Main ----------------------------------------------------
  167.  
  168. def main():
  169.     selected = unreal.EditorUtilityLibrary.get_selected_asset_data()
  170.     if not selected:
  171.         show_dialog(
  172.             "T3D Export",
  173.             "Nothing selected.\n\nHighlight one or more assets in the Content Browser and try again.",
  174.         )
  175.         return
  176.  
  177.     total = len(selected)
  178.     unreal.log("Destination : %s" % DEST_ROOT)
  179.     unreal.log("Selected    : %d asset(s)" % total)
  180.  
  181.     exported = skipped = failed = 0
  182.     skipped_names = []
  183.     failed_list = []
  184.     total_orig = 0
  185.     total_new = 0
  186.  
  187.     for i, ad in enumerate(selected, start=1):
  188.         name = str(ad.asset_name)
  189.         cls = str(ad.asset_class)
  190.  
  191.         if cls not in WHITELIST:
  192.             skipped += 1
  193.             skipped_names.append("%s (%s)" % (name, cls))
  194.             unreal.log_warning(
  195.                 "[%d/%d] SKIP (not in whitelist: %s)  %s" % (i, total, cls, name)
  196.             )
  197.             continue
  198.  
  199.         pkg_path = str(ad.package_path)
  200.         asset = ad.get_asset()
  201.         if asset is None:
  202.             failed += 1
  203.             failed_list.append("%s (load failed)" % name)
  204.             continue
  205.  
  206.         rel = content_path_to_relative(pkg_path)
  207.         out_dir = os.path.normpath(os.path.join(DEST_ROOT, rel))
  208.         try:
  209.             os.makedirs(out_dir, exist_ok=True)
  210.         except Exception as e:
  211.             failed += 1
  212.             failed_list.append("%s (mkdir: %s)" % (name, e))
  213.             continue
  214.  
  215.         out_file = os.path.join(out_dir, name + ".t3d")
  216.         ok = export_asset(asset, out_file)
  217.         if not ok:
  218.             failed += 1
  219.             failed_list.append(name)
  220.             unreal.log_warning("[%d/%d] FAIL %s" % (i, total, name))
  221.             continue
  222.  
  223.         try:
  224.             orig, new = slim_file_in_place(out_file)
  225.             total_orig += orig
  226.             total_new += new
  227.             reduction = (1 - new / orig) * 100 if orig else 0
  228.             exported += 1
  229.             unreal.log(
  230.                 "[%d/%d] OK   %s   %d -> %d bytes (-%.0f%%)"
  231.                 % (i, total, name, orig, new, reduction)
  232.             )
  233.         except Exception as e:
  234.             exported += 1
  235.             unreal.log_warning(
  236.                 "[%d/%d] OK (slim failed: %s) %s" % (i, total, e, name)
  237.             )
  238.  
  239.     unreal.log("=" * 60)
  240.     unreal.log(
  241.         "Done.  Exported=%d   Skipped(not-whitelisted)=%d   Failed=%d"
  242.         % (exported, skipped, failed)
  243.     )
  244.     if total_orig:
  245.         overall = (1 - total_new / total_orig) * 100
  246.         unreal.log(
  247.             "Total size: %d -> %d bytes  (saved %.0f%%)"
  248.             % (total_orig, total_new, overall)
  249.         )
  250.  
  251.     # -- Popup summary --
  252.     if exported > 0:
  253.         msg = "Exported %d of %d asset(s) to:\n%s" % (exported, total, DEST_ROOT)
  254.         if total_orig:
  255.             overall = (1 - total_new / total_orig) * 100
  256.             msg += "\n\nSize:  %s  ->  %s   (saved %.0f%%)" % (
  257.                 format_bytes(total_orig), format_bytes(total_new), overall,
  258.             )
  259.         if skipped:
  260.             msg += "\n\nSkipped (not a supported type): %d" % skipped
  261.         if failed:
  262.             msg += "\nFailed: %d" % failed
  263.         show_dialog("T3D Export Complete", msg)
  264.     else:
  265.         msg = "No assets were exported.\n\n"
  266.         if skipped:
  267.             preview = "\n".join("  - " + n for n in skipped_names[:8])
  268.             more = "" if len(skipped_names) <= 8 else "\n  ...and %d more" % (len(skipped_names) - 8)
  269.             msg += "Skipped (not a supported type): %d\n%s%s\n\n" % (skipped, preview, more)
  270.         if failed:
  271.             msg += "Failed: %d\n\n" % failed
  272.         msg += "Supported: Blueprint, WidgetBlueprint, BehaviorTree, BlackboardData, EnvQuery, UserDefinedEnum/Struct, AnimBlueprint, Material, MaterialFunction, MaterialInstanceConstant."
  273.         show_dialog("T3D Export - Nothing Done", msg)
  274.  
  275.  
  276. main()
Advertisement
Add Comment
Please, Sign In to add comment