911x
001825
9.8.2023

Webová služba & API pro Analýzu fází výstavby

Při výpočtu pravidelných konstrukcí není často zadání složité, je ale časově náročné. Automatizace zadávání pak může ušetřit drahocenný čas. V předkládaném případě je úkolem uvažovat podlaží domu jako jednotlivé fáze výstavby. Zadání bude provedeno pomocí programu C#, aby uživatel nemusel ručně zadávat prvky jednotlivých podlaží.

Předpoklady

Následující program pracuje za následujících podmínek:

  • Roviny podlaží jsou rovnoběžné s rovinou xy.
  • Model nesmí obsahovat žádné prvky, které souvisejí s více podlažími (např. plocha fasády přes dvě nebo více podlaží).
  • Základ a první deska patří do nultého podlaží.
  • Tělesa nejsou uvažována.
  • Používají se pouze rovinné plochy.
  • Osa z směřuje ve směru tíhového zrychlení („dolů“).

Teoretické pozadí

Z předpokladů je zřejmé, že je třeba zadat pouze výšky podlaží a prvek, například uzel, musí souřadnicí z ležet nad dolní deskou podlaží a maximálně do výšky příslušného podlaží.
Uživatel proto musí nejprve vybrat řadu uzlů, které reprezentují výšku podlaží. Souřadnice z a tím i výšky podlaží lze stanovit pomocí cyklu.
Následně se všechny uzly přiřadí podlažím podle výšek.
Při zadání linií je možné použít k určení podlaží uzlů. Pokud linie začíná na dolní desce a končí na desce nad ní, měla by být přiřazena k horní desce. Linie je tak přiřazena k nejvyššímu podlaží, které lze najít u vašich uzlů.
Vzhledem k tomu, že pruty leží na liniích, a protože je lze předem přiřadit, patří prut ke stejnému podlaží jako jeho linie.
Totéž platí pro plochy. Stanoví se nejvyšší podlaží příslušných uzlů.
Pokud byly přiřazeny všechny prvky podlaží, lze fáze výstavby vytvořit ze seznamů podlaží.

Realizace

Program je založen na šabloně z balíčku NuGet Dlubal.RFEMWebServiceLibrary. Zde se dozvíte, jak ji nainstalovat:

Nejdříve se aktivní model napojí následujícím způsobem:


            

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

    }
    catch (Exception exception)
    {
        if (application != null)
        {
            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));
...


Pro lepší ošetření chyb se ve funkci main používá ve zdrojovém kódu blok try-catch. V tomto bloku se nejdříve připojí aplikace, což se opět provádí v bloku try-catch. Po úspěšném připojení aplikace je aktuálně aktivní model (aktuální v programu RFEM 6) připojen pomocí metody get_active_model. Pokud se vyskytnou problémy, použije se vnější blok try-catch.
Protože je třeba analyzovat všechny uzly, linie, pruty a plochy, dává smysl získat všechny prvky z programu RFEM. Pro získání informace o počtu příslušných prvků se nejdříve načtou čísla objektů všech kategorií. Následně lze příslušné objekty přenést pomocí jejich čísel:


            

...
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 měl uživatel přehled o době trvání procesu, je v konzole vypsáno "+" pro každých 10 prvků.
Před přenosem dalších prvků se v dalším kroku stanoví výšky podlaží. Nejdříve je třeba načíst vybrané objekty. Přenesené pole (pole/vektor s prvky) typu object_location obsahuje všechny vybrané objekty s jejich typem a číslem. Tímto způsobem lze uzly vyfiltrovat pomocí cyklu. Vzhledem k tomu, že všechny uzly jsou již k dispozici, je možné v tomto cyklu stanovit souřadnice z těchto uzlů. Vybrané objekty procházejí cyklem, a jakmile je nalezen prvek typu uzel, vyhledá se tento uzel mezi uzly a poté se do pole floor_heights zadá jeho souřadnice z. Pole se rozšíří o prvek pro každou nalezenou souřadnici:


            

...
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 = null;
floor_heights = tmp_arr;
...


Vzhledem k tomu, že souřadnice z nejsou ve správném pořadí, seřadí se pole nejdříve podle velikosti. Souřadnice z je však v záporném směru, a proto je třeba řazení obrátit pomocí funkce Reverse. Na začátku jsem předpokládali, že základ a deska prvního podlaží se počítají jako jedno podlaží, proto se odstraní první prvek ve výškách podlaží.
V dalším kroku se přenesou zbývající prvky, jako jsou linie, pruty a plochy:


            

...
//  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("");
...


Pro rozřazení prvků do jednotlivých podlaží se nejdříve vytvoří dvourozměrné seznamy. Seznam nds_floor_numbers obsahuje jako první rozměr podlaží a jako druhý rozměr čísla uzlů podlaží. Následující cyklus nejdříve prochází všechny uzly a má dílčí cyklus, který prochází všemi výškami podlaží. Tímto způsobem se pro každý uzel v budově odspodu nahoru ověří, zda souřadnice Z leží v dané výšce podlaží. Kvůli numerickým nepřesnostem se ještě odečte tolerance 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;
		}
	}
}
...


Pokud uzel leží v zadané oblasti, zapíše se k němu jako komentář číslo podlaží a číslo uzlu se doplní do seznamu. V tomto okamžiku by bylo možné také přenést komentář do programu RFEM (je zakomentováno). Důvodem pro použití komentáře je, že v následujícím textu se nevyhodnocují souřadnice z, ale čísla podlaží. To je zřejmé při pohledu na cyklus přes linie:


            

...
//  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]);

}
...


Cyklus prochází všechny linie a má dílčí cyklus, který prochází všemi uzly linie. Uvnitř tohoto dílčího cyklu prochází další cyklus všemi uzly, aby se našel uzel odpovídající číslu uzlu z linie. Z tohoto uzlu se načte číslo podlaží z komentáře. Jak již bylo zmíněno v teoretických základech, stačí stanovit nejvyšší číslo podlaží. Toto číslo se pak uloží jako číslo podlaží v komentáři k linii a číslo linie se vloží do seznamu.
Pruty mají stejná čísla podlaží jako linie, na kterých leží. Proto je zapotřebí cyklus přes všechny pruty s dílčím cyklem přes všechny linie. Jakmile se číslo linie shoduje s číslem prutu, dostane prut svou výšku podlaží jako komentář a číslo se přidá do seznamu:


            

...
//  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]);

}
...


U ploch se postupuje podobně jako u linií. Nad plochami je cyklus s dílčím cyklem přes jejich hraniční linie. Nejvyšší podlaží z hraničních linií se stává číslem podlaží plochy.
Po roztřídění všech prvků do podlaží je třeba vytvořit fáze výstavby. Pro každé podlaží se vytvoří fáze výstavby, takže se vytvoří cyklus přes výšky podlaží:


            

...
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;
	}
}
...


Pro vytváření prvků v programu RFEM se ještě používá blok begin_modification/finish_modification, aby se maximalizovala přenosová rychlost. Pokud se vyskytnou problémy, poskytuje ochranu přídavný blok try-catch, který v případě přerušení zavolá cancel_modification, čímž se zpracování řádně ukončí. Ještě jedna důležitá poznámka k přenosu: prvky, které se mají přenášet, jako například continue_on_construction_stage, lze přenést pouze tehdy, pokud byla příslušná vlastnost "Specified" nastavena na "true" (continue_on_construction_stageSpecified). Ne všechny prvky mají tuto vlastnost "Specified", ale pokud je k dispozici, je třeba ji zohlednit.

Shrnutí

Díky všestrannosti rozhraní webových služeb a API je možné automatizovat řadu zadání, která by byla při ručním provádění časově náročná. V našem příkladu je automatizováno zadání pro addon Analýza fází výstavby, které ale také může sloužit jako vzor pro mnoho dalších addonů a možností. Vaší kreativitě se tak u časově úsporných řešení meze nekladou a další příklady budou následovat.


Autor

Ing. Günthel zajišťuje technickou podporu zákazníkům.

Stahování