Loading wpf User Control issue

Hi All, @c.poupin

In my main XAML layout and WPF form, I’m having trouble referencing and declaring the namespace xmlns:local for my user control IntegerUpDown, which allows me to use it.
I’m getting this error:

Please check my references below:

IntegerUpDown.py
import clr
import os
clr.AddReference("IronPython.Wpf")
clr.AddReference("PresentationFramework")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")

from System.Windows import Application, Window
from System.IO import StringReader
from System.Windows.Controls import UserControl
import wpf

script_path = os.path.dirname(__file__)

class IntegerUpDown(UserControl):
    def __init__(self):
        xaml_path = os.path.join(script_path, 'IntegerUpDown.xaml')
        wpf.LoadComponent(self, xaml_path)

    def btnIncrease_Click(self, sender, e):
        num = int(self.NumericText.Text)
        num += 1
        self.NumericText.Text = str(num)

    def btnDecrease_Click(self, sender, e):
        num = int(self.NumericText.Text)
        num -= 1
        if num > -1:
            self.NumericText.Text = str(num)
        else:
            self.NumericText.Text = str(0)       
IntegerUpDown.xaml
<UserControl 
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             MinWidth="50">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>

        <Grid Grid.Column="0">
            <TextBox x:Name="NumericText" Grid.Column="0"
                     Text="0"
                     TextAlignment="Center"
                     BorderBrush="Black" Margin="0,0,0.2,0" />
        </Grid>
        <Grid Grid.Column="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <RepeatButton x:Name="PART_IncreaseButton" Click="btnIncrease_Click" Grid.Row="0" Margin="0,0,0,0.1"
				  Width="15" BorderThickness="0.75">
                <Polygon x:Name="PART_Up_Polygon" 
                         HorizontalAlignment="Center" 
                         VerticalAlignment="Center" Margin="2"
						 StrokeThickness="0.5" Stroke="Transparent" 
                         Points="0,0 -2,5 2,5" Stretch="Fill" Fill="Black"/>
            </RepeatButton>
            <RepeatButton x:Name="PART_DecreaseButton" Click="btndecrease_Click"  Grid.Row="1" Margin="0,0.1,0,0" 
				  Width="15" BorderThickness="0.75">
                <Polygon x:Name="PART_Down_Polygon" HorizontalAlignment="Center" 
                         VerticalAlignment="Center" Margin="2"
						 StrokeThickness="0.5" Stroke="Transparent" 
                         Points="-2,0 2,0 0,5 " Stretch="Fill" Fill="Black"/>
            </RepeatButton>
        </Grid>

    </Grid>
</UserControl>

UC3.dyn (8.5 KB)

Edit: Take a look below to the Form design

Any help would be appreciated.

Thanks.

Hi

you can use WPF Toolkit instead (the library is accessible via Dynamo)

wpf_NumericUpDown

code IronPython

import clr
import System
from System.Threading import Thread, ThreadStart, ApartmentState
import wpf

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
clr.AddReference("DynamoCore")
from System.Windows import LogicalTreeHelper

clr.AddReference("DotNetProjects.Wpf.Extended.Toolkit")

class MyWindow(System.Windows.Window):
    xaml = '''
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        Height="200" Width="350">
        <StackPanel Orientation="Vertical" Margin="10">
            <xctk:DateTimePicker x:Name="DateTimePicker" Margin="10"/>
            <xctk:IntegerUpDown Name="myUpDownControl" Margin="10" />
            <Button x:Name="Button" Content="Close" Margin="10" Click="buttonClick"/>
        </StackPanel>
    </Window>'''
    
    
    def __init__(self):
        super().__init__()
        xr = System.Xml.XmlReader.Create(System.IO.StringReader(MyWindow.xaml))
        wpf.LoadComponent(self, xr)

    def buttonClick(self, sender, e):
        self.Close()
            
def main():   
    try:      
        window = MyWindow()
        window.ShowDialog()
        globals()['results'] = [window.DateTimePicker.Text, window.myUpDownControl.Text]
    except Exception as ex:
        print(ex)

results = []
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()

OUT = results
2 Likes

@c.poupin

Thank you for your excellent example!

First, I noticed that I’m using Revit 2023 with the IronPython 2.7 engine, which supports WPF implementation. However, I’m encountering the following error when running your code. Could this be caused by a missing WPF Toolkit assembly in IronPython 2.7, or could it be another issue?

Second, I intend to implement a tool in pyrevit where I need to use the IntegerUpDown control. I’m posting this question here because I didn’t receive an answer in the pyrevit forum, and I believe there isn’t a significant difference in WPF implementation between Dynamo and pyrevit.

I tried using the WPF Toolkit in my pyrevit and xaml scripts, but I encountered the same error as in Dynamo, as shown below:

image

Can I use WPF Toolkit within IronPython 2.7 engine in Dynamo and pyrevit and how to correctly reference it ? or else how can I reference my own IntegerUpDown control in my main xaml layout to be able to use it in my main pyrevit and xaml scripts?

Edit:
Can I use wpf ResourceDictionary to add my custom control to my main XAML layout? If so, how can I implement this?

Thanks.

The library is different for Revit 2022, 2023, 2024 (Net Framework vs Net Core)

import clr
import System
import wpf

clr.AddReference("System.Xml")
clr.AddReference("PresentationFramework")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
clr.AddReference("DynamoCore")
from System.Windows import LogicalTreeHelper

clr.AddReference("Xceed.Wpf.Toolkit")

class MyWindow(System.Windows.Window):
    xaml = '''
    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        Height="200" Width="350">
        <StackPanel Orientation="Vertical" Margin="10">
            <xctk:DateTimePicker x:Name="DateTimePicker" Margin="10"/>
            <xctk:IntegerUpDown Name="myUpDownControl" Margin="10" />
            <Button x:Name="Button" Content="Close" Margin="10" Click="buttonClick"/>
        </StackPanel>
    </Window>'''
    
    
    def __init__(self):
        xr = System.Xml.XmlReader.Create(System.IO.StringReader(MyWindow.xaml))
        wpf.LoadComponent(self, xr)

    def buttonClick(self, sender, e):
        self.Close()
        
        
window = MyWindow()
window.ShowDialog()
            
results =  [window.DateTimePicker.Text, window.myUpDownControl.Text]

OUT = results

in pyRevit, if the assembly is missing, you probably need to import the dll

3 Likes

Hi, here is an example I made with
online writing
possible interdistances to mix regular esp and multiple different ones
(another way of putting things)
rep25_01_14
here is a class easily transposable into python with your level

code
//Fonction pour transfomer une serie de texte en list de doubles
		public List<double> TransformTexteDouble(string cont)
		{
			string [] cont2=cont.Split(' ');
			List<double> numd=new List<double>();
			NumberFormatInfo provider = new NumberFormatInfo();
			provider.NumberDecimalSeparator = ".";
			
			foreach (string s in cont2)
			{
				if(s.Contains('x'))
				{
					int idx=s.IndexOf('x');
					int l=s.Length;
					int nb=Convert.ToInt32(s.Substring(0,idx));
					for (int i=1;i<=nb;i++)
					{
						string str=s.Substring(idx+1,l-(idx+1));
						numd.Add(Convert.ToDouble(str,provider));
					}	
				}
				else
					numd.Add(Convert.ToDouble(s,provider));
			}
			
			return numd;
		}
		// Tracer un quadrillage à partir d'un winform
		public void essaifiles()
		{
			boiteg.ShowDialog();
			NumberFormatInfo provider = new NumberFormatInfo();
			provider.NumberDecimalSeparator = ".";
			
			// les extensions en verticales et horizontales
			double dv=Convert.ToDouble(etiq5,provider)/0.3048;
			double dh=Convert.ToDouble(etiq6,provider)/0.3048;
			
			double somtotalx=0;
			double somtotaly=0;
			List<double> filesXcumulees=TransformTexteDouble(etiq3);
			
			foreach (double d in filesXcumulees)
			{
				somtotalx+=d;
			}
			List<double> filesYcumulees=TransformTexteDouble(etiq4);
			
			foreach (double d in filesYcumulees)
			{
				somtotaly+=d;
			}
			Transaction t= new Transaction(doc,"création grilles verticales et horizontales");
			t.Start();
			double sumcumX=0;
			double sumcumY=0;
			// 1ere file Verticale pour attribuer numerotation
			XYZ sx =new XYZ(0,-dh,0);
			XYZ ex= new XYZ(0,somtotaly/0.3048+dh,0);
			Grid gx=Grid.Create(doc,Line.CreateBound(sx,ex));
			if (etiq2=="Files Horizontales en Alphabétique")
				{
				gx.Name="1";
				}
			else
				gx.Name="A";
			//Autres files verticales
			foreach (double d in filesXcumulees)
			{
				sumcumX+=d/0.3048;
				XYZ s =new XYZ(sumcumX,-dh,0);
				XYZ e= new XYZ(sumcumX,somtotaly/0.3048+dh,0);
				Grid g=Grid.Create(doc,Line.CreateBound(s,e));
			}
			// 1ere file Horizontale pour attribuer numerotation
			XYZ sy =new XYZ(-dv,0,0);
			XYZ ey= new XYZ(somtotalx/0.3048+dv,0,0);
			Grid gy=Grid.Create(doc,Line.CreateBound(sy,ey));
			//Autres files Horizontales
			if (etiq2=="Files Horizontales en Alphabétique")
				{
				gy.Name="A";
				}
			else
				gy.Name="1";
			foreach (double d in filesYcumulees)
			{
				sumcumY+=d/0.3048;
				XYZ s =new XYZ(-dv,sumcumY,0);
				XYZ e= new XYZ(somtotalx/0.3048+dv,sumcumY,0);
				Grid g=Grid.Create(doc,Line.CreateBound(s,e));
			}
			t.Commit();	
		}

Sincerely
christian.stan

3 Likes

Good job @christian.stan :wink:

You became a C# developer? That’s great news. I wish you continued success.

It’s the same thing I intended to implement, but I struggled with how to add a counter for regular spaces in my WPF form and you dit it in a different way! :+1:

1 Like

let’s remain humble, more of a chatterbox than a developer

thank you for the compliment,
this was the purpose of the sending

Have a good day

Sincerely
christian.stan

1 Like

@c.poupin

I tried your updated code, and it works in Dynamo. However, in pyrevit, as you suggested, I had to import Wpf Toolkit to make it work.

I noticed that the IntegerUpDown control from Wpf Toolkit does not have a limit set for the minimum value, which should be 0 in my case. Is it possible to access and modify its limit?

Alternatively, as I mentioned earlier, could you please help me use my custom control by referencing it in my main xaml file, considering the structure of my project as shown below?

Thanks.

There are some properties

3 Likes

You are right… I rushed without fully exploring WPF Toolkit properties, but I discovered its Value property, which allowed me to set the minimum value to 0 using this code:

self.Repeter_x.Value = 0

To conclude regarding the usage of a custom control, based on what I understand (please correct me if I’m wrong), in order to use a custom control like my IntegerUpDown or the WPF Toolkit, it should be part of a DLL assembly, and we need to reference its namespace to import and use it ?

Thanks.

for Dynamo just
clr.AddReference()

for other, you can try to use,
clr.AddReferenceToFileAndPath()

1 Like

@c.poupin @christian.stan

Here is an updated version of my tool, grids_v2, for testing. It creates grids and levels, and here are a few points to clarify:

  1. The code is quite long, and I know it could be optimized by using a collection to manage the X, Y, and Z tabs along with their data. However, implementing this is difficult for me at the moment.
  2. The Centrer / Ne pas centrer option is not working as expected and needs to be fixed.
  3. To ensure that levels are created correctly, there must be no existing levels with the same name, as this leads to the following error:

Exception: Autodesk.Revit.Exceptions.ArgumentException: Name must be unique.
Parameter name: name.

Grids

grids_v2
import clr    
import sys
import os
import System

sys.path.append(r"C:\Users\nono\AppData\Roaming\Extended.WPF.Toolkit.Binaries.NET.4.x")
clr.AddReference("IronPython.Wpf")
clr.AddReference("PresentationFramework")
clr.AddReference("System.Xml")
clr.AddReference("PresentationCore")
clr.AddReference("System.Windows")
clr.AddReference('System.Windows.Forms')
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

from System.Collections.ObjectModel import ObservableCollection
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
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 *
from System.Windows.Forms import MessageBox
from string import ascii_uppercase
import wpf

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

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument

class ObservableObject(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 Grids_X_VM(ObservableObject):
	"""Set Properties for grids X"""
	def __init__(self, Label, Position):
		super(Grids_X_VM, self).__init__()
		self._label = Label
		self._position = Position
	
	@property
	def Label(self):
		return self._label
		
	@Label.setter
	def Label(self, value):
		if self._label != value:
			self._label = value
			self.notify_property_changed("Label")
			
	@property
	def Position(self):
		return self._position
		
	@Position.setter
	def Position(self, value):
		if self._position != value:
			self._position = value
			self.notify_property_changed("Position")


class Grids_Y_VM(ObservableObject):
	"""Set Properties for grids Y"""
	def __init__(self, Label, Position):
		super(Grids_Y_VM, self).__init__()
		self._label = Label
		self._position = Position
	
	@property
	def Label(self):
		return self._label
		
	@Label.setter
	def Label(self, value):
		if self._label != value:
			self._label = value
			self.notify_property_changed("Label")
			
	@property
	def Position(self):
		return self._position
		
	@Position.setter
	def Position(self, value):
		if self._position != value:
			self._position = value
			self.notify_property_changed("Position")


class Grids_Z_VM(ObservableObject):
	"""Set Properties for levels """
	def __init__(self, Label, Position):
		super(Grids_Z_VM, self).__init__()
		self._label = Label
		self._position = self._format_position(Position)
		
	@property
	def Label(self):
		return self._label
		
	@Label.setter
	def Label(self, value):
		if self._label != value:
			self._label = value
			self.notify_property_changed("Label")
			
	def _format_position(self, value):
		# Format the position to two decimal places
		return "{:.2f}".format(float(value))
		
	@property
	def Position(self):
		return self._position
		
	@Position.setter
	def Position(self, value):
		formatted_value = self._format_position(value)
		if self._position != formatted_value:
			self._position = formatted_value
			self.notify_property_changed("Position")


class MyWindow(Window):
	layout = '''
		<Window
			xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
			xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
			xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
			Title="Grids"
			Height="530" Width="340"
			WindowStartupLocation="CenterScreen">
			<Grid Margin="10">
				<Grid.RowDefinitions>
					<RowDefinition Height="7*" />
					<RowDefinition Height="76*" />
					<RowDefinition Height="10*" />
					<RowDefinition Height="7*"/>
				</Grid.RowDefinitions>
				<DockPanel>
					<Label Content="Nom:" />
					<TextBlock Text="Grid for Revit" Width="150" Margin="0,4,0,0" />
				</DockPanel>
				<TabControl Grid.Row="1">
					<!-- TabItem X -->
					<TabItem Header="X" Width="50">
						<Grid>
							<Grid.ColumnDefinitions>
								<ColumnDefinition Width="65*" />
								<ColumnDefinition Width="35*" />
							</Grid.ColumnDefinitions>
							<Grid.RowDefinitions>
								<RowDefinition Height="7*" />
								<RowDefinition Height="7*" />
								<RowDefinition Height="8*" />
								<RowDefinition Height="71*" />
								<RowDefinition Height="7*" />
							</Grid.RowDefinitions>
							<!-- Labels and Controls -->
							<DockPanel Grid.ColumnSpan="2">
								<Label Content="Position:" Margin="0,0,40,0" />
								<Label Content="Repeter x:" Margin="0,0,40,0" />
								<Label Content="Espacement:" />
							</DockPanel>
							<!-- Input Fields -->
							<DockPanel Grid.Row="1" Grid.ColumnSpan="2">
								<TextBox x:Name="positionX" Text="0" Width="50" Margin="0,0,5,0" />
								<TextBlock Text="(m)" Margin="0,0,25,0" Width="20" />
								<xctk:IntegerUpDown x:Name="Repeter_x" Minimum="0" Margin="0,0,50,0" />
								<TextBox x:Name="EspaceX" Width="50" Text="0.00" Margin="0,0,5,0" />
								<TextBlock Text="(m)" Width="20" HorizontalAlignment="Left" />
							</DockPanel>
							<DockPanel Grid.Row="2" Grid.ColumnSpan="2">
								<Label Content="Extention Axe Y ey :" Width="120" Height="25" />
								<TextBox x:Name="Ey"  Width="60" Height="25" Margin="10,0,0,0"  />
								<TextBlock Text="(m)" Height="25" Margin="10,0,0,0" VerticalAlignment="Center"/>
							</DockPanel>
							<!-- DataGrid for X -->
							<DataGrid x:Name="ListX" ItemsSource="{Binding DataX}"
									SelectionChanged="listX_SelectionChanged"
									AutoGenerateColumns="False"
									Background="Transparent" GridLinesVisibility="None"
									Grid.Row="3">
								<DataGrid.Columns>
									<DataGridTextColumn Header="Libelle" Binding="{Binding Path=Label}" Width="2.5*" />
									<DataGridTextColumn Header="Position" IsReadOnly="True" Binding="{Binding Path=Position}" Width="2.5*" />
								</DataGrid.Columns>
							</DataGrid>
							<!-- Numeric Alphabetic Labels -->
							<DockPanel Grid.Row="4">
								<Label Content="Libelle:" />
								<ComboBox x:Name="libX" Width="100" HorizontalAlignment="Right" SelectionChanged="libX_SelectionChanged">
								<ComboBoxItem Content="1 2 3 ..." Tag="Numeric"/>
								<ComboBoxItem Content="A B C ..." Tag="Alphabetic"/>
								</ComboBox>
							</DockPanel>
							<!-- Buttons -->
							<StackPanel Grid.Column="1" Grid.Row="3">
								<Button Name="ajouter_X" Content="Ajouter" Click="btnX_Click" VerticalAlignment="Top" Margin="5,50,5,10" />
								<Button Name="supprimer_X" Content="Supprimer" Click="supprX_Click" Margin="5,0,5,10" />
								<Button Name="supprimer_tout_X" Content="Supprimer tout" Click="supprallX_Click" Margin="5,0,5,10" />
							</StackPanel>
						</Grid>
					</TabItem>
					<!-- TabItem Y -->
					<TabItem Header="Y" Width="50">
						<Grid>
							<Grid.ColumnDefinitions>
								<ColumnDefinition Width="65*" />
								<ColumnDefinition Width="35*"/>
							</Grid.ColumnDefinitions>
							<Grid.RowDefinitions>
								<RowDefinition Height="7*" />
								<RowDefinition Height="7*" />
								<RowDefinition Height="8*" />
								<RowDefinition Height="71*" />
								<RowDefinition Height="7*" />
							</Grid.RowDefinitions>
							<!-- Labels and Controls -->
							<DockPanel Grid.ColumnSpan="3">
								<Label Content="Position:" Margin="0,0,40,0" />
								<Label Content="Repeter x:" Margin="0,0,40,0" />
								<Label Content="Espacement:" />
							</DockPanel>
							<!-- Input Fields -->
							<DockPanel Grid.Row="1" Grid.ColumnSpan="2">
								<TextBox x:Name="positionY" Text="0" Width="50" Margin="0,0,5,0" />
								<TextBlock Text="(m)" Margin="0,0,25,0" Width="20" />
								<xctk:IntegerUpDown x:Name="Repeter_y" Minimum="0" Margin="0,0,50,0" />
								<TextBox x:Name="EspaceY" Width="50" Text="0.00" Margin="0,0,5,0" />
								<TextBlock Text="(m)" Width="20" HorizontalAlignment="Left" />
							</DockPanel>
							<DockPanel Grid.Row="2" Grid.ColumnSpan="2">
								<Label Content="Extention Axe X ex :" Width="120" Height="25" />
								<TextBox x:Name="Ex"  Width="60" Height="25" Margin="10,0,0,0"  />
								<TextBlock Text="(m)" Height="25" Margin="10,0,0,0" VerticalAlignment="Center"/>
							</DockPanel>
							<!-- DataGrid for Y -->
							<DataGrid x:Name="ListY" ItemsSource="{Binding DataY}"
									SelectionChanged="listY_SelectionChanged"
									AutoGenerateColumns="False"
									Background="Transparent" GridLinesVisibility="None"
									Grid.Row="3" >
								<DataGrid.Columns>
									<DataGridTextColumn Header="Libelle" Binding="{Binding Path=Label}" Width="2.5*" />
									<DataGridTextColumn Header="Position" IsReadOnly="True" Binding="{Binding Path=Position}" Width="2.5*" />
								</DataGrid.Columns>
							</DataGrid>
							<!-- Numeric Alphabetic Labels -->
							<DockPanel Grid.Row="4">
								<Label Content="Libelle:" />
								<ComboBox x:Name="libY" Width="100" HorizontalAlignment="Right" SelectionChanged="libY_SelectionChanged">
								<ComboBoxItem Content="1 2 3 ..." Tag="Numeric"/>
								<ComboBoxItem Content="A B C ..." Tag="Alphabetic"/>
								</ComboBox>
							</DockPanel>
							<!-- Buttons -->
							<StackPanel Grid.Column="1" Grid.Row="3">
								<Button Name="ajouter_Y" Content="Ajouter" Click="btnY_Click" VerticalAlignment="Top" Margin="5,50,5,10" />
								<Button Name="supprimer_Y" Content="Supprimer" Click="supprY_Click" Margin="5,0,5,10" />
								<Button Name="supprimer_tout_Y" Content="Supprimer tout" Click="supprallY_Click" Margin="5,0,5,10" />
							</StackPanel>
						</Grid>
					</TabItem>
					<!-- TabItem Z -->
					<TabItem Header="Z" Width="50">
						<Grid>
							<Grid.ColumnDefinitions>
								<ColumnDefinition Width="65*" />
								<ColumnDefinition Width="35*" />
							</Grid.ColumnDefinitions>
							<Grid.RowDefinitions>
								<RowDefinition Height="7*" />
								<RowDefinition Height="7*" />
								<RowDefinition Height="79*" />
								<RowDefinition Height="7*" />
							</Grid.RowDefinitions>
							<!-- Labels and Controls -->
							<DockPanel Grid.ColumnSpan="2">
								<Label Content="Position:" Margin="0,0,40,0" />
								<Label Content="Repeter x:" Margin="0,0,40,0" />
								<Label Content="Espacement:" />
							</DockPanel>
							<!-- Input Fields -->
							<DockPanel Grid.Row="1" Grid.ColumnSpan="2">
								<TextBox x:Name="positionZ" Text="0" Width="50" Margin="0,0,5,0" />
								<TextBlock Text="(m)" Margin="0,0,25,0" Width="20" />
								<xctk:IntegerUpDown x:Name="Repeter_z" Minimum="0" Margin="0,0,50,0" />
								<TextBox x:Name="EspaceZ" Width="50" Text="0.00" Margin="0,0,5,0" />
								<TextBlock Text="(m)" Width="20" HorizontalAlignment="Left" />
							</DockPanel>
							<!-- DataGrid for Z -->
							<DataGrid x:Name="ListZ" ItemsSource="{Binding DataZ}"
									SelectionChanged="listZ_SelectionChanged"
									AutoGenerateColumns="False"
									Background="Transparent" GridLinesVisibility="None"
									Grid.Row="2">
								<DataGrid.Columns>
									<DataGridTextColumn Header="Libelle" Binding="{Binding Path=Label}" Width="2.5*" />
									<DataGridTextColumn Header="Position" IsReadOnly="True" Binding="{Binding Path=Position}" Width="2.5*" />
								</DataGrid.Columns>
							</DataGrid>
							<!-- Buttons -->
							<StackPanel Grid.Column="1" Grid.Row="2">
								<Button Name="ajouter_Z" Content="Ajouter" Click="btnZ_Click" VerticalAlignment="Top" Margin="5,50,5,10" />
								<Button Name="supprimer_Z" Content="Supprimer" Click="supprZ_Click" Margin="5,0,5,10" />
								<Button Name="supprimer_tout_Z" Content="Supprimer tout" Click="supprallZ_Click" Margin="5,0,5,10" />
							</StackPanel>
						</Grid>
					</TabItem>
				</TabControl>
				<StackPanel Grid.Row="2" >
					<DockPanel>
						<Label Content="Centrer la grille / Point d'Origin (0,0,0)" HorizontalAlignment="Center" FontWeight="Bold" />
					</DockPanel>
					<DockPanel>
						<RadioButton x:Name="CenterLines" Content="Centrer" Margin="80,0,0,0" GroupName="CenterOptions"/>
						<RadioButton x:Name="DoNotCenterLines" Content="Ne pas centrer" IsChecked="True" GroupName="CenterOptions"/>
					</DockPanel>
				</StackPanel>			
				<!-- Submit and Close Buttons -->
				<DockPanel Grid.Row="3">
					<Button Content="Appliquer" Width="100" Height="25" Click="Appliquer_Click" />
					<Button Content="Fermer" Width="100" Height="25" Click="Fermer_Click" HorizontalAlignment="Right" />
				</DockPanel>
			</Grid>
		</Window>'''
	def __init__(self):
		wpf.LoadComponent(self, StringReader(MyWindow.layout))
		"""Initialize values for TabItem X"""
		self.DataX = ObservableCollection[Grids_X_VM]()
		self.points_X = []
		self.ListX.ItemsSource = self.DataX
		self.positionX.Text = "0"
		self.Repeter_x.Value = 0
		self.EspaceX.Text = "0"
		self.libX.SelectedIndex = 0
		"""Initialize values for TabItem Y"""
		self.DataY = ObservableCollection[Grids_Y_VM]()
		self.points_Y = []
		self.ListY.ItemsSource = self.DataY
		self.positionY.Text = "0"
		self.Repeter_y.Value = 0
		self.EspaceY.Text = "0"
		self.libY.SelectedIndex = 0
		"""Initialize values for TabItem Z"""
		self.DataZ = ObservableCollection[Grids_Z_VM]()
		self.points_Z = []
		self.ListZ.ItemsSource = self.DataZ
		self.positionZ.Text = "0.00"
		self.Repeter_z.Value = 0
		self.EspaceZ.Text = "0"
		"""Event to switch between alphabetic and numeric symbols for grids X and Y"""
		self.libX.SelectionChanged += self.libX_SelectionChanged
		self.libY.SelectionChanged += self.libY_SelectionChanged
		
	def libX_SelectionChanged(self, sender, e):
		self.update_labelsX()
		
	def libY_SelectionChanged(self, sender, e):
		self.update_labelsY()
		
	def listX_SelectionChanged(self, sender, e):
		if sender.SelectedItem is not None:
			selected_position = sender.SelectedItem
			self.positionX.Text = selected_position.Position
			
	def listY_SelectionChanged(self, sender, e):
		if sender.SelectedItem is not None:
			selected_position = sender.SelectedItem
			self.positionY.Text = selected_position.Position
			
	def listZ_SelectionChanged(self, sender, e):
		if sender.SelectedItem is not None:
			selected_position = sender.SelectedItem
			self.positionZ.Text = selected_position.Position
			
	def update_labelsX(self):
		for i, item in enumerate(self.DataX):
			item.Label = self.generate_labelX(i + 1)
			
	def update_labelsY(self):
		for i, item in enumerate(self.DataY):
			item.Label = self.generate_labelY(i + 1)
			
	def update_labelsZ(self):
		for i, item in enumerate(self.DataZ):
			item.Label = "Niveau" + self.generate_labelZ(i + 1)
			
	def generate_labelX(self, index):
		"""Generate numeric and alphabetic labels for DataX collection"""
		if self.libX.SelectedIndex == 0:  # Numeric
			return str(index)
		else:
			if index <= 26:  # Alphabetic
				return ascii_uppercase[index - 1]
			else:
				prefix = ascii_uppercase[(index - 1) // 26]
				suffix = ascii_uppercase[(index - 1) % 26]
				return prefix + suffix
				
	def generate_labelY(self, index):
		"""Generate numeric and alphabetic labels for DataY collection"""
		if self.libY.SelectedIndex == 0:  # Numeric
			return str(index)
		else:
			if index <= 26:  # Alphabetic
				return ascii_uppercase[index - 1]
			else:
				prefix = ascii_uppercase[(index - 1) // 26]
				suffix = ascii_uppercase[(index - 1) % 26]
				return prefix + suffix
				
	def generate_labelZ(self, index):
		"""Generate labels for DataZ collection"""
		return str(index-1)
			
	def btnX_Click(self, sender, e):
		"""Populate DataX collection"""
		n = self.Repeter_x.Value  # Number of items to add
		start_x = float(self.positionX.Text)
		spacing = float(self.EspaceX.Text)	
		for i in range(n + 1):
			position = start_x + spacing * i	
			if any(item.Position == str(position) for item in self.DataX):
				continue	
			# Generate label based on current selection
			label = self.generate_labelX(len(self.DataX) + 1)
			new_item = Grids_X_VM(label, str(position))	
			# Insert item at the correct position
			insert_index = 0
			for index, item in enumerate(self.DataX):
				if float(item.Position) > position:
					break
				insert_index += 1
			self.DataX.Insert(insert_index, new_item)	
		# Update self.x_position.Text to the last item's position
		if len(self.DataX) > 0:
			last_item = self.DataX[len(self.DataX) - 1]
			self.positionX.Text = last_item.Position
		else:
			self.positionX.Text = "0"
			
	def supprX_Click(self, sender, e):
		"""Remove selected Item in DataX collection"""
		if self.ListX.SelectedItem is not None:
			self.DataX.Remove(self.ListX.SelectedItem)
			self.update_labelsX()
			
	def supprallX_Click(self, sender, e):
		"""Remove all Items in DataX collection"""
		self.DataX.Clear()
		self.positionX.Text = "0"
		
	def btnY_Click(self, sender, e):
		"""Populate DataY collection"""
		n = self.Repeter_y.Value  # Number of items to add
		start_x = float(self.positionY.Text)
		spacing = float(self.EspaceY.Text)	
		for i in range(n + 1):
			position = start_x + spacing * i	
			if any(item.Position == str(position) for item in self.DataY):
				continue	
			# Generate label based on current selection
			label = self.generate_labelY(len(self.DataY) + 1)
			new_item = Grids_Y_VM(label, str(position))	
			# Insert item at the correct position
			insert_index = 0
			for index, item in enumerate(self.DataY):
				if float(item.Position) > position:
					break
				insert_index += 1
			self.DataY.Insert(insert_index, new_item)	
		# Update self.y_position.Text to the last item's position
		if len(self.DataY) > 0:
			last_item = self.DataY[len(self.DataY) - 1]
			self.positionY.Text = last_item.Position
		else:
			self.positionY.Text = "0"
			
	def supprY_Click(self, sender, e):
		"""Remove selected Item in DataY collection"""
		if self.ListY.SelectedItem is not None:
			self.DataY.Remove(self.ListY.SelectedItem)
			self.update_labelsY()
			
	def supprallY_Click(self, sender, e):
		"""Remove all Items in DataY collection"""
		self.DataY.Clear()
		self.positionY.Text = "0"
		
	def btnZ_Click(self, sender, e):
		"""Populate DataZ collection"""
		n = self.Repeter_z.Value  # Number of items to add
		start_x = float(self.positionZ.Text)
		spacing = float(self.EspaceZ.Text)	
		for i in range(n + 1):
			position = start_x + spacing * i	
			if any(item.Position == str(position) for item in self.DataZ):
				continue	
			# Generate label based on current selection
			label = "Niveau " + self.generate_labelZ(len(self.DataZ) + 1)
			new_item = Grids_Z_VM(label, str(position))	
			# Insert item at the correct position
			insert_index = 0
			for index, item in enumerate(self.DataZ):
				if float(item.Position) > position:
					break
				insert_index += 1
			self.DataZ.Insert(insert_index, new_item)	
		# Update self.z_position.Text to the last item's position
		if len(self.DataZ) > 0:
			last_item = self.DataZ[len(self.DataZ) - 1]
			self.positionZ.Text = last_item.Position
		else:
			self.positionZ.Text = "0"
			
	def supprZ_Click(self, sender, e):
		"""Remove selected Item in DataZ collection"""
		if self.ListZ.SelectedItem is not None:
			self.DataZ.Remove(self.ListZ.SelectedItem)
			self.update_labelsZ()
			
	def supprallZ_Click(self, sender, e):
		"""Remove all Items in DataZ collection"""
		self.DataZ.Clear()
		self.positionZ.Text = "0"
		
	def _symbols_have_conflict(self):
		"""Check if the symbols in X and Y are conflicting."""
		if not self.DataX or not self.DataY:
			return False  # No conflict if one is empty
		labels_X = [item.Label for item in self.DataX][0]
		
		labels_Y = [item.Label for item in self.DataY][0]
		return labels_X == labels_Y
		
	def Appliquer_Click(self, sender, e):
		"""Collect and convert input data from the UI"""
		if not self.DataX:
			MessageBox.Show("Veuillez remplir correctement la liste : X")
			return
			
		elif not self.Ey.Text:
			MessageBox.Show("Veuillez entrer une valeur valide pour : ey")
			return
			
		elif not self.DataY:
			MessageBox.Show("Veuillez remplir correctement la liste : Y")
			return
			
		elif not self.Ex.Text:
			MessageBox.Show("Veuillez entrer une valeur valide pour : ex")
			return
			
		elif not self.DataZ:
			MessageBox.Show("Veuillez remplir correctement la liste : Z")
			return
			
		elif self._symbols_have_conflict():
			MessageBox.Show("Les Libellés dans la lists X et la list Y ne doivent pas être identiques.")
			return
		else:
			try:
				self.points_X = _input_to_meters(self.DataX)
				
				self.points_Y = _input_to_meters(self.DataY)
				
				self.points_Z = _input_to_meters(self.DataZ)
				
				self.symbols_X = _populate_symbol(self.DataX)
				
				self.symbols_Y = _populate_symbol(self.DataY)
				
				self.symbols_Z = _populate_symbol(self.DataZ)
				
				self.exstention_X = UnitUtils.ConvertToInternalUnits(float(self.Ex.Text), UnitTypeId.Meters)
				
				self.exstention_Y = UnitUtils.ConvertToInternalUnits(float(self.Ey.Text), UnitTypeId.Meters)
				
			except (ValueError, AttributeError):
				MessageBox.Show("Invalid input. Please correct and try again.")
				
		"""Check center/ do not center option for grids"""
		self.center_Lines = self.CenterLines.IsChecked
		
		self.Close()
		
	def Fermer_Click(self, sender, e):
		self.Close()
		
def _input_to_meters(Pts_list):
	"""Convert Position item from collections to meters."""
	return [UnitUtils.ConvertToInternalUnits(float(item.Position), UnitTypeId.Meters) for item in Pts_list]

def _populate_symbol(Data):
	"""Set symbol lists from collections ."""
	return [item.Label for item in Data]
	
def create_lines(points_X, points_Y, ex, ey, y_axis=False, center=False):
	"""Create lines along X, Y axis and set center/ do not center option"""
	lines = []
	if center:
		# Calculate the center offset
		center_offset_X = (points_X[-1] - points_X[0]) / 2
		
		center_offset_Y = (points_Y[-1] - points_Y[0]) / 2
		
		# Adjust points to center them
		points_X = [x - center_offset_X for x in points_X]
		
		points_Y = [y - center_offset_Y for y in points_Y]
	if y_axis:
		# Create vertical lines (Y-axis)
		for x in points_X:
			lines.append(Line.CreateBound(XYZ(x, points_Y[0] - ey, 0), XYZ(x, points_Y[-1] + ey, 0)))
	else:
		# Create horizontal lines (X-axis)
		for y in points_Y:
			lines.append(Line.CreateBound(XYZ(points_X[0] - ex, y, 0), XYZ(points_X[-1] + ex, y, 0)))
	return lines
	
def create_grids(lines_x, lines_y, Sx, Sy, doc):
	"""Create grids along X, Y axis """
	with Transaction(doc, "Create Grids") as t:
		t.Start()
		
		grids_X = [Grid.Create(doc, line) for line in lines_x]
		for g, s in zip(grids_X, Sy):
			g.Name = s
			
		grids_Y = [Grid.Create(doc, line) for line in lines_y]
		for g, s in zip(grids_Y, Sx):
			g.Name = s
			
		t.Commit()
		
def create_levels(points_Z, Sz, doc):
	"""Create levels"""
	with Transaction(doc, "Create levels") as t:
		t.Start()
		
		# Collect existing levels and sort by elevation
		levels = sorted(FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Levels)\
				.WhereElementIsNotElementType().ToElements(), key=lambda l: l.Elevation)
				
		for p, s in zip(points_Z, Sz):
			level_found = None
			insert_index = 0
			
			for i, l in enumerate(levels):
				if abs(l.Elevation - p) < 0.001:
					level_found = l
					break
					
				elif p < l.Elevation:
					insert_index = i
					break

			else:
				insert_index = len(levels)
				
			if level_found:
				if level_found.Name != s:
					level_found.Name = s
			else:
				new_level = Level.Create(doc, p)
				new_level.Name = s
				
				levels.insert(insert_index, new_level)
				
			for i in range(insert_index + 1, len(levels)):
				levels[i].Name = Sz[i]
				
		t.Commit()


Grids = MyWindow()
Grids.ShowDialog()

DataX = Grids.DataX
DataY = Grids.DataY
DataZ = Grids.DataZ

ex = Grids.exstention_X
ey = Grids.exstention_Y

center_Lines = Grids.CenterLines

points_X = Grids.points_X
points_Y = Grids.points_Y
points_Z = Grids.points_Z

Sx = Grids.symbols_X
Sy = Grids.symbols_Y
Sz = Grids.symbols_Z
	
lines_x = create_lines(points_X, points_Y, ex, ey, y_axis=False, center=center_Lines)
lines_y = create_lines(points_X, points_Y, ex, ey, y_axis=True, center=center_Lines)

create_grids(lines_x, lines_y, Sx, Sy, doc)
create_levels(points_Z, Sz, doc)
OUT = 0

Thanks.

3 Likes