Hi All, @sanzoghenzo
I’m implementing a UI using WPF to create grids in Revit. To allow users to switch between numeric values (‘1, 2, 3, …’) and alphabetic values (‘A, B, C, …’) for grid names as shown below:
I used a ComboBox to display symbol types in a dropdown list; however, I’m struggling to set up event handlers to manage the selection for each grid orientation (X and Y) and to prevent the user from selecting the same symbols for both.
Note: As shown in the image above, I want to retain the content describing numeric and alphabetic symbols as they are in the dropdown, while using the actual symbol values in the code-behind.
Pls check my code below:
Grid script + Xaml Layout
# -*- coding: UTF-8 -*-
from pyrevit import forms, revit
import wpf, os, clr
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
from System.IO import StringReader
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
import System.Windows.Controls
from System.Windows.Controls import StackPanel, DockPanel, TextBlock, TextBox, Button, Separator
from Autodesk.Revit.DB import *
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
app = __revit__.Application
class Grids(Window):
LAYOUT = '''
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Grids"
Height="Auto"
Width="300"
SizeToContent ="Height"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="10">
<!--Input LX-->
<StackPanel Margin="0,0,0,10">
<TextBlock Text ="Longueur Total suivant X (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
<DockPanel Width="200" HorizontalAlignment="Left" >
<TextBlock Text =" Lx =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
<TextBox x:Name="input_Lx" Width="75" Margin="0,0,5,0" />
<TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
</DockPanel>
</StackPanel>
<!--Input LY-->
<StackPanel Margin="0,0,0,10">
<TextBlock Text ="Longueur Total suivant Y (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
<DockPanel Width="200" HorizontalAlignment="Left" >
<TextBlock Text =" Ly =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
<TextBox x:Name="input_Ly" Width="75" Margin="0,0,5,0" />
<TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
</DockPanel>
</StackPanel>
<!--Grid Extension -->
<StackPanel Margin="0,0,0,10">
<TextBlock Text ="Extension des Axes X, Y (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
<DockPanel Width="200" HorizontalAlignment="Left" >
<TextBlock Text =" e =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
<TextBox x:Name="input_extension" Width="75" Margin="0,0,5,0" />
<TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
</DockPanel>
</StackPanel>
<!--Spacing along X axis -->
<StackPanel Margin="0,0,0,10">
<TextBlock Text ="Espacement suivant X (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
<DockPanel Width="200" HorizontalAlignment="Left" >
<TextBlock Text =" dx =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
<TextBox x:Name="input_dx" Width="75" Margin="0,0,5,0" />
<TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
</DockPanel>
</StackPanel>
<!--Spacing along Y axis -->
<StackPanel Margin="0,0,0,10">
<TextBlock Text ="Espacement suivant Y (m):" Width="170" Height="25" HorizontalAlignment="Left" FontSize="12" FontWeight="Bold" />
<DockPanel Width="200" HorizontalAlignment="Left" >
<TextBlock Text =" dy =" Width="50" Height="25" Margin="0,0,5,0" RenderTransformOrigin="1.733,0.495" />
<TextBox x:Name="input_dy" Width="75" Margin="0,0,5,0" />
<TextBlock Text="m" Width="25" HorizontalAlignment="Left" />
</DockPanel>
</StackPanel>
<!--bubbles symbol -->
<StackPanel>
<TextBlock Text="Choix de symbols pour Lx, Ly:" FontWeight="Bold"/>
<!-- Symbol Type for Lx -->
<DockPanel Margin="0,0,0,10">
<TextBlock Text="Type de symbole Lx:" Width="120"/>
<ComboBox x:Name="selected_symbol_x" Width="150" HorizontalAlignment="Right" >
<ComboBoxItem Content="1 2 3 ..." />
<ComboBoxItem Content="A B C ..." />
</ComboBox>
</DockPanel>
<!-- Symbol Type for Ly -->
<DockPanel Margin="0,0,0,10">
<TextBlock Text="Type de symbole Ly:" Width="120"/>
<ComboBox x:Name="selected_symbol_y" Width="150" HorizontalAlignment="Right" >
<ComboBoxItem Content="1 2 3 ..." />
<ComboBoxItem Content="A B C ..." />
</ComboBox>
</DockPanel>
</StackPanel>
<!--Separator, OK and Cancel Button-->
<Separator Margin="0,0,0,10"/>
<DockPanel HorizontalAlignment="Center">
<Button Content="OK" Width="75" Height="25" Margin="0,0,10,0" Click="OK_Clicked" />
<Button Content="Cancel" Width="75" Height="25" Click="Cancel_Clicked" />
</DockPanel>
</StackPanel>
</Window>'''
def __init__(self):
wpf.LoadComponent(self, StringReader(Grids.LAYOUT))
"""Symbol sets"""
self.all_symbols_numeric = ['1', '2', '3', '4', '5'] # Numeric
self.all_symbols_alpha = ['A', 'B', 'C', 'D', 'E'] # Alphabetic
"""I'm struggling with how to handle the event triggered
by a user selection to correctly populate the symbols Sx and Sy in self.data
while preventing the user from selecting the same symbols for both Sx and Sy"""
self.selected_symbol_x = None
self.selected_symbol_y = None
self.data = {}
self.ShowDialog()
def _collect_input_data(self):
"""Collect and convert input data from the UI"""
# Identify which key has missing or invalid input and alert the user
if not self.input_Lx.Text:
forms.alert("Please enter a valid value for : Lx")
elif not self.input_Ly.Text:
forms.alert("Please enter a valid value for : Ly")
elif not self.input_extension.Text:
forms.alert("Please enter a valid value for : e")
elif not self.input_dx.Text:
forms.alert("Please enter a valid value for : dx")
elif not self.input_dy.Text:
forms.alert("Please enter a valid value for : dy")
elif self.select_symbol_x.ItemsSource is None:
forms.alert("Please select symbols for : Sx")
elif self.select_symbol_y.ItemsSource is None:
forms.alert("Please select symbols for : Sy")
if self.select_symbol_x.SelectedItem == self.select_symbol_y.SelectedItem:
forms.alert("Symbols for Lx and Ly must be different.")
else:
try:
self.data = {
"Lx": _input_to_meters(self.input_Lx.Text),
"Ly": _input_to_meters(self.input_Ly.Text),
"e": _input_to_meters(self.input_extension.Text),
"dx": _input_to_meters(self.input_dx.Text),
"dy": _input_to_meters(self.input_dy.Text),
"Sx": [self.select_symbol_x.SelectedItem],
"Sy": [self.select_symbol_y.SelectedItem]
}
except ValueError:
forms.alert(
"Invalid input detected. "
"Please ensure all inputs are numeric where required."
)
def OK_Clicked(self, sender, e):
self._collect_input_data()
# self.data
if not self.data:
return
self.Close()
def Cancel_Clicked(self, sender, e):
self.Close()
def _input_to_meters(value):
"""Convert a textual value to meters."""
return UnitUtils.ConvertToInternalUnits(float(value), UnitTypeId.Meters)
def create_Lines_along_X(Lx, Ly, dy, e):
#Create lines for X-oriented grids
Start_pts_X = [XYZ(-(Lx / 2) - e, y, 0)
for y in [i * dy - Ly / 2 for i in range(int(Ly / dy) + 1)]]
End_pts_X = [XYZ((Lx / 2) + e, y, 0)
for y in [i * dy - Ly / 2 for i in range(int(Ly / dy) + 1)]]
if Ly % dy != 0:
last_y = Ly / 2
Start_pts_X.append(XYZ(-(Lx / 2) - e, last_y, 0))
End_pts_X.append(XYZ((Lx / 2) + e, last_y, 0))
return [Line.CreateBound(start, end) for start, end in zip(Start_pts_X, End_pts_X)]
def create_Lines_along_Y(Lx, Ly, dx, e):
#Create lines for Y-oriented grids
Start_pts_Y = [XYZ(x, -(Ly / 2) - e, 0)
for x in [i * dx - Lx / 2 for i in range(int(Lx / dx) + 1)]]
End_pts_Y = [XYZ(x, (Ly / 2) + e, 0)
for x in [i * dx - Lx / 2 for i in range(int(Lx / dx) + 1)]]
if Lx % dx != 0:
last_x = Lx / 2
Start_pts_Y.append(XYZ(last_x, -(Ly / 2) - e, 0))
End_pts_Y.append(XYZ(last_x, (Ly / 2) + e, 0))
return [Line.CreateBound(start, end) for start, end in zip(Start_pts_Y, End_pts_Y)]
def create_grids(lines_x, lines_y, Sx, Sy, doc):
with Transaction(doc, "Create Grids") as t:
t.Start()
grids_X = [Grid.Create(doc, line) for line in lines_x]
grids_Y = [Grid.Create(doc, line) for line in lines_y]
for i, grid in enumerate(grids_X):
if i < len(Sx):
grid.Name = Sx[i]
for i, grid in enumerate(grids_Y):
if i < len(Sy):
grid.Name = Sy[i]
t.Commit()
def main(doc=None):
#Main entrypoint for the script.
doc = doc or revit.doc
form = Grids()
if not form.data:
return
data = form.data
lines_x = create_Lines_along_X(data["Lx"], data["Ly"], data["dy"], data["e"])
lines_y = create_Lines_along_X(data["Lx"], data["Ly"], data["dx"], data["e"])
create_grids(lines_x, lines_y, data["Sx"], data["Sy"], doc)
if __name__ == "__main__":
main()
Thanks.