Skip to content

Instantly share code, notes, and snippets.

@mstevenson
Created February 27, 2025 03:15
Show Gist options
  • Save mstevenson/1d3d712a7e0c90e27c8437e4611b8ec6 to your computer and use it in GitHub Desktop.
Save mstevenson/1d3d712a7e0c90e27c8437e4611b8ec6 to your computer and use it in GitHub Desktop.
Regenerate Unity asset GUIDs while preserving local asset references. Useful for duplicating an entire directory of inter-dependent assets.
import os
import sys
import argparse
import uuid
import logging
from concurrent.futures import ThreadPoolExecutor
from typing import List, Tuple, Dict, Optional
logging.basicConfig(level=logging.INFO, format='%(message)s')
def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(description='Remap asset references in a directory.')
parser.add_argument('--dry-run', action='store_true', help='Perform a dry run without making changes.')
parser.add_argument('directory', type=str, help='The directory to process.')
return parser.parse_args()
def validate_directory(directory: str) -> None:
if not os.path.isdir(directory):
logging.error(f"Directory {directory} does not exist.")
sys.exit(1)
def find_files(directory: str, extensions: List[str]) -> List[str]:
return [os.path.join(root, file)
for root, _, files in os.walk(directory)
for file in files if any(file.endswith(ext) for ext in extensions)]
def create_replacement_guid_for_meta_file(file_path: str) -> Optional[Tuple[str, str]]:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
if line.startswith('guid: '):
guid = line.split(' ')[1].strip()
new_guid = uuid.uuid4().hex
return guid, new_guid
return None
def replace_guid_in_file(file_path: str, guid_map: Dict[str, str], dry_run: bool) -> None:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
original_content = content
for old_guid, new_guid in guid_map.items():
content = content.replace(old_guid, new_guid)
if content != original_content:
logging.info(f"Updated GUIDs in {file_path}")
if not dry_run:
with open(file_path, 'w', encoding='utf-8', errors='ignore') as f:
f.write(content)
def process_meta_files(meta_files: List[str], dry_run: bool) -> Dict[str, str]:
guid_map = {}
for meta_file in meta_files:
result = create_replacement_guid_for_meta_file(meta_file)
if result:
old_guid, new_guid = result
guid_map[old_guid] = new_guid
with ThreadPoolExecutor() as executor:
futures = [executor.submit(replace_guid_in_file, meta_file, guid_map, dry_run) for meta_file in meta_files]
for future in futures:
future.result()
return guid_map
def main() -> None:
args = parse_arguments()
validate_directory(args.directory)
meta_files = find_files(args.directory, ['.meta'])
asset_files = find_files(args.directory, ['.prefab', '.mat', '.asset', '.unity'])
guid_map = process_meta_files(meta_files, args.dry_run)
with ThreadPoolExecutor() as executor:
futures = [executor.submit(replace_guid_in_file, file_path, guid_map, args.dry_run) for file_path in asset_files]
for future in futures:
future.result()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment