Automaticaly load Family Symbol

Hi All,

In the script below, I’m trying to automatically load/use a family symbol so that I can use it in my main tool.
Inget_family_symbols function:

  • ARCH_FAMILY_NAME refers to the searched-for family "Base rectangulaire" if a new Architectural Template project is started.
  • STRUCT_FAMILY_NAME refers to the default-loaded family "M_Base-Rectangulaire" when a new Structural Template project is started.
  • family_symbols dictionary will contain either one family or the other

When I run the script, no family is loaded and no error is displayed.

# -*- coding: utf-8 -*-
import clr
import os
from Autodesk.Revit.DB import *
from pyrevit import revit, forms

ARCH_FAMILY_NAME = "Base rectangulaire"
ARCH_FAMILY_PATH = r"C:\ProgramData\Autodesk\RVT 2023\Libraries\French\Structure\Fondations\Base rectangulaire.rfa"
STRUCT_FAMILY_NAME = "M_Base-Rectangulaire"

def get_family_symbols():
    doc = revit.doc
    family_symbols = {}
    
    try:
        collector = FilteredElementCollector(doc).OfClass(Family)
        for symbol in collector:
            if symbol.Name == STRUCT_FAMILY_NAME:
                family_symbols[STRUCT_FAMILY_NAME] = symbol
                break
        
        
        if STRUCT_FAMILY_NAME not in family_symbols:
            # Check if architectural family is already loaded
            existing_family = next((f for f in collector if f.Name == ARCH_FAMILY_NAME), None)
            
            if not existing_family and os.path.exists(ARCH_FAMILY_PATH):
                with revit.Transaction("Load Family"):
                    if doc.LoadFamily(ARCH_FAMILY_PATH):
                        existing_family = next((f for f in collector if f.Name == ARCH_FAMILY_NAME), None)
            
            if existing_family:
                
                symbol_ids = list(existing_family.GetFamilySymbolIds())
                if symbol_ids:
                    symbol = doc.GetElement(symbol_ids[0])
                    family_symbols[ARCH_FAMILY_NAME] = symbol
                if not symbol.IsActive:
                    symbol.Activate()
                    doc.Regenerate()
        
        return family_symbols

    except ValueError:
        
        forms.alert("Error")
        return {}

Any help would be appricieted.
Thanks.

Have a look at the way it is done with content bundles in pyrevity

C# example, but you should be able to understand

1 Like

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.

@sanzoghenzo @GavinCrump have you an idea how to solve this ?

Thanks.

My only advice is to use plenty of logger.debug statements to understand the code branches that get executed :wink:

1 Like

I finally solved my issue…it was caused by the XAML part, where I had forgotten to implement the SelectionChanged event. So, this part:

                        <DataTemplate>
                            <ComboBox x:Name="Types" ItemsSource="{Binding LstTypes}"
                                      SelectedItem="{Binding SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                      DisplayMemberPath="Name">
                            </ComboBox>
                        </DataTemplate>

was replaced by this part:

                        <DataTemplate>
                            <ComboBox x:Name="Types"
                                      ItemsSource="{Binding LstTypes}"
                                      SelectedItem="{Binding SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                      SelectionChanged="Types_SelectionChanged"
                                      DisplayMemberPath="Name">
                            </ComboBox>
                        </DataTemplate>

Thanks.

1 Like