{"id":1765,"date":"2024-01-18T10:30:00","date_gmt":"2024-01-18T00:30:00","guid":{"rendered":"https:\/\/digitalbbq.au\/?p=1765"},"modified":"2024-01-22T07:51:11","modified_gmt":"2024-01-21T21:51:11","slug":"purging-material-assets-using-the-revit-api-the-right-way","status":"publish","type":"post","link":"https:\/\/digitalbbq.au\/index.php\/2024\/01\/18\/purging-material-assets-using-the-revit-api-the-right-way\/","title":{"rendered":"Purging Material Assets Using the Revit API the Right Way"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I was asked about a problematic Revit model with 57,000 purgeable material assets. &#8220;Just purge them&#8221;, you say, but the problem is that even using a shift+click to select the assets locked up the user&#8217;s Revit for around 30 minutes while Revit thought about its life choices.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"458\" height=\"439\" src=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image.png\" alt=\"\" class=\"wp-image-1769\" srcset=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image.png 458w, https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-300x288.png 300w\" sizes=\"auto, (max-width: 458px) 100vw, 458px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I had experience previously <a href=\"https:\/\/thebuildingcoder.typepad.com\/blog\/2018\/08\/purge-unused-using-performance-adviser.html\" data-type=\"link\" data-id=\"https:\/\/thebuildingcoder.typepad.com\/blog\/2018\/08\/purge-unused-using-performance-adviser.html\">purging elements<\/a> using the <a href=\"https:\/\/gitlab.com\/MattTaylor\/RevitPurgeUnused\" data-type=\"link\" data-id=\"https:\/\/gitlab.com\/MattTaylor\/RevitPurgeUnused\">performance advisor<\/a>, but that doesn&#8217;t purge materials. Harry Mattson has <a href=\"https:\/\/boostyourbim.wordpress.com\/2016\/10\/21\/purge-unused-materials-for-another-rtceur-api-wish\/\" data-type=\"link\" data-id=\"https:\/\/boostyourbim.wordpress.com\/2016\/10\/21\/purge-unused-materials-for-another-rtceur-api-wish\/\">posted a great code snippet on boostyourbim<\/a> that shows how to purge materials using a method that checks for errors on deletion; if the code encounters an error, it rolls back the transaction. Harry points out that it takes a bit of time, as it is deleting and undeleting all the materials within the project. When I tested this code on the Snowden Towers demo project that comes with Revit 2024, it took about 15 minutes from start to finish. It doesn&#8217;t purge material assets though, so unfortunately, it doesn&#8217;t do what I&#8217;m after. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then I stumbled across a great post on <a href=\"https:\/\/www.linkedin.com\/pulse\/why-revit-purge-command-act-love-via-api-emiliano-capasso\/\" data-type=\"link\" data-id=\"https:\/\/www.linkedin.com\/pulse\/why-revit-purge-command-act-love-via-api-emiliano-capasso\/\">LinkedIn by Emiliano Capasso<\/a> where he asked the question <em>&#8220;How does the eTransmit tool do it?&#8221;<\/em> his solution is to reference <em><strong>eTransmitForRevitDB.dll<\/strong> <\/em>in his application and call the public method <em><strong>eTransmitUpgradeOMatic<\/strong><\/em> which he does like so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">bool<\/span> <span class=\"hljs-title\">Purge<\/span>(<span class=\"hljs-params\">Application app, Document doc\r\n        {\r\n            eTransmitUpgradeOMatic eTransmitUpgradeOMatic = new eTransmitUpgradeOMatic(app<\/span>)<\/span>;\r\n            UpgradeFailureType result = eTransmitUpgradeOMatic.purgeUnused(doc);\r\n            <span class=\"hljs-keyword\">if<\/span> (result == UpgradeFailureType.UpgradeSucceeded) <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">true<\/span>;\r\n            <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">false<\/span>;\r\n        })<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">The problem with Emiliano&#8217;s method is that you don&#8217;t have control over what is purged, just like when you tick the purge unused button in eTransmit, it simply purges everything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It got me thinking: could I implement the same method used in eTransmit but with the ability to filter only the material assets? Turns out you can. A quick peek at the code in the DLL for Revit 2024 reveals that the purgeUnused the <a href=\"https:\/\/www.revitapidocs.com\/2024\/dcb1f497-dfa6-bb3a-b9dd-f9a580f990f2.htm\" data-type=\"link\" data-id=\"https:\/\/www.revitapidocs.com\/2024\/dcb1f497-dfa6-bb3a-b9dd-f9a580f990f2.htm\">GetUnusedElements method<\/a>, which is a new method introduced in the 2024 API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This led me to a quick implementation as a C# macro to help the user out in deleting their 57,000 unnamed material assets:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">ListAndPurgeUnusedMaterialAssets<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\r\n{\r\n    UIDocument uidoc = <span class=\"hljs-keyword\">this<\/span>.ActiveUIDocument;\r\n    Document doc = uidoc.Document;\r\n\r\n    <span class=\"hljs-comment\">\/\/ Get all unused elements in the document<\/span>\r\n    ISet&lt;ElementId&gt; unusedElementIds = doc.GetUnusedElements(<span class=\"hljs-keyword\">new<\/span> HashSet&lt;ElementId&gt;());\r\n\r\n    List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; unusedAssetNames = <span class=\"hljs-keyword\">new<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;();\r\n    List&lt;ElementId&gt; unusedAssetIds = <span class=\"hljs-keyword\">new<\/span> List&lt;ElementId&gt;();\r\n\r\n    <span class=\"hljs-comment\">\/\/ Process each unused element and filter for material assets and appearance assets<\/span>\r\n    <span class=\"hljs-keyword\">foreach<\/span> (ElementId elementId <span class=\"hljs-keyword\">in<\/span> unusedElementIds)\r\n    {\r\n        Element elem = doc.GetElement(elementId);\r\n        <span class=\"hljs-keyword\">if<\/span> (elem != <span class=\"hljs-literal\">null<\/span>)\r\n        {\r\n            <span class=\"hljs-comment\">\/\/ Check if the element is an AppearanceAssetElement<\/span>\r\n            <span class=\"hljs-keyword\">if<\/span> (elem <span class=\"hljs-keyword\">is<\/span> AppearanceAssetElement)\r\n            {\r\n                unusedAssetNames.Add(elem.Name + <span class=\"hljs-string\">\" (Appearance)\"<\/span>);\r\n                unusedAssetIds.Add(elementId);\r\n            }\r\n            <span class=\"hljs-comment\">\/\/ Check if the element is in the Material Assets category<\/span>\r\n            <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (elem.Category != <span class=\"hljs-literal\">null<\/span> &amp;&amp; elem.Category.Name == <span class=\"hljs-string\">\"Material Assets\"<\/span>)\r\n            {\r\n                unusedAssetNames.Add(elem.Name + <span class=\"hljs-string\">\" (Material Asset)\"<\/span>);\r\n                unusedAssetIds.Add(elementId);\r\n            }\r\n        }\r\n    }\r\n\r\n    <span class=\"hljs-comment\">\/\/ Sort the list of names for better readability<\/span>\r\n    unusedAssetNames.Sort();\r\n\r\n    <span class=\"hljs-comment\">\/\/ Display the names of unused assets in a Task Dialog<\/span>\r\n    <span class=\"hljs-keyword\">string<\/span> dialogContent = <span class=\"hljs-keyword\">string<\/span>.Join(Environment.NewLine, unusedAssetNames);\r\n    TaskDialog.Show(<span class=\"hljs-string\">\"Unused Assets\"<\/span>, dialogContent);\r\n\r\n    <span class=\"hljs-comment\">\/\/ Confirm with the user before deletion<\/span>\r\n    TaskDialogResult result = TaskDialog.Show(<span class=\"hljs-string\">\"Confirm Deletion\"<\/span>, <span class=\"hljs-string\">\"Are you sure you want to delete the following unused assets?\\n\\n\"<\/span> + dialogContent, TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No);\r\n\r\n    <span class=\"hljs-keyword\">if<\/span> (result == TaskDialogResult.Yes)\r\n    {\r\n        <span class=\"hljs-comment\">\/\/ Delete the unused assets<\/span>\r\n        <span class=\"hljs-keyword\">using<\/span> (Transaction t = <span class=\"hljs-keyword\">new<\/span> Transaction(doc, <span class=\"hljs-string\">\"Delete Unused Assets\"<\/span>))\r\n        {\r\n            t.Start();\r\n            <span class=\"hljs-keyword\">try<\/span>\r\n            {\r\n                doc.Delete(unusedAssetIds);\r\n                t.Commit();\r\n            }\r\n            <span class=\"hljs-keyword\">catch<\/span> (Exception ex)\r\n            {\r\n                <span class=\"hljs-comment\">\/\/ Handle the exception (could be element can't be deleted, etc.)<\/span>\r\n                t.RollBack();\r\n                TaskDialog.Show(<span class=\"hljs-string\">\"Error\"<\/span>, <span class=\"hljs-string\">\"An error occurred while deleting assets: \"<\/span> + ex.Message);\r\n            }\r\n        }\r\n    }\r\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This macro picks up all material assets and cross-checks their element IDs with those in the unused element ID list. It&#8217;s a tidy solution that gives full control of what you remove from the model.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"832\" height=\"1024\" src=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-2-832x1024.png\" alt=\"\" class=\"wp-image-1772\" srcset=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-2-832x1024.png 832w, https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-2-244x300.png 244w, https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-2-768x945.png 768w, https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-2.png 930w\" sizes=\"auto, (max-width: 832px) 100vw, 832px\" \/><figcaption class=\"wp-element-caption\">Before and after running the macro on the Snowden Towers example project<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">However, this method is new in the 2024 API, which doesn&#8217;t help if you work in Revit 2023 and below. Revit 2023 still has an eTransmit tool though, and that eTransmit tool still can purge the models. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If we take a look at the 2023 eTransmit DLL, the code calls the <em>GetUnusedAppearances, GetUnusedMaterials, GetUnusedFamilies, GetUnusedImportCategories, GetUnusedStructures, GetUnusedSymbols, GetUnusedThermals<\/em> and <em>GetNonDeletableUnusedElements<\/em> methods.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These methods aren&#8217;t documented parts of the API; they are private, so that means they won&#8217;t appear in the Visual Studio object browser either. However, using a decompiler like <a href=\"https:\/\/www.jetbrains.com\/decompiler\/\" data-type=\"link\" data-id=\"https:\/\/www.jetbrains.com\/decompiler\/\">JetBrains dotPeek<\/a> will reveal their presence.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"425\" height=\"493\" src=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-1.png\" alt=\"\" class=\"wp-image-1771\" srcset=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-1.png 425w, https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-1-259x300.png 259w\" sizes=\"auto, (max-width: 425px) 100vw, 425px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The problem is, if you attempt to access one of the GetUnused methods directly, you will be hit with the following error when you try to compile your code:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-small-font-size wp-block-paragraph\">&#8216;Autodesk.Revit.DB.Document&#8217; does not contain a definition for &#8216;GetUnusedAppearances&#8217; and no extension method &#8216;GetUnusedAppearances&#8217; accepting a first argument of type &#8216;Autodesk.Revit.DB.Document&#8217; could be found (are you missing a using directive or an assembly reference?)<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">So how can you use them? You can invoke the method using System.Reflection like so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-keyword\">using<\/span> System;\r\n<span class=\"hljs-keyword\">using<\/span> System.Reflection;\r\n<span class=\"hljs-keyword\">using<\/span> Autodesk.Revit.DB;\r\n\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">PurgeUnusedMaterialAssets<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\r\n{\r\n\tUIDocument uidoc = <span class=\"hljs-keyword\">this<\/span>.ActiveUIDocument;\r\n\tDocument doc = uidoc.Document;\r\n\t\r\n\t<span class=\"hljs-keyword\">try<\/span>\r\n\t{\r\n\t\t<span class=\"hljs-comment\">\/\/ Get the MethodInfo for the internal method<\/span>\r\n\t\tMethodInfo getUnusedAppearancesMethod = <span class=\"hljs-keyword\">typeof<\/span>(Document).GetMethod(<span class=\"hljs-string\">\"GetUnusedAppearances\"<\/span>, BindingFlags.NonPublic | BindingFlags.Instance);\r\n\t\t\r\n\t\t<span class=\"hljs-comment\">\/\/ Invoke the method and get the result<\/span>\r\n\t\t<span class=\"hljs-keyword\">if<\/span> (getUnusedAppearancesMethod != <span class=\"hljs-literal\">null<\/span>)\r\n\t\t{\r\n\t\t\tICollection&lt;ElementId&gt; unusedAppearances = (ICollection&lt;ElementId&gt;)getUnusedAppearancesMethod.Invoke(doc, <span class=\"hljs-literal\">null<\/span>);\r\n\t\t\t\r\n\t\t\t<span class=\"hljs-comment\">\/\/ Now you have the unused appearances and can proceed to delete them<\/span>\r\n\t\t\t<span class=\"hljs-comment\">\/\/ Similar reflection calls would need to be made for the other GetUnused... methods<\/span>\r\n\t\t}\r\n\t}\r\n\t<span class=\"hljs-keyword\">catch<\/span> (Exception ex)\r\n\t{\r\n\t\t<span class=\"hljs-comment\">\/\/ Handle exceptions<\/span>\r\n\t\tTaskDialog.Show(<span class=\"hljs-string\">\"Error\"<\/span>, <span class=\"hljs-string\">\"An error occurred: \"<\/span> + ex.Message);\r\n\t}\r\n}\r\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This solution for purging model content using the API provides the same level of granularity as the new Revit 2024 API method; there are just a few extra steps to run through each GetUnused method. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"930\" height=\"646\" src=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-3.png\" alt=\"\" class=\"wp-image-1776\" srcset=\"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-3.png 930w, https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-3-300x208.png 300w, https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/image-3-768x533.png 768w\" sizes=\"auto, (max-width: 930px) 100vw, 930px\" \/><figcaption class=\"wp-element-caption\">Before and after running the macro on the RAC_basic_sample_project.rvt example project in Revit 2023<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Here is my streamlined version of the 2023 macro, which specifically looks to purge material assets:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"C#\" data-shcb-language-slug=\"cs\"><span><code class=\"hljs language-cs\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> ICollection&lt;ElementId&gt; <span class=\"hljs-title\">GetUnusedAssets<\/span>(<span class=\"hljs-params\">Document doc, <span class=\"hljs-keyword\">string<\/span> methodName<\/span>)<\/span>\r\n{\r\n\tMethodInfo method = <span class=\"hljs-keyword\">typeof<\/span>(Document).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);\r\n\t<span class=\"hljs-keyword\">if<\/span> (method != <span class=\"hljs-literal\">null<\/span>)\r\n\t{\r\n\t\t<span class=\"hljs-keyword\">return<\/span> (ICollection&lt;ElementId&gt;)method.Invoke(doc, <span class=\"hljs-literal\">null<\/span>);\r\n\t}\r\n\t<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> List&lt;ElementId&gt;();\r\n}\r\n\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">AddUnusedAssets<\/span>(<span class=\"hljs-params\">Document doc, ICollection&lt;ElementId&gt; elementIds, <span class=\"hljs-keyword\">string<\/span> assetType, List&lt;<span class=\"hljs-keyword\">string<\/span>&gt; names, List&lt;ElementId&gt; ids<\/span>)<\/span>\r\n{\r\n\t<span class=\"hljs-keyword\">foreach<\/span> (<span class=\"hljs-keyword\">var<\/span> id <span class=\"hljs-keyword\">in<\/span> elementIds)\r\n\t{\r\n\t\tElement elem = doc.GetElement(id);\r\n\t\t<span class=\"hljs-keyword\">if<\/span> (elem != <span class=\"hljs-literal\">null<\/span>)\r\n\t\t{\r\n\t\t\tnames.Add(elem.Name + <span class=\"hljs-string\">\" (\"<\/span> + assetType + <span class=\"hljs-string\">\")\"<\/span>);\r\n\t\t\tids.Add(id);\r\n\t\t}\r\n\t}\r\n}\r\n\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">PurgeUnusedMaterialAssets<\/span>(<span class=\"hljs-params\"><\/span>)<\/span>\r\n{\r\n\tUIDocument uidoc = <span class=\"hljs-keyword\">this<\/span>.ActiveUIDocument;\r\n\tDocument doc = uidoc.Document;\r\n\t\r\n\tList&lt;<span class=\"hljs-keyword\">string<\/span>&gt; unusedAssetNames = <span class=\"hljs-keyword\">new<\/span> List&lt;<span class=\"hljs-keyword\">string<\/span>&gt;();\r\n\tList&lt;ElementId&gt; unusedAssetIds = <span class=\"hljs-keyword\">new<\/span> List&lt;ElementId&gt;();\r\n\r\n\t<span class=\"hljs-keyword\">try<\/span>\r\n\t{\r\n\t\t<span class=\"hljs-comment\">\/\/ Retrieve unused assets using reflection<\/span>\r\n\t\tAddUnusedAssets(doc, GetUnusedAssets(doc, <span class=\"hljs-string\">\"GetUnusedMaterials\"<\/span>), <span class=\"hljs-string\">\"Material\"<\/span>, unusedAssetNames, unusedAssetIds);\r\n\t\tAddUnusedAssets(doc, GetUnusedAssets(doc, <span class=\"hljs-string\">\"GetUnusedAppearances\"<\/span>), <span class=\"hljs-string\">\"Appearance\"<\/span>, unusedAssetNames, unusedAssetIds);\r\n\t\tAddUnusedAssets(doc, GetUnusedAssets(doc, <span class=\"hljs-string\">\"GetUnusedStructures\"<\/span>), <span class=\"hljs-string\">\"Structure\"<\/span>, unusedAssetNames, unusedAssetIds);\r\n\t\tAddUnusedAssets(doc, GetUnusedAssets(doc, <span class=\"hljs-string\">\"GetUnusedThermals\"<\/span>), <span class=\"hljs-string\">\"Thermal\"<\/span>, unusedAssetNames, unusedAssetIds);\r\n\r\n\t\t<span class=\"hljs-comment\">\/\/ Sort the list of names for better readability<\/span>\r\n\t\tunusedAssetNames.Sort();\r\n\r\n\t\t<span class=\"hljs-comment\">\/\/ Display the list to the user and ask for confirmation before deletion<\/span>\r\n\t\t<span class=\"hljs-keyword\">string<\/span> dialogContent = <span class=\"hljs-keyword\">string<\/span>.Join(Environment.NewLine, unusedAssetNames);\r\n\t\tTaskDialogResult result = TaskDialog.Show(<span class=\"hljs-string\">\"Confirm Deletion\"<\/span>, <span class=\"hljs-string\">\"Are you sure you want to delete the following unused assets?\\n\\n\"<\/span> + dialogContent, TaskDialogCommonButtons.Yes | TaskDialogCommonButtons.No);\r\n\r\n\t\t<span class=\"hljs-keyword\">if<\/span> (result == TaskDialogResult.Yes)\r\n\t\t{\r\n\t\t\t<span class=\"hljs-comment\">\/\/ Delete the unused assets within a transaction<\/span>\r\n\t\t\t<span class=\"hljs-keyword\">using<\/span> (Transaction t = <span class=\"hljs-keyword\">new<\/span> Transaction(doc, <span class=\"hljs-string\">\"Delete Unused Assets\"<\/span>))\r\n\t\t\t{\r\n\t\t\t\tt.Start();\r\n\t\t\t\tdoc.Delete(unusedAssetIds);\r\n\t\t\t\tt.Commit();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t<span class=\"hljs-keyword\">catch<\/span> (Exception ex)\r\n\t{\r\n\t\tTaskDialog.Show(<span class=\"hljs-string\">\"Error\"<\/span>, <span class=\"hljs-string\">\"An error occurred: \"<\/span> + ex.Message);\r\n\t}\r\n\r\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">C#<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">cs<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\t\t\t<input type=\"hidden\" id=\"wplinkpress-user-email\" value=\"\" \/>\n<input type=\"hidden\" id=\"wplinkpress-authorize-url\" value=\"https:\/\/www.linkedin.com\/oauth\/v2\/authorization?response_type=code&client_id=86vyqs2786ll59&redirect_uri=https%3A%2F%2Fdigitalbbq.au%2Fauthorize-linkedin%2F&state=https%3A%2F%2Fdigitalbbq.au%2Fwp-json%2Fwp%2Fv2%2Fposts%2F1765&scope=openid%20profile%20email%20w_member_social\" \/>\n<input type=\"hidden\" id=\"wplinkpress-post-id\" value=\"1765\" \/>\n<div class=\"ui wplinkpress comments\">\n<h3 class=\"ui dividing header\">Comments<\/h3>\n<form id=\"add-wplinkpress-comment\" method=\"POST\"> \n<div class=\"comment add-comment\">\n\t<a class=\"avatar\"><img src=\"https:\/\/digitalbbq.au\/wp-content\/plugins\/wplinkpress\/assets\/media\/non-user-icon.jpg\" \/><\/a>\n\t<div class=\"content\">\n\t<textarea id=\"wplinkpress-comment-text-0\" class=\"wplinkpress-comment-text\" style=\"width:100%;\" placeholder=\"Add a comment...\"><\/textarea>\n\t<div class=\"bottom-layer\">\n\t\t<div class=\"comment-atts\" style=\"float:left;\">\n\t\t\t\t<div class=\"feed-share\">\n\t\t<label class=\"switch tips\">\n\t\t\t<input type=\"checkbox\" id=\"toggle-linkedin-feed\" >\n        \t<span class=\"slider round\"><\/span>\n\t\t<\/label>\n\t\t<span>Share on activity feed<\/span>\n\t\t<\/div>\n\t\t<\/div>\n\t<div class=\"wplinkpress_buttons\">\n\t\t<button id=\"authorize_comment_0\" class=\"authorize_comment\" disabled=\"disabled\">Post with LinkedIn<\/button>\n\t\t<\/div>\n\t<\/div>\n\t<\/div>\n<\/div>\n<\/form>\n<h3 class=\"ui dividing header\"><span class=\"wplinkpress-brand\">Powered by WP LinkPress<\/span><\/h3>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>I was asked about a problematic Revit model with 57,000 purgeable material assets. &#8220;Just purge them&#8221;, you say, but the problem is that even using a shift+click to select the assets locked up the user&#8217;s Revit for around 30 minutes while Revit thought about its life choices. I had experience previously purging elements using the performance advisor, but that doesn&#8217;t&#8230;<\/p>\n","protected":false},"author":1,"featured_media":1775,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"Learn to purge material assets in Revit with our guide on using undocumented API methods for efficient BIM management.","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"jetpack_seo_schema_type":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"Discover how to purge material assets using the Revit API in my latest blog post. I explore undocumented methods and practical solutions for efficient project management. Read more at digitalBBQ.au #RevitAPI #BIM #DigitalBBQ","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[1,78],"tags":[126,127,73,128],"class_list":["post-1765","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-digital","category-how-to-guides","tag-coding","tag-macro","tag-process","tag-revit"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/digitalbbq.au\/wp-content\/uploads\/2024\/01\/cleaning.jpg","jetpack-related-posts":[{"id":2595,"url":"https:\/\/digitalbbq.au\/index.php\/2026\/03\/26\/revit-cloud-model-upgrades-part-3-the-pre-upgrade-checklist-that-actually-prevents-pain\/","url_meta":{"origin":1765,"position":0},"title":"Revit Cloud Model Upgrades Part 3 &#8211; The Pre\u2011Upgrade Checklist That Actually Prevents Pain","author":"Ryan Lenihan","date":"26 March 2026","format":false,"excerpt":"Most upgrade failures don\u2019t happen during the upgrade. They happen six months earlier when everyone was \u201cjust trying to get drawings out the door\u201d. By the time you hit Upgrade, the Revit upgrade process is simply showing you the consequences. This is the checklist I run before pushing a single\u2026","rel":"","context":"In &quot;Digital&quot;","block_context":{"text":"Digital","link":"https:\/\/digitalbbq.au\/index.php\/category\/digital\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2026\/03\/checklist_sm.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2026\/03\/checklist_sm.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2026\/03\/checklist_sm.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2026\/03\/checklist_sm.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2026\/03\/checklist_sm.jpg?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2026\/03\/checklist_sm.jpg?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":2275,"url":"https:\/\/digitalbbq.au\/index.php\/2025\/01\/23\/reflection-and-goals-for-2025\/","url_meta":{"origin":1765,"position":1},"title":"Reflection and Goals for 2025","author":"Ryan Lenihan","date":"23 January 2025","format":false,"excerpt":"Stepping into 2025, I\u2019ve been reflecting on the content from last year here on digitalbbq. It\u2019s been a year of exploration, learning, and sharing insights, but admittedly, I also hit the pause button for a bit. I decided to take time off for the \u201csilly season\u201d \u2014 a term I\u2026","rel":"","context":"In &quot;Personal&quot;","block_context":{"text":"Personal","link":"https:\/\/digitalbbq.au\/index.php\/category\/personal\/"},"img":{"alt_text":"New Year holidays event background, 2025 numbers","src":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/new-year-holidays-event-background-2025-numbers.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/new-year-holidays-event-background-2025-numbers.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/new-year-holidays-event-background-2025-numbers.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/new-year-holidays-event-background-2025-numbers.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/new-year-holidays-event-background-2025-numbers.jpg?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/new-year-holidays-event-background-2025-numbers.jpg?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":2409,"url":"https:\/\/digitalbbq.au\/index.php\/2025\/07\/31\/dynamo-vs-revit-api-choosing-the-right-tool-for-your-bim-automation-needs\/","url_meta":{"origin":1765,"position":2},"title":"Dynamo vs. Revit API. Choosing the Right Tool for Your BIM Automation Needs","author":"Ryan Lenihan","date":"31 July 2025","format":false,"excerpt":"A recent post on the r\/BIM subreddit sparked an interesting discussion: should you learn Dynamo or the Revit API for automating tasks in Revit? As a BIM professional, you're likely no stranger to the quest for efficiency and automation in your workflows. But with multiple tools at your disposal, it\u2026","rel":"","context":"In &quot;Digital&quot;","block_context":{"text":"Digital","link":"https:\/\/digitalbbq.au\/index.php\/category\/digital\/"},"img":{"alt_text":"Programmer working with program code","src":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/07\/programmer-working-with-program-code.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/07\/programmer-working-with-program-code.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/07\/programmer-working-with-program-code.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/07\/programmer-working-with-program-code.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/07\/programmer-working-with-program-code.jpg?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/07\/programmer-working-with-program-code.jpg?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":2322,"url":"https:\/\/digitalbbq.au\/index.php\/2025\/02\/13\/automating-metadata-cleanup-in-revit-a-custom-live-updater\/","url_meta":{"origin":1765,"position":3},"title":"Automating Metadata Cleanup in Revit \u2013 A Custom Live Updater","author":"Ryan Lenihan","date":"13 February 2025","format":false,"excerpt":"When working in Revit, copied elements will inherit instance parameter values that should be unique\u2014room numbers, tracking codes, asset IDs, or any other metadata that doesn\u2019t logically carry over. Problem: Copying elements can introduce data inconsistencies if certain instance parameters retain values they shouldn\u2019t. Solution: A Revit API Updater that\u2026","rel":"","context":"In &quot;Digital&quot;","block_context":{"text":"Digital","link":"https:\/\/digitalbbq.au\/index.php\/category\/digital\/"},"img":{"alt_text":"Vintage computer from the seventies representing retro technology and innovation","src":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/vintage-computer-from-the-seventies-representing-retro-technology-and-innovation.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/vintage-computer-from-the-seventies-representing-retro-technology-and-innovation.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/vintage-computer-from-the-seventies-representing-retro-technology-and-innovation.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/vintage-computer-from-the-seventies-representing-retro-technology-and-innovation.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/vintage-computer-from-the-seventies-representing-retro-technology-and-innovation.jpg?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/01\/vintage-computer-from-the-seventies-representing-retro-technology-and-innovation.jpg?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":1828,"url":"https:\/\/digitalbbq.au\/index.php\/2024\/02\/22\/revit-bulk-unit-conversion-addin\/","url_meta":{"origin":1765,"position":4},"title":"Revit Bulk Unit Conversion Addin","author":"Ryan Lenihan","date":"22 February 2024","format":false,"excerpt":"Journal scripting once offered a quick fix for converting Revit model and family units; it helped speed up the process, but it had the habit of failing on certain family types or dialogue boxes. To tackle this, I developed an add-in, which I shared on revit.com.au; which both overcame the\u2026","rel":"","context":"In &quot;Digital&quot;","block_context":{"text":"Digital","link":"https:\/\/digitalbbq.au\/index.php\/category\/digital\/"},"img":{"alt_text":"Tape measure with numbers","src":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2024\/02\/tape-measure-with-numbers.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2024\/02\/tape-measure-with-numbers.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2024\/02\/tape-measure-with-numbers.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2024\/02\/tape-measure-with-numbers.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2024\/02\/tape-measure-with-numbers.jpg?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2024\/02\/tape-measure-with-numbers.jpg?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":2601,"url":"https:\/\/digitalbbq.au\/index.php\/2026\/03\/31\/bim-cache-cleaner-3-5-3-update\/","url_meta":{"origin":1765,"position":5},"title":"BIM Cache Cleaner 3.5.3 Update","author":"Ryan Lenihan","date":"31 March 2026","format":false,"excerpt":"Revit 2026 changed how cloud model caches can be configured, and BIM Cache Cleaner wasn\u2019t picking that up properly. In Revit.ini, Revit now allows the cloud cache to be redirected using: [CloudModelCache] CacheLocation=... On a few machines I tested, [Directories] ProjectPath was empty, but CacheLocation was set. BIM Cache Cleaner\u2026","rel":"","context":"In &quot;Digital&quot;","block_context":{"text":"Digital","link":"https:\/\/digitalbbq.au\/index.php\/category\/digital\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/10\/bim_cleaner.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/10\/bim_cleaner.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/10\/bim_cleaner.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/10\/bim_cleaner.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/digitalbbq.au\/wp-content\/uploads\/2025\/10\/bim_cleaner.jpg?resize=1050%2C600&ssl=1 3x"},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/posts\/1765","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/comments?post=1765"}],"version-history":[{"count":8,"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/posts\/1765\/revisions"}],"predecessor-version":[{"id":1778,"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/posts\/1765\/revisions\/1778"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/media\/1775"}],"wp:attachment":[{"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/media?parent=1765"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/categories?post=1765"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/digitalbbq.au\/index.php\/wp-json\/wp\/v2\/tags?post=1765"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}