After a whole day of research I pretty much arrived at a satisfying solution. Just for clarity - I am going to reference to Autodesk Forge Design Automation API for Revit, simply as "Forge".
Basically the code provided above is correct. I did not find any possible way to create a file on Forge instance, in a directory different than the Workitem working directory which is:
T:\Aces\Jobs\<workitem_id>\texture.png
Interestingly, there is a C:\ drive on the Forge instance, which contains Windows, Revit and .NET Framework installations (as Forge instance is basically some sort of Windows instance with Revit installed). It is possible to enumerate a lot of these directories, but none of the ones I've tried (and I've tried a lot - mostly the most obvious, public access Windows directories like C:\Users\Public, C:\Program Files, etc.) allow for creation of directories or files. This corresponds to what is stated in "Restrictions" area of the Forge documentation:
Your application is run with low privileges, and will not be able to freely interact with the Windows OS :
- Write access is typically restricted to the job’s working folder.
- Registry access is mostly restricted, writing to the registry should be avoided.
- Any sub-process will also be executed with low privileges.
So after trying to save the "dummy" texture file somewhere on the Forge C:\ drive, I've found another solution - the texture path for your texture actually does not matter.
This is because Revit offers an alternative for re-linking your textures. If you fire up Revit, you can go to File -> Options -> Rendering, and under "Additional render appearance paths" field, you can specify the directories on your local machine, that Revit can use to look for missing textures. With these, you can do the following operations in order to have full control on creating materials on Forge:
- Send Workitem to Forge, create the materials.
- Create a dummy texture in working directory, with the correct file name.
- Attach the dummy texture file to the material.
- Output the resulting file (.rvt or .rfa, depending on what you're creating on Forge).
- Place all textures into one folder (or multiple, this doesn't matter that much).
- Add the directories with the textures to the Additional render apperance paths.
- Revit will successfully re-link all the textures to new paths.
I hope someone will find this useful!
Additionally, as per Jeremy request, I post a code sample for creating material with texture and modifying different Appearance properties in Revit by using Revit API (in C#):
private void SetAppearanceParameters(Document project, Material mat, MaterialData data) {
using(Transaction setParameters = new Transaction(project, "Set material parameters")) {
setParameters.Start();
AppearanceAssetElement genericAsset = new FilteredElementCollector(project)
.OfClass(typeof(AppearanceAssetElement))
.ToElements()
.Cast < AppearanceAssetElement > ().Where(i = >i.Name.Contains("Generic"))
.FirstOrDefault();
AppearanceAssetElement newAsset = genericAsset.Duplicate(data.Name);
mat.AppearanceAssetId = newAsset.Id;
using(AppearanceAssetEditScope editAsset = new AppearanceAssetEditScope(project)) {
Asset editableAsset = editAsset.Start(newAsset.Id);
AssetProperty assetProperty = editableAsset["generic_diffuse"];
SetColor(editableAsset, data.MaterialAppearance.Color);
SetGlossiness(editableAsset, data.MaterialAppearance.Gloss);
SetReflectivity(editableAsset, data.MaterialAppearance.Reflectivity);
SetTransparency(editableAsset, data.MaterialAppearance.Transparency);
if (data.MaterialAppearance.Texture != null && data.MaterialAppearance.Texture.Length != 0)
AddTexturePath(assetProperty, $@"C:\{data.MaterialIdentity.Manufacturer}\textures\{data.MaterialAppearance.Texture}");
editAsset.Commit(true);
}
setParameters.Commit();
}
}
private void SetTransparency(Asset editableAsset, int transparency) {
AssetPropertyDouble genericTransparency = editableAsset["generic_transparency"] as AssetPropertyDouble;
genericTransparency.Value = Convert.ToDouble(transparency);
}
private void SetReflectivity(Asset editableAsset, int reflectivity) {
AssetPropertyDouble genericReflectivityZero = (AssetPropertyDouble) editableAsset["generic_reflectivity_at_0deg"];
genericReflectivityZero.Value = Convert.ToDouble(reflectivity) / 100;
AssetPropertyDouble genericReflectivityAngle = (AssetPropertyDouble) editableAsset["generic_reflectivity_at_90deg"];
genericReflectivityAngle.Value = Convert.ToDouble(reflectivity) / 100;
}
private void SetGlossiness(Asset editableAsset, int gloss) {
AssetPropertyDouble glossProperty = (AssetPropertyDouble) editableAsset["generic_glossiness"];
glossProperty.Value = Convert.ToDouble(gloss) / 100;
}
private void SetColor(Asset editableAsset, int[] color) {
AssetPropertyDoubleArray4d genericDiffuseColor = (AssetPropertyDoubleArray4d) editableAsset["generic_diffuse"];
Color newColor = new Color((byte) color[0], (byte) color[1], (byte) color[2]);
genericDiffuseColor.SetValueAsColor(newColor);
}
private void AddTexturePath(AssetProperty asset, string texturePath) {
Asset connectedAsset = null;
if (asset.NumberOfConnectedProperties == 0) asset.AddConnectedAsset("UnifiedBitmapSchema");
connectedAsset = (Asset) asset.GetConnectedProperty(0);
AssetProperty prop = connectedAsset.FindByName(UnifiedBitmap.UnifiedbitmapBitmap);
AssetPropertyString path = (AssetPropertyString) connectedAsset.FindByName(UnifiedBitmap.UnifiedbitmapBitmap);
string fileName = Path.GetFileName(texturePath);
File.Create(fileName);
texturePath = Path.GetFullPath(fileName);
path.Value = texturePath;
}