Voraussetzungen
Das folgende Programm soll unter folgenden Randbedingungen arbeiten:
- Geschossebenen sind parallel zur x-y-Ebene
- Das Modell darf keine geschossübergreifenden Elemente enthalten (z.B. Fassadenfläche über zwei oder mehr Etagen)
- Das Fundament und die erste Decke zählen zum 0. Geschoss
- Volumen werden nicht berücksichtigt
- Es werden nur ebene Flächen verwendet
- Die z-Achse ist in Richtung der Erdbeschleunigung ("nach unten") gerichtet
Theoretische Grundlagen
Aus den Voraussetzungen ergibt sich, dass nur die Geschosshöhen gegeben sein müssen und ein Element, wie ein Knoten, muss dann mit seiner z-Koordinate oberhalb der unteren Geschosshöhe und bis maximal gleich der aktuellen Geschosshöhe liegen.
Der Nutzer muss also eine Reihe von Knoten wählen, welche repräsentativ für die Geschosshöhen sind. Über eine Schleife können die z-Koordinaten und damit die Geschosshöhen ermittelt werden.
Im Folgenden werden allen Knoten anhand der Höhen die Etagen zugewiesen.
Bei Linien kann auf die Etagen der Knoten zurückgegriffen werden. Falls eine Linie an einer Decke beginnt und auf der darüberliegenden Decke endet, soll diese der oberen Decke zugeordnet werden. Die Linie wird also der höchsten Etage zugewiesen, die in Ihren Knoten zu finden ist.
Da Stäbe auf Linien liegen, und da diese davor zugeordnet werden können, hat der Stab die gleiche Etage wie seine Linie.
Bei Flächen verhält es sich ähnlich wie bei den Linien. Es wird die höchste Etage der zugehörigen Knoten ermittelt.
Wenn alle Elemente einer Etage zugeordnet werden konnten, können aus den Listen der Etagen die Bauzustände angelegt werden.
Umsetzung
Als Grundlage für das Programm wurde die Vorlage aus dem NuGet-Paket Dlubal.RFEMWebServiceLibrary verwendet. Wie man dieses installiert, erfahren Sie hier:
https://github.com/Dlubal-Software/Dlubal_CSharp_ClientZuerst wird das aktive Modell, wie folgt, angebunden:
...
#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));
...
Für eine bessere Fehlerbehandlung wird über den gesamten Quellcode in der main-Funktion ein try-catch-Block verwendet. Innerhalb dieses Blocks wird zuerst die Anwendung angebunden, was wiederum in einem try-catch-Block geschieht. Nach erfolgreicher Anbindung der Anwendung wird über die Methode get_active_model das derzeit aktive Modell (im Vordergrund von RFEM 6) angebunden. Falls es hierbei zu Problemen kommt, greift der äußere try-catch-Block.
Da alle Knoten, Linien, Stäbe und Flächen untersucht werden müssen, ist es sinnvoll, alle Elemente davon aus RFEM zu holen. Um die Anzahl der jeweiligen Elemente zu haben, werden zuerst die Objektnummern aller Kategorien ausgelesen. Im Folgenden können dann die jeweiligen Objekte anhand ihrer Nummern übertragen werden:
...
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("");
...
Damit der Nutzer einen Überblick über die Dauer des Vorgangs hat, wird alle 10 Elemente ein "+" in die Konsole geschrieben.
Vor der Übertragung weiterer Elemente werden im nächsten Schritt die Geschosshöhen ermittelt. Dazu müssen zuerst die selektierten Objekte ausgelesen werden. Das übergebene Array (Feld/Vector mit Elementen) vom Typ object_location beinhaltet alle selektierten Objekte mit deren Typ und Nummer. In einer Schleife können so die Knoten herausgefiltert werden. Da alle Knoten bereits verfügbar sind, ist in derselben Schleife eine Ermittlung der z-Koordinaten dieser Knoten möglich. Es gibt dabei eine Schleife, welche durch die selektierten Objekte geht und sobald ein Element vom Typ Knoten ist, wird in den Knoten nach diesem Knoten gesucht und dann dessen z-Koordinate in das Array floor_heights eingetragen. Das Array wird bei jeder gefundenen Koordinate um ein Element verlängert:
...
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;
...
Da die richtige Reihenfolge der z-Koordinaten nicht gegeben ist, wird das Array zunächst nach der Größe sortiert. Die z-Koordinate ist allerdings in negativer Richtung und deshalb muss die Sortierung noch über Reverse umgekehrt werden. Zu Beginn wurde die Festlegung getroffen, dass das Fundament und die erste Geschossdecke als ein Geschoss zählen, daher wird das erste Element in den Geschosshöhen entfernt.
Als nächstes werden die restlichen Elemente wie Linien, Stäbe und Flächen übertragen:
...
// 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("");
...
Für die Einsortierung der Elemente in die einzelnen Geschosse werden zunächst zweidimensionale Listen angelegt. Die Liste nds_floor_numbers hat als erste Dimension die Geschosse und als zweite Dimension die Knotennummern des Geschosses. Die folgende Schleife geht zunächst über alle Knoten und hat eine Unterschleife, welche über alle Geschosshöhen geht. Bei jedem Knoten wird damit von unten im Gebäude nach oben hin geprüft, ob die Z-Koordinate innerhalb der Geschosshöhe liegt. Wegen numerischer Ungenauigkeiten wurde noch eine Toleranz von 1 mm abgezogen:
...
// 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;
}
}
}
...
Sobald ein Knoten im vorgegebenen Bereich liegt, wird diesem als Kommentar die Nummer des Geschosses eingetragen und die Knotennummer der Liste hinzugefügt. An dieser Stelle wäre es optional möglich, den Kommentar auch an RFEM zu übertragen (ist auskommentiert). Der Kommentar wird hier genutzt, da im Folgenden nicht die z-Koordinaten, sondern die Geschossnummer ausgewertet werden soll.
Ersichtlich wird das anhand der Schleife über die Linien:
...
// 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]);
}
...
Die Schleife durchläuft alle Linien und hat eine Unterschleife, welche durch alle Knoten der Linie geht. Innerhalb dieser Unterschleife gibt es eine weitere Schleife durch alle Knoten, um den Knoten zur Knotennummer aus der Linie zu finden. Von diesem Knoten wird die Geschossnummer aus dem Kommentar gelesen.
Wie bereits bei den theoretischen Grundlagen erwähnt, genügt die Ermittlung der höchsten Geschossnummer. Diese wird dann als Geschossnummer im Kommentar der Linie hinterlegt und die Nummer der Linie in der Liste eingefügt.
Die Stäbe erhalten die gleichen Geschossnummern wie die Linien, auf denen sie liegen. Damit wird eine Schleife über alle Stäbe mit Unterschleife über alle Linien benötigt. Sobald die Liniennummer mit der aus dem Stab übereinstimmt, bekommt der Stab seine Geschosshöhe als Kommentar und die Nummer wird der Liste hinzugefügt:
...
// 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]);
}
...
Die Vorgehensweise in den Flächen gleicht der in den Linien. Es gibt eine Schleife über die Flächen mit Unterschleife über deren Berandungslinien. Die höchste Etage aus der Berandungslinie wird die Geschossnummer der Fläche.
Nach dem Einsortieren aller Elemente in die Geschosse müssen die Bauzustände angelegt werden. Für jedes Geschoss wird ein Bauzustand angelegt, daher erfolgt eine Schleife über die Geschosshöhen:
...
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;
}
}
...
Für das Anlegen von Elementen in RFEM wird zusätzlich ein begin_modification/finish_modification Block genutzt, um die Übertragungsgeschwindigkeit zu maximieren. Falls es zu Problemen kommt, schützt ein zusätzlicher try-catch-Block, welcher bei einem Abbruch cancel_modification aufruft und damit die Bearbeitung ordnungsgemäß beendet.
Als wichtiger Hinweis zur Übergabe bleibt noch, dass zu übertragende Elemente wie z.B. continue_on_construction_stage nur dann übertragen werden, wenn die entsprechende "Specified"-Eigenschaft auf "true" gesetzt wurde (continue_on_construction_stageSpecified). Nicht alle Elemente haben eine solche "Specified"-Eigenschaft, aber dort, wo sie vorhanden ist, muss sie berücksichtigt werden.
Zusammenfassung
Durch die Vielseitigkeit der WebService & API Schnittstelle ist es möglich, eine Reihe von manuell aufwändigen Eingaben zu automatisieren. Im konkreten Beispiel wird die Eingabe des Add-Ons Analyse von Bauzuständen automatisiert, welches aber als Platzhalter für viele weitere Add-Ons und Möglichkeiten dient. Damit sind der Kreativität für zeitsparende Lösungen nur wenige Grenzen gesetzt und weitere Beispiele werden folgen.