Why is AppearanceAssetElement empty in API?

Why is AppearanceAssetElement empty in API?

sofia_feist
Enthusiast Enthusiast
946 Views
11 Replies
Message 1 of 12

Why is AppearanceAssetElement empty in API?

sofia_feist
Enthusiast
Enthusiast

Hello,

I was retrieving Appearance Assets from Elements in my project and came across this door (.rvt attached) which has a material (Door - Architrave) that, even though it has Appearance assets in the UI (see image "AppearanceAssets Revit UI"), Revit Lookup cannot seem to find them, as shown by AssetProperties.Size = 0 (see image "AppearanceAssets Revit Lookup"). This is the only material in my entire project with AssetProperties.Size = 0. Is this normal? How else can I get my Appearance assets if AssetProperties.Size = 0?

Thanks in advance,

Sofia

0 Likes
Accepted solutions (2)
947 Views
11 Replies
Replies (11)
Message 2 of 12

jeremy_tammik
Alumni
Alumni

Dear Sofia, 

  

how funny. We talked about that very thing right here just two weeks ago:

  

  

Cheers

  

Jeremy

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 3 of 12

sofia_feist
Enthusiast
Enthusiast

Hi Jeremy,

I did indeed miss that thread. However, it does not solve my problem as that is already what I am doing, with the exception of using FindByName(propertyName). For reference, I'm trying to get specific appearance asset properties from various materials, including appearance description, category and main appearance image filepath. 

I've tried using renderingAsset.FindByName("Description"), renderingAsset.FindByName("Category"), renderingAsset.FindByName("UnifiedBitmapSchema") and renderingAsset.FindByName("BaseSchema") but they all return null since renderingAssets is empty, i.e. AssetProperties.Size = 0.

For reference, here is my code:

private (string, string, string) GetMaterialAssets(Document doc, int materialId)
{
    string texturePath = "";
    string description = "";
    string category = "";

    // Get material element
    Material material = doc.GetElement(new ElementId(materialId)) as Material;
    if (material != null) 
    {
        // Get appearance assets
        ElementId appearanceAssetId = material.AppearanceAssetId;
        AppearanceAssetElement appearanceAssetElem = doc.GetElement(appearanceAssetId) as AppearanceAssetElement;
        if (appearanceAssetElem == null) return (texturePath, description, category);

        // Get rendering asset
        Asset assetRend = appearanceAssetElem.GetRenderingAsset();

        if (assetRend != null)
        {
            if (assetRend.Size == 0)
            {
                AssetProperty baseSchema = assetRend.FindByName("BaseSchema");
                TaskDialog.Show("Base Schema", baseSchema?.ToString());
            }

            // Go through properties
            for (int assetIdx = 0; assetIdx < assetRend.Size; assetIdx++)
            {
                AssetProperty assetProperty = assetRend[assetIdx];
                Type type = assetProperty.GetType();

                if (assetProperty.Name.ToLower() == "description")
                {
                    var prop = type.GetProperty("Value");
                    if (prop != null && prop.GetIndexParameters().Length == 0)
                    {
                        description = prop.GetValue(assetProperty).ToString();
                        continue;
                    }
                }
                else if (assetProperty.Name.ToLower() == "category")
                {
                    var prop = type.GetProperty("Value");
                    if (prop != null && prop.GetIndexParameters().Length == 0)
                    {
                        category = prop.GetValue(assetProperty).ToString();
                        continue;
                    }
                }

                if (assetProperty.NumberOfConnectedProperties < 1)
                    continue;

                Asset connectedAsset = assetProperty.GetConnectedProperty(0) as Asset;
                if (connectedAsset.Name == "UnifiedBitmapSchema")
                {
                    if (assetProperty.Name.Contains("bump") || assetProperty.Name.Contains("pattern_map") ||
                        assetProperty.Name.Contains("shader") || assetProperty.Name.Contains("opacity"))
                        continue;

                    // UnifiedBitmap contains file name and path
                    AssetPropertyString path = connectedAsset.FindByName(UnifiedBitmap.UnifiedbitmapBitmap) as AssetPropertyString;
                    if (path == null || string.IsNullOrEmpty(path.Value))
                        continue;
                    string pathString = path.Value;
                    if (pathString.Contains("|"))
                    {
                        pathString = pathString.Split('|')[0];
                    }
                    // remove | this character
                    if (Path.IsPathRooted(pathString))
                    {
                        texturePath = pathString;
                        continue;
                    }

                    // return path using default texture
                    string defaultTexturePath = @"C:\Program Files\Common Files\Autodesk Shared\Materials\Textures\";
                    string defaultTexturePathx86 = @"C:\Program Files (x86)\Common Files\Autodesk Shared\Materials\Textures\";

                    if (Directory.Exists(defaultTexturePath))
                        texturePath = defaultTexturePath + pathString;
                    else if (Directory.Exists(defaultTexturePathx86))
                        texturePath = defaultTexturePathx86 + pathString;
                }
            }
        }
    }
    
    return (texturePath, description, category);
}


Best Regards,
Sofia

0 Likes
Message 4 of 12

TripleM-Dev.net
Advisor
Advisor
Accepted solution

Hi @sofia_feist,

 

I think it's because the AppearanceAsset doesn't really exist.

I had this when editing materials in a Rvt created by a Ifc convertion.

 

When opening the material in the material editor revit assigns the AppearanceAsset, But it's not saved to the material on closing the editor if no changes are made to the AppearanceAsset.

Change for instance the description field of the asset and save it. Now the AppearanceAsset will also be found by the API

 

In my case it always was a Generic class material (ifc convertion) and not a Wood Class, maybe that's why a AppearanceAsset is created by revit with wood "settings",  likely a copy of the first existing Wood Class Asset?? (or some default)

 

I solved it in my case by assigning my own AppearanceAsset (copy of a template AppearanceAsset with correct settings), editing some properties an assign it to the material.

 

- Michel

Message 5 of 12

sofia_feist
Enthusiast
Enthusiast

Hello @TripleM-Dev.net 

 

It was indeed as you described. By changing a property of my Appearance asset and saving the material re-applies the appearance assets in the API.

No solution in my case, other then warning the user to this issue, since I can't programatically re-assign the properties without reading the assigned values first, which I can't since renderingAssets is empty. My materials do not come from IFC conversion.

But thanks for your input!!

 

Best Regards,

Sofia

Message 6 of 12

TripleM-Dev.net
Advisor
Advisor

Could be the material original also was created/duplicated by API and no asset was attached, or from a material library.

Or even a (very) old Revit style material

0 Likes
Message 7 of 12

sofia_feist
Enthusiast
Enthusiast

@TripleM-Dev.net 
I've been told that the family I'm using (attached in my original post above), that has the material with the empty Appearance Assets (Door - Architrave) is actually one of the default families that comes with Revit 2024 installation. So assuming this is a very old family, recycled between revit versions, it might be a very old material as well. 

0 Likes
Message 8 of 12

GaryOrrMBI
Collaborator
Collaborator

I haven't tried this via the API yet but, after checking the Appearance Asset Element and finding it to have a size of 0, try duplicating it and see if Revit creates a properly defined version of it, if so then you could assign the newly created Appearance Asset to the material (along with searching for and redefining any other materials that might be using it as well).

 

-G

Gary J. Orr
GaryOrrMBI (MBI Companies 2014-Current)
aka (past user names):
Gary_J_Orr (GOMO Stuff 2008-2014);
OrrG (Forum Studio 2005-2008);
Gary J. Orr (LHB Inc 2002-2005);
Orr, Gary J. (Gossen Livingston 1997-2002)
Message 9 of 12

TripleM-Dev.net
Advisor
Advisor

@sofia_feist, I just checked the 2024 revit project template "Default-Multi-discipline". It seems to have 22 materials without a appearance asset.

Like "Analytical Spaces", "Metal - Stainless Steel" etc..(others do have a appearance asset.)

 

Duplicating such material (duplicate incl. asset, shared asset not tried) will result in a material with a appearance asset (size > 0)

The original wil still miss it as nothing changed to that one.

 

Duplicating such material in the API will result in a material with no appearance asset, because the UI version adds it internally.

So that's what then also should be done in the API in you're addin.

How revit's UI creates the asses....well that's another topic, I think it depends on the material class and what maybe already exists in the project/family and/or some internal default assets.

0 Likes
Message 10 of 12

GaryOrrMBI
Collaborator
Collaborator
Accepted solution

@sofia_feist,

I went in and tried my suggestion.

It does work. here's the code in VB (where RDB = Revit.DB and RDV =Revit.DB.Visual):

 

	'Forum Issue resolution
	Public Sub ReplaceEmptyAppearanceAssets(thisDoc As RDB.Document)
		Dim printStr As String = "Invalid Assets: " & vbCrLf

		'collect document materials
		Dim matCollector As RDB.FilteredElementCollector = New RDB.FilteredElementCollector(thisDoc)
		Dim allMats As IList(Of RDB.Material) = matCollector.OfClass(GetType(RDB.Material)).OfType(Of RDB.Material).ToList
		'collect document appearance Asset Elements
		Dim assetCollector As New RDB.FilteredElementCollector(thisDoc)
		Dim allAppearanceAssets As IList(Of RDB.AppearanceAssetElement) = assetCollector.OfClass(GetType(RDB.AppearanceAssetElement)) _
			.OfType(Of RDB.AppearanceAssetElement).ToList
		'create a list of asset names in use
		Dim currentNames As New List(Of String)
		For Each appAsset As RDB.AppearanceAssetElement In allAppearanceAssets
			If currentNames.Contains(appAsset.Name) = False Then
				currentNames.Add(appAsset.Name)
			End If
		Next

		'prep for creating Asset if required
		'this takes a while on first run to expand the default library,
		'could be moved to seperate function and called if neede
		Dim assetList As List(Of RDV.Asset) = thisDoc.Application.GetAssets(RDV.AssetType.Appearance)
		Dim genericAsset As RDV.Asset = Nothing

		'really shouldn't start a transaction unless modification is needed...
		Using thisTrans As New RDB.Transaction(thisDoc, "Create new material")
			thisTrans.Start()

			Dim nameStr As String = ""

			'parse materials looking for invalid Appearance Assets
			Dim thisAssetElem As RDB.AppearanceAssetElement
			For Each thisMat As RDB.Material In allMats
				If thisMat.AppearanceAssetId <> RDB.ElementId.InvalidElementId Then
					Dim renderAssetElem As RDB.AppearanceAssetElement = TryCast(thisDoc.GetElement(thisMat.AppearanceAssetId), RDB.AppearanceAssetElement)

					If renderAssetElem IsNot Nothing Then
						'Check to see if it's fully defined
						'from API help
						'AppearanceAssetElement.GetRenderingAsset
						'The retrieved Asset may be empty if it is loaded from material library without any modification.
						'In this case, you can use Application.GetAssets(AssetType.Appearance) to load all preset appearance assets,
						'and retrieve the asset by its name.

						Dim thisAsset As RDV.Asset = renderAssetElem.GetRenderingAsset()
						If thisAsset.Size = 0 Then
							printStr += "Invalid Asset Size in Material: " & thisMat.Name & " - Asset: " & renderAssetElem.Name & vbCrLf
							genericAsset = assetList.FirstOrDefault(Function(eachAsset) eachAsset.Name = thisAsset.Name)
							'We could read the default properties directly from this genericAsset or
							'create new as duplicate

							'the following would be a seperate function due to replication
							nameStr = renderAssetElem.Name

							Dim numb As Integer = 1
							Dim testStr As String = nameStr

							Do While currentNames.Contains(testStr) = True
								testStr = nameStr & "_" & numb.ToString()
								numb = numb + 1
							Loop
							nameStr = testStr

							If genericAsset IsNot Nothing Then
								printStr += "  : Duplicating Asset :" & nameStr & vbCrLf
								Dim newAssetElem As RDB.AppearanceAssetElement = RDB.AppearanceAssetElement.Create(thisDoc, nameStr, genericAsset)
								thisMat.AppearanceAssetId = newAssetElem.Id
								currentNames.Add(nameStr)
							Else
								printStr += "  !! Could not aquire Asset !!" & vbCrLf
							End If
						End If
					Else
						printStr += "Cannot aquire Asset from Material: " & thisMat.Name & vbCrLf
					End If
				Else
					'from API help
					'Material.AppearanceAssetId
					'The id of the AppearanceAssetElement, or InvalidElementId if the material does not have an associated appearance asset.
					'This is the id to the element that contains visual material information used for rendering.
					'In some cases where the material is created without setting up custom render appearance properties
					'(for example, when the material is created via an import, or when it is created by the API),
					'this property will be InvalidElementId. In that situation the standard material properties
					'such as Color and Transparency will dictate the appearance of the material during rendering.

					printStr += "Invalid Asset ID in Material: " & thisMat.Name & vbCrLf
					'create new from existing generic

					'the following would be a seperate function due to replication
					nameStr = thisMat.Name
					Dim numb As Integer = 1
					Dim testStr As String = nameStr

					Do While currentNames.Contains(testStr) = True
						testStr = nameStr & "_" & numb.ToString()
						numb = numb + 1
					Loop
					nameStr = testStr

					genericAsset = assetList.FirstOrDefault(Function(eachAsset) eachAsset.FindByName(RDV.Generic.GenericDiffuse) IsNot Nothing)
					If genericAsset IsNot Nothing Then
						printStr += "  : Creating Asset :" & nameStr & vbCrLf
						Dim newAssetElem As RDB.AppearanceAssetElement = RDB.AppearanceAssetElement.Create(thisDoc, nameStr, genericAsset)
						thisMat.AppearanceAssetId = newAssetElem.Id
						currentNames.Add(nameStr)
					Else
						printStr += "  !! Could not aquire Asset from App !!" & vbCrLf
					End If

				End If
			Next

			thisTrans.Commit()
		End Using

		RUI.TaskDialog.Show("Material Info", printStr)

	End Sub

 

You will also notice a couple of comments taken from the API help that directly answer your original question of "Why is... empty..." and further to those that do not have a valid AppearanceAssetId

 

Running this code on your posted file found many materials with no Appearance Asset Element as well as many Assets with size=0. It creates valid Assets in both cases.

 

This was a good exercise, thanks for the question and I hope this helps you in some way.

 

-G

 

Gary J. Orr
GaryOrrMBI (MBI Companies 2014-Current)
aka (past user names):
Gary_J_Orr (GOMO Stuff 2008-2014);
OrrG (Forum Studio 2005-2008);
Gary J. Orr (LHB Inc 2002-2005);
Orr, Gary J. (Gossen Livingston 1997-2002)
Message 11 of 12

sofia_feist
Enthusiast
Enthusiast

Hello @GaryOrrMBI ,

Thank you for your answer and sample code! I tried as you suggested and it does indeed work wonderfully!
Thanks everyone for the suggestions and input!

Have a wonderful day,
Sofia

0 Likes
Message 12 of 12

GaryOrrMBI
Collaborator
Collaborator

Thank you @sofia_feist .

It could have been better of course, and it really should have two other additions to it:

1) When duplicating a 0 size asset, it should keep a record of which ones it creates a duplicate for and then check the remaining materials to see if they had that same original asset and simply assign the previously created duplicate asset to it as well.

2) When creating an asset for a material whose asset ID is InvalidElementId it could read the color and transparency values from the material properties and modify the created asset to have those values (which, of course, would require adding an AppearanceAssetEditScope to make those edits).

 

-G

Gary J. Orr
GaryOrrMBI (MBI Companies 2014-Current)
aka (past user names):
Gary_J_Orr (GOMO Stuff 2008-2014);
OrrG (Forum Studio 2005-2008);
Gary J. Orr (LHB Inc 2002-2005);
Orr, Gary J. (Gossen Livingston 1997-2002)
0 Likes