9 août 2020

[Dynamo += Python ] Gestion des sous-projets des maquettes liées

 




Comment gérer les états des sous projets en Python avec l'API Revit ?

#DynamoBIM #Python #AutodeskExpertElite #AutodeskCommunity 

if this article is not in your language, use the Google Translate widget ⬈ (bottom of page for Mobile version )

MAJ 21/05/2025 (version ACC +  WPF) voir ici 🔗 


Suite à une idée d'amélioration, voici un exemple de script permettant de gérer l'ouverture et la fermeture des sous-projets d'une maquette liée (avec une interface Winform)

Le seul moyen à ce jour pour changer les états des sous-projets d'une maquette liée via l'API Revit, c'est de recharger le lien avec une configuration via la méthode LoadFrom()

Dans l'exemple ci-après, on crée une configuration par défaut où les sous projets "utilisateur" sont fermés. Puis, on ouvre ceux que l'on souhaite avec la méthode Open() 

WorksetConfiguration(WorksetConfigurationOption.CloseAllWorksets)


Pour ceux qu'ils souhaitent que ce script peut être encore amélioré avec l'intégration d'un TreeNode regroupant l'ensemble des maquettes liées (mode collaboratif activé), ou chaque nœud principal serait une maquette liée.

J'en profite pour y introduire un bref exemple de gestion multi-langage sur une Interface User grâce à l'API Revit

 if app.Language == Autodesk.Revit.ApplicationServices.LanguageType.French


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

#import net library
clr.AddReference('System')
from System.Collections.Generic import List

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

#Get Important vars
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application

clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')
import System.Drawing
import System.Windows.Forms

from System.Drawing import *
from System.Windows.Forms import *

class WksetLnkUtils(Form):
    def __init__(self):
        rvtLinkInst = FilteredElementCollector(doc).OfClass(RevitLinkInstance)
        self._dictRvtInstance = {}
        for x in rvtLinkInst:
            _lkdoc = x.GetLinkDocument()
            if _lkdoc.IsWorkshared:
                self._dictRvtInstance[x.Name] = x
        self._dictWkset = {}
        self.rvtRvtlinkType = None
        self.out = None
        self.InitializeComponent()
    
    def InitializeComponent(self):
        self._groupBox1 = System.Windows.Forms.GroupBox()
        self._checkedListBox1 = System.Windows.Forms.CheckedListBox()
        self._buttonOpen = System.Windows.Forms.Button()
        self._buttonClose = System.Windows.Forms.Button()
        self._comboBoxRvtLink = System.Windows.Forms.ComboBox()
        self._groupBox2 = System.Windows.Forms.GroupBox()
        self._groupBox1.SuspendLayout()
        self._groupBox2.SuspendLayout()
        self.SuspendLayout()
        # 
        # groupBox1
        self._groupBox1.Controls.Add(self._checkedListBox1)
        self._groupBox1.Location = System.Drawing.Point(30, 123)
        self._groupBox1.Name = "groupBox1"
        self._groupBox1.Size = System.Drawing.Size(361, 423)
        self._groupBox1.TabIndex = 0
        self._groupBox1.TabStop = False
        self._groupBox1.Text = "Select Workset"
        # 
        # checkedListBox1
        self._checkedListBox1.FormattingEnabled = True
        self._checkedListBox1.Items.Clear()
        self._checkedListBox1.Location = System.Drawing.Point(16, 42)
        self._checkedListBox1.Name = "checkedListBox1"
        self._checkedListBox1.Size = System.Drawing.Size(326, 361)
        self._checkedListBox1.TabIndex = 0
        # 
        # buttonOpen
        self._buttonOpen.Location = System.Drawing.Point(60, 560)
        self._buttonOpen.Name = "buttonOpen"
        self._buttonOpen.Size = System.Drawing.Size(300, 40)
        self._buttonOpen.TabIndex = 1
        if app.Language == Autodesk.Revit.ApplicationServices.LanguageType.French:
            self._buttonOpen.Text = "Ouvrir les sous-projets selectionnés \n(les autres seront fermés)"
        else:
            self._buttonOpen.Text = "Open Selected Worksets (others will be close)"     
        self._buttonOpen.UseVisualStyleBackColor = True
        self._buttonOpen.Click += self.ButtonOpenClick
        # 
        # buttonClose
        self._buttonClose.Location = System.Drawing.Point(60, 620)
        self._buttonClose.Name = "buttonClose"
        self._buttonClose.Size = System.Drawing.Size(300, 40)
        self._buttonClose.TabIndex = 1
        if app.Language == Autodesk.Revit.ApplicationServices.LanguageType.French:
            self._buttonClose.Text = "Fermer les sous-projets selectionnés \n(les autres seront ouverts)"
        else:
            self._buttonClose.Text = "Close Selected Worksets (others will be open)"
        self._buttonClose.UseVisualStyleBackColor = True
        self._buttonClose.Click += self.ButtonCloseClick
        # 
        # comboBoxRvtLink
        self._comboBoxRvtLink.Items.AddRange(System.Array[System.Object](self._dictRvtInstance.keys()))
        self._comboBoxRvtLink.FormattingEnabled = True
        self._comboBoxRvtLink.Location = System.Drawing.Point(19, 41)
        self._comboBoxRvtLink.Name = "comboBoxRvtLink"
        self._comboBoxRvtLink.Size = System.Drawing.Size(323, 24)
        self._comboBoxRvtLink.TabIndex = 2
        self._comboBoxRvtLink.SelectedIndexChanged += self.ComboBoxRvtLinkSelectedIndexChanged
        # 
        # groupBox2
        self._groupBox2.Controls.Add(self._comboBoxRvtLink)
        self._groupBox2.Location = System.Drawing.Point(30, 17)
        self._groupBox2.Name = "groupBox2"
        self._groupBox2.Size = System.Drawing.Size(361, 89)
        self._groupBox2.TabIndex = 3
        self._groupBox2.TabStop = False
        self._groupBox2.Text = "Select Revit Link"
        # 
        # MainForm
        self.ClientSize = System.Drawing.Size(430, 700)
        self.Controls.Add(self._groupBox2)
        self.Controls.Add(self._buttonClose)
        self.Controls.Add(self._buttonOpen)
        self.Controls.Add(self._groupBox1)
        self.Name = "WksetLnkUtils"
        self.Text = "Manage linked model Worksets"
        self._groupBox1.ResumeLayout(False)
        self._groupBox2.ResumeLayout(False)
        self.ResumeLayout(False)


    def setLinkstatut(self, wksetsId):
        if self.rvtRvtlinkType is not None:
            #create WorksetConfiguration with CloseAllWorksets by default
            wkstConfig = WorksetConfiguration(WorksetConfigurationOption.CloseAllWorksets)
            lstWksetId = List[WorksetId](wksetsId)
            wkstConfig.Open(lstWksetId) 
            loada = self.rvtRvtlinkType.LoadFrom(self.mpath, wkstConfig)
            self.out = loada.LoadResult
            wkstConfig.Dispose()        
            
    def ButtonOpenClick(self, sender, e):
        wksetsId = [ self._dictWkset.get(wksetnam) for wksetnam in self._checkedListBox1.CheckedItems]
        self.setLinkstatut(wksetsId)


    def ButtonCloseClick(self, sender, e):
        self.wksetsId = []
        #get items are not checked for open others Worksets
        setDiff = set(self._checkedListBox1.Items) - set(self._checkedListBox1.CheckedItems)
        wksetsId = [ self._dictWkset.get(wksetnam) for wksetnam in setDiff]
        self.setLinkstatut(wksetsId)


    def ComboBoxRvtLinkSelectedIndexChanged(self, sender, e):
        rvtLinkInstance = self._dictRvtInstance.get(sender.Text)
        self.rvtRvtlinkType = doc.GetElement(rvtLinkInstance.GetTypeId())
        _linkdoc = rvtLinkInstance.GetLinkDocument()
        _worksetTable = _linkdoc.GetWorksetTable()
        self.mpath = _linkdoc.GetWorksharingCentralModelPath()
        
        lstPreview =  WorksharingUtils.GetUserWorksetInfo(self.mpath)
        self._dictWkset = {item.Name : _worksetTable.GetWorkset(item.Id).Id for item in lstPreview }
        self._checkedListBox1.Items.Clear()
        self._checkedListBox1.Items.AddRange(System.Array[System.Object](self._dictWkset.keys()))
        
objForm = WksetLnkUtils()
objForm.Show()

OUT = 0
 
Nouvelle version (21/05/2025):
  • conversion en WPF 
  • compatibles liens ACC
  • ajout de style (visualisation)
  • compatible seulement avec IronPython3 et PythonNet3
video


code 
 
# Python Script  | Main
import clr
import sys
import System
from System.Collections.Generic import List

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

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

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

#Get Important vars
doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application

clr.AddReference('System.Data')
from System.Data import *

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
import System.Windows.Controls 
from System.Windows.Controls import *
import System.Windows.Controls.Primitives 
from System.IO import StringReader
from System.Xml import XmlReader
from System.Windows import LogicalTreeHelper 
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
from System.Windows.Data import IValueConverter

import time
import traceback

class MainWindow(Window):
    __slot__ = "Window"
    string_xaml = '''
    <Window 
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:system="clr-namespace:System;assembly=System.Runtime"
            x:Name="MainWindow"
            Title="Manage linked model Worksets"
            MinHeight="700" MinWidth="430"
            Width="430" Height="720"
            ResizeMode="CanResizeWithGrip">
            <Window.Resources>
                <TextBlock x:Key="tooltipTextBlock" TextWrapping="Wrap">
                    <Run>Green → Open Workset</Run>
                    <LineBreak/>
                    <Run>Red → Closed Workset</Run>
                    <LineBreak/>
                    <Run>use 'Shift + click' for multiple selection</Run>
                </TextBlock>
            </Window.Resources>
            <Grid>
                <GroupBox
                    Header="Select Revit Link"
                    Grid.Column="0" Grid.Row="0"
                    HorizontalAlignment="Stretch" VerticalAlignment="Top"
                    Margin="30,17,30,0" Height="70">
                    <ComboBox
                        Name="comboBoxRvtLink" Background="LightBlue"
                        DisplayMemberPath="Name"
                        VerticalAlignment="Top"
                        Margin="5,10,5,5"
                        HorizontalAlignment="Stretch" />
                </GroupBox>
                <GroupBox
                    Header="Select Workset"
                    Grid.Column="0" Grid.Row="0"
                    HorizontalAlignment="Stretch"
                    ToolTip="{StaticResource tooltipTextBlock}"
                    Margin="30,102,30,116">
                    <ListView
                        Name="checkedListBox1"
                        Grid.Column="0" Grid.Row="0"
                        HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                        SelectionMode="Extended"
                        ToolTip="{StaticResource tooltipTextBlock}"
                        Margin="5,10,5,5">
                        <ListView.ItemContainerStyle>
                            <Style TargetType="ListViewItem">
                                <Setter Property="IsSelected">
                                    <Setter.Value>
                                        <Binding Path="IsChecked"
                                                 Mode="OneWayToSource"
                                                 UpdateSourceTrigger="PropertyChanged"/>
                                    </Setter.Value>
                                </Setter>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Path=IsOpen}" Value="True">
                                        <Setter Property="Background" Value="LightGreen" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding Path=IsOpen}" Value="False">
                                        <Setter Property="Background" Value="LightCoral" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </ListView.ItemContainerStyle>
                        <ListView.ItemTemplate>
                            <DataTemplate>
                            <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                                <CheckBox VerticalAlignment="Center" Margin="0,0,0,0" IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}">
                                    <CheckBox.Content>
                                        <TextBlock Text = "{Binding Name}"/>
                                    </CheckBox.Content>
                                </CheckBox>
                            </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </GroupBox>
                <Button
                    Name="buttonOpen"
                    Content="Open the selected worksets\n(the others will be closed)"
                    Height="40" VerticalAlignment="Bottom"
                    Margin="30,0,30,68" />
                <Button
                    Name="buttonClose"
                    Content="Close the selected worksets\n(the others will be opened)"
                    Height="40" VerticalAlignment="Bottom"
                    Margin="30,0,30,15" />
            </Grid>
    </Window>'''
        
  
    def __init__(self):
        super().__init__()
        xr = XmlReader.Create(StringReader(MainWindow.string_xaml))
        self.winLoad = XamlReader.Load(xr) 
        #
        self._rvtLinkInst = FilteredElementCollector(doc).OfClass(RevitLinkInstance)\
                            .Where(System.Func[DB.Element, System.Boolean](lambda x : doc.GetElement(x.GetTypeId()).GetLinkedFileStatus() == LinkedFileStatus.Loaded))\
                            .ToList()
                            
        self.dt = DataTable("CustomData")
        self.link_doc = None
        self.InitializeComponent()
        
    def InitializeComponent(self):
        #
        self.checkedListBox1 = LogicalTreeHelper.FindLogicalNode(self.winLoad, "checkedListBox1")
        
        self.comboBoxRvtLink = LogicalTreeHelper.FindLogicalNode(self.winLoad, "comboBoxRvtLink")
        self.comboBoxRvtLink.ItemsSource  = self._rvtLinkInst
        self.comboBoxRvtLink.SelectionChanged += self.ComboBoxRvtLinkSelectedIndexChanged
        #
        self.buttonClose = LogicalTreeHelper.FindLogicalNode(self.winLoad, "buttonClose")
        self.buttonClose.Click += self.ButtonCloseClick
        #
        self.buttonOpen = LogicalTreeHelper.FindLogicalNode(self.winLoad, "buttonOpen")
        self.buttonOpen.Click += self.ButtonOpenClick
        #

            
    def ButtonCloseClick(self, sender, e):
        try:
            wkset_not_selected = [row["Workset"] for row in self.dt.Rows if not row["IsChecked"]]
            wksetsId = [wkset.Id for wkset in wkset_not_selected]
            self.SetLinkStatus(wksetsId)
            self.UpdateWorksetsList()
        except Exception as ex:
            print(traceback.format_exc())
        
    def ButtonOpenClick(self, sender, e):
        try:
            selected_worksets = [row["Workset"] for row in self.dt.Rows if row["IsChecked"]]
            wksetsId = [wkset.Id for wkset in selected_worksets]
            self.SetLinkStatus(wksetsId)
            self.UpdateWorksetsList()
        except Exception as ex:
            print(traceback.format_exc())
            
    def GetDataFromLink(self):
        try:
            rvtLinkInstance = self.comboBoxRvtLink.SelectedItem
            self.rvtRvtlinkType = doc.GetElement(rvtLinkInstance.GetTypeId())
            self.link_doc = rvtLinkInstance.GetLinkDocument()
            return self.link_doc
        except Exception as ex:
            print(traceback.format_exc())
            
    def ComboBoxRvtLinkSelectedIndexChanged(self, sender, e):
        try:
            self.UpdateWorksetsList()
        except Exception as ex:
            print(traceback.format_exc())
            
    def UpdateWorksetsList(self):
        try:
            self.GetDataFromLink() # populate self.mpath
            self.dt = DataTable("CustomData")
            # Create columns
            self.dt.Columns.Add("Workset", Workset)
            self.dt.Columns.Add("Name", System.String)
            self.dt.Columns.Add("IsChecked", System.Boolean)
            self.dt.Columns.Add("IsOpen", System.Boolean)
            if self.link_doc  is not None:
                userWorksets = FilteredWorksetCollector(self.link_doc).OfKind(WorksetKind.UserWorkset).ToWorksets()
                lst_workset_infos = sorted(userWorksets, key=lambda x : x.Name)
                # add rows
                for wkset in lst_workset_infos:
                    if wkset is not None:
                        self.dt.Rows.Add(wkset, wkset.Name, False, wkset.IsOpen)
            #
            print("change checkedListBox1")
            self.checkedListBox1.ClearValue(ItemsControl.ItemsSourceProperty)
            self.checkedListBox1.ItemsSource = self.dt.DefaultView
        except Exception as ex:
            print(traceback.format_exc())
        
        
    def SetLinkStatus(self, workset_ids):
        if self.rvtRvtlinkType is not None:
            dict_extRef = self.rvtRvtlinkType.GetExternalResourceReferences()
            externalResourceReference = None
            for item in dict_extRef:
                externalResourceType = item.Key
                externalResourceReference = item.Value
                print(externalResourceReference.InSessionPath )
                break
            if externalResourceReference is not None:
                wkstConfig = WorksetConfiguration(WorksetConfigurationOption.CloseAllWorksets)
                wkstConfig.Open(List[WorksetId](workset_ids))
                load_result = self.rvtRvtlinkType.LoadFrom(externalResourceReference, wkstConfig)
                self.out = load_result.LoadResult
                wkstConfig.Dispose()

objWindow = MainWindow()
objWindow.winLoad.Show()

OUT = 0

Voilà à vos claviers, avec ou sans glaces 
 Il fait chaud! | Dessin smiley, Emoticone amour, Smileys

4 commentaires:

  1. Excellent. Je vais l'essayer de suite. Merci

    RépondreSupprimer
  2. Adding
    # -*- coding: latin-1 -*-
    and it works well in a pyrevit toolbar

    RépondreSupprimer
  3. Bonjour Cyril, excellent script! Quelles sont les modifications à faire pour que ce script fonctionne avec des maquettes hébergées sur le BIM360 (cloud)? De mon côté le script fonctionne seulement sur les maquettes sur un réseau "local". Merci!

    RépondreSupprimer
    Réponses
    1. Bonjour,
      Actuellement je n'ai pas accès à BIM360, donc je ne sais
      (je suppose qu'il faut utiliser la méthode 'LoadFrom Method (ExternalResourceReference, WorksetConfiguration)')
      Je mettrai l'article à jour si le cas se présente

      Supprimer