4 déc. 2020

[Dynamo += Python] Un peu de décoration

Credit RevitCity pour le Sapin
Une fonction du moment décorée 




À l'approche des fêtes de fin d'année les décorations sont de sorties, un moment opportun pour voir ou revoir les décorateurs Python.


Rappel Définition
Un décorateur est une fonction qui modifie le comportement d'autres fonctions.

Un bon exemple est l'utilisation des Transactions dans le contexte de l'API Revit.
Une transaction est un contexte requis pour apporter des modifications à un modèle Revit.

Transaction Start 
           ↓
Modification du Modèle
           ↓
Transaction End

Généralement 1 seule Transaction suffit par nœud Python, mais il peut s'avérés qu'il soit nécessaire d'implémenter plusieurs Transactions.

Pour un premier exemple prenons une création d'une ligne de Modèle basée sur un plan de construction (plan de la vue active à laquelle on ajoute une élévation).

Bien qu'une seule Transaction pourrait suffire nous en mettons 2 pour l'exemple.

  • Exemple avec les Transactions Dynamo


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

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

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument

def decoTransaction(func):
    def wrapper(*args, **kwargs):
        TransactionManager.Instance.ForceCloseTransaction()
        TransactionManager.Instance.EnsureInTransaction(doc)
        ret = func(*args, **kwargs) 
        TransactionManager.Instance.TransactionTaskDone()
        return ret      
    return wrapper  


@decoTransaction
def create_planarRef():
	"""
	create a plane to allow pickPoint (split Point) in 3D View 
	"""
	new_plan = Plane.CreateByNormalAndOrigin(doc.ActiveView.ViewDirection, doc.ActiveView.Origin + XYZ(0,0,1))
	sp = SketchPlane.Create(doc, new_plan)
	#active the SketchPlane
	doc.ActiveView.SketchPlane = sp
	return sp
	
	
@decoTransaction	
def drawModelLine(Skplan):
	"""
	create model Line 
	"""
	pt1 = uidoc.Selection.PickPoint(Selection.ObjectSnapTypes.Nearest, "choisir le 1er Point ")
	pt2 = uidoc.Selection.PickPoint(Selection.ObjectSnapTypes.Nearest, "choisir le 2e Point ")
	lineBase = Line.CreateBound(pt1,pt2)
	modelCurv = doc.Create.NewModelCurve(lineBase, Skplan)
	return modelCurv
	
skPlan = create_planarRef()
modelLine = drawModelLine(skPlan)

OUT = modelLine

                               




  • Le même exemple avec les Transactions de l'API Revit, où l'on passe le nom de la fonction à décorer au nom de la Transaction avec la propriété __name__


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

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

clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
uidoc = uiapp.ActiveUIDocument

pf_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
sys.path.append(pf_path + '\\IronPython 2.7\\Lib')
import functools

def decoTransaction(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        t = Transaction(doc, func.__name__)
        t.Start()
        ret = func(*args, **kwargs)
        doc.Regenerate()
        t.Commit()
        t.Dispose()
        return ret      
    return wrapper  


@decoTransaction
def create_planarRef():
    """
    create a plane to allow pickPoint (split Point) in 3D View 
    """
    new_plan = Plane.CreateByNormalAndOrigin(doc.ActiveView.ViewDirection, doc.ActiveView.Origin + XYZ(0,0,1))
    sp = SketchPlane.Create(doc, new_plan)
    #active the SketchPlane
    doc.ActiveView.SketchPlane = sp
    return sp
    
    
@decoTransaction    
def drawModelLine(Skplan):
    """
    create model Line 
    """
    pt1 = uidoc.Selection.PickPoint(Selection.ObjectSnapTypes.Nearest, "choisir le 1er Point ")
    pt2 = uidoc.Selection.PickPoint(Selection.ObjectSnapTypes.Nearest, "choisir le 2e Point ")
    lineBase = Line.CreateBound(pt1,pt2)
    modelCurv = doc.Create.NewModelCurve(lineBase, Skplan)
    return modelCurv
    
skPlan = create_planarRef()
modelLine = drawModelLine(skPlan)

OUT = modelLine





  • Un second exemple avec une version plus poussée ou l'on passe un argument au décorateur, ici on choisit si l'on doit valider la Transaction (Commit) ou revenir en arrière (Rollback).

Le script efface temporairement un sol afin de récupérer sa géométrie d'esquisse pour ensuite créer une hachure correspondante.

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

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

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

from System.Collections.Generic import List

pf_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ProgramFilesX86)
sys.path.append('%sIronPython 2.7Lib' % pf_path)
import functools

def decoTransaction(commit):
    def subDecoTransaction(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            TransactionManager.Instance.ForceCloseTransaction()
            t = Transaction(doc, func.__name__)
            t.Start()
            ret = func(*args, **kwargs)
            if commit:
                t.Commit()
            else:
                t.RollBack()            
            t.Dispose()
            return ret      
        return wrapper  
    return subDecoTransaction   


def getPerimeterFloor(floor):
    #subfunction
    @decoTransaction(commit = False)
    def tempDelete(elem):
        deleted = doc.Delete(elem.Id)
        return deleted
    #main function  
    for d in tempDelete(floor):
        test_el = doc.GetElement(d)
        if isinstance(test_el, Sketch):
            profileArry = test_el.Profile
            return profileArry
        
@decoTransaction(commit = True) 
def drawhatchfromLoop(arrayCurves, filledRegionName):
    """
    create hatch from Loop
    """
    lstCurveLoop = [CurveLoop.Create([x for x in sublst]) for sublst in arrayCurves]
    boundariesLoop = List[CurveLoop](lstCurveLoop)
    filledType =  FilteredElementCollector(doc).OfClass(FilledRegionType).ToElements().Find(lambda x : Element.Name.GetValue(x) == filledRegionName)
    filled = FilledRegion.Create(doc, filledType.Id, doc.ActiveView.Id, boundariesLoop)
    return filled
    
floor = UnwrapElement(IN[0])
filledRegionName = IN[1]

arrayCurves = getPerimeterFloor(floor)
if arrayCurves is not None:
    OUT = drawhatchfromLoop(arrayCurves, filledRegionName)





Note :
Le décorateur @functools.wraps(func)  est optionnel, il permet de conserver la chaine de documentation de la fonction décorée. (voir module functools)

Bien entendu les possibilités d'utilisation des décorateurs Python ne se limitent pas aux Transactions.
Voici deux très bons articles sur le sujet

 À vos décorateurs ! Ou à vos décorations 🎄🎄🎅 

0 commentaires:

Enregistrer un commentaire