Search code examples
c#autodesk-forgerevit-apiautodesk-designautomation

Creating a material with texture in Autodesk Revit Forge Design Automation


I'm currently working on some Revit API code which is running in the Autodesk Forge Design Automation cloud solution. Basically, I'm trying to create a material and attach a texture to it via the following code:

 private void AddTexturePath(AssetProperty asset, string texturePath) {
  Asset connectedAsset = null;

  if (asset.NumberOfConnectedProperties == 0)
   asset.AddConnectedAsset("UnifiedBitmapSchema");

  connectedAsset = (Asset) asset.GetConnectedProperty(0);
  AssetPropertyString path = (AssetPropertyString) connectedAsset.FindByName(UnifiedBitmap.UnifiedbitmapBitmap);

  if (!path.IsValidValue(texturePath)) {
   File.Create("texture.png");
   texturePath = Path.GetFullPath("texture.png");
  }

  path.Value = texturePath;

 }

This is actually working well, as the value for the texture path:

path.Value = texturePath;

Needs to be a reference to an existing file. I do not have this file on the cloud instance of Forge, because the path to the texture name is specified by the user when he sends the request for the Workitem.

The problem is that this sets the texture path for the material as something like this:

T:\Aces\Jobs\<workitem_id>\texture.png

Which is basically the working folder for the Workitem instance. This path is useless, because a material with texture path like this needs to be manually re-linked in Revit.

The perfect outcome for me would be if I could somehow map the material texture path to some user-friendly directory like "C:\Textures\texture.png" and it seems that the Forge instance has a "C:\" drive present (being probably a Windows instance of some sorts), but my code runs on low privileges, so it cannot create any kind of directories/files outside the working directory.

Does somebody has any idea how this could be resolved? Any help would be greatly appreciated!


Solution

  • 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:

    1. Send Workitem to Forge, create the materials.
    2. Create a dummy texture in working directory, with the correct file name.
    3. Attach the dummy texture file to the material.
    4. Output the resulting file (.rvt or .rfa, depending on what you're creating on Forge).
    5. Place all textures into one folder (or multiple, this doesn't matter that much).
    6. Add the directories with the textures to the Additional render apperance paths.
    7. 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;
    }