Thanks @Jean-Marc for the shared code.
As I mentioned above, I’m trying to retrieve footing symbols, and I want my script to work in both Structural Template and Architectural Template projects.
In a Structural Template project I can directly use the available symbols from the "M_Base-Rectangulaire"
family.
However, if an Architectural Template project is started, I need to load the "Base rectangulaire"
family and use its available symbols.
Initially, I was able to achieve this by testing my updated _myFamilies
script separately (I removed the main()
function so I could call the get_family_symbols
function from my main script).
Now, when I run the tool, Revit crashes, and I’m not sure what the actual issue is?
Please check my refrences:
_myFamilies
# -*- coding: utf-8 -*-
import clr
import os
from Autodesk.Revit.DB import *
from pyrevit import revit, forms
Family_path = r"C:\ProgramData\Autodesk\RVT 2023\Libraries\French\Structure\Fondations\Base rectangulaire.rfa"
def get_family_symbols():
doc = revit.doc
try:
"""Use default footing symbols from "M_Base-Rectangulaire" Family
if new "Structural template" project is started"""
collector = FilteredElementCollector(doc).OfClass(Family)
all_symbols = [
doc.GetElement(symbol_id)
for fam in collector
if fam.Name == "M_Base-Rectangulaire"
for symbol_id in fam.GetFamilySymbolIds()
]
if all_symbols:
return all_symbols
"""Else, try loading "Base rectangulaire" Family and activating its
symbols if new "Archituctural template" project is started"""
if not os.path.exists(Family_path):
forms.alert("Family file not found.")
return None
all_symbols = []
with revit.Transaction("Load and Activate Base rectangulaire Family"):
if not doc.LoadFamily(Family_path):
forms.alert("Failed to load family file.")
return None
# After loading, collect symbols
collector = FilteredElementCollector(doc).OfClass(Family)
all_symbols = [
doc.GetElement(symbol_id)
for fam in collector
if fam.Name == "Base rectangulaire"
for symbol_id in fam.GetFamilySymbolIds()
]
if not all_symbols:
forms.alert("No symbols found in Base rectangulaire family.")
return None
for symbol in all_symbols:
if not symbol.IsActive:
symbol.Activate()
doc.Regenerate()
return all_symbols
except Exception as ex:
forms.alert("Error occurred:\n{}".format(str(ex)), title="Critical Error")
return None
Footings_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.Generic import List
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
from Snippets._myMaterials import ConcreteMaterial, get_or_create_concrete_material
from Snippets._mySymbols import FootingType
from Snippets._myFamilies import get_family_symbols
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
app = __revit__.Application
results = clr.Reference[IntersectionResultArray]()
class ViewModelBase(INotifyPropertyChanged):
# Implementing INotifyPropertyChanged interface
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):
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.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 = {}
self.DataTypes = ObservableCollection[Footings_VM]()
symbols = self._grids_symbols()
for s in symbols:
foot = Footings_VM(s)
self.DataTypes.Add(foot)
self.DataContext = self.DataTypes
def Types_SelectionChanged(self, sender, e):
if sender.SelectedItem is not None:
self.Types.SelectedItem = sender.SelectedItem
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:
# Assurer un format "Lettre-Chiffre" (Ex: "A-1", "B-2", ...)
if symA.isalpha() and symB.isdigit():
symbol = "{}-{}".format(symA, symB)
elif symA.isdigit() and symB.isalpha():
symbol = "{}-{}".format(symB, symA)
else:
continue # Éviter les cas non conformes (ex: "1-2" ou "A-B")
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.DataTypes:
vm.AddType(foot)
self.long_value.Clear()
self.larg_value.Clear()
self.ep_value.Clear()
except ValueError:
forms.alert("Please enter valid numeric values for dimensions.")
def del_Click(self, sender, e):
if self.Types.SelectedItem is not None:
self.DataTypes.Remove(self.Types.SelectedItem)
def delAll_Click(self, sender, e):
self.DataTypes.Clear()
def _collect_input_data(self):
for item in self.DataTypes:
if len(item.LstTypes) < 2:
forms.alert("Veuillez ajouter au moins un type de semelle")
return
elif item.SelectedType is None or item.SelectedType.H == 0:
forms.alert("Veuillez selectionner un type de semelle")
return
if not self.contrainte_value.Text:
forms.alert("Veuillez taper une valeur valide pour : contrainte")
return
else:
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:
forms.alert(
"Invalid input detected. "
"Please ensure all inputs are numeric where required."
)
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()
print(self.Data)
self.Close()
def Fermer_Click(self, sender, e):
self.Close()
grids = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Grids) \
.WhereElementIsNotElementType().ToElements()
def get_foundation_level():
"""Return the foundation level in the document with the minimum elevation."""
levels = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels) \
.WhereElementIsNotElementType().ToElements()
return min(levels, key=lambda l: l.Elevation)
def get_grid_intersection_points(grids):
lstPts = []
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
# check if lines are parallel
if abs(vectA.CrossProduct(vectB).Z) < 0.01:
# if true go to the next iteration
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_types = list(LstTypes)
created_types = []
# Get all symbols from the loaded family
family_symbols = get_family_symbols()
base_symbol = family_symbols[0]
with Transaction(doc, "Create Footing Types") as t:
t.Start()
for ft in all_types:
# Check if type already exists
existing_symbol = None
# Try to find an existing symbol by name
for sym in family_symbols:
if Element.Name.GetValue(sym) == ft.Name:
existing_symbol = sym
break
if existing_symbol:
created_types.append(existing_symbol)
else:
# Duplicate the base symbol and set parameters
symbol = base_symbol.Duplicate(ft.Name)
symbol.LookupParameter("Largeur").Set(ft.H / 304.8)
symbol.LookupParameter("Longueur").Set(ft.W / 304.8)
symbol.LookupParameter("Epaisseur de fondation").Set(ft.T / 304.8) # cm to feet
created_types.append(symbol)
t.Commit()
return created_types
def Create_footings(points, family_symbol, level, material_id, doc):
with Transaction(doc, "Create Columns") as t:
t.Start()
print("Level Elevation: {}".format(level.Elevation))
for p, s in zip(points, family_symbol):
# Create the footing at the proper elevation
foot = doc.Create.NewFamilyInstance(p, s, level, StructuralType.Footing)
# Assign material
material_param = foot.get_Parameter(BuiltInParameter.STRUCTURAL_MATERIAL_PARAM)
if material_param:
material_param.Set(material_id)
t.Commit()
def main(doc=None):
"""Main entrypoint for the script."""
doc = doc or revit.doc
foundation = MyWindow()
foundation.ShowDialog()
if not foundation.Data:
return
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 is None:
print("Failed to create or find the footing type.")
return
points = get_grid_intersection_points(grids)
Create_footings(points, family_symbol, Data["lvl"], material_id, doc)
if __name__ == "__main__":
main()
Optional, here the xaml layout:
footings
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Footings"
Height="Auto" Width="470"
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="60*"/>
<ColumnDefinition Width="20*"/>
<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.75*">
<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>
Thanks.