- Home /
Can I duplicate folder with internal dependencies between objects?
Hi guys!
I have a folder with some prefabs, materials, textures and etc. Prefabs refers to textures in this folder. Can I clone this folder with saving local dependencies? For example: Folder is named "Assets/BaseFolder". And prefab "Assets/BaseFolder/preFab" refers to "Assets/BaseFolder/texture". I need to Copy "Assets/BaseFolder" to "Assets/CloneFolder", so that "Assets/CloneFolder/preFab" refer to "Assets/CloneFolder/texture".
By default, when I dublicate folder - dependencies refers to the old resources with old paths.
I can do it manually in editor for every object. But I have many objects and many dependencies, and I need to automate this process.
Does anyone have any ideas?
Thanks for any answer, Vlad
Hey Vlad, im running into the same problem, if you happened to have found a solution, please let me know, i'm pretty desperate!
Answer by dandrea · May 30, 2018 at 03:02 PM
I've just had the same problem. In 22 years of industry, that's the first time I see a program where you duplicate something and the internal dependencies aren't properly redirected to the adequate duplicates.! I considered writing something in Unity for a while, but I ended up deciding for an easier (although not elegant) hack that will do the trick, as long as you're using visible meta files as the version control mode.
I wrote a python script that changes the guids of the assets as well as the internal references in materials and prefabs. I'm using python 3.5 on this one, but it should work on later versions as well. Have a back-up of the project folder, for I've tested it enough for my own personal use only.
>> USE AT YOUR OWN RISK <<
Usage: python scriptname.py -i foldername
I pulled a copy of the folder I wanted to duplicate out of the project, renamed it and ran the script. As it doesn't affect guid references that are not found inside the folder itself, when you move the renamed folder to the project , Unity correctly matches the external references. :-)
Just to make sure you've read it: >> USE AT YOUR OWN RISK <<
import fnmatch
import os
import argparse
import uuid
# PARAMETERS
parser = argparse.ArgumentParser(description='Rearrange a Unity\'s project folder guids.')
parser.add_argument('-i, --input', metavar='FOLDER', type=str, nargs=1, required=True, dest='input_folder',
help='input folder')
args = parser.parse_args()
input_folder = None if args.input_folder is None else args.input_folder[0]
if not os.path.isdir(input_folder):
print('ERROR: Path not found.')
quit(1)
# SCAN FILES
register_list = []
print('')
metas = dict()
mats = []
prefabs = []
for root, folders, files in os.walk(input_folder):
for fname in files:
if fnmatch.fnmatch(fname, '*.meta'):
metaname = root + os.sep + fname
file = open(metaname, 'r')
for line in file:
elements = line.split(': ')
if elements[0] == 'guid':
id = elements[1].replace('\n', '')
metas.update({id: [metaname, uuid.uuid4().hex]})
file.close()
if fnmatch.fnmatch(fname, '*.mat'):
mats += [root + os.sep + fname]
if fnmatch.fnmatch(fname, '*.prefab'):
prefabs += [root + os.sep + fname]
# CHANGE METAS
for k, v in metas.items():
file = open(v[0], 'r')
met = file.readlines()
for i, l in enumerate(met):
if 'guid:' in l:
met[i] = l.replace(k, v[1])
file.close()
file = open(v[0], 'w')
file.writelines(met)
file.close()
#CHANGE PREFABS AND MATERIALS
for pname in prefabs + mats:
file = open(pname, 'r')
changed = False
prefab = file.readlines()
for i, l in enumerate(prefab):
if 'guid:' in l:
key = None
for k in metas.keys():
if k in l:
key = k
changed = True
if key is not None:
prefab[i] = l.replace(key, metas[key][1])
print(prefab[i], end='')
print(key, '->', metas[key][1])
else:
print(prefab[i])
file.close()
if changed:
file = open(pname, 'w')
file.writelines(prefab)
file.close()
Traceback (most recent call last): File ".\scriptname.py", line 49, in file = open(v[0], 'w') PermissionError: [Errno 13] Permission denied:
@aaronfranke The error kind of self explains itself. The script assumes you can write to the folder. Either you don't have write permission or the file is being held by some other process.
What's your operational system?
$$anonymous$$ake a copy of the folder to outside the Unity's project folder beforehand. Run the script. Only then you move it back. And use it at your own risk.
OS is Windows, I usually use Linux but in this case I'm doing VR development. I did make a copy outside and Unity was and is closed, but it still fails.
¯\_(ツ)_/¯.
I have no idea, since I can't reproduce.
But really: the script is simple and readable. You should have no big problem figuring out what's wrong. For instance. Wrong filenames, perhaps?
Answer by odintk45 · Mar 19, 2021 at 10:11 AM
My solution:
copy the directory
recursively retrieve the guid of all the files contained in the .meta of the destination folder
generate a correspondence table between the original guid (that I just got) and the new ones that I get thanks to GUID.Generate
replace the original guid with the new ones in all files
This procedure allows you to simply perform a deep copy of an entire folder.
This code can surely be improved and optimized, but it is functional.
public static void CopyDirectoryDeep(string sourcePath, string destinationPath)
{
CopyDirectoryRecursively(sourcePath, destinationPath);
List<string> metaFiles = GetFilesRecursively(destinationPath, (f) => f.EndsWith(".meta"));
List<(string originalGuid, string newGuid)> guidTable = new List<(string originalGuid, string newGuid)>();
foreach (string metaFile in metaFiles)
{
StreamReader file = new StreamReader(metaFile);
file.ReadLine();
string guidLine = file.ReadLine();
file.Close();
string originalGuid = guidLine.Substring(6, guidLine.Length - 6);
string newGuid = GUID.Generate().ToString().Replace("-", "");
guidTable.Add((originalGuid, newGuid));
}
List<string> allFiles = GetFilesRecursively(destinationPath);
foreach (string fileToModify in allFiles)
{
string content = File.ReadAllText(fileToModify);
foreach (var guidPair in guidTable)
{
content = content.Replace(guidPair.originalGuid, guidPair.newGuid);
}
File.WriteAllText(fileToModify, content);
}
AssetDatabase.Refresh();
}
private static void CopyDirectoryRecursively(string sourceDirName, string destDirName)
{
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
}
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
CopyDirectoryRecursively(subdir.FullName, temppath);
}
}
private static List<string> GetFilesRecursively(string path, Func<string, bool> criteria = null, List<string> files = null)
{
if (files == null)
{
files = new List<string>();
}
files.AddRange(Directory.GetFiles(path).Where(f =>criteria == null || criteria(f)));
foreach (string directory in Directory.GetDirectories(path))
{
GetFilesRecursively(directory, criteria, files);
}
return files;
}
You may want to call AssetDatabase.StartAssetEditing befor you start copying and call AssetDatabase.StopAssetEditing once you're done to prevent any issues during the process. Note that you probably should wrap all your code in a try-finally block to ensure StopAssetEditing is called at the end.
public static void CopyDirectoryDeep(string sourcePath, string destinationPath)
{
try
{
AssetDatabase.StartAssetEditing();
// the original code goes here
}
finally
{
AssetDatabase.StopAssetEditing();
}
}
If you have a lot of large files you may want to add EditorUtility.DisplayProgressBar in between your code to update the progress and call EditorUtility.ClearProgressBar also in the finally block to give visual feedback. Instead of DisplayProgressBar you can also use DisplayCancelableProgressBar which allows the user to click the cancel button. The method would return true if the action should be cancelled. So it's still your responsiblility what you do when the user clicks cancel. In this case you probably want to undo everything and get rid of the already created new folder since the process was interrupted in between. Though this is all just optional.
Answer by drudiverse · Oct 18, 2014 at 07:44 PM
sounds like you need to write arrays or file strings to reference many prefabs, because otherwise they just save with scene views if you save them in the player. you have to copy them with unity explorer window and then they all have file names, prefab names texture names. a texture should refer to a variable or an image file. you have to be familiar with accessing prefabs in unity. straings can code file names, you can add them togther in loops to deal with just one string being different in batches of prefabs.
the example with quotes was confusing... texture isnt prefab?
Answer by HenTeMon · Oct 19, 2014 at 09:49 AM
There is many unity objects, who refer to another.
It is not only prefabs. For example, Animator controller refer to animations. Scene refer to prefabs. Prefab refer to textures, materials, etc. I can change these links in unity editor (refer to objects with same names, but in another folder), manually, each individually. But I don't know how do that for multiple objects in editor, or script.
When I copy prefab and texture to another folder - it refer to texture from old folder. I need, that prefab refer to texture in new folder.
I am not sure, what you mean. Are you propose to make connections between objects through "Resources" class or analog?