create and assign subcategories in revit using python
Asked Answered
H

1

6

I have a question for some of you who are familiar with the Revit API and python:

I’ve been using the spring nodes package in dynamo to create a rather large series of freeform objects each in their own family. The way that the FamilyInstance.ByGeometry works, it takes a list of solids and creates a family instance for each using a template family file. The result is quite good. (spring nodes can be found here: https://github.com/dimven/SpringNodes)

However, the drawback is that that now I have roughly 200 separate instances, so to make changes to each is rather painful. I thought at first it would be possible to use dynamo to create a new subcategory and set the solid inside each family instance to this new subcategory. Unfortunately, I realized this is not possible since dynamo cannot be open in two different Revit environments simultaneously (the project I am working in and each instance of the family). This leads me to look to see if I can do this using python.

I have used python in rhino and can get along pretty well, I am still learning the Revit API however. But basically my idea would be to: 1. select a series of family instances in the Revit project environment 2. loop through each instance 3. save it to a specified location 4. create a new subcategory in each family instances (the subcategory would the same for all the selected family instances) 5. select the solid in each in instance 6. set the solid to this newly created subcategory 7. close the family instance and save

My question for you is does this sound like it is achievable based on your knowledge of the Revit API?

Many thanks for your time and advice.


UPDATE:

I've found a section in the revit api that describes what i'm looking to do: http://help.autodesk.com/view/RVT/2015/ENU/?guid=GUID-FBF9B994-ADCB-4679-B50B-2E9A1E09AA48

I've made a first pass at inserting this into the python code of the dynamo node. The rest of the code works fine except for the new section im adding (see below). Please excuse the variables, I am simply keeping with logic of the original author of the code i am hacking:

(Note: the variables come in are in arrays)

#set subcategory    
try:
     #create new sucategory
     fam_subcat = famdoc.Settings.Categories.NewSubcategory(fam_cat, get_Item(subcat1.Name))                

     #assign the mataterial(fam_mat.Id) to the subcategory
     fam_subcat.Material = famdoc.GetElement(fam_mat.Id)

     #assign the subcategory to the element (s2)
     s2.Subcategory = fam_subcat
except: pass

Any help or advice with this section of code would be much appreciated.


UPDATE: See full code below for context of the section in question:

#Copyright(c) 2015, Dimitar Venkov
# @5devene, [email protected]

import clr
import System
from System.Collections.Generic import *

pf_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
import sys
sys.path.append("%s\IronPython 2.7\Lib" %pf_path)
import traceback

clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
app = DocumentManager.Instance.CurrentUIApplication.Application

clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import StructuralType

clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

def tolist(obj1):
    if hasattr(obj1,"__iter__"): return obj1
    else: return [obj1]

def output1(l1):
    if len(l1) == 1: return l1[0]
    else: return l1

def PadLists(lists):
    len1 = max([len(l) for l in lists])
    for i in xrange(len(lists)):
        if len(lists[i]) == len1:
            continue
        else:
            len2 = len1 - len(lists[i])
            for j in xrange(len2):
                lists[i].append(lists[i][-1])
    return lists

class FamOpt1(IFamilyLoadOptions):
    def __init__(self):
        pass
    def OnFamilyFound(self,familyInUse, overwriteParameterValues):
        return True
    def OnSharedFamilyFound(self,familyInUse, source, overwriteParameterValues):
        return True

geom = tolist(IN[0])
fam_path = IN[1]
names = tolist(IN[2])
category = tolist(IN[3])
material = tolist(IN[4])
isVoid = tolist(IN[5])
subcategory = tolist(IN[6])

isRvt2014 = False
if app.VersionName == "Autodesk Revit 2014": isRvt2014 = True
units = doc.GetUnits().GetFormatOptions(UnitType.UT_Length).DisplayUnits
factor = UnitUtils.ConvertToInternalUnits(1,units)
acceptable_views = ["ThreeD", "FloorPlan", "EngineeringPlan", "CeilingPlan", "Elevation", "Section"]
origin = XYZ(0,0,0)
str_typ = StructuralType.NonStructural

def NewForm_background(s1, name1, cat1, isVoid1, mat1, subcat1):
    t1 = TransactionManager.Instance
    TransactionManager.ForceCloseTransaction(t1)
    famdoc = doc.Application.NewFamilyDocument(fam_path)
    message = None
    temp_path = System.IO.Path.GetTempPath()
    sat_path = "%s%s.sat" % (temp_path, name1)
    try:
        if factor != 1:
            s1 = s1.Scale(factor)
        sat1 = Geometry.ExportToSAT(s1, sat_path)
        satOpt = SATImportOptions()
        satOpt.Placement = ImportPlacement.Origin
        satOpt.Unit = ImportUnit.Foot
        view_fec = FilteredElementCollector(famdoc).OfClass(View)
        view1 = None
        for v in view_fec:
            if str(v.ViewType) in acceptable_views:
                view1 = v
                break
        t1.EnsureInTransaction(famdoc)
        satId = famdoc.Import(sat1, satOpt, view1)
        opt1 = Options()
        opt1.ComputeReferences = True
        el1 = famdoc.GetElement(satId)
        geom1 = el1.get_Geometry(opt1)
        enum = geom1.GetEnumerator()
        enum.MoveNext()
        geom2 = enum.Current.GetInstanceGeometry()
        enum2 = geom2.GetEnumerator()
        enum2.MoveNext()
        s1 = enum2.Current
        famdoc.Delete(satId)
        TransactionManager.ForceCloseTransaction(t1)
        System.IO.File.Delete(sat_path)
    except:
        message = traceback.format_exc()
        pass
    if message == None:
        try:
            save_path = "%s%s.rfa" % (temp_path, name1)
            SaveAsOpt = SaveAsOptions()
            SaveAsOpt.OverwriteExistingFile = True
            t1.EnsureInTransaction(famdoc)
            #set the category
            try:
                fam_cat = famdoc.Settings.Categories.get_Item(cat1.Name)
                famdoc.OwnerFamily.FamilyCategory = fam_cat
            except: pass
            s2 = FreeFormElement.Create(famdoc,s1)
            if isVoid1:
                void_par = s2.get_Parameter("Solid/Void")
                void_par.Set(1)
                void_par2 = famdoc.OwnerFamily.get_Parameter("Cut with Voids When Loaded")
                void_par2.Set(1)
            else: #voids do not have a material value
                try:
                    mat_fec = FilteredElementCollector(famdoc).OfClass(Material)
                    for m in mat_fec:
                        if m.Name == mat1:
                            fam_mat = m
                            break
                    mat_par = s2.get_Parameter("Material")
                    mat_par.Set(fam_mat.Id)
                except: pass
            #set subcategory    
            try:
                #create new sucategory
                fam_subcat = document.Settings.Categories.NewSubcategory(document.OwnerFamily.FamilyCategory, get_Item(subcat1.Name))               

                #assign the mataterial(fam_mat.Id) to the subcategory
                fam_subcat.Material = famdoc.GetElement(fam_mat.Id)

                #assign the subcategory to the element (s2)
                s2.Subcategory = fam_subcat
            except: pass

            TransactionManager.ForceCloseTransaction(t1)
            famdoc.SaveAs(save_path, SaveAsOpt)
            family1 =  famdoc.LoadFamily(doc, FamOpt1())
            famdoc.Close(False)
            System.IO.File.Delete(save_path)
            symbols = family1.Symbols.GetEnumerator()
            symbols.MoveNext()
            symbol1 = symbols.Current
            t1.EnsureInTransaction(doc)
            if not symbol1.IsActive: symbol1.Activate()
            inst1 = doc.Create.NewFamilyInstance(origin, symbol1, str_typ)
            TransactionManager.ForceCloseTransaction(t1)
            return inst1.ToDSType(False), family1.ToDSType(False)
        except:
            message = traceback.format_exc()
            return message
    else:
        return message

def NewForm_background_R16(s1, name1, cat1, isVoid1, mat1, subcat1):
    t1 = TransactionManager.Instance
    TransactionManager.ForceCloseTransaction(t1)
    famdoc = doc.Application.NewFamilyDocument(fam_path)
    message = None
    temp_path = System.IO.Path.GetTempPath()
    sat_path = "%s%s.sat" % (temp_path, name1)
    try:
        if factor != 1:
            s1 = s1.Scale(factor)
        sat1 = Geometry.ExportToSAT(s1, sat_path)
        satOpt = SATImportOptions()
        satOpt.Placement = ImportPlacement.Origin
        satOpt.Unit = ImportUnit.Foot
        view_fec = FilteredElementCollector(famdoc).OfClass(View)
        view1 = None
        for v in view_fec:
            if str(v.ViewType) in acceptable_views:
                view1 = v
                break
        t1.EnsureInTransaction(famdoc)
        satId = famdoc.Import(sat1, satOpt, view1)
        opt1 = Options()
        opt1.ComputeReferences = True
        el1 = famdoc.GetElement(satId)
        geom1 = el1.get_Geometry(opt1)
        enum = geom1.GetEnumerator()
        enum.MoveNext()
        geom2 = enum.Current.GetInstanceGeometry()
        enum2 = geom2.GetEnumerator()
        enum2.MoveNext()
        s1 = enum2.Current
        famdoc.Delete(satId)
        TransactionManager.ForceCloseTransaction(t1)
        System.IO.File.Delete(sat_path)
    except:
        message = traceback.format_exc()
        pass
    if message == None:
        try:
            save_path = "%s%s.rfa" % (temp_path, name1)
            SaveAsOpt = SaveAsOptions()
            SaveAsOpt.OverwriteExistingFile = True
            t1.EnsureInTransaction(famdoc)
            #set the category
            try:
                fam_cat = famdoc.Settings.Categories.get_Item(cat1.Name)
                famdoc.OwnerFamily.FamilyCategory = fam_cat
            except: pass
            s2 = FreeFormElement.Create(famdoc,s1)
            if isVoid1:
                void_par = s2.LookupParameter("Solid/Void")
                void_par.Set(1)
                void_par2 = famdoc.OwnerFamily.LookupParameter("Cut with Voids When Loaded")
                void_par2.Set(1)
            else: #voids do not have a material value
                try:
                    mat_fec = FilteredElementCollector(famdoc).OfClass(Material)
                    for m in mat_fec:
                        if m.Name == mat1:
                            fam_mat = m
                            break
                    mat_par = s2.LookupParameter("Material")
                    mat_par.Set(fam_mat.Id)
                except: pass

            #apply same subcategory code as before
            #set subcategory    
            try:
                #create new sucategory
                fam_subcat = famdoc.Settings.Categories.NewSubcategory(fam_cat, get_Item(subcat1.Name))             

                #assign the mataterial(fam_mat.Id) to the subcategory
                fam_subcat.Material = famdoc.GetElement(fam_mat.Id)

                #assign the subcategory to the element (s2)
                s2.Subcategory = fam_subcat
            except: pass


            TransactionManager.ForceCloseTransaction(t1)
            famdoc.SaveAs(save_path, SaveAsOpt)
            family1 =  famdoc.LoadFamily(doc, FamOpt1())
            famdoc.Close(False)
            System.IO.File.Delete(save_path)
            symbols = family1.GetFamilySymbolIds().GetEnumerator()
            symbols.MoveNext()
            symbol1 = doc.GetElement(symbols.Current)
            t1.EnsureInTransaction(doc)
            if not symbol1.IsActive: symbol1.Activate()
            inst1 = doc.Create.NewFamilyInstance(origin, symbol1, str_typ)
            TransactionManager.ForceCloseTransaction(t1)
            return inst1.ToDSType(False), family1.ToDSType(False)
        except:
            message = traceback.format_exc()
            return message
    else:
        return message

if len(geom) == len(names) == len(category) == len(isVoid) == len(material) == len(subcategory):
    if isRvt2014:
        OUT = output1(map(NewForm_background, geom, names, category, isVoid, material, subcategory))
    else:
        OUT = output1(map(NewForm_background_R16, geom, names, category, isVoid, material, subcategory))
elif len(geom) == len(names):
    padded = PadLists((geom, category, isVoid, material, subcategory))
    p_category = padded[1]
    p_isVoid = padded[2]
    p_material = padded[3]
    p_subcategory = padded [4]
    if isRvt2014:
        OUT = output1(map(NewForm_background, geom, names, p_category, p_isVoid, p_material, p_subcategory))
    else:
        OUT = output1(map(NewForm_background_R16, geom, names, p_category, p_isVoid, p_material, subcategory))
else: OUT = "Make sure that each geometry\nobject has a unique family name."

Update:

Was able to get it working:

    try:
        #create new sucategory
        fam_subcat = famdoc.Settings.Categories.NewSubcategory(famdoc.OwnerFamily.FamilyCategory, subcat1)          

        #assign the mataterial(fam_mat.Id) to the subcategory
        #fam_subcat.Material = famdoc.GetElement(fam_mat.Id)

        #assign the subcategory to the element (s2)
        s2.Subcategory = fam_subcat
    except: pass
Hysteric answered 13/12, 2015 at 21:54 Comment(0)
S
1

As I answered on your initial query per email, what you are aiming for sounds perfectly feasible to me in the Revit API. Congratulations on getting as far as you have. Looking at the link to the Revit API help file and developer guide that you cite above, it seems that the code has to be executed in the family document while defining the family. The context in which you are trying to execute it is not clear. Have you used EditFamily to open the family definition document? What context are you executing in?

Seersucker answered 14/12, 2015 at 9:2 Comment(2)
I decided that rather than looping through each family and editing it, i would edit the original python code in the dynamo graph from the spring nodes plugin. In the code of this node, the solids are each placed in a new family which is created from a family template file. The portion of the code I posted is already opperating inside the family itself, creating the freeform geometry, setting the category, adding a material and so forth, I'm just adding the subcategory part. I can' try to post the full code...Hysteric
I just got it to work with a some help form a college at work... first I needed to pass the variable not the name of the variable in the create subcategory part. Second I was operating in the wrong def for my version of revit. I've posted the corrected code as an update above.Hysteric

© 2022 - 2024 — McMap. All rights reserved.