Set footings Type using WPF

@c.poupin

After a few days’ break, I was finally able to implement the del_Click event, which allows me to delete the selected FootingType instance from the combobox dropdown list. However, for now, it’s still difficult for me to implement delAll_Click, which would allow deleting all FootingType instances.

here the final code:
Note: It is necessary to add the path for the lib subfolder within the attached Footings folder for the code to work as expected.

Footings.7z (1.5 KB)

Footings
import clr  
import sys
sys.path.append(r"C:\Users\nono\OneDrive\Desktop\Footings\lib")
import System
import math
from System import Array
from System.Collections.Generic import List, KeyValuePair
from System.Collections.ObjectModel import ObservableCollection

# Import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
from Autodesk.Revit.DB.Structure import StructuralType

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument

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

clr.AddReference("IronPython.Wpf")
clr.AddReference('System.Core')
clr.AddReference('System.Xml')
clr.AddReference('PresentationCore')
clr.AddReference('PresentationFramework')
clr.AddReference('System.Windows.Forms')  # For MessageBox

from System.IO import StringReader
from System.Windows.Markup import XamlReader, XamlWriter
from System.Windows import Window, Application
from System.ComponentModel import INotifyPropertyChanged
from System.ComponentModel import PropertyChangedEventArgs
from System.Windows import Window
from System.Windows.Forms import MessageBox

import wpf
import itertools
import traceback

from _mySymbols import FootingType
from _myMaterials import ConcreteMaterial, get_or_create_concrete_material


class ViewModelBase(INotifyPropertyChanged):
    def __init__(self):
        self.propertyChangedHandlers = []

    def RaisePropertyChanged(self, propertyName):
        args = PropertyChangedEventArgs(propertyName)
        for handler in self.propertyChangedHandlers:
            handler(self, args)

    def add_PropertyChanged(self, handler):
        self.propertyChangedHandlers.append(handler)

    def remove_PropertyChanged(self, handler):
        self.propertyChangedHandlers.remove(handler)
        
class Footings_VM(ViewModelBase):
    def __init__(self, Position):
        ViewModelBase.__init__(self)
        self._position = Position
        self._types = ObservableCollection[FootingType]() 
        self._types.Add(FootingType(0, 0, 0))
        self._selected_type = self._types[0]

    @property
    def Position(self):
        return self._position

    @Position.setter
    def Position(self, value):
        self._position = value
        self.RaisePropertyChanged('Position')

    @property
    def LstTypes(self):
        return self._types

    @LstTypes.setter
    def LstTypes(self, lst_value):
        self._types = ObservableCollection[object](lst_value)
        self.RaisePropertyChanged('LstTypes')

    @property
    def SelectedType(self):
        return self._selected_type

    @SelectedType.setter
    def SelectedType(self, value):
        self._selected_type = value
        self.RaisePropertyChanged('SelectedType')

    def AddType(self, footing_type):
        self._types.Add(footing_type)
        self.RaisePropertyChanged('LstTypes')

def _grids_symbols():
    symbols = []
    grids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Grids) \
        .WhereElementIsNotElementType().ToElements()

    for gd_a, gd_b in itertools.combinations(grids, 2):
        symA = gd_a.Name
        symB = gd_b.Name
        if gd_a.Curve.Intersect(gd_b.Curve) == SetComparisonResult.Overlap:
            if symA.isalpha() and symB.isdigit():
                symbol = "{}-{}".format(symA, symB)
                symbols.append(symbol)
            else:
                symbol = "{}-{}".format(symB, symA)
                symbols.append(symbol)
                
    return sorted(symbols)

class MyWindow(Window):
    xaml_str = '''
    <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" ItemsSource="{Binding}"
                    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}"
                                          SelectionChanged="Types_SelectionChanged"
                                          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" Click="del_Click" 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>
    '''

    def __init__(self):
        wpf.LoadComponent(self, StringReader(MyWindow.xaml_str))
        self.Data = {}
        self.DataTypes = ObservableCollection[Footings_VM]()
        self.symbols = _grids_symbols()
        for s in self.symbols:
            foot = Footings_VM(s)
            self.DataTypes.Add(foot)
        self.DataContext = self.DataTypes
        self.selected_position = None
                
    def Types_SelectionChanged(self, sender, e):
        if sender.SelectedItem is not None:
            self.selected_position = sender.SelectedItem

    def Add_Click(self, sender, e):
        try:
            long_value = float(self.long_value.Text)
            larg_value = float(self.larg_value.Text)
            ep_value = float(self.ep_value.Text)

            foot = FootingType(long_value, larg_value, ep_value)
            for vm in self.DataTypes:
                vm.AddType(foot)

            self.long_value.Clear()
            self.larg_value.Clear()
            self.ep_value.Clear()

        except ValueError:
            MessageBox.Show("Please enter valid numeric values for dimensions.", "Error")

    def del_Click(self, sender, e):
        
        if self.selected_position is not None:
            index_to_remove = None
            
            for item in self.DataTypes:
                if self.selected_position in item.LstTypes:
                    index_to_remove = item.LstTypes.IndexOf(self.selected_position)
                    break

            if index_to_remove is not None:
                for item in self.DataTypes:
                    if index_to_remove < len(item.LstTypes):
                        item.LstTypes.RemoveAt(index_to_remove)

                        if len(item.LstTypes) == 0:
                            default_type = FootingType(0, 0, 0)
                            item.LstTypes.Add(default_type)
                            item.SelectedType = default_type
                        elif item.SelectedType is None or item.SelectedType == self.selected_position:
                            item.SelectedType = item.LstTypes[0]

        
            self.selected_position = None

    def _collect_input_data(self):
        for item in self.DataTypes:
            if len(item.LstTypes) < 2:
                MessageBox.Show("Please add at least one footing type", "Error")
                return False
            elif item.SelectedType is None or item.SelectedType.H == 0:
                MessageBox.Show("Please select a footing type for each Position", "Error")
                return False

        if not self.contrainte_value.Text:
            MessageBox.Show("Please enter a valid value for : contrainte", "Error")
            return False

        try:
            self.Data["foot"] = [vm.SelectedType for vm in self.DataTypes if vm.SelectedType.Name != "??"]
            self.Data["contrainte"] = self.contrainte_value.Text
            return True
        except ValueError:
            MessageBox.Show("Invalid input detected. Please ensure all inputs are numeric where required.", "Error")
            return False

    def Appliquer_Click(self, sender, e):
        if not self._collect_input_data():
            return
        if not self.Data:
            return
        self.Data["lvl"] = get_foundation_level()
        self.Close()

    def Fermer_Click(self, sender, e):
        self.Close()

def get_foundation_level():
    levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels) \
            .WhereElementIsNotElementType().ToElements()
    return min(levels, key=lambda l: l.Elevation)

def get_grid_intersection_points(grids):
    lstPts = []
    results = clr.Reference[IntersectionResultArray]()
    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

        if abs(vectA.CrossProduct(vectB).Z) < 0.01:
            continue
            
        result = curveA.Intersect(curveB, results)
        if result == SetComparisonResult.Overlap:
            interResult = results.Value
            lstPts.append(interResult[0].XYZPoint)
            lstPts.sort(key=lambda p: (p.Y, p.X))
    return lstPts

def CreateFootingTypes(LstTypes, doc):
    all = list(LstTypes)
    created_types = []
    symbols = FilteredElementCollector(doc) \
            .WhereElementIsElementType() \
            .OfCategory(BuiltInCategory.OST_StructuralFoundation) \
            .ToElements()

    footing_names = {"Base rectangulaire", "M_Base-Rectangulaire"}
    existing_types_dict = {Element.Name.GetValue(fs): fs for fs in symbols if fs.FamilyName in footing_names}
    created_types_dict = {}

    with Transaction(doc, "Create Footing Types") as t:
        t.Start()
        for ft in all:
            if ft.Name in existing_types_dict:
                created_types.append(existing_types_dict[ft.Name])
            elif ft.Name in created_types_dict:
                created_types.append(created_types_dict[ft.Name])
            else:
                first_type = next(iter(existing_types_dict.values()), None)
                if not first_type:
                    MessageBox.Show("No existing footing family found.", "Error")
                    return None

                new_type = first_type.Duplicate(ft.Name)
                new_type.LookupParameter("Largeur").Set(ft.H / 30.48)
                new_type.LookupParameter("Longueur").Set(ft.W / 30.48)
                new_type.LookupParameter("Epaisseur de fondation").Set(ft.T / 30.48)
                created_types.append(new_type)
                created_types_dict[ft.Name] = new_type
        t.Commit()
    return created_types

def Create_footings(points, family_symbol, level, material_id, doc):
    with Transaction(doc, "Create Footings") as t:
        t.Start()
        for p, s in zip(points, family_symbol):
            foot = doc.Create.NewFamilyInstance(p, s, level, StructuralType.Footing)
            material_param = foot.get_Parameter(BuiltInParameter.STRUCTURAL_MATERIAL_PARAM)
            if material_param:
                material_param.Set(material_id)
        t.Commit()

# Main execution
grids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Grids) \
        .WhereElementIsNotElementType().ToElements()

foundation = MyWindow()
foundation.ShowDialog()

Data = foundation.Data
Data = foundation.Data
family_symbol = CreateFootingTypes(Data["foot"], doc)
concrete_material = ConcreteMaterial(Data["contrainte"])
material_id = get_or_create_concrete_material(concrete_material, doc)
    
if family_symbol:
    points = get_grid_intersection_points(grids)
    Create_footings(points, family_symbol, Data["lvl"], material_id, doc)
    
OUT = 0

Thanks.