Generic Annotation Placement for a Family Showing Incorrectly in RCP?

Hi there, brand new and completely incompetent when it comes to Dynamo, so figured I would ask the experts first. How would I build a script that helps with the following?

I have a sprinkler family that is required to be shown correctly rotated in section, though it does not have a rotation parameter attached to it. In plan, it shows up incorrectly because of this rotation, so I would like to cover it with its generic annotation symbol. Is there a way for Dynamo to read the centerpoints of where the family lands in plan and place the symbol over the top of it? There are over 300 of these in the model, so would be great if this is an option.

It is not an option to show them in normal orientation due to the section views that are required for the drawing set.

TIA!

Ashley

Why don’t you add that information to the family itself?

2 Likes

It is part of the family, it’s just that it doesn’t show because the sprinkler fixture itself isn’t perpendicular/normal to the plan. It’s rotated, so it shows in plan as a 3D cylinder that’s rotated, and that makes it illegible.

This means I need to manually add it over the top of the family as a symbol to show it correctly.

Since I suggested the solution that won’t work, in the first place, I feel like I should provide an answer here, now that you proved that my suggestion is not valid. Here it is.

If we got some families, and you know their type ie. Dining Chair (3):

We can select all instances of that chair, obtain its location point and then place an annotation like so:

Here’s the python code:

# Copyright(c) 2019, Konrad K Sobon
# @arch_laboratory, http://archi-lab.net

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

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

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

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

import System
from System import Array
from System.Collections.Generic import *

import sys
pyt_path = r'C:\Program Files (x86)\IronPython 2.7\Lib'
sys.path.append(pyt_path)


def process_list(_func, _list):
    return map(lambda x: process_list(_func, x) if type(x) == list else _func(x), _list)

fType = UnwrapElement(IN[0])

def place_annotation(loc):
	return doc.Create.NewFamilyInstance(loc.ToXyz(), fType, doc.ActiveView)

try:
    errorReport = None
    TransactionManager.Instance.EnsureInTransaction(doc)
    output = process_list(place_annotation, IN[1])
    TransactionManager.Instance.TransactionTaskDone()
except:
    import traceback
    errorReport = traceback.format_exc()

if None == errorReport:
    OUT = output
else:
    OUT = errorReport

Here’s the result:

Please see attached for the script: familylocationtoannotation.dyn (10.6 KB)

Cheers!

@Konrad_K_Sobon – wow, exactly this! Amazing help, thank you so much :smiley:

Nailed it!

Does anyone know how would we go about relocating existing annotations to a new location?

You would need to write the ID of the related object when you create it initially, then get the actual element in Revit with that Id, get its location point and then use element.setlocation to move the annotations that were placed before. Generally if theyre already placed it is too late as they have no relationship to their placement family.

Alternatively, use a tag that looks like the symbol maybe.

Thanks for replying and reminding me to report what I found : ) I eventually figured it out (See below)

@GavinCrump I had to remove the design script references that were conflicting with the Python references to Revit points. Really wish we had an editor with full intellisense and only allowed strict formatting!

...
       objPoint = XYZ(x, y, 0)                             ##GET POINT
...
       if GAiv:
            AnnoEXST.append(GAFamType)                        ##Add it to the existing list
            for GAinVIew in AnnosInView:                    ##Cycle through to set it
                if GAinVIew.Name==GetGAName(GAFamType):     ##GenericAnnotaitonFamilyTypeCompare  
                   GAiv.Location.Point = objPoint          ##<<<Sets Fixed Position for element in list.
                    break

Script in full context:

##https://gist.github.com/gtalarico/e6be055472dfcb6f597e3dcd20d11f37
#import System.Collections.Generic
#from System.Collections.Generic import List
##clr.AddReference('RevitAPIUI')
##from Autodesk.Revit.UI import *

import clr
clr.AddReference('System')
clr.AddReference('RevitAPI')
##clr.AddReference('RevitNodes')
##clr.ImportExtensions(Revit.Elements)
##clr.ImportExtensions(Revit.GeometryConversion)

###Creates a Drafting View###
from Autodesk.Revit.DB import Transaction, Element, ElementTransformUtils
##https://forum.dynamobim.com/t/collecting-all-elements-of-family-types-in-active-view/19838/2
from Autodesk.Revit.DB import FilteredElementCollector, BuiltInCategory, BuiltInParameter, ParameterValueProvider, ElementId, ElementParameterFilter, FilterNumericEquals, FilterElementIdRule, ElementTransformUtils

#  Drafting Views
from Autodesk.Revit.DB import ViewFamilyType, ViewDrafting, Element
from Autodesk.Revit.DB import ViewFamily

clr.AddReference('RevitServices')              ##May need to re-add for Document Manager

import sys
import System                                   ##filterAnnot = System.Predicate           
from System.Collections.Generic import List     ##Not same as type() = List

import math                                     ##For truncate to integer-RA
import re                                       ##Regular expressions for 'natural' sort

from itertools import groupby
#from Autodesk.Revit.DB import *
from Autodesk.Revit.DB import FamilySymbol, FamilyInstance, XYZ, AnnotationSymbol

#import Revit
#from Revit import *

#from Autodesk.DesignScript.Geometry import *    ##Point(X,Y,Z) ##cannot use for PY nodes outside Dynamo

import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
uidoc=DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

##uidoc = __revit__.ActiveUIDocument
##doc = __revit__.ActiveUIDocument.Document
#####################################################################################
##Create a Drafting View#
def get_drafting_type_id(DraftingType):
    ##Selects First available ViewType that Matches Drafting Type.##    ##https://mgfx.co.za/blog/building-architectural-design/revit-view-type-creation-using-excel-with-dynamo-and-python/
    viewfamily_types = FilteredElementCollector(doc).OfClass(ViewFamilyType)
    for i in viewfamily_types:
        if i.ViewFamily == ViewFamily.Drafting:     ##*.ViewFamily Backwards 'parent' ref I've been looking for!##<<<<<<<<<<<<<<<<<<<<<<<<<<HERE
            return i.Id




def GetDraftingViews():
    ##https://forums.autodesk.com/t5/revit-api-forum/view3d-collector/td-p/5277451
    DraftingViewsList = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements()
    ##DraftingViewTemplates = [v.Id for v in DraftingViewsList if v.IsTemplate == True]
    DraftingViewsList = [v for v in DraftingViewsList if v.IsTemplate == False]
    return [DraftingViewsList]

def GetDraftingViewTemplates():
    ##https://forums.autodesk.com/t5/revit-api-forum/view3d-collector/td-p/5277451
    DraftingViewsList = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements()
    DraftingViewTemplates = [v for v in DraftingViewsList if v.IsTemplate == True]
    ##DraftingViewsList = [v.Id for v in DraftingViewsList if v.IsTemplate == False]
    return [DraftingViewTemplates]

def GetDraftingViewByBame(strName=""):                  ##Check if drafting view existds = if not, create it and assign view name
    if not strName == "":                               ##If string name is set look for the drafting view
        DraftingView = FilteredElementCollector(doc).OfClass(ViewDrafting).ToElements()  ##Get all drafting views
        DraftingView = [v for v in DraftingView if v.Name == strName]   ##Return Drafting view for each Drafting view eq. Name
        if DraftingView:
            DraftingView =DraftingView[0] ##Return single item not list<<<<<<<<<<<<<<<

    if not DraftingView:                                ##If no drafting view found
        drafting_type_id = get_drafting_type_id()       ##Get TYPE ID
        DraftingView = ViewDrafting.Create(doc, drafting_type_id)   ##Create View
        ##After creating view-
        if not strName=="" :     ##IF string is not null set the name of the new view
            DraftingView.Name = strName

    return DraftingView                               ##Return single value

def GetGenericAnnotationTypes():
    ##BAsed in part
    ##https://forum.dynamobim.com/t/how-can-i-collect-all-family-types-that-are-considered-annotation-symbols-in-revit/37480/10
    ##https://forum.dynamobim.com/t/get-all-generic-annotations-in-a-view/80514/2
    filterAnnot = System.Predicate[System.Object](lambda x : x.Family.FamilyCategory.Name ==  "Generic Annotations")
    symbAnnot = List[Element](FilteredElementCollector(doc).OfClass(FamilySymbol).ToElements()).FindAll(filterAnnot)

    return symbAnnot

def Lambda_Natural_sort(l):                             ##Slow 50x but effective ##ORiginal function doesn't work with Objects.
    ConvertText = lambda ConvertText: int(ConvertText) if ConvertText.isdigit() else ConvertText.lower()
    alphanum_key = lambda key: [ ConvertText(c) for c in re.split('([0-9]+)', key) ]
    return sorted(l, key = alphanum_key)

def Lambda_GenAnno_Sort_w_fam_name(lstObj):

    ConvertText = lambda ConvertText: int(ConvertText) if ConvertText.isdigit() else ConvertText.lower()

    alphanum_key = lambda i: [ ConvertText(c) for c in re.split('(\d+)', i) ]

    ##GenAnnoTypeName##https://forum.dynamobim.com/t/python-script-to-get-type-name-from-an-element/63306/2
    GAstrName = lambda j: [ alphanum_key(c) for c in (j.Family.Name + GetGAName(j))]

    return sorted(lstObj, key = GAstrName)

def GetGAName(GA):  ##GenericAnnotationFamilyType".Name"
    return doc.GetElement(GA.Id).get_Parameter(BuiltInParameter.ALL_MODEL_TYPE_NAME).AsString()


def GroupGenAnnos(lstElems):
    ##test to Group By Key https://forum.dynamobim.com/t/group-by-key-in-python/39145/2
    grp=[]
    ukey=[]

    ##Presorted by genannolistfort_w_family_name

    for k, g in groupby(lstElems, lambda x:x.Family.Name):
        grp.append(list(g))
        ukey.append(k)

    #Assign your output to the OUT variable.
    return  grp

def GetAnnosInView(viewView):
    ##https://forum.dynamobim.com/t/get-all-generic-annotations-in-a-view/80514/2
    symbAnnot = [x for x in FilteredElementCollector(doc,viewDraft.Id).OfCategory(BuiltInCategory.OST_GenericAnnotation).WhereElementIsNotElementType()]

def ListConv(x):                    ## Returns list if single item
    if type(x) is list :
        return x                    ##Leave as list if laready list
    else:
        try:
            z=iter(x)               ##if element is iterable- e.g. Generic.List - it can be converted to regular list
            return [x for x in x]   ##Convert to list if single item
        except:
            return[x]               ##Convert single element to list
def ListOfList1InList2(list1,list2):
    list1Out=[]
    for x in list1:                 ##For each element in 1st list (Check against elements in 2nd list)
        found = False               ##Reset to not found for next element
        for y in list2:
            if x==y:                ##If found,
                found =True         ##Set founc to TRUE
                break               ##Can stop looking in second list this round
        list1Out.append(found)      ##Append result
    return list1Out

def List1InList2(list1,list2):      ##If any of list 1 in list 2 return TRUE else False
    for x in list1:                 ##For each element in 1st list (Check against elements in 2nd list)
        for y in list2:
            if x==y:                ##If found,
                return True         ##Return True
    return False                    ##Return false


def GenAnnosInView(viewView):
    ##GET ANNOTATIONSIN VIEW
    ##https://forum.dynamobim.com/t/get-all-generic-annotations-in-a-view/80514/2
    FEC=FilteredElementCollector(doc,viewView).OfCategory(BuiltInCategory.OST_GenericAnnotation).WhereElementIsNotElementType()
    return FEC


def PlaceGenAnnoArray(GAFamTypes, viewView):   #Single family of generic annotaiton types to place
    ##cast CONSTANTS
    xSpace = 8.5/12.0                                   ##Horizontal spacing 8-1/2" Wide
    ySpace = 3.0/8.0/12.0                               ##Vertical spacing  3/16" tall (Text is 3/32" high)

    yLimit = 10.0/12.0                                  ##Limit of Y column height 10" (8-1/2"x11" Guide to format into sheet size references)
    yCount = int(math.floor(yLimit/ySpace))             ##Number of rows in a column for reset y value
    column = 0                                          ##Column location

    count = 1                                           ##Count position in array of all family types

    AnnoEXST=[]                                         ##Existing annotaitons in view
    AnnoNEWW=[]                                         ##New annotaitons creted

    ##get AllGenAnnosInView  - if any...
    AnnosInView =[]
    #AnnosInView = FilteredElementCollector(doc,viewView.Id).OfCategory(BuiltInCategory.OST_GenericAnnotation).WhereElementIsNotElementType().ToElements
    ##https://forum.dynamobim.com/t/get-all-generic-annotations-in-a-view/80514/13 SeanP
    filterAnnot = System.Predicate[System.Object](lambda x : doc.GetElement(x.GetTypeId()).Family.FamilyCategory.Name ==  "Generic Annotations")
    AnnosInView = List[Element](FilteredElementCollector(doc, viewView.Id).OfClass(FamilyInstance).ToElements()).FindAll(filterAnnot)
    AnnosInView = UnwrapElement(AnnosInView)                ##Unwrap to get to get to Revit Type Elements

    for GAFamType in GAFamTypes :        
        #from 0,0 run down then over for simplicity. These typically will NOT end up on printed sheets
        #But are head to use to schedule. Any object in any view will schedule regardless of on/off sheet palcement.
        column = math.floor( yLimit / count * ySpace)       ##Get column based on placed elements and Y limit sizes

        x= column * xSpace                                  ##Get x location
        y= -ySpace * (count-1 - (column * yCount))          ##Offset Y by column displacemnet

        objPoint = XYZ(x, y, 0)                             ##GET POINT

        #return List1InList2([GetGAName(GAFamType)], [x.Name for x in AnnosInView]) ##DEBUG

        ##if List1InList2([GetGAName(GAFamType)], [x.Name for x in AnnosInView]):  ##if GAFamType Name in list of names in view
        #return 3
        GAiv  = None
        
        if AnnosInView :                                    ##If ther eare annos in the view
            for GAivx in AnnosInView:                         ##For each Gen Anno in view
                Found = False                               ##Reset Found
                for y in GAFamTypes:
                    if GetGAName(GAFamType)==GAivx.Name:       ##If found,
                        Found = True                        ##Return True
                        GAiv=GAivx
                        break                               ##Break with Found set
                if Found:                                   ##If found (after for y)
                    break                                   ##Exit out of outer loop  with GAiv (Gen Anno In View) set
        else:
            GAiv=None
       
        if GAiv:
            AnnoEXST.append(GAFamType)                        ##Add it to the existing list
            for GAinVIew in AnnosInView:                    ##Cycle through to set it
                if GAinVIew.Name==GetGAName(GAFamType):     ##GenericAnnotaitonFamilyTypeCompare  
                    GAiv.Location.Point = objPoint          ##Sets Fixed Position for element in list.  
                    break
        else:                                               ##Not List1InList2
            GAinVIew = doc.Create.NewFamilyInstance(objPoint, GAFamType, viewView) ##Create NEW annotaiton in its loaiton
            AnnoNEWW.append(GAinVIew)                       ##Append it to the new list as a double check

        count +=1                                           ##increment counter

    return [AnnoNEWW,AnnoEXST]                              ##Return the New and Existing. Re-running should only return existing.
####
###

#######################################################################################
def Main():  ###Entry point to procedures

    t = Transaction (doc, 'Create Drafting View')
    t.Start()
    
    outNEWW=[]
    outEXST=[]
    GenAnnoTypes = []

    GenAnnoTypes = GetGenericAnnotationTypes()           ##LIST of FAMILY TYPE of GenAnnoTypes

    GenAnnoTypes = Lambda_GenAnno_Sort_w_fam_name(GenAnnoTypes)   ##Sort in natural order- numbers as numbers text as text

    GenAnnoTypes = GroupGenAnnos(GenAnnoTypes)                ##Create nested list by Family and Types

    for GAFamType in GenAnnoTypes:                          ##2D list [1.GenAnnoFamilies][2.GenAnnoTypes]
        DraftViewName = ""
        DraftViewName = GAFamType[0].Family.Name       ##FAmily name used for drafting view name
        viewDraft = GetDraftingViewByBame(DraftViewName)  ##Get or create drafting view by name

        OUT = PlaceGenAnnoArray (GAFamType, viewDraft) ##Place or update gfams per view

        ##Separate lists into NEW and EXISTING##
        #outNEWW.append(OUT[0])                      ##New is index 0
        #outEXST.append(OUT[1])                      ##Existing is index 1

    #OUT=[outNEWW,outEXST]                           ##Set final OUT to [NEW,Existing] Lists

    t.Commit()

    return OUT

############################################################################################
############################################################################################
OUT=Main () ##Entry point to run all scripts
############################################################################################
############################################################################################