Automatiser le placement de vannes dans Revit ?
Voici un exemple de script pour créer des raccords et accessoires sur des canalisations MEP
#DynamoBIM #Python #RevitAPI #PythonNet3 #MEP #Fittings #Vectors
#Connectors #AutodeskExpertElite #AutodeskCommunity 
if this article is not in your language, use the Google Translate
widget ⬈ (bottom of page for Mobile version ⬇)
Le script présenté ci-dessous automatise, l’insertion de raccord MEP (de type T) ou d’accessoires (vanne,
clapet…) sur des canalisations MEP Revit à des points donnés, en coupant les
tronçons, orientant correctement les familles et en connectant leurs
connecteurs aux éléments hôtes.
Entrées :
- IN[0] : éléments MEP linéaires (canalisations) avec une géométrie de type
LocationCurve
- IN[1] : liste de listes de points (un sous-ensemble de points par
élément)
- IN[2] : type de famille (raccord en T ou accessoire).
Sorties :
- OUT : [liste des éléments d’origine (coupés), liste des familles
créées]
Fonctions :
-
get_shape
: pour le contrôle de la structure des listes d’entrée
-
get_order_connectors
: ordonne les connecteurs des 3 tronçons pour créer
un raccord en T valide
-
get_FamilyInstance_direction
: détermine la direction principale d’un
accessoire via ses connecteurs pour l’orienter grâce au système de cordonnées des connecteurs
Flux Principal :
Pour chaque élément MEP et sa liste de points
Pour chaque élément MEP et sa liste de points
- Coupe de la canalisation au point
- Calcul d’un axe de rotation en fonction de l’orientation (verticale/horizontale)
- Cas d'un Raccord en T :
- Duplication temporaire d’un tronçon pour créer la branche
- Rotation de 90° de la branche autour de l’axe
-
Tri des connecteurs (A, B, C) puis création du raccord en T
(
NewTeeFitting
) et application du type - Suppression de la branche temporaire
- Cas d'un Accessoire :
- Placement de l’élément (
NewFamilyInstance
) au niveau du point - Détermination de la direction de la famille via ses connecteurs, alignement angulaire avec l’axe de la conduite (gestion verticale/horizontale)
- Connexion des connecteurs de l’accessoire aux deux tronçons
Notes :
- Le code Python a été tester avec le dernier moteur Python "DynamoPythonNet3", il est possible que vous deviez faire des ajustements si vous choisissez un autre moteur.
-
Le script ne fonctionne pas pour les autres types de catégorie
(exemple : Chemin de Câbles)
- Une attention particulière est faite sur la détermination de l'angle de rotation à appliquer :
- positif ou négatif selon le résultat de la composante Z du Produit Vectoriel (direction instance et direction de la canalisation)
-
attention à la direction de la ligne d'axe de la méthode
ElementTransformUtils.RotateElement()
, si la composante Z est négative, le sens de rotation sera inversé
code
import clr
import sys
import System
from System.Collections.Generic import List
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS
#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
#import specify namespace
from Autodesk.Revit.DB.Plumbing import *
from Autodesk.Revit.DB.Mechanical import *
from Autodesk.Revit.DB.Structure import *
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
import math
def get_shape(lst, shape=()):
"""
returns the shape of nested lists
"""
if not hasattr(lst, "__iter__") or isinstance(lst, (str, System.String)):
# base case
return {"shape":shape, "ndim":len(shape)}
if hasattr(lst[0], "__iter__") and not isinstance(lst[0], (str, System.String)):
l = len(lst[0])
shape += (len(lst), )
# recurse
shape = get_shape(lst[0], shape)
return shape #{"shape":shape, "ndim":len(shape)}
def get_order_connectors(elemA, elemB, elemC):
test = elemA
lst = [elemA, elemB, elemC]
# create group by direction
groupA = [i for i in lst if abs(test.Location.Curve.Direction.CrossProduct(i.Location.Curve.Direction).Z) < 0.001]
groupB = [i for i in lst if abs(test.Location.Curve.Direction.CrossProduct(i.Location.Curve.Direction).Z) > 0.001]
lstGroup = [groupA, groupB]
# sort list of group by len
lstGroup.sort(key = len, reverse = True)
# flat the group : the last element is the branch
lst_sort_elem = sum(lstGroup, [])
lst_curve_elems = [i.Location.Curve for i in lst_sort_elem]
orderConnectors = []
# get closest connectors
for idxA, elem in enumerate(lst_sort_elem):
other_curve = [c for idxB, c in enumerate(lst_curve_elems) if idxA != idxB][0]
print(other_curve)
closest_connectors = sorted(elem.ConnectorManager.Connectors, key = lambda c : other_curve.Project(c.Origin).Distance)
orderConnectors.append(closest_connectors[0])
#
return orderConnectors
def get_FamilyInstance_direction(elem):
connectors = [c for c in elem.MEPModel.ConnectorManager.Connectors if c.Domain in [Domain.DomainPiping, Domain.DomainHvac]]
i = 0
groups_arr = [ [connectors.pop(0)] ]
while connectors and i < 10:
i += 1
con = connectors.pop(0)
is_placed = False
for idx, group in enumerate(groups_arr):
# if connector CoordinateSystem is parrallel add to current group
if con.CoordinateSystem.BasisZ.CrossProduct(group[-1].CoordinateSystem.BasisZ).GetLength() < 0.02:
group.append(con)
is_placed = True
if not is_placed :
groups_arr.append([con])
#
print(f"{groups_arr=}")
main_connectors = next((g for g in groups_arr if len(g) == 2), None)
if main_connectors is not None:
pta, ptb = sorted([c.Origin for c in main_connectors], key = lambda p : p.X)
pta, ptb = [c.Origin for c in main_connectors]
return DB.Line.CreateBound(pta, ptb).Direction.Normalize(), main_connectors
return None, []
def toList(x):
if isinstance(x, (list, dict)) or \
(hasattr(x, "GetType") and x.GetType().GetInterface("ICollection") is not None):
return x
else : return [x]
#Preparing input from dynamo to revit
lst_elemCurves = [x for x in toList(UnwrapElement(IN[0])) if isinstance(x.Location, LocationCurve)]
lst_elemCurveIds = [x.Id for x in lst_elemCurves]
arr_arr_DSPoints = toList(IN[1])
arr_arr_DSPoints[0] = toList(arr_arr_DSPoints[0])
#
# check if data input structure is correct
shape_Elem = get_shape(lst_elemCurves)
shape_DSPoints = get_shape(arr_arr_DSPoints)
#
if shape_Elem["ndim"] == 1\
and shape_DSPoints["ndim"] == 2\
and shape_Elem["shape"][0] == shape_DSPoints["shape"][0]:
pass
else:
raise Exception("wrong list input structure")
fitting_type = UnwrapElement(IN[2])
out_fitting = []
debug = []
#
is_tee = fitting_type.Family.get_Parameter(BuiltInParameter.FAMILY_CONTENT_PART_TYPE).AsInteger() == 6
#Do some action in a Transaction
TransactionManager.Instance.EnsureInTransaction(doc)
for array_DSPts, elem in zip(arr_arr_DSPoints, lst_elemCurves):
print("pass")
# get datas from Curve
c_system = list(elem.ConnectorManager.Connectors)[0].CoordinateSystem
curve_elem = elem.Location.Curve.Clone()
curve_direction = curve_elem.Direction.Normalize()
level = doc.GetElement(elem.LevelId)
#
array_pts = [p.ToXyz() for p in array_DSPts]
array_pts.sort(key = lambda p : p.DistanceTo(curve_elem.GetEndPoint(0)) )
elemId = elem.Id
#
for pt in array_pts:
newfam = None
try:
newId = MechanicalUtils.BreakCurve(doc, elemId, pt)
except Exception as ex:
print(ex)
newId = PlumbingUtils.BreakCurve(doc, elemId, pt)
#
doc.Regenerate()
elemA = doc.GetElement(elemId)
curveA = elemA.Location.Curve
elemB = doc.GetElement(newId)
curveB = elemB.Location.Curve
try :
v_axis = curve_direction.CrossProduct(XYZ.BasisX)
axis = Line.CreateUnbound(pt, v_axis)
except:
v_axis = curve_direction.CrossProduct(XYZ.BasisY)
axis = Line.CreateUnbound(pt, v_axis)
# tee creation
if is_tee :
print("is tee")
# create temapary the third element (branch)
elemCId = list(DB.ElementTransformUtils.CopyElement(doc, elemA.Id, XYZ.Zero))[0]
elemC = doc.GetElement(elemCId)
# rotate if verticale
if abs(c_system.BasisZ.Z) > 0.99:
axis = Line.CreateUnbound(pt, c_system.BasisY)
DB.ElementTransformUtils.RotateElement(doc, elemCId, axis, math.pi / 2)
else:
DB.ElementTransformUtils.RotateElement(doc, elemCId, axis, math.pi / 2)
#
doc.Regenerate()
conA, conB, conC = get_order_connectors(elemA, elemB, elemC)
newfam = doc.Create.NewTeeFitting(conA, conB, conC)
newfam.ChangeTypeId(fitting_type.Id)
out_fitting.append(newfam)
# delete the temp branch
doc.Delete(elemCId)
#
# accessory creation
else:
if not fitting_type.IsActive:
fitting_type.Activate()
newfam = doc.Create.NewFamilyInstance(pt, fitting_type, level, StructuralType.NonStructural)
out_fitting.append(newfam)
doc.Regenerate()
# get direction of familyInstance by connectors position
v1, connectors = get_FamilyInstance_direction(newfam)
print(f"v1={v1.ToString()}")
if v1 is not None:
# check if vectors are Clockwise
factor = 1 if v1.CrossProduct(curve_direction).Z > 0.01 else -1
angle = v1.AngleTo(curve_direction)
# align direction of familyinstance
## rotate if verticale
if abs(c_system.BasisZ.Z) > 0.99 :
DB.ElementTransformUtils.RotateElement(doc, newfam.Id, axis, factor * angle )
factorX = 1 if c_system.BasisX.CrossProduct(XYZ.BasisX).Z < 0 else -1
DB.ElementTransformUtils.RotateElement(doc, newfam.Id, curve_elem, factorX * c_system.BasisX.AngleTo(XYZ.BasisX))
## if horizontal redifine the axis
else:
axis = Line.CreateBound(pt, pt + XYZ(0,0,1))
DB.ElementTransformUtils.RotateElement(doc, newfam.Id, axis, factor * angle )
#
doc.Regenerate()
# connect connectors to pipes/ducts
for con in connectors:
for curv_, elem_ in [[curveA, elemA], [curveB, elemB]]:
interR = curv_.Project(con.Origin)
#print(curv_.ComputeNormalizedParameter(interR.Parameter))
#
if interR is not None and 0.005 < curv_.ComputeNormalizedParameter(interR.Parameter) < 0.995:
closest_connectors = sorted(elem_.ConnectorManager.Connectors, key = lambda c : c.Origin.DistanceTo(con.Origin))
closest_connector = closest_connectors[0]
closest_connector.ConnectTo(con)
closest_connector.Origin = con.Origin
TransactionManager.Instance.TransactionTaskDone()
OUT = lst_elemCurves, out_fitting
ont été générés par IA :
- l'image
- la musique de vidéo
0 commentaires:
Enregistrer un commentaire