Hi All, @c.poupin
I’m implementing a pyrevit tool using MVVM (the logic may be similar when using Dynamo) that allows the user to select the desired Footing Type from a combobox dropdown list for each grid intersection point. For now, I’m focusing on assigning a Footing Type Instance to each position corresponding to an intersection point.
As you can see in the example below, the positions (i.e., intersection points) are correctly displayed in the UI, and I can add Footing Type Instances each time I click the Ajouter
button. However, when I select a Type Instance for each position from the combobox dropdown lists, an exclamation mark appears for each selection. Additionally, I cannot determine whether those instances have been selected correctly because no message is printed when I click the Appliquer
button.
Remark: In the Types
comboboxes, I want to display the Name
property for each FootingType
instance while storing those instances for later use in the code. That’s why I used DisplayMemberPath="Name"
in the xaml layout.
Please check my references below:
xaml layout
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Footings"
Height="Auto" Width="460"
SizeToContent="Height"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="5*"/>
<RowDefinition Height="85*"/>
<RowDefinition Height="5*"/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*"/>
<ColumnDefinition Width="30*"/>
<ColumnDefinition Width="20*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Grid.ColumnSpan="2" Text="Caracteristiques des semelles" FontWeight="Bold" FontSize="11" />
<DataGrid x:Name="Symbols" Grid.Row="1" Grid.RowSpan="2" AutoGenerateColumns="False"
Background="Transparent" GridLinesVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Header="Position" Binding="{Binding Position}" Width="*" />
<DataGridTemplateColumn Header="Type de Semelle" Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="Types" ItemsSource="{Binding LstTypes}"
SelectedItem="{Binding SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid >
<StackPanel Grid.Row="1" Grid.Column="1">
<Label Content="Longueur :" VerticalAlignment="Center" Width="75" HorizontalAlignment="Left"/>
<DockPanel>
<TextBox x:Name="long_value" Height="20" Width="75" VerticalContentAlignment="Center" Margin="5,0,0,0"/>
<Label Content="cm"/>
</DockPanel>
<Label Content="Largeur :" VerticalAlignment="Center" Width="75" HorizontalAlignment="Left"/>
<DockPanel>
<TextBox x:Name="larg_value" Height="20" Width="75" VerticalContentAlignment="Center" Margin="5,0,0,0"/>
<Label Content="cm"/>
</DockPanel>
<Label Content="Epaisseur :" VerticalAlignment="Center" Width="75" HorizontalAlignment="Left"/>
<DockPanel>
<TextBox x:Name="ep_value" Height="20" Width="75" VerticalContentAlignment="Center" Margin="5,0,0,0"/>
<Label Content="cm"/>
</DockPanel>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Vertical">
<Button Content="Ajouter" Width="85" Click="Add_Click" VerticalAlignment="Top" Margin="0,30,0,10"/>
<Button Content="Supprimer" Width="85" VerticalAlignment="Top" Margin="0,20,0,10"/>
<Button Content="Supprimer tous" Width="85" VerticalAlignment="Top" Margin="0,20,0,10"/>
</StackPanel>
<StackPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2">
<Label Content="Contrainte de Béton :" VerticalAlignment="Center"/>
<DockPanel>
<TextBox x:Name="contrainte_value" Height="20" Width="75" VerticalContentAlignment="Center" Margin="5,0,0,0"/>
<Label Content="MPA" />
</DockPanel>
</StackPanel>
<StackPanel Grid.Row="3" Grid.ColumnSpan="3" Orientation="Horizontal" VerticalAlignment="Center" Width="210" Margin="0,5,0,0">
<Button x:Name="Appliquer" Click="Appliquer_Click" Content="Appliquer" Width="100"/>
<Button x:Name="Fermer" Click="Fermer_Click" Content="Fermer" Width="100" Margin="10,0,0,0"/>
</StackPanel>
</Grid>
</Window>
Footing_script
# -*- coding: UTF-8 -*-
import wpf, clr, os, itertools, math
from pyrevit import forms, revit
from System.Windows import Window
from System import Array
from System.Collections.ObjectModel import ObservableCollection
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
from System.Windows.Controls import CheckBox, TextBlock, TextBox, ListBoxItem
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Structure import StructuralType # Add this import
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
app = __revit__.Application
results = clr.Reference[IntersectionResultArray]()
class FootingType:
def __init__(self, d1, d2, d3):
self.d1 = d1
self.d2 = d2
self.d3 = d3
if d1 < d2:
self._dim = [d1, d2, d3]
else:
self._dim = [d2, d1, d3]
@property
def H(self):
return self._dim[0]
@property
def W(self):
return self._dim[1]
@property
def T(self):
return self._dim[2]
@property
def Name(self):
return "{}cm x {}cm x {}cm".format(self.W, self.H, self.T)
def __eq__(self, other):
return self.H == other.H and self.W == other.W and self.T == other.T
def __hash__(self):
return hash(self.Name)
class ViewModelBase(INotifyPropertyChanged):
def __init__(self):
self._property_changed = None
def add_PropertyChanged(self, handler):
self._property_changed = handler
def remove_PropertyChanged(self, handler):
self._property_changed = None
def notify_property_changed(self, property_name):
if self._property_changed:
self._property_changed(self, PropertyChangedEventArgs(property_name))
class Footings_VM(ViewModelBase):
def __init__(self, Position):
self._position = Position
self._types = ObservableCollection[object]()
self._selected_type = None
@property
def Position(self):
return self._position
@Position.setter
def Position(self, value):
self._position = value
self.notify_property_changed('Position')
@property
def LstTypes(self):
return self._types
@LstTypes.setter
def LstTypes(self, lst_value):
self._types = ObservableCollection[object](lst_value)
self.notify_property_changed('LstTypes')
@property
def SelectedType(self):
return self._selected_type
@SelectedType.setter
def SelectedType(self, value):
self._selected_type = value
self.notify_property_changed('SelectedType')
def AddType(self, footing_type):
self._types.Add(footing_type)
self.notify_property_changed('LstTypes')
class MyWindow(Window):
def __init__(self):
script_path = os.path.dirname(__file__)
xaml_path = os.path.join(script_path, 'footings.xaml')
wpf.LoadComponent(self, xaml_path)
self.Data = ObservableCollection[Footings_VM]()
symbols = self._grids_symbols()
for s in symbols:
foot = Footings_VM(s)
self.Data.Add(foot)
self.Symbols.ItemsSource = self.Data
def _grids_symbols(self):
Symbols = []
grids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Grids) \
.WhereElementIsNotElementType().ToElements()
for gd_a, gd_b in itertools.combinations(grids, 2):
curveA = gd_a.Curve
curveB = gd_b.Curve
vectA = curveA.ComputeDerivatives(0.5, True).BasisX
vectB = curveB.ComputeDerivatives(0.5, True).BasisX
symA = gd_a.Name
symB = gd_b.Name
# check if lines are parallel
if abs(vectA.CrossProduct(vectB).Z) < 0.01:
# if true go to the next iteration
continue
results = clr.Reference[IntersectionResultArray]()
result = curveA.Intersect(curveB, results)
if result == SetComparisonResult.Overlap:
if symA.isalpha() and symB.isdigit():
symbol = "{}-{}".format(symA, symB)
elif symA.isdigit() and symB.isalpha():
symbol = "{}-{}".format(symB, symA)
else:
continue
Symbols.append(symbol)
return sorted(Symbols)
def Add_Click(self, sender, e):
try:
# Get values from the text boxes
long_value = float(self.long_value.Text)
larg_value = float(self.larg_value.Text)
ep_value = float(self.ep_value.Text)
# Create a new FootingType instance
foot = FootingType(long_value, larg_value, ep_value)
# Add the FootingType instance to all Footings_VM instances in the Data collection
for vm in self.Data:
vm.AddType(foot)
print("Footing Added: {}".format(foot.Name)) # Debugging
self.long_value.Clear()
self.larg_value.Clear()
self.ep_value.Clear()
except ValueError:
forms.alert("Please enter valid numeric values for dimensions.")
def Appliquer_Click(self, sender, e):
selected_foot = [vm.SelectedType for vm in self.Data if vm.SelectedType is not None]
if selected_foot:
print("Selected Footings: {}".format([foot.Name for foot in selected_foot]))
else:
print("No footings selected.")
self.Close()
def Fermer_Click(self, sender, e):
self.Close()
def main(doc=None):
"""Main entrypoint for the script."""
doc = doc or revit.doc
foundation = MyWindow()
foundation.ShowDialog()
if __name__ == "__main__":
main()
How can I fix this issue?
Thanks.