Forms - Revit Python Wrapper 1.7.4 Documentation
Forms - Revit Python Wrapper 1.7.4 Documentation
Search docs
Forms
Revit Python Wrapper
Installa on The forms module provide several pre-build forms as well as a framework from which you can build
rpw and rpw.revit your own forms.
rpw.db
All classes documented in this sec on can be imported as such:
rpw.ui
Forms
>>> from rpw.ui.forms import Console
QuickForms
SelectFromList
TextInput
QuickForms
SelectFromList
TaskDialogs
OS Dialogs
Console
FlexForm
Implementa ons
Selec on
rpw.base
rpw.u ls
rpw.extras
rpw.excep ons
Known Issues
Usage:
TextInput
Usage:
TaskDialogs
TaskDialog
Wrapped Element:
self._revit_object = Revit.UI.TaskDialog
Warning
Any Wrapper that inherits and overrides __init__ class MUST ensure _revit_object is
created by calling super().__init__ before se ng any self a ributes. Not doing so will
cause recursion errors and Revit will crash. BaseObjectWrapper should define a class
variable _revit_object_class to define the object class being wrapped.
show(exit=False)
Show TaskDialog
Parameters: exit (bool, op onal) – Exit Script a er Dialog. Useful for displaying Errors. Default
is False.
Usage:
Alert
Usage:
OS Dialogs
Select Folder
rpw.ui.forms.select_folder()
Selects a Folder Path using the standard OS Dialog. Uses Forms.FolderBrowserDialog(). For
more informa on see: h ps://msdn.microso .com/en-
us/library/system.windows.forms.openfiledialog.
Select File
Parameters: extensions (str, op onal) – File Extensions Filtering op ons. Default is All Files
(.)|*.*
tle (str, op onal) – File Extensions Filtering op ons
mul ple (bool) – Allow selec on of mul ple files. Default is False
restore_directory (bool) – Restores the directory to the previously selected
directory before closing
Console
Keyboard Shortcuts:
UP Iterate history up
Down Iterate history down
Tab Iterate possible autocomplete op ons (works for do ed lookup)
Note
The last stack frame is automa cally injected is the context of the evalua on loop of the
console: the local and global variables from where the Console was called from should be
available.
Inspec on of the stack requires stack frames to be enabled. If an excep on is raised sta ng
`object has no attribute '_getframe' it means IronPython stack frames is not enabled. You can
If you are trying to use it from within Dynamo, stack inspec on is currently not available due to
how the engine is setup, but you can s ll use it by manually passing the context you want to
inspect:
>>> Console(context=locals()) # or
>>> Console(context=globals())
FlexForm
values
Type: dict
close()
sta c get_values(sender, e)
Default Get Values. Set form.values a ribute with values from controls and closes form.
show()
FlexForm Controls
Windows.Controls.Label Wrapper
__init__(label_text, **kwargs)
Windows.Controls.TextBox Wrapper
>>> TextBox()
Windows.Controls.Checkbox Wrapper
>>> CheckBox('Label')
Windows.Controls.ComboBox Wrapper
Windows.Controls.Bu on Wrapper
>>> Button('Select')
class rpw.ui.forms.Separator(**kwargs)
WPF Separator
Implementations
FlexForm
class FlexForm(Window):
"""
Flex Form Usage
"""
layout = """
<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:local="clr-namespace:WpfApplication1" mc:Ignorable="d"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Topmost="True"
SizeToContent="WidthAndHeight">
Attributes:
values (``dict``): Dictionary of selected values
"""
V_SPACE = 5
if n > 0:
prev_comp = components[n - 1]
top = prev_comp.Margin.Top + prev_comp.Height + V_SPACE
top += getattr(component, 'top_offset', 0)
component.Margin = Thickness(0, top, 0, 0)
def show(self):
"""
Initializes Form. Returns ``True`` or ``False`` if form was exited.
"""
return self.ShowDialog()
@staticmethod
def get_values(sender, e):
"""
Default Get Values. Set form.values attribute with values from controls
and closes form.
"""
component_values = {}
window = Window.GetWindow(sender)
for component in window.MainGrid.Children:
try:
component_values[component.Name] = component.value
except AttributeError:
pass
window.values = component_values
window.close()
def close(self):
""" Exits Form. Returns ``True`` to ``show()`` method """
self.DialogResult = True
self.Close()
class RpwControlMixin():
""" Control Mixin """
_index = count(0)
# Default Values
control_type = self.__class__.__name__
if not self.Name:
self.Name = kwargs.get('Name', '{}_{}'.format(control_type, self.index))
>>> TextBox()
"""
def __init__(self, name, default='', **kwargs):
"""
Args:
name (``str``): Name of control. Will be used to return value
default (``bool``): Sets ``Text`` attribute of textbox [Default: '']
wpf_params (kwargs): Additional WPF attributes
"""
self.Name = name
self.Text = default
self.set_attrs(**kwargs)
if 'Height' not in kwargs:
self.Height = 25
@property
def value(self):
return self.Text
>>> Button('Select')
"""
def __init__(self, button_text, on_click=None, **kwargs):
"""
Args:
button_text (``str``): Button Text
on_click (``func``): Registers Click event Function [Default: :any:`FlexForm.get_values`]
wpf_params (kwargs): Additional WPF attributes
"""
self.Content = button_text
self.on_click = on_click or FlexForm.get_values
self.set_attrs(**kwargs)
>>> CheckBox('Label')
"""
def __init__(self, name, checkbox_text, default=False, **kwargs):
"""
Args:
name (``str``): Name of control. Will be used to return value
checkbox_text (``str``): Checkbox label Text
default (``bool``): Sets IsChecked state [Default: False]
wpf_params (kwargs): Additional WPF attributes
"""
self.Name = name
self.Content = checkbox_text
self.IsChecked = default
self.set_attrs(top_offset=5, **kwargs)
@property
def value(self):
return self.IsChecked
self.options = options
if hasattr(options, 'keys'):
options = options.keys()
if sort:
options.sort()
if default is None:
index = 0
else:
index = options.index(default)
self.Items.Clear()
self.ItemsSource = options
self.SelectedItem = options[index]
@property
def value(self):
selected = self.SelectedItem
if isinstance(self.options, dict):
selected = self.options[selected]
return selected
if __name__ == '__main__':
""" TESTS """
components = [
Label('Pick Style:'),
ComboBox('combobox1', {'Opt 1': 10.0, 'Opt 2': 20.0}),
Label('Enter Name:'),
TextBox('textbox1', Text="Default Value"),
CheckBox('checkbox1', 'Check this:'),
Separator(),
Button('Select')]
print(form.values)
QuickForm
import sys
Args:
title (str): Title of form
options (dict,list[str]): Dictionary (string keys) or List[strings]
description (str): Optional Description of input requested [default: None]
sort (bool): Optional sort flag - sorts keys [default: True]
exit_on_close (bool): Form will call sys.exit() if Closed on X. [default: True]
Usage:
Args:
title (str): Title of form
default (str): Optional default value for text box [default: None]
description (str): Optional Description of input requested [default: None]
exit_on_close (bool): Form will call sys.exit() if Closed on X. [default: True]
Usage:
if __name__ == '__main__':
rv = SelectFromList('Title', ['A','B'], description="Your Options",
exit_on_close=True)
print(rv)
TaskDialog
import sys
from rpw import UI
from rpw.exceptions import RpwValueError
from rpw.base import BaseObjectWrapper, BaseObject
class Alert():
"""
A Simple Revit TaskDialog for displaying quick messages
Usage:
>>> from rpw.ui.forms import Alert
>>> Alert('Your Message', title="Title", header="Header Text")
>>> Alert('You need to select Something', exit=True)
Args:
message (str): TaskDialog Content
title (str, optional): TaskDialog Title
header (str, optional): TaskDialog content header
exit (bool, optional): Exit Script after Dialog.
Useful for displayin Errors. Default is False
"""
def __init__(self, content, title='Alert', header='', exit=False):
dialog = UI.TaskDialog(title)
dialog.TitleAutoPrefix = False
dialog.MainInstruction = header
dialog.MainContent = content
self.result = dialog.Show()
if exit:
sys.exit(1)
class CommandLink(BaseObject):
"""
Command Link Helper Class
Usage:
>>> from rpw.ui.forms import CommandLink, TaskDialog
>>> CommandLink('Open Dialog', return_value=func_to_open)
>>> TaskDialog('Title', commands=[CommandLink])
Args:
text (str): Command Text
subtext (str, optional): Button Subtext
return_value (any, optional): Value returned if button is clicked.
If none is provided, text is returned.
"""
def __init__(self, text, subtext='', return_value=None):
self._id = None # This will later be set to TaskDialogCommandLinkId(n)
self.text = text
self.subtext = subtext
self.return_value = return_value if return_value is not None else text
def __repr__(self):
return super(CommandLink, self).__repr__(data={'id': self._id,
'text':self.text})
class TaskDialog(BaseObjectWrapper):
"""
Task Dialog Wrapper
Wrapped Element:
self._revit_object = `Revit.UI.TaskDialog`
Args:
content (str): Main text of TaskDialog.
commands (list, optional): List of CommandLink Instances.
Default is no buttons.
buttons (list, optional): List of TaskDialogCommonButtons names.
Default is no buttons. 'Close' is shown if no commands are passed.
title (str, optional): Title of TaskDialog. Default is 'Task Dialog'.p
instruction (str, optional): Main Instruction.
footer (str, optional): Footer Text. Default is ``blank``.
expanded_content (str, optional): Expandable Text. Default is ``blank``.
verification_text (str, optional): Checkbox text. Default is ``blank``.
title_prefix (bool, optional): Prefix Title with app name.
Default is ``False``
show_close (bool, optional): Show X to close. Default is False.
"""
_revit_object_class = UI.TaskDialog
_common_buttons = ['Ok', 'Yes', 'No', 'Cancel', 'Retry', 'Close']
super(TaskDialog, self).__init__(UI.TaskDialog(title))
self.dialog = self._revit_object
# Settings
self.dialog.TitleAutoPrefix = title_prefix
self.dialog.AllowCancellation = show_close
# Properties
self.dialog.Title = title
self.dialog.MainInstruction = instruction
self.dialog.MainContent = content
self.dialog.FooterText = footer
self.dialog.ExpandedContent = expanded_content
self.dialog.VerificationText = verification_text
self.verification_checked = None if not verification_text else False
# Add Buttons
self.buttons = buttons or []
common_buttons_names = []
for button_name in [b.capitalize() for b in self.buttons]:
if button_name not in self._common_buttons:
raise RpwValueError('TaskDialogCommonButtons member', button_name)
button_full_name = 'UI.TaskDialogCommonButtons.' + button_name
common_buttons_names.append(button_full_name)
if common_buttons_names:
common_buttons = eval('|'.join(common_buttons_names))
self.dialog.CommonButtons = common_buttons
# Validate Commands
commands = commands or []
if len(commands) > 4:
raise RpwValueError('4 or less command links', len(commands))
# Process Commands
self.commands = {}
for link_index, command_link in enumerate(commands, 1):
command_id = 'CommandLink{}'.format(link_index)
command_link._id = getattr(UI.TaskDialogCommandLinkId, command_id)
self.commands[command_id] = command_link
self.dialog.AddCommandLink(command_link._id,
command_link.text,
command_link.subtext)
Args:
exit (bool, optional): Exit Script after Dialog. Useful for
displaying Errors. Default is False.
Returns:
Returns is ``False`` if dialog is Cancelled (X or Cancel button).
If CommandLink button is clicked, ``CommandLink.return_value``
is returned - if one was not provided, ``CommandLink.text`` is used.
If CommonButtons are clicked ``TaskDialog.TaskDialogResult`` name is
returned ie('Close', 'Retry', 'Yes', etc).
"""
self.result = self.dialog.Show()
try:
self.verification_checked = self.dialog.WasVerificationChecked()
except:
self.verification_checked = None
# Handle Cancel
if self.result == UI.TaskDialogResult.Cancel:
if exit:
sys.exit(1)
return None
if __name__ == '__main__':
Alert('Test Alert!')
def sample_callback():
print('Calling B')
d = UI.TaskDialog("Revit Build Information")
d.MainInstruction = "Button 1"
d.Show()
t = TaskDialog(commands=commands, buttons=['Yes'])
OS Dialogs
def select_folder():
"""
Selects a Folder Path using the standard OS Dialog.
Uses Forms.FolderBrowserDialog(). For more information see:
https://msdn.microsoft.com/en-us/library/system.windows.forms.openfiledialog.
form = Forms.FolderBrowserDialog()
if form.ShowDialog() == Forms.DialogResult.OK:
return form.SelectedPath
Args:
extensions (str, optional): File Extensions Filtering options. Default is All Files (*.*)|*.*
title (str, optional): File Extensions Filtering options
multiple (bool): Allow selection of multiple files. Default is `False`
restore_directory (bool): Restores the directory to the previously selected directory before clo
Returns:
filepath (list, string): filepath string if ``multiple=False`` otherwise list of filepath string
"""
form = Forms.OpenFileDialog()
form.Filter = extensions
form.Title = title
form.Multiselect = multiple
form.RestoreDirectory = restore_directory
if form.ShowDialog() == Forms.DialogResult.OK:
return form.FileName if not multiple else list(form.FileNames)
# Tests
if __name__ == '__main__':
# print(select_folder())
# print(select_file('Python Files|*.py'))
print(select_file('Python Files|*.py', multiple=False))
print(select_file('Python Files|*.py', multiple=True))
Console
import os
import inspect
import logging
import tempfile
from collections import defaultdict
import traceback
class Console(Window):
LAYOUT = """
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DeployWindow" Height="400" Width="800" SnapsToDevicePixels="True"
UseLayoutRounding="True" WindowState="Normal"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontSize" Value="12.0"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch"
Name="tbox" Margin="6,6,6,6" VerticalAlignment="Stretch"
AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
FontFamily="Consolas" FontSize="12.0"
/>
</Grid>
</Window>
"""
# <Button Content="Terminate" Margin="6,6,6,6" Height="30"
# Grid.Column="1" Grid.Row="1" VerticalAlignment="Bottom"
# Click="terminate"></Button>
# History Helper
tempdir = tempfile.gettempdir()
filename = 'rpw-history'
self.history_file = os.path.join(tempdir, filename)
self.stack_locals = {}
self.stack_globals = {}
self.stack_level = stack_level
if context:
self.stack_locals.update(context)
# Allows to pass context manually, so it can be used in Dynamo
# Where inspection does not work
else:
# Stack Info
# stack_frame = inspect.currentframe().f_back
stack_frame = inspect.stack()[stack_level][0] # Finds Calling Stack
self.stack_locals.update(stack_frame.f_locals)
self.stack_globals.update(stack_frame.f_globals)
# Debug Console
self.stack_globals.update({'stack': inspect.stack()})
stack_code = stack_frame.f_code
stack_filename = os.path.basename(stack_code.co_filename)
stack_lineno = stack_code.co_firstlineno
stack_caller = stack_code.co_name
self._update_completer()
# Form Setup
self.ui = wpf.LoadComponent(self, StringReader(Console.LAYOUT))
self.ui.Title = 'RevitPythonWrapper Console'
self.PreviewKeyDown += self.KeyPressPreview
self.KeyUp += self.OnKeyUpHandler
self.is_loaded = False
# Form Init
self.ui.tbox.Focus()
if not context and stack_info:
self.write_line('Caller: {} [ Line:{}] | File: {}'.format(
stack_caller,
stack_lineno,
stack_filename))
elif msg:
self.write_line(msg)
else:
self.tbox.Text = Console.CARET
self.ui.tbox.CaretIndex = len(self.tbox.Text)
# Vars
self.history_index = 0
self.ac_options = defaultdict(int)
self.ShowDialog()
def _update_completer(self):
# Updates Completer. Used at start, and after each exec loop
context = self.stack_locals.copy()
context.update(self.stack_globals)
# context.update(vars(__builtins__))
self.completer = Completer(context)
def get_last_line(self):
try:
last_line = self.get_lines()[-1]
except IndexError:
last_line = self.get_line(0)
logger.debug('Last Line: {}'.format(last_line))
return last_line
def get_last_entered_line(self):
try:
last_line = self.get_lines()[-2]
except IndexError:
last_line = self.get_line(0)
logger.debug('Last Line: {}'.format(last_line))
return last_line
def get_lines(self):
last_line_index = self.tbox.LineCount
lines = []
for index in range(0, last_line_index):
line = self.get_line(index)
lines.append(line)
logger.debug('Lines: {}'.format(lines))
return lines
def format_exception(self):
""" Formats Last Exception"""
exc_type, exc_value, exc_traceback = sys.exc_info()
tb = traceback.format_exception(exc_type, exc_value, exc_traceback)
last_exception = tb[-1]
output = 'Traceback:\n' + last_exception[:-1]
return output
@property
def last_caret_start_index(self):
return self.tbox.Text.rfind(Console.CARET)
@property
def last_caret_end_index(self):
return self.last_caret_start_index + len(Console.CARET)
@property
def last_caret_line_start_index(self):
return self.last_caret_start_index - len(Console.CARET)
def reset_caret(self):
self.tbox.CaretIndex = self.last_caret_end_index
def autocomplete(self):
text = self.tbox.Text[self.last_caret_end_index:self.tbox.CaretIndex]
logger.debug('Text: {}'.format(text))
logger.debug('ac_options: {}'.format(self.ac_options))
logger.debug('Sug: {}'.format(suggestion))
if not suggestion:
self.ac_options[text] = 0
else:
self.ac_options[text] += 1
if suggestion.endswith('('):
suggestion = suggestion[:-1]
def get_all_history(self):
# TODO: Add clean up when history > X
with open(self.history_file) as fp:
lines = fp.read().split('\n')
return [line for line in lines if line != '']
def history_up(self):
self.history_index += 1
line = self.history_iter()
if line is not None:
self.write_text(line)
def history_down(self):
self.history_index -= 1
line = self.history_iter()
if line is not None:
self.write_text(line)
def history_iter(self):
lines = self.get_all_history()
logger.debug('Lines: {}'.format(lines))
try:
line = lines[::-1][self.history_index - 1]
# Wrap around lines to loop and up down infinetly.
except IndexError:
if len(lines) == 0:
return None
if len(lines) < 0:
self.history_index += len(lines)
if len(lines) > 0:
self.history_index -= len(lines)
line = lines[0]
return line
def __repr__(self):
'<rpw:Console stack_level={}>'.format(self.stack_level)
if __name__ == '__main__':
def test():
x = 1
# Console()
Console(context=locals())
test()
z = 2
Resources
import sys
# WPF/Form Imports
clr.AddReference("PresentationFramework") # System.Windows: Controls, ?
clr.AddReference("WindowsBase") # System.Windows.Input
clr.AddReference("System.Drawing") # FontFamily
clr.AddReference('System.Windows.Forms') # Forms
import System.Windows
from System.Windows import Window
from System.IO import StringReader
# Console
from System.Environment import Exit, NewLine
from System.Drawing import FontFamily
from System.Windows.Input import Key
# FlexForm Imports
from System.Windows import Controls, Window
from System.Windows import HorizontalAlignment, VerticalAlignment, Thickness
# OS Dialogs
from System.Windows import Forms
if revit.host == 'Dynamo':
# IronPython 2.7.3 - Dynamo + RPS w/out pyRevit
# Conflicts with PyRevit. Must Ensure exact path is specified
# https://github.com/architecture-building-systems/revitpythonshell/issues/46
clr.AddReferenceToFileAndPath(r'C:\Program Files (x86)\IronPython 2.7\Platforms\Net40\IronPython.Wpf
import wpf
# on 2.7.7 this raises wpf import error
else:
# IronPython 2.7.7 - pyRevit
# clr.AddReference('IronPython') # Works W/Out
clr.AddReference('IronPython.Wpf') # 2.7.
from IronPython.Modules import Wpf as wpf
# on 2.7.7 this works. On 2.7.3 you get a LoadComponent 3 args error
Previous Next
Community Ad