1006x
001825
2023-08-09

WebService i API do analizy etapów budowy

Jeśli chcesz obliczyć zwykłe konstrukcje, wprowadzanie danych często nie jest skomplikowane, ale jest po prostu czasochłonne. Dzięki automatycznemu wprowadzaniu danych można zaoszczędzić cenny czas. W niniejszym przypadku należy uwzględnić kondygnacje domu jako poszczególne etapy budowy. Dane są wprowadzane przy pomocy programu w języku C#, aby użytkownik nie musiał ręcznie wprowadzać elementów poszczególnych pięter.

Wymagania

Poniższy program działa pod następującymi warunkami:

  • Płaszczyzny kondygnacji są równoległe do płaszczyzny xy.
  • Model nie może zawierać elementów rozciągających się na kilka kondygnacji (np. powierzchnia elewacji rozciągająca się na dwie lub więcej kondygnacji).
  • Fundament i pierwsze piętro należą do kondygnacji zerowej.
  • Bryły nie są uwzględniane.
  • Używane są tylko powierzchnie płaskie.
  • Oś z jest skierowana w kierunku przyspieszenia ziemskiego („w dół”).

Podstawy teoretyczne

Z założeń jasno wynika, że należy określić tylko wysokości kondygnacji, a element, na przykład węzeł, musi znajdować się nad wysokością dolnej kondygnacji we współrzędnej z i to maksymalnie do wysokości odpowiedniej kondygnacji.
Dlatego użytkownik musi wybrać grupę węzłów reprezentatywnych dla wysokości kondygnacji. Współrzędne z, a tym samym wysokości kondygnacji, można określić za pomocą pętli.
Poniżej przypisano kondygnacje do wszystkich węzłów za pomocą wysokości.
W przypadku linii można cofnąć się do kondygnacji węzłów. Jeżeli linia zaczyna się na stropie, a kończy na stropie leżącym powyżej, należy ją przypisać do stropu górnego. Linia zostaje zatem przypisana do najwyższej kondygnacji, jaką można znaleźć w węzłach.
Ponieważ pręty leżą na liniach i mogą być wcześniej przydzielane, pręt ten ma taką samą kondygnację, jak jego linia.
To samo dotyczy powierzchni. Określana jest najwyższa kondygnacja z odpowiednich węzłów.
Jeżeli wszystkie elementy kondygnacji zostały już przypisane, etapy budowy można utworzyć z list kondygnacji.

Aplikacja

Program wykorzystuje szablon z pakietu NuGet Dlubal.RFEMWebServiceLibrary jako podstawę. Tutaj dowiesz się, jak go zainstalować:

Najpierw aktywny model jest łączony w następujący sposób:


            

...
#region Application Settings
try
{
    application_information ApplicationInfo;
    try
    {
        // connects to RFEM6 or RSTAB9 application
        application = new ApplicationClient(Binding, Address);

    }
    catch (Exception exception)
    {
        if (application != zero)
        {
            if (application.State != CommunicationState.Faulted)
            {
                application.Close();
            }
            else
            {
                application.Abort();
            }

            application = null;
        }
    }
    finally
    {
        ApplicationInfo = application.get_information();
        Console.WriteLine("Name: {0}, Version:{1}, Type: {2}, language: {3} ", ApplicationInfo.name, ApplicationInfo.version, ApplicationInfo.type, ApplicationInfo.language_name);
    }
    #endregion

    // get active model
    string modelUrl = application.get_active_model();

    // connects to RFEM6/RSTAB9 model
    ModelClient model = new ModelClient(Binding, new EndpointAddress(modelUrl));
...


Dla lepszej obsługi błędów, w kodzie źródłowym w głównej funkcji zastosowano blok try-catch. W ramach tego bloku aplikacja jest połączona jako pierwsza, co ponownie odbywa się w bloku try-catch. Po pomyślnym połączeniu aplikacji, aktualnie aktywny model (na pierwszym planie programu RFEM 6) jest podłączany za pomocą metody get_active_model. W przypadku wystąpienia problemów zaczyna działać zewnętrzny blok try-catch.
Ponieważ wszystkie węzły, linie, pręty i powierzchnie muszą zostać przeanalizowane, sensowne jest pobranie wszystkich elementów z programu RFEM. Aby uzyskać numery odpowiednich elementów, najpierw odczytywane są numery obiektów wszystkich kategorii. W poniższym przykładzie odpowiednie obiekty można przenieść za pomocą ich numerów:


            

...
int[] node_nums = model.get_all_object_numbers(object_types.E_OBJECT_TYPE_NODE, 0);
int[] line_nums = model.get_all_object_numbers(object_types.E_OBJECT_TYPE_LINE, 0);
int[] member_nums = model.get_all_object_numbers(object_types.E_OBJECT_TYPE_MEMBER, 0);
int[] surface_nums = model.get_all_object_numbers(object_types.E_OBJECT_TYPE_SURFACE, 0);

//  get all nodes
Console.WriteLine("1. Get all nodes:");
node[] nds = new node[node_nums.Length];
for (int i = 0; i < node_nums.Length; ++i)
{
	nds[i] = model.get_node(node_nums[i]);
	if ((i%10)==0)
		Console.Write("+");

}
Console.WriteLine("");
...


Aby użytkownik miał podgląd na czas trwania procesu, co 10 elementów w konsoli wyświetlany jest znak "+".
Przed przeniesieniem innych elementów w kolejnym kroku określane są wysokości kondygnacji. Najpierw należy odczytać wybrane obiekty. Przekazywana macierz (pole/wektor z elementami) typu object_location zawiera wszystkie wybrane obiekty wraz z ich typem i numerem. W ten sposób węzły można wyfiltrować w pętli. Ponieważ wszystkie węzły są już dostępne, współrzędne z tych węzłów można wyznaczyć w tej samej pętli. Przez wybrane obiekty przechodzi pętla, a jeżeli element jest elementem typu węzeł, węzeł ten jest wyszukiwany w węzłach, a następnie jego współrzędna z jest wprowadzana do macierzy floor_heights. Dla każdej znalezionej współrzędnej dodawany jest jeden element do macierzy:


            

...
Console.WriteLine("2. Get selected nodes");
//  get all selected objects
object_location[] obj_locs = model.get_all_selected_objects();

//  get all selected node numbers
double[] floor_heights = new double[1];
foreach (object_location obj in obj_locs)
{
	if (obj.type == object_types.E_OBJECT_TYPE_NODE)
	{
		for (int i = 0; i < nds.Length; ++i)
		{
			if (nds[i].no == obj.no)
			{
				floor_heights[floor_heights.Length - 1] = nds[i].coordinate_3;
				Array.Resize(ref floor_heights, floor_heights.Length + 1);
				break;
			}
		}
	}
}
Array.Resize(ref floor_heights, floor_heights.Length - 1);

//  sort array
//  z-axis is negative, most positive value is ground
Array.Sort(floor_heights);
Array.Reverse(floor_heights);
//  ground and first level are one, remove first entry
double[] tmp_arr = new double[floor_heights.Length - 1];
Array.Copy(floor_heights, 1, tmp_arr, 0, floor_heights.Length - 1);

floor_heights = zero;
floor_heights = tmp_arr;
...


Ponieważ współrzędne z nie są w prawidłowej kolejności, szyk jest najpierw sortowany według rozmiaru. Współrzędna z znajduje się jednak w kierunku ujemnym, dlatego sortowanie musi zostać odwrócone za pomocą opcji Odwrócić. Wstępnie ustalono, że fundament i pierwsze piętro stanowią jedną kondygnację, w związku z czym usuwany jest pierwszy element w wysokości  kondygnacji.
W kolejnym kroku przenoszone są pozostałe elementy, takie jak linie, pręty i powierzchnie:


            

...
//  get all lines
Console.WriteLine("3. Get all lines:");
line[] lns = new line[line_nums.Length];
for (int i = 0; i < line_nums.Length; ++i)
{
	lns[i] = model.get_line(line_nums[i]);
	if ((i % 10) == 0)
		Console.Write("+");
}
Console.WriteLine("");


//  get all members
Console.WriteLine("4. Get all members:");
member[] mems = new member[member_nums.Length];
for (int i = 0; i < member_nums.Length; ++i)
{
	mems[i] = model.get_member(member_nums[i]);
	if ((i % 10) == 0)
		Console.Write("+");
}
Console.WriteLine("");

//  get all surfaces
Console.WriteLine("5. Get all surfaces:");
surface[] srfs = new surface[surface_nums.Length];
for (int i = 0; i < surface_nums.Length; ++i)
{
	srfs[i] = model.get_surface(surface_nums[i]);
	if ((i % 10) == 0)
		Console.Write("+");
}
Console.WriteLine("");
...


Aby posortować elementy na poszczególne kondygnacje, najpierw tworzone są dwuwymiarowe listy. Lista nds_floor_numbers obejmuje kondygnacje jako pierwszy wymiar, a numery węzłów kondygnacji jako drugi wymiar. Po pierwsze, następująca pętla przebiega przez wszystkie węzły i posiada podpętlę, która przebiega przez wszystkie wysokości kondygnacji. Dla każdego węzła program sprawdza, od dołu w górę budynku, czy współrzędna Z leży na wysokości kondygnacji. Ze względu na niedokładności numeryczne odjęto tolerancję 1 mm:


            

...
//  loop through nodes and set their floor
Console.WriteLine("6. Loop through nodes and get their floor");
for (int i = 0; i < nds.Length; ++i)
{
	for (int j = 0; j < floor_heights.Length; ++j)
	{
		if (nds[i].coordinate_3 >= floor_heights[j] - 0.001)
		{
			nds[i].comment = j.ToString();
			//Console.WriteLine("node " + nds[i] + " floor " + j + ";" + nds[i].coordinate_3 + ";" + (floor_heights[j] - 0.001));
			nds_floor_numbers[j].Add(nds[i].no);
			//model.set_node(nds[i]);
			break;
		}
	}
}
...


Jeżeli węzeł znajduje się we wskazanym obszarze, numer kondygnacji jest wprowadzany jako komentarz do niego, a numer węzła zostaje dodany do listy. W tym miejscu możliwe byłoby również przeniesienie komentarza do programu RFEM (skomentowany). Powodem zastosowania tego komentarza jest to, że w analizowanym punkcie nie są uwzględniane współrzędne z, lecz numer kondygnacji. Jest to widoczne, gdy spojrzymy na pętlę nad liniami:


            

...
//  loop through lines, get their nodes and set their floor
Console.WriteLine("7. Loop through lines and get their floor");
for (int i = 0; i < lns.Length; ++i)
{
	//  get nodes of line
	int[] ln_node_nums = lns[i].definition_nodes;
	Int32 floor_max = 0;
	//  loop through nodes of line
	for (int j = 0; j SMALLERTHAN ln_node_nums.Length; ++j)
	{
		//  loop through nodes
		for (int l = 0; l SMALLERTHAN nds.Length; ++l)
		{
			if (nds[l].no == ln_node_nums[j])
			{
				Int32 floor = Int32.Parse(nds[l].comment);
				if (floor > floor_max)
				{
					floor_max = floor;
					break;
				}
			}
		}

	}
	//  enter maxiumum floor in line
	lns[i].comment = floor_max.ToString();
	lns_floor_numbers[floor_max].Add(lns[i].no);
	//model.set_line(lns[i]);

}
...


Pętla przebiega przez wszystkie linie i posiada podpętlę, która przebiega przez wszystkie węzły linii. W ramach tej podpętli przez wszystkie węzły przebiega kolejna pętla, w której znajduje się węzeł pasujący do numeru węzła linii. Z tego węzła odczytywany jest numer kondygnacji z komentarza. Jak już wspomniano w podstawach teorii, wystarczy określić najwyższy numer kondygnacji. Numer ten zostaje następnie zapisany jako numer kondygnacji w komentarzu do linii, a numer linii zostaje wstawiony na listę.
Pręty otrzymują takie same numery kondygnacji, jak linie, na których leżą. Z tego względu wymagana jest pętla na wszystkich prętach z podpętlą na wszystkich liniach. Jeżeli numer linii odpowiada numerowi pręta, wysokość pręta zostaje oznaczona w komentarzu do wysokości kondygnacji, a numer zostaje dodany do listy:


            

...
//  loop through members, get their line and set their floor
Console.WriteLine("8. Loop through members and get their floor");
for (int i = 0; i < mems.Length; ++i)
{
	//  get line number of member
	int mem_ln_num = mems[i].line;

	//  loop through lines
	for (int j = 0; j < lns.Length; ++j)
	{
		if (lns[j].no == mem_ln_num)
		{
			mems[i].comment = lns[j].comment;
			mems_floor_numbers[Int32.Parse(lns[j].comment)].Add(mems[i].no);
			break;
		}
	}

}
//  loop through surfaces, get their lines and set their floor
Console.WriteLine("9. Loop through surfaces and get their floor");
for (int i = 0; i < srfs.Length; ++i)
{
	//  get lines of surface
	int[] srf_line_nums = srfs[i].boundary_lines;
	Int32 floor_max = 0;
	//  loop through lines of surface
	for (int j = 0; j < srf_line_nums.Length; ++j)
	{
		//  loop through lines
		for (int l = 0; l SMALLERTHAN lns.Length; ++l)
		{
			if (lns[l].no == srf_line_nums[j])
			{
				Int32 floor = Int32.Parse(lns[l].comment);
				if (floor > floor_max)
				{
					floor_max = floor;
					break;
				}
			}
		}

	}
	//  enter maxiumum floor in surface
	srfs[i].comment = floor_max.ToString();
	srfs_floor_numbers[floor_max].Add(srfs[i].no);

	//model.set_surface(srfs[i]);

}
...


Postępowanie w przypadku powierzchni jest podobne jak w przypadku linii. Nad powierzchniami poprowadzona jest pętla, a podpętla nad ich liniami granicznymi. Najwyższa kondygnacja za linią graniczną staje się numerem kondygnacji powierzchni.
Po posortowaniu wszystkich elementów na kondygnacje należy utworzyć etapy budowy. Dla każdej kondygnacji tworzony jest etap budowy, a zatem pętla jest wykonywana nad wysokościami kondygnacji:


            

...
Console.WriteLine("10. Set construction stages");
try
{
	model.begin_modification("set construction stages");
	//  create construction stages
	for (int i = 0; i < floor_heights.Length; ++i)
	{
		construction_stage cstg = new construction_stage();

		cstg.no = i + 1;
		cstg.continue_on_construction_stage = i;
		cstg.continue_on_construction_stageSpecified = true;

		cstg.are_members_enabled_to_modify = true;
		cstg.are_members_enabled_to_modifySpecified = true;
		cstg.added_members = mems_floor_numbers[i].ToArray();
		cstg.active_members = cstg.added_members;

		cstg.are_surfaces_enabled_to_modify = true;
		cstg.are_surfaces_enabled_to_modifySpecified = true;
		cstg.added_surfaces = srfs_floor_numbers[i].ToArray();
		cstg.active_surfaces = cstg.added_surfaces;

		cstg.name = "floor " + i;
		cstg.user_defined_name_enabled = true;
		cstg.user_defined_name_enabledSpecified = true;

		model.set_construction_stage(cstg);

	}
}
catch (Exception exception)
{
	model.cancel_modification();
	Console.WriteLine("Something happen when creation of geometry" + exception.Message);
	throw;
}
finally
{
	try
	{
		model.finish_modification();
	}
	catch (Exception exception)
	{
		Console.WriteLine("Something happen when finishing creation of geometry" + exception.Message);
		throw;
	}
}
...


W przypadku tworzenia elementów w programie RFEM, dodatkowo wykorzystywany jest blok begin_modification/Finish_modification, aby zmaksymalizować prędkość transferu. W przypadku wystąpienia problemów, dodatkowy blok try-catch zapewnia ochronę, która wywołuje cancel_modification w przypadku zerwania połączenia, co powoduje prawidłowe zakończenie przetwarzania. Ważną informacją dotyczącą przenoszenia danych jest to, że elementy, które mają zostać przeniesione, takie jak continue_on_construction_stage, mogą być przenoszone tylko wtedy, gdy odpowiadająca im właściwość „Specified” ma wartość „true” (continue_on_construction_stageSpecified). Nie wszystkie elementy mają taką właściwość "Specified", ale jeśli jest ona dostępna, należy ją uwzględnić.

Podsumowanie

Ze względu na wszechstronność interfejsu WebService i API, możliwe jest zautomatyzowanie wielu wpisów, które byłyby czasochłonne w przypadku ręcznego wprowadzania. W omawianym przykładzie automatycznie wprowadzane są dane dla rozszerzenia Analiza etapów budowy, które służy również jako obiekt zastępczy dla wielu innych rozszerzeń i opcji. Kreatywność jest więc nieograniczona, jeśli chodzi o rozwiązania oszczędzające czas, a więcej przykładów pojawi się w przyszłości.


Autor

Pan Günthel zapewnia wsparcie techniczne klientom firmy Dlubal Software i zajmuje się ich zapytaniami.

Pobrane