Journey into pyRevit
Having a good library of drafting view details has proven to be an important component of completing a project quickly and not repeating work. Native Revit has a function "insert views from file" but it has the following drawbacks:
- No ability to filter views shown by view type
- No ability to zoom in or resize the preview window for larger views
- No ability to choose a custom name for the view on import or to choose the view to replace an existing view
- No control on the view type of the view after import
- Can't export views from the current file to another (ie the same process in reverse)
I sought out to create our own internal tool which would allow us to transfer views to or from our detail library files that solved all these drawbacks using pyRevit. In order for any of the code within this post to be of use to anyone, the below context needs to be understood:
- We have a "Master" detail Revit file for each of the major materials--Concrete, Cold Form, Masonry, Steel, Wood. These are stored on a network drive under a common folder, such that the path to a single file resembles the below:
network_drive->Structural->Drawing Tools->Revit->Details->Steel->Steel Master File.rvt
- Each Master detail Revit file is workshared. This serves a few purposes:
- Revit automatically creates and manages backup versions of the files and creates and easy way to add change messages.
- A local copy is created on users machines which adds a layer of protection in the event that the server file is lost or compromised.
The general outline of the script operation is as such (note target document is the document that the views are being transferred into):
- Verify that the current project Revit version is 2024 (which is what our Master detail files are).
- Verify that the library files can be found.
- Asks the user to select a library file.
- Attempts to read the current users
Revit.inifile to determine the directory that local versions of workshared files are stored. - Attempts to open the workshared library file.
- Opens the WPF form containing available views to transfer.
Past this point, the user has a WPF form that displays a data grid of all available views with a combo box to filter by drafting view type. The form displays a preview of the currently selected view in the right hand pane that is able to be zoomed, panned and reset.
The checkbox in the first data grid column is to select the view for transfer. The next column contains the original view name but can be clicked into in order to rename the view upon transfer. The third column is a toggle button that will allow the checked view to replace the matching view if one with the same name is found in the target document. Lastly, the fourth column is a combo box to determine the transferred view type (either "Retain Original" or one of the view types from the target document).
The code pertaining to the WPF form is quite long and is used in both the import and export scripts so I've elected to move it into a library file common to the overall extension. A preview of the import tool is shown below:

NOTE: all of the code below is put together by referencing multiple internet sources and by heavily modifying existing pyRevit functions and forms. I am *not* a programmer so it is likely poorly written and can be improved upon.
ViewSelectorForm.xaml
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShowInTaskbar="False"
WindowStartupLocation="CenterScreen"
HorizontalContentAlignment="Center"
MouseWheel="view_zoom">
<Window.Resources>
<Style x:Key="ClearButton" TargetType="Button">
<Setter Property="Background" Value="White"/>
</Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Viewbox>
<Border x:Name="Border" CornerRadius="10"
Background="#FFFFFFFF"
Width="40" Height="20">
<Border.Effect>
<DropShadowEffect ShadowDepth="0.5" Direction="0" Opacity="0.3" />
</Border.Effect>
<Ellipse x:Name="Ellipse" Fill="#FFFFFFFF" Stretch="Uniform"
Margin="2 1 2 1"
Stroke="Gray" StrokeThickness="0.2"
HorizontalAlignment="Stretch">
<Ellipse.Effect>
<DropShadowEffect BlurRadius="10" ShadowDepth="1" Opacity="0.3" Direction="260" />
</Ellipse.Effect>
</Ellipse>
</Border>
</Viewbox>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Checked">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#FF4CD661"
Duration="0:0:0.1" />
<ThicknessAnimation Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin"
To="20 1 2 1"
Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Unchecked">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="White"
Duration="0:0:0.1" />
<ThicknessAnimation Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin"
To="2 1 2 1"
Duration="0:0:0.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,10">
<DockPanel>
<StackPanel>
<TextBox x:Name="search_tb" Height="25"
Padding="5,0,24,0"
VerticalContentAlignment="Center"/>
<Button x:Name="clrsearch_b" Visibility="Collapsed" Margin="0,-25,2,0" Padding="0,-4,0,0"
Width="22px" Height="22px" HorizontalAlignment="Right"
BorderThickness="0"
Style="{StaticResource ClearButton}"
Click="clear_search">
<Button.Content>
<Canvas Width="25" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center">
<Path Canvas.Top="3" Canvas.Left="1" Data="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" Fill="DimGray" />
</Canvas>
</Button.Content>
</Button>
</StackPanel>
</DockPanel>
<DockPanel x:Name="ctx_groups_dock" Margin="0,10,0,0" Visibility="Collapsed">
<TextBlock x:Name="ctx_groups_title_tb" FontSize="14" Margin="0,3,10,0"
DockPanel.Dock="Left">List Group:</TextBlock>
<ComboBox x:Name="ctx_groups_selector_cb" Height="25"/>
</DockPanel>
</StackPanel>
<StackPanel DockPanel.Dock="Bottom">
<Button x:Name="select_b" Content="" Height="32" Click="button_select" Margin="0,10,0,0"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<DataGrid x:Name="views_dg"
Grid.Column="0"
SelectionChanged="selected_item_changed"
SelectionMode="Extended"
HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Path=., Mode=OneWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="transfer_col"
Width="Auto">
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox VerticalAlignment="Center"
HorizontalAlignment="Center"
IsChecked="{Binding checked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding checkable}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="View Name"
Width="*"
Binding="{Binding view_name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Header="Replace?"
Width="Auto">
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton VerticalAlignment="Center"
HorizontalAlignment="Center"
IsChecked="{Binding replace, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding replacable, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="View Type"
Width="Auto">
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
</Style>
</DataGridColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox VerticalAlignment="Center"
IsSynchronizedWithCurrentItem="True"
SelectedValue="{Binding view_type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding available_view_types}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<GridSplitter Grid.Column="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
ResizeDirection="Columns"
Visibility="Collapsed"/>
<Border x:Name="view_border"
Grid.Column="2"
ClipToBounds="True"
BorderBrush="Black"
BorderThickness="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Image x:Name="view_i"
Source="{Binding Path=., Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
MouseRightButtonDown="view_reset"
MouseLeftButtonDown="view_pan_start"
MouseLeftButtonUp="view_pan_stop"
MouseMove="view_pan_move"/>
</Border>
</Grid>
</DockPanel>
</Window>
DB Tools.py
from pyrevit import revit, DB
from pyrevit import coreutils
from pyrevit import forms
from pyrevit.framework import List, Windows, Media, Controls, ObservableCollection
import os
import shutil
import tempfile
from collections import OrderedDict
class TemporaryDirectory(object):
def __enter__(self):
self.name = tempfile.mkdtemp()
return self.name
def __exit__(self, exc_type, exc_value, traceback):
shutil.rmtree(self.name)
class MasterDetailFile:
def __init__(self, name, path=""):
self.name = name
self.path = path
class TransferDraftingViews:
def __init__(self, doc, targetDoc, transfer_col_header=""):
self._doc = doc
self._targetDoc = targetDoc
self._transfer_col_header = transfer_col_header
self._draftingViews = [x for x in DB.FilteredElementCollector(self._targetDoc).OfClass(DB.View)\
.WhereElementIsNotElementType().ToElements()
if x.ViewType == DB.ViewType.DraftingView]
try:
with revit.TransactionGroup("DB Tools - Transfer Drafting Views", doc=self._doc, assimilate=True, log_errors=False):
with TemporaryDirectory() as temp_dir:
self._viewSelectorForm = TransferDraftingViews.ViewSelectorForm(self, self._doc, self._targetDoc,
temp_dir, self._draftingViews)
self._viewSelectorForm.ShowDialog()
except:
forms.alert("Unknown error. Aborting...", title="DB Tools", exitscript=True)
class ViewSelectorFormItem(forms.Reactive):
def __init__(self, TransferDraftingViews, orig_item,
checked=False, checkable=True,
replace=False, replacable=False):
super(TransferDraftingViews.ViewSelectorFormItem, self).__init__()
self.item = orig_item
self._state = checked
self._checkable = checkable
self._available_view_types = []
self._available_view_types.append("Retain Original")
self._view_type = self._available_view_types[0]
self.view_name = self.name
self._replace = replace
self._replacable = replacable
def __nonzero__(self):
return self._state
def __str__(self):
return self.name
def __contains__(self, value):
return value in self._name
def __getattr__(self, param_name):
return getattr(self.item, param_name)
@property
def name(self):
return self.item.Name
def unwrap(self):
return self.item
@forms.reactive
def checked(self):
return self._state
@checked.setter
def checked(self, value):
self._state = value
@property
def checkable(self):
return forms.WPF_VISIBLE if self._checkable else forms.WPF_COLLAPSED
@checkable.setter
def checkable(self, value):
self._checkable = value
@property
def available_view_types(self):
return self._available_view_types
@property
def view_type(self):
return self._view_type
@view_type.setter
def view_type(self, value):
self._view_type = value
@forms.reactive
def replace(self):
return self._replace
@replace.setter
def replace(self, value):
self._replace = value
@property
def replacable(self):
return forms.WPF_VISIBLE if self._replacable else forms.WPF_COLLAPSED
@replacable.setter
def replacable(self, value):
self._replacable = value
class ViewSelectorForm(forms.WPFWindow):
def __init__(self, TransferDraftingViews, doc, targetDoc, temp_dir, draftingViews):
self._wpfPath = os.path.normpath(r"network_drive\Structural\Drawing Tools\Revit\DB Tools PyRevit Extension\DB Tools.extension\WPF\ViewSelectorForm.xaml")
forms.WPFWindow.__init__(self, self._wpfPath)
self._parent = TransferDraftingViews
self._doc = doc
self._targetDoc = targetDoc
self._temp_dir = temp_dir
self._draftingViews = draftingViews
self.Title = "Select Views To Transfer"
self.transfer_col.Header = self._parent._transfer_col_header
self.Width = 1200
self.Height = 600
self.in_check = False
self.in_uncheck = False
self._origin = Windows.Point(0, 0)
self._start = Windows.Point(0, 0)
self._reset = self.view_i.RenderTransform.Value
self._result = ""
self._numAdded = 0
self._numReplaced = 0
self._viewsToTransfer = []
self._destViewTypes = []
self._update_dest_types()
self._targetViewTypes = []
targetViewTypeCollector = DB.FilteredElementCollector(self._targetDoc).OfClass(DB.ViewFamilyType)
for targetViewType in targetViewTypeCollector:
if targetViewType.ViewFamily == DB.ViewFamily.Drafting:
if not targetViewType in self._targetViewTypes:
self._targetViewTypes.append(targetViewType)
self._context = {revit.query.get_name(self._targetViewTypes[k]): [self._parent.ViewSelectorFormItem(self._parent,self._draftingViews[x]) for x in range(len(self._draftingViews))
if revit.query.get_name(revit.query.get_type(self._draftingViews[x])) == revit.query.get_name(self._targetViewTypes[k])]
for k in range(len(self._targetViewTypes))}
for k in self._context:
for v in self._context[k]:
for destViewType in self._destViewTypes:
if not revit.query.get_name(destViewType) in v.available_view_types:
v.available_view_types.append(revit.query.get_name(destViewType))
self._setup()
def _update_dest_types(self):
destViewTypeCollector = DB.FilteredElementCollector(self._doc).OfClass(DB.ViewFamilyType)
for destViewType in destViewTypeCollector:
if destViewType.ViewFamily == DB.ViewFamily.Drafting:
if not destViewType in self._destViewTypes:
self._destViewTypes.append(destViewType)
def _setup(self):
button_name = "Transfer"
self.select_b.Content = button_name
self.multiselect = True
self.views_dg.SelectionMode = Controls.DataGridSelectionMode.Extended
self.ctx_groups_title = "View Type"
self.ctx_groups_title_tb.Text = self.ctx_groups_title
self.ctx_groups_active = None
self.sort_groups = "sorted"
self._prepare_context()
self.hide_element(self.clrsearch_b)
self.search_tb.TextChanged += self.search_txt_changed
self.ctx_groups_selector_cb.SelectionChanged += self.selection_changed
self.clear_search(None, None)
self.views_dg.Items.Refresh()
def _prepare_context_items(self, ctx_items):
new_ctx = []
for item in ctx_items:
if isinstance(item, self._parent.ViewSelectorFormItem):
item.checkable = self.multiselect
new_ctx.append(item)
else:
new_ctx.append(self._parent.ViewSelectorFormItem(self._parent, item, checked=False,
checkable=self.multiselect, replace=False, replacable=False))
return new_ctx
@staticmethod
def _natural_sort_key(key):
return [int(c) if c.isdigit() else c.lower() for c in re.split(r'(\d+)', key)]
def _prepare_context(self):
if isinstance(self._context, dict) and self._context.keys():
if self.sort_groups == "sorted":
sorted_groups = sorted(self._context.keys())
elif self.sort_groups == "natural":
sorted_groups = sorted(self._context.keys(), key=self._natural_sort_key)
else:
sorted_groups = self._context.keys()
self._update_ctx_groups(sorted_groups)
new_ctx = OrderedDict()
for ctx_grp in sorted_groups:
items = self._prepare_context_items(self._context[ctx_grp])
new_ctx[ctx_grp] = items
self._context = new_ctx
else:
self._context = self._prepare_context_items(self._context)
def _update_ctx_groups(self, ctx_group_names):
self.show_element(self.ctx_groups_dock)
self.ctx_groups_selector_cb.ItemsSource = ctx_group_names
if self.ctx_groups_active in ctx_group_names:
self.ctx_groups_selector_cb.SelectedIndex = ctx_group_names.index(self.ctx_groups_active)
else:
self.ctx_groups_selector_cb.SelectedIndex = 0
def _get_active_ctx_group(self):
return self.ctx_groups_selector_cb.SelectedItem
def _get_active_ctx(self):
if isinstance(self._context, dict):
return self._context[self._get_active_ctx_group()]
else:
return self._context
def _list_options(self, option_filter=None):
if option_filter:
fuzzy_matches = sorted(
[(x,
coreutils.fuzzy_search_ratio(
target_string=x.view_name,
sfilter=option_filter,
regex=False))
for x in self._get_active_ctx()],
key=lambda x: x[1],
reverse=True
)
self.views_dg.ItemsSource = ObservableCollection[self._parent.ViewSelectorFormItem]([x[0] for x in fuzzy_matches if x[1] >= 80])
else:
self.views_dg.ItemsSource = ObservableCollection[self._parent.ViewSelectorFormItem](self._get_active_ctx())
def button_select(self, sender, args):
matching_views = []
for k in self._context:
if isinstance(self._context, dict):
for v in self._context[k]:
if v.checked or v in self.views_dg.SelectedItems:
matching_view = next((x for x in DB.FilteredElementCollector(self._doc).OfClass(DB.View) if
x.Name == str(v.view_name)), None)
v.replacable = True
if matching_view is not None and not v.replace:
matching_views.append(v)
else:
self._viewsToTransfer.append(v)
else:
if k.checked or k in self.views_dg.SelectedItems:
matching_view = next((x for x in DB.FilteredElementCollector(self._doc).OfClass(DB.View) if
x.Name == str(k.view_name)), None)
k.replacable = True
if matching_view is not None and not k.replace:
matching_views.append(k)
else:
self._viewsToTransfer.append(k)
self.hide_element(self.ctx_groups_dock)
self.ctx_groups_selector_cb.SelectedIndex = 0
self._context = matching_views
self._list_options()
if len(matching_views) == 0 and len(self._viewsToTransfer) > 0:
for v in self._viewsToTransfer:
self._update_dest_types()
if str(v.view_type) != "Retain Original":
destType = next((x for x in self._destViewTypes if revit.query.get_name(x) == v.view_type), None)
else:
targetTypeName = revit.query.get_name(revit.query.get_type(v.unwrap()))
destType = next((x for x in self._destViewTypes if revit.query.get_name(x) == targetTypeName), None)
if destType is None:
try:
with revit.Transaction("DB Tools - Copying View Type", doc=self._doc, clear_after_rollback=True, swallow_errors=True, log_errors=False):
destType = self._destViewTypes[0].Duplicate(targetTypeName)
except:
pass
if destType is None:
forms.alert("View could not retain original type: " + v.view_name + ". Setting to default ('Detail')...", title="DB Tools")
destType = next((x for x in self._destViewTypes if revit.query.get_name(x) == "Detail"), None)
try:
with revit.Transaction("DB Tools - Transfer View", doc=self._doc, clear_after_rollback=True, swallow_errors=True, log_errors=False):
self._result = self._parent.transfer_view(v.unwrap(), v.view_name, destType, v.replace)
if self._result and not v.replace:
self._numAdded += 1
elif self._result and v.replace:
self._numReplaced += 1
except:
forms.alert("View failed to transfer: " + v.view_name + ". Contining to next view...", title="DB Tools")
continue
self.Close()
else:
forms.alert("Views remaining in list need to be renamed or set to be replaced before transfer.", title="DB Tools")
def search_txt_changed(self, sender, args):
if self.search_tb.Text == '':
self.hide_element(self.clrsearch_b)
else:
self.show_element(self.clrsearch_b)
self._list_options(option_filter=self.search_tb.Text)
def selection_changed(self, sender, args):
self._list_options(option_filter=self.search_tb.Text)
def selected_item_changed(self, sender, args):
self.view_i.RenderTransform = Media.MatrixTransform(self._reset)
match = next((x for x in self._draftingViews if x.Name == getattr(self.views_dg.SelectedItem, "Name", None)), None)
if match is not None:
self.show_element(self.view_i)
file_name = DB.ImageExportOptions.GetFileName(self._targetDoc, match.Id)
file_match = next((x for x in os.listdir(self._temp_dir) if str(file_name) in str(x)), None)
if file_match is None:
originalBG = self._targetDoc.Application.BackgroundColor
self._targetDoc.Application.BackgroundColor = DB.Color(255, 255, 255)
img = DB.ImageExportOptions()
img.ZoomType = DB.ZoomFitType.FitToPage
img.PixelSize = 1024
img.ImageResolution = DB.ImageResolution.DPI_150
img.FitDirection = DB.FitDirectionType.Horizontal
img.ExportRange = DB.ExportRange.SetOfViews
img.HLRandWFViewsFileType = DB.ImageFileType.PNG
img.FilePath = os.path.join(self._temp_dir, "preview.png")
img.ShouldCreateWebSite = False
img.ShadowViewsFileType = DB.ImageFileType.PNG
img.SetViewsAndSheets([match.Id])
self._targetDoc.ExportImage(img)
self._targetDoc.Application.BackgroundColor = originalBG
file_match = next((x for x in os.listdir(self._temp_dir) if str(file_name) in str(x)), None)
self.set_image_source(self.view_i, os.path.join(self._temp_dir, file_match))
else:
self.hide_element(self.view_i)
def clear_search(self, sender, args):
self.search_tb.Text = " "
self.search_tb.Clear()
self.search_tb.Focus()
def view_zoom(self, sender, args):
p = args.MouseDevice.GetPosition(self.view_i)
m = self.view_i.RenderTransform.Value
if (args.Delta > 0):
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y)
else:
m.ScaleAtPrepend(1/1.1, 1/1.1, p.X, p.Y)
self.view_i.RenderTransform = Media.MatrixTransform(m)
def view_reset(self, sender, args):
self.view_i.RenderTransform = Media.MatrixTransform(self._reset)
def view_pan_start(self, sender, args):
if self.view_i.IsMouseCaptured is False:
self.view_i.CaptureMouse()
self._start = args.GetPosition(self.view_border)
self._origin.X = self.view_i.RenderTransform.Value.OffsetX
self._origin.Y = self.view_i.RenderTransform.Value.OffsetY
def view_pan_stop(self, sender, args):
self.view_i.ReleaseMouseCapture()
def view_pan_move(self, sender, args):
if self.view_i.IsMouseCaptured is not False:
p = args.MouseDevice.GetPosition(self.view_border)
m = self.view_i.RenderTransform.Value
m.OffsetX = self._origin.X + (p.X - self._start.X)
m.OffsetY = self._origin.Y + (p.Y - self._start.Y)
self.view_i.RenderTransform = Media.MatrixTransform(m)
@property
def result(self):
return self._result
@property
def numAdded(self):
return self._numAdded
@property
def numReplaced(self):
return self._numReplaced
def copy_elements(self, source, dest, elements):
class CustomCopyHandler(DB.IDuplicateTypeNamesHandler):
def OnDuplicateTypeNamesFound(self, args):
return DB.DuplicateTypeAction.UseDestinationTypes
transform = DB.Transform.Identity
cpOptions = DB.CopyPasteOptions()
cpOptions.SetDuplicateTypeNamesHandler(CustomCopyHandler())
new_element_ids = DB.ElementTransformUtils.CopyElements(source, elements, dest, transform, cpOptions)
return new_element_ids
def clear_view_contents(self, doc, view):
viewElements = DB.FilteredElementCollector(doc, view.Id)
viewElementsCollection = List[DB.ElementId]()
for element in viewElements:
viewElementsCollection.Add(element.Id)
doc.Delete(viewElementsCollection)
def transfer_view(self, copyView, newViewName, newViewType, replace=False):
if replace:
newViews = [x for x in DB.FilteredElementCollector(self._doc).OfClass(DB.View).WhereElementIsNotElementType().ToElements()
if x.ViewType == DB.ViewType.DraftingView]
newView = next((x for x in newViews if x.Name == newViewName), None)
if newView is not None:
self.clear_view_contents(self._doc, newView)
else:
forms.alert("View was set to replace: " + newViewName + " however no matching view was found. Transferring as new instead.", title="DB Tools")
newView = DB.ViewDrafting.Create(self._doc, newViewType.Id)
newView.Name = newViewName
newView.Scale = copyView.Scale
newView.Parameter[DB.BuiltInParameter.VIEW_DESCRIPTION].Set(copyView.Parameter[DB.BuiltInParameter.VIEW_DESCRIPTION].AsString())
filter = DB.ElementCategoryFilter(DB.ElementId.InvalidElementId, True)
copyElements = DB.FilteredElementCollector(self._targetDoc, copyView.Id).WherePasses(filter).WhereElementIsNotElementType()
copyElementsCollection = List[DB.ElementId]()
for element in copyElements:
if (element.Category and element.Category.Name == "Title Blocks"):
continue
elif isinstance(element, DB.ScheduleSheetInstance):
continue
if isinstance(element, DB.Viewport) or "ExtentElem" in element.Name:
continue
elif isinstance(element, DB.Element) and element.Category and "guide" in str(element.Category.Name).lower():
continue
elif isinstance(element, DB.Element) and element.Category and "views" == str(element.Category.Name).lower():
continue
else:
copyElementsCollection.Add(element.Id)
if copyElements.GetElementCount() > 0:
newElementsCollection = self.copy_elements(copyView, newView, copyElementsCollection)
for dest, src in zip(newElementsCollection, copyElementsCollection):
try:
newView.SetElementOverrides(dest, copyView.GetElementOverrides(src))
except:
continue
return True
@property
def numAdded(self):
return self._viewSelectorForm.numAdded
@property
def numReplaced(self):
return self._viewSelectorForm.numReplaced
script.py (for importing)
from DBTools import *
from pyrevit import HOST_APP
from pyrevit import script
import ConfigParser
import codecs
app = HOST_APP.uiapp.Application
doc = revit.doc
uidoc = revit.uidoc
logger = script.get_logger()
search_dir = os.path.normpath(r"network_drive\Structural\Drawing Tools\Revit\Details")
steel = MasterDetailFile("Steel")
concrete = MasterDetailFile("Concrete")
masonry = MasterDetailFile("Masonry")
wood = MasterDetailFile("Wood")
cold_form = MasterDetailFile("Cold Form")
master_files = [steel, concrete, masonry, wood, cold_form]
numAdded = 0
numReplaced = 0
REVIT_YEAR = app.VersionNumber
if str(REVIT_YEAR) == "2024":
REVIT_YEAR = "R24"
else:
forms.alert("Revit year " + str(REVIT_YEAR) + " is not supported.", title="DB Tools", exitscript=True)
for master in master_files:
files = [x for x in os.listdir(os.path.join(search_dir, master.name)) if x.endswith(REVIT_YEAR + ".rvt")]
if len(files) == 0:
forms.alert("Unable to locate " + master.name + " master library file.")
continue
if len(files) > 1:
forms.alert("Multiple matching .rvt files found in " + master.name + " directory. Using most recently modified file found.", title="DB Tools")
files.sort(key=os.path.getmtime)
master.path = os.path.join(search_dir, master.name, files[0])
dest_master_file = None
dest_master_file = forms.SelectFromList.show(master_files, title="Select Destination", name_attr="name", multiselect=False)
if dest_master_file is not None:
targetDoc = None
try:
ini_path = os.path.join(app.CurrentUsersDataFolderPath, "Revit.ini")
ini_config = ConfigParser.RawConfigParser()
ini_config.readfp(codecs.open(ini_path, "r", "utf-16-le"))
local_path = os.path.join(
os.path.expandvars(ini_config.get("Directories", "ProjectPath")),
os.path.splitext(os.path.basename(dest_master_file.path))[0] + "_" + str(app.Username) + ".rvt")
if os.path.exists(local_path):
os.remove(local_path)
local_model_path = DB.ModelPathUtils.ConvertUserVisiblePathToModelPath(local_path)
central_model_path = DB.ModelPathUtils.ConvertUserVisiblePathToModelPath(dest_master_file.path)
DB.WorksharingUtils.CreateNewLocal(central_model_path, local_model_path)
openConfig = DB.WorksetConfiguration(DB.WorksetConfigurationOption.OpenAllWorksets)
openOptions = DB.OpenOptions()
openOptions.DetachFromCentralOption = DB.DetachFromCentralOption.DoNotDetach
openOptions.Audit = False
openOptions.AllowOpeningLocalByWrongUser = True
openOptions.SetOpenWorksetsConfiguration(openConfig)
targetDoc = app.OpenDocumentFile(local_model_path, openOptions)
except:
forms.alert("Unable to open selected master for editing...", title="DB Tools", exitscript=True)
transfer = TransferDraftingViews(doc, targetDoc, "Import?")
targetDoc.Close(False)
if transfer.numAdded != 0 and transfer.numReplaced != 0:
forms.alert("Successfully imported ["+ str(transfer.numAdded) + "] details and replaced [" + str(transfer.numReplaced) + "] details...", title="DB Tools")
The last code block above for importing can be easily modified for exporting since the overall WPF code is based upon just transferring views.
Let me know if anyone has any comments or criticisms!
AK
