Set a material for a new wall type

Hi All,

I’ve written the code below to iterate over all materials within the document. I grabbed the material named Béton, coulé sur place with the ID 183853 , which I’m trying to use as a layer material in my new wall type. However, I’m encountering the following error related to the MaterialId ."

Exception : Microsoft.Scripting.ArgumentTypeException: expected Reference, got Int32

import math
from Autodesk.Revit.DB import *
from System.Collections.Generic import IList, List
uidoc = __revit__.ActiveUIDocument
doc = uidoc.Document
t = Transaction(doc, 'create New wall type')

# getting the first wall type within the document
first_wall_type = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls).WhereElementIsElementType().FirstElement()

# looking for the material named "Béton, coulé sur place " in the document
mat_Ids = FilteredElementCollector(doc).OfClass(Material).ToElementIds()

for m in mat_Ids:
    mat = doc.GetElement(m)
    if mat.Name == "Béton, coulé sur place":
        mid = m

c = 1/0.3048

# Starting transaction

t.Start()

voile_BA_15 = first_wall_type.Duplicate('voile-BA_15')

cs = voile_BA_15.GetCompoundStructure()
Thick_wall = 0.20*c

mat = doc.GetElement(mid)

cl = CompoundStructureLayer()
cl.Width = Thick_wall
cl.MaterialId = mat.Id
cs.SetLayeres.LIST[CompoundStructureLayer](cl)
voile_BA_15.SetCompoundStructure(cs)

t.Commit()

Any help would be appreciated

Thanks.

@Jean-Marc
Have you any idea how to solve this issue?

Thanks.

This code generally works in my pyRevit build. There were some errors in code like: cs.SetLayeres.LIST[CompoundStructureLayer](cl) or other bugs like retrieving Curtain Wall as first element from FilteredElementCollector.

As I’m not using Revit Python Shell (just regular pyRevit :slight_smile:) it’s hard for me to pinpoint the issue but my guess would be that for some reason you are getting integer values of element ids instead references. To make sure, you should debug your code by simply printing types of suspicious elements and solving issues step by step. You can also try to convert integer values to element id by using DB.ElementId(integer_value).

2 Likes

@MarcinTalipski @Jean-Marc

hopefully correctly setting the CompoundStructureLayer of the wall type I made some changes in this part of code :

voile_BA_15 = first_wall_type.Duplicate('voile-BA_15')
	
cs = voile_BA_15.GetCompoundStructure()
Thick_wall = 0.20*c

for structLayer in cs.GetLayers():
	cs.SetMaterialId(structLayer.LayerId, mat.Id)
	structLayer.Width = Thick_wall
voile_BA_15.SetCompoundStructure(cs)

However, after debugging the code, I received the following error:

Exception : System.MissingMemberException: ‘NoneType’ object has no attribute ‘GetLayers’

I discovered that the first_wall_type is a curtain wall, which does not seem to have a CompoundStructureLayer. In this case, I need to apply a filter that selects only the Basic Wall kind, but I’m struggling with how to do that.

I tried this syntax for the filter but it does not work!!

filter = ElementCategoryFilter(BuiltInCategory.OST_Walls)
First_wall_types = FilteredElementCollector(doc).WherePasses(filter).WhereElementIsElementType().First(lambda x : x.Kind == WallKind.Basic)

I’m getting this error:

Exception : System.MissingMemberException: ‘FilteredElementCollector’ object has no attribute ‘First’

Thanks.

^^^yes like he said you should print all your variables while you are debugging

and you are mixing C# linq with ironpython. Python could use next() function

You will have to translate to python, but Jeremy Tammik has some good examples in the Revit SDK for working with walls and materials.
Revit SDK example

The message is quite explicit and when you go through the members of the filteredElementCollector class in the revit API, quickly you find the right attribute

1 Like

@Jean-Marc

I know that, to get the first element in FilteredElementCollector I should use the syntax: FirstElement() and I’ve used it in my first script above, but as I said in my previous post, I was unable to GetLayers for the CompoundStructureLayer because the first Wall Type in the FilteredElementCollector is a Curtain Wall, for that I should use another filter to get Basic Wall Kind in my filtered elements, for that I used a sample code I found in dynamobim forum which look for a specific wall type name, but I dont know why id doesn’t work!! (Maybe I should import its module or function)

Thanks.

@MarcinTalipski
I finally solved my issue and tested my code by creating walls, and it works as shown in the image below. However, I want to change the material’s physical characteristics as described in the image. Can you guide me on how to do that?

Here my final code:

import math
from Autodesk.Revit.DB import *
from System.Collections.Generic import IList, List
uidoc = __revit__.ActiveUIDocument

doc = uidoc.Document


t = Transaction(doc, 'create Manhole')


wall_types = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Walls).WhereElementIsElementType().ToElements()

mat_Ids = FilteredElementCollector(doc).OfClass(Material).ToElementIds()

levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels).ToElements()

types = []
for w in wall_types:
	if str(getattr(w, 'Kind')) == 'Basic':
		types.append(w)

for m in mat_Ids:
    mat = doc.GetElement(m)
    if mat.Name == "Béton, coulé sur place": 
        mid = m
        
for l in levels:
    lvl_Name = l.Name
    if lvl_Name == "Niveau 0":
        lvl_Id = l.Id
        
# Create Points and Lines:

c = 1/0.3048
Thick_wall = 0.20
Pt0 = XYZ(-(0.60 + Thick_wall/2)*c,-(0.60 + Thick_wall/2)*c,0)
Pt1 = XYZ((0.60 + Thick_wall/2)*c,-(0.60 + Thick_wall/2)*c,0)
Pt2 = XYZ((0.60 + Thick_wall/2)*c, (0.60 + Thick_wall/2)*c,0)
Pt3 = XYZ(-(0.60 + Thick_wall/2)*c,(0.60 + Thick_wall/2)*c,0)

Lines = []
Line1 = Line.CreateBound(Pt0, Pt1)
Lines.append(Line1)
Line2 = Line.CreateBound(Pt1, Pt2)
Lines.append(Line2)
Line3 = Line.CreateBound(Pt2, Pt3)
Lines.append(Line3)
Line4 = Line.CreateBound(Pt3, Pt0)
Lines.append(Line4)

t.Start()
# Create new wall type and its material:
voile_BA_15 = types[0].Duplicate('voile-BA_15')
	
cs = voile_BA_15.GetCompoundStructure()


for structLayer in cs.GetLayers():
	cs.SetLayerFunction(structLayer.LayerId, MaterialFunctionAssignment.Structure)
	cs.SetMaterialId(structLayer.LayerId, mid)
	structLayer.Width = Thick_wall*c
voile_BA_15.SetCompoundStructure(cs)

# Create walls:
for l in Lines:
    Wall = Wall.Create(doc, l, voile_BA_15.Id, lvl_Id, c*2.00,0.00, False, True)

t.Commit()

Thanks.

I was about to send you a cleaned up working version.
it changes all the layers material to the same material

# -*- coding: utf-8 -*-
from pyrevit import revit, DB, script
doc = revit.DOCS.doc
output = script.get_output().close_others()

WALL_TYPE_NAME = "SIP 202mm Wall - conc clad"
TYPENAME_PARAM_NAME = 'Nom du type'
WALL_TYPE = "Mur de base"
DUPLICTED_NAME = "voile-BA_15"
C = 1/0.3048

wall_types = DB.FilteredElementCollector(doc).OfCategory(DB.BuiltInCategory.OST_Walls).WhereElementIsElementType()

for wt in wall_types:
    if wt.FamilyName == WALL_TYPE and wt.LookupParameter(TYPENAME_PARAM_NAME).AsString() == WALL_TYPE_NAME:
        first_wall_type = wt
        break

mat_Ids = DB.FilteredElementCollector(doc).OfClass(DB.Material).ToElements()

for mat in mat_Ids:
    if mat.Name == "Béton, coulé sur place":
        material = mat
        break


with revit.Transaction('Change Wall Type'):
    duplicated_wall = first_wall_type.Duplicate(DUPLICTED_NAME)
    cs = duplicated_wall.GetCompoundStructure()
    thickness = 0.20*C
    for structLayer in cs.GetLayers():
        cs.SetLayerFunction(structLayer.LayerId, DB.MaterialFunctionAssignment.Structure)
        cs.SetMaterialId(structLayer.LayerId, material.Id)
        structLayer.Width = thickness*C
    duplicated_wall.SetCompoundStructure(cs)
2 Likes

For declaring material properties for structural analysis, you will need to access material’s StructuralAssetId property. From there you will be able modify each parameter or assign new structural asset

Tahnks @Jean-Marc…praticaly I used the same approch as you :wink:

@MarcinTalipski @Jean-Marc

To enhance my code I should use a concrete structural material named (Béton - Coulé sur place - Béton20) which I can use its properties such as (concrete compression , yong modulus)…I notice That I’m working with an architecture template, and the material I’m looking for is listed in the structural template, I attempted to retrieve this material using the following code, but I encountered the following error (I’m sure that this material is not listed in the architecture template):

Exception : Autodesk.Revit.Exceptions.ArgumentException: The given value for name is already in use as a material element name.
Parameter name: name

materials = FilteredElementCollector(doc).WherePasses(ElementCategoryFilter(BuiltInCategory.OST_Materials))

t = Transaction(doc, 'create material')

t.Start()
for m in materials:
    if m.Name == "Béton - Coulé sur place - Béton20":
        mat = m
        mat_id = mat.Id
    else:
        mat_id = Material.Create(doc, "Béton - Coulé sur place - Béton20")
        mat = doc.GetElement(mat_id)
t.Commit()

This code will work only if you have exactly one material in your project.

1 Like

@MarcinTalipski, @Jean-Marc

I finaly create the material, but I can’t set the “concrete compression” property to be equal to (20 MPa); it remains unchanged at (0.1 MPa) as shown in the image below

As far as I know, the internal unit for pressure in Revit is N/ft² . I used a formula to convert the unit to MPa , but I don’t know what’s wrong with my code

Here my code:

import math
from Autodesk.Revit.DB import *
from System.Collections.Generic import IList, List
uidoc = __revit__.ActiveUIDocument

doc = uidoc.Document


materials = FilteredElementCollector(doc).WherePasses(ElementCategoryFilter(BuiltInCategory.OST_Materials))

material = [i for i in materials if i.Name == "Béton - Coulé sur place - Béton20"]

# converting unit from **`N/ft²`**  to MPA
c = 1/92903.04
if len(material) > 0:
    mat = m
    m_Id = mat.Id
else:
    t = Transaction(doc, 'Get/create concrete material')
    t.Start()
    m_Id = Material.Create(doc, "Béton - Coulé sur place - Béton20")
    mat = doc.GetElement(m_Id)
    mat.MaterialClass = "Concrete"
    asset = StructuralAsset("b", StructuralAssetClass.Concrete)
    asset.ConcreteCompression = 20*c
    struc = PropertySetElement.Create(doc, asset)
    mat.SetMaterialAspectByPropertySet(MaterialAspect.Structural, struc.Id)
    t.Commit()

Any help would be appreciated.

Thanks.

You should try to get into unit conversion utils in the Revit API.

This might help as well (and needs some love)
https://docs.pyrevitlabs.io/reference/pyrevit/revit/units/#pyrevit.revit.units.project_to_viewport

I made a mistake in the unit conversion, but I corrected it, and here is my final code

import math
from Autodesk.Revit.DB import *
from System.Collections.Generic import IList, List
uidoc = __revit__.ActiveUIDocument

doc = uidoc.Document


materials = FilteredElementCollector(doc).WherePasses(ElementCategoryFilter(BuiltInCategory.OST_Materials))

material = [i for i in materials if i.Name == "Béton - Coulé sur place - Béton20"]

if len(material) > 0:
    mat = m
    m_Id = mat.Id
else:
    t = Transaction(doc, 'Get/create concrete material')
    t.Start()
    m_Id = Material.Create(doc, "Béton - Coulé sur place - Béton20")
    mat = doc.GetElement(m_Id)
    mat.MaterialClass = "Concrete"
    asset = StructuralAsset("ConcreteStructuralAsset", StructuralAssetClass.Concrete)
    asset.ConcreteCompression = UnitUtils.ConvertToInternalUnits(20, UnitTypeId.Megapascals)
    asset.Density = UnitUtils.ConvertToInternalUnits(2500, UnitTypeId.KilogramsPerCubicMeter)
    asset.SetPoissonRatio(0.2)
    asset.SetYoungModulus(UnitUtils.ConvertToInternalUnits(11000*20**(1/3), UnitTypeId.Megapascals))
    struc = PropertySetElement.Create(doc, asset)
    mat.SetMaterialAspectByPropertySet(MaterialAspect.Structural, struc.Id)
    t.Commit()

Thanks for All.

1 Like