Hi,
Due to frustation with manual placing of flanges, I have written a python script for putting flanges to pipe networks. (And for those who are wondering why I don’t use Revits built-in function for flanges: This puts flanges next to every pipe fitting as well, where they are not supposed to be).
The script is heavily based on nodes in the MEPover packages, so huge thanks to the author of it.
Most of the script works fine, except roughly 50% of the flanges and pipe accessories get wrong system type, as seen on the image below, where different system types are given different colors:
I have spent a lot of time trying different ways to manipulate this, but I’m stuck. So, I paste my code here, both in case someone find it useful for their own projects, but also I hope someone can help with assigning the right system type to the parts.
Short explanation of script:
Loop all pipe accessories and mechanical equipement.
(I sort out pipe accessorires with type=“standard”, as our flanges are also modelled as pipe accessories, and our valves etc. using flanges do NOT have type = “standard”)
Find all instances where these are connected to a pipe.
Put a flange there.
Flip it if necessary, and move to right place.
Disconnect pipe and pipe accessory/mech equip
Connect flange with pipe and with PA/ME.
Adjust pipe length.
So, the code:
import clr
import sys
import math
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
clr.AddReference("RevitNodes")
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference("RevitAPI")
import Autodesk
from Autodesk.Revit.DB import *
from System.Collections.Generic import List
from Autodesk.Revit.DB.Plumbing import *
pipingSystem = FilteredElementCollector(doc).OfClass(PipingSystemType).ToElements()
list_piping_system = []
list_piping_system_id = []
for i in pipingSystem:
list_piping_system.append(i)
list_piping_system_id.append(i.Id)
def measure(startpoint, point):
return startpoint.DistanceTo(point)
def copyElement(element, oldloc, loc):
TransactionManager.Instance.EnsureInTransaction(doc)
elementlist = List[ElementId]()
elementlist.Add(element.Id)
OffsetZ = (oldloc.Z - loc.Z)*-1
OffsetX = (oldloc.X - loc.X)*-1
OffsetY = (oldloc.Y - loc.Y)*-1
direction = XYZ(OffsetX,OffsetY,OffsetZ)
newelementId = ElementTransformUtils.CopyElements(doc,elementlist,direction)
newelement = doc.GetElement(newelementId[0])
TransactionManager.Instance.TransactionTaskDone()
return newelement
def GetDirection(faminstance):
for c in faminstance.MEPModel.ConnectorManager.Connectors:
conn = c
break
return conn.CoordinateSystem.BasisZ
def GetClosestDirection(faminstance, lineDirection):
conndir = None
flat_linedir = XYZ(lineDirection.X,lineDirection.Y,0).Normalize()
for conn in faminstance.MEPModel.ConnectorManager.Connectors:
conndir = conn.CoordinateSystem.BasisZ
if flat_linedir.IsAlmostEqualTo(conndir):
return conndir
return conndir
#
debug = []
#global variables for rotating new families
tempfamtype = None
xAxis = XYZ(1,0,0)
def placeFitting(duct,point,familytype,lineDirection):
toggle = False
isVertical = False
global tempfamtype
if tempfamtype == None:
tempfamtype = familytype
toggle = True
elif tempfamtype != familytype:
toggle = True
tempfamtype = familytype
level = duct.ReferenceLevel
width = 4
height = 4
radius = 2
round = False
connectors = duct.ConnectorManager.Connectors
for c in connectors:
if c.ConnectorType != ConnectorType.End:
continue
shape = c.Shape
if shape == ConnectorProfileType.Round:
radius = c.Radius
round = True
break
elif shape == ConnectorProfileType.Rectangular or shape == ConnectorProfileType.Oval:
if abs(lineDirection.Z) == 1:
isVertical = True
yDir = c.CoordinateSystem.BasisY
width = c.Width
height = c.Height
break
TransactionManager.Instance.EnsureInTransaction(doc)
point = XYZ(point.X,point.Y,point.Z-level.Elevation)
## THIS LINE IS DEPENDENT ON UNITS AND PROJECT SETTINGS. LINE BELOW IS FOR PROEJCT USING MM AS UNIT, AND THERE IS NOT ADDED FLANGES FOR DN<55 MM
if radius < 55/304.8/2:
return 0
newfam = doc.Create.NewFamilyInstance(point,familytype,level,Structure.StructuralType.NonStructural)
doc.Regenerate()
transform = newfam.GetTransform()
axis = Line.CreateUnbound(transform.Origin, transform.BasisZ)
global xAxis
if toggle:
xAxis = GetDirection(newfam)
zAxis = XYZ(0,0,1)
if isVertical:
angle = xAxis.AngleOnPlaneTo(yDir,zAxis)
else:
angle = xAxis.AngleOnPlaneTo(lineDirection,zAxis)
ElementTransformUtils.RotateElement(doc,newfam.Id,axis,angle)
doc.Regenerate()
if lineDirection.Z != 0:
newAxis = GetClosestDirection(newfam,lineDirection)
yAxis = newAxis.CrossProduct(zAxis)
angle2 = newAxis.AngleOnPlaneTo(lineDirection,yAxis)
axis2 = Line.CreateUnbound(transform.Origin, yAxis)
ElementTransformUtils.RotateElement(doc,newfam.Id,axis2,angle2)
result = {}
connpoints = []
#famconns = newfam.MEPModel.ConnectorManager.Connectors
newfam.LookupParameter('Nominal Radius').Set(radius)
##
#famconns = UnwrapElement(famconns)
#if round:
#for conn in famconns:
## JF
# for j,conn in enumerate(famconns):
# if IsParallel(lineDirection,conn.CoordinateSystem.BasisZ) == False:
# continue
# if conn.Shape != shape:
# continue
#conn.Radius = radius
## JF
# if j == 0:
#conn.Radius = 0.32
# debug.append('radius ' + str(radius))
# debug.append('conn.radius ' + str(conn.Radius))
# debug.append('anyway')
# connpoints.append(conn.Origin)
#else:
# for conn in famconns:
# if IsParallel(lineDirection,conn.CoordinateSystem.BasisZ) == False:
# continue
# if conn.Shape != shape:
# continue
# conn.Width = width
# conn.Height = height
# connpoints.append(conn.Origin)
TransactionManager.Instance.TransactionTaskDone()
#result[newfam] = connpoints
return newfam
def ConnectElements(duct, fitting):
ductconns = duct.ConnectorManager.Connectors
fittingconns = fitting.MEPModel.ConnectorManager.Connectors
TransactionManager.Instance.EnsureInTransaction(doc)
for conn in fittingconns:
for ductconn in ductconns:
result = ductconn.Origin.IsAlmostEqualTo(conn.Origin)
if result:
ductconn.ConnectTo(conn)
break
TransactionManager.Instance.TransactionTaskDone()
return result
def SortedPoints(fittingspoints,ductStartPoint):
sortedpoints = sorted(fittingspoints, key=lambda x: measure(ductStartPoint, x))
return sortedpoints
###########################################################
## start algorithm for finding missing flanges
###########################################################
#COMPANY SPESIFIC CODE FOR FLANGE TYPES TO BE SELECTED
PA1 = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_PipeAccessory).WhereElementIsElementType()
flange_family_type = [0,0,0,0]
for i in PA1:
if 'Krage-Løsflens_med pakning' in i.Family.Name:
flange_family_type[0] = i
break
for j in PA1:
if 'Krage-Løsflens_uten pakning' in j.Family.Name:
flange_family_type[1] = j
break
for k in PA1:
if 'Sveiseflens_uten pakning' in k.Family.Name:
flange_family_type[2] = k
break
for l in PA1:
if 'Sveiseflens_uten pakning' in l.Family.Name:
flange_family_type[3] = l
break
#collect all mechanical equipment and pipe accessories in project
cat_list = [BuiltInCategory.OST_PipeAccessory, BuiltInCategory.OST_MechanicalEquipment]
typed_list = List[BuiltInCategory](cat_list)
filter = ElementMulticategoryFilter(typed_list)
EQ = FilteredElementCollector(doc).WherePasses(filter).WhereElementIsNotElementType().ToElements()
pipe = []
pipe_connector = []
pipe_endpoints = []
pipe_endpoint_id = []
valve_connector = []
opposite_valve_connector = []
gasket = []
valve =[]
for i in EQ:
#for i in []:
#Filter out flanges and other parts where type-name i "Standard"
if i.Name != 'Standard':
#find connectorer to valve
try:
connectors = i.MEPModel.ConnectorManager.Connectors
except:
try:
connectors = i.ConnectorManager.Connectors
except:
connectors = []
#modify connectorset to be subscriptable
cons = []
for kk in connectors:
cons.append(kk)
#check what type of parts the connectors are connected to
# from node "connector.connectedElements from MEPover
for n,j in enumerate(cons):
refs = None
try:
refs = [doc.GetElement(x.Owner.Id) for x in j.AllRefs if x.Owner.Id != j.Owner.Id and x.ConnectorType != ConnectorType.Logical]
except:
refs = None
if type(refs)==list:
if refs == []:
reft = None
else:
refs = refs[0]
for y in j.AllRefs:
if y.Owner.Id != j.Owner.Id and y.ConnectorType != ConnectorType.Logical:
pipecon = y
# check if connected to straight pipe
if refs is not None:
cat_name = None
try:
cat_name = refs.Category.Name
except:
continue
if cat_name == 'Pipes':
#checking if pipe is longer than 10mm. don't want to add flanges on very short pipes which should not be there, stuck between valves.
#LINE BELOW IS ONLY FOR PROJECTS USING MM AS UNIT
if refs.Location.Curve.GetEndPoint(0).DistanceTo(refs.Location.Curve.GetEndPoint(1)) > 10/304.8:
#Preparing lists with corresponding indexes:
pipe.append(refs)
pipe_endpoints.append([refs.Location.Curve.GetEndPoint(0), refs.Location.Curve.GetEndPoint(1)])
pipe_connector.append(pipecon)
valve_connector.append(j)
opposite_valve_connector.append(cons[1-n])
valve.append(i)
if refs.Location.Curve.GetEndPoint(0).DistanceTo(j.Origin) < refs.Location.Curve.GetEndPoint(1).DistanceTo(j.Origin):
pipe_endpoint_id.append(0)
else:
pipe_endpoint_id.append(1)
#Company spesific code determining which type of flange to be used
if 'innspent' in i.Symbol.FamilyName:
gasket.append(False)
else:
gasket.append(True)
############################################################################
##### Start script for adding flanges. Based on MEPover node
###########################################################################
TransactionManager.Instance.EnsureInTransaction(doc)
for typ in flange_family_type:
if typ != 0:
if typ.IsActive == False:
typ.Activate()
doc.Regenerate()
TransactionManager.Instance.TransactionTaskDone()
debug2 = []
for i,duct in enumerate(pipe):
pointlist = valve_connector[i].Origin
#Krage-løsflens
if gasket[i]:
familytype = flange_family_type[0]
else:
familytype = flange_family_type[1]
#Sveiseflens
#if gasket[i]:
# familytype = flange_family_type[0]
#else:
# familytype = flange_family_type[1]
#create duct location line
ductline = duct.Location.Curve
#ductStartPoint = ductline.GetEndPoint(0)
#ductEndPoint = ductline.GetEndPoint(1)
#get end connector to reconnect later
#endIsConnected = False
#endrefconn = None
#for ductconn in duct.ConnectorManager.Connectors:
# if ductconn.Origin.DistanceTo(ductEndPoint) < 5/304.8:
# if ductconn.IsConnected:
# endIsConnected = True
# for refconn in ductconn.AllRefs:
# if refconn.ConnectorType != ConnectorType.Logical and refconn.Owner.Id.IntegerValue != duct.Id.IntegerValue:
# endrefconn = refconn
#sort the points from start of duct to end of duct
#pointlist = SortedPoints(ListOfPoints,ductStartPoint)
#pointlist = ListOfPoints
#debug.append(pointlist)
#ductlist = []
#newFittings = []
#ductlist.append(duct)
#tempStartPoint = None
#tempEndPoint = None
lineDirection = ductline.Direction
new_flange = placeFitting(duct,pointlist,familytype,lineDirection)
#check if flange was created. If not, probably due to too small DN
if new_flange == 0:
continue
debug2.append(' ')
#check if flange need to be flipped
try:
flange_connectors = new_flange.MEPModel.ConnectorManager.Connectors
except:
try:
flange_connectors = new_flange.ConnectorManager.Connectors
except:
flange_connectors = []
cons = []
#make subscriptable
f_cons = []
for a in flange_connectors:
f_cons.append(a)
# valve_cons.append([x for x in connectors])
flange_a_con_position = f_cons[0].Origin
flange_b_con_position = f_cons[1].Origin
if flange_a_con_position.DistanceTo(opposite_valve_connector[i].Origin) < flange_b_con_position.DistanceTo(opposite_valve_connector[i].Origin):
#flange side a is facing the valve
need_to_flip = f_cons[0].GetMEPConnectorInfo().IsPrimary
else:
need_to_flip = f_cons[1].GetMEPConnectorInfo().IsPrimary
TransactionManager.Instance.EnsureInTransaction(doc)
if need_to_flip:
vector = valve_connector[i].CoordinateSystem.BasisY
line = Autodesk.Revit.DB.Line.CreateBound(valve_connector[i].Origin , valve_connector[i].Origin + vector)
line = UnwrapElement(line)
flipped = new_flange.Location.Rotate(line, math.pi)
##Move to right place
#find primary and secondardy connector
doc.Regenerate()
if f_cons[0].GetMEPConnectorInfo().IsPrimary:
#debug2.append('primary')
primary_con_id = 0
secondary_con_id = 1
else:
primary_con_id = 1
secondary_con_id = 0
#debug2.append('secondary')
doc.Regenerate()
new_flange.Location.Move((valve_connector[i].Origin - f_cons[secondary_con_id].Origin))
doc.Regenerate()
#modify pipe endpoints
if pipe_endpoint_id[i] == 0:
new_pipeline = Autodesk.Revit.DB.Line.CreateBound(f_cons[primary_con_id].Origin, pipe[i].Location.Curve.GetEndPoint(1))
pipe[i].Location.Curve = new_pipeline
#debug2.append('a')
else :
new_pipeline = Autodesk.Revit.DB.Line.CreateBound(pipe[i].Location.Curve.GetEndPoint(0), f_cons[primary_con_id].Origin)
pipe[i].Location.Curve = new_pipeline
#debug2.append('b')
doc.Regenerate()
#duct_piping_system_type = pipe_connector[i].MEPSystem
duct_piping_system_type = pipe[i].get_Parameter(BuiltInParameter.RBS_PIPING_SYSTEM_TYPE_PARAM).AsElementId()
for k, sys in enumerate(list_piping_system_id):
if sys == duct_piping_system_type:
rorsystem = list_piping_system[k]
#disconnect
valve_connector[i].DisconnectFrom(pipe_connector[i])
doc.Regenerate()
#make new connections
pipe_connector[i].ConnectTo(f_cons[primary_con_id])
#connset = ConnectorSet()
#connset.Insert(f_cons[0])
#connset.Insert(f_cons[1])
#connset.Insert(valve_connector[i])
#duct_piping_system_type.Add(connset)
doc.Regenerate()
f_cons[secondary_con_id].ConnectTo(valve_connector[i])
doc.Regenerate()
#pipe_connector[i].MEPSystem.Add(connset)
doc.Regenerate()
TransactionManager.Instance.TransactionTaskDone()
debug2.append(new_flange)
OUT = debug2
Edited 02.09.2020 due to typos in code