Requisitos
O seguinte programa é executado nas seguintes condições:
- Os planos dos pisos são paralelos ao plano xy.
- O modelo não pode conter elementos que se extendem por vários pisos (por exemplo, uma superfície de fachada com dois ou mais pisos).
- A fundação e o rés do chão pertencem ao piso zero.
- Os sólidos não são considerados.
- Apenas são utilizadas superfícies planas.
- O eixo z está direccionado na direção da aceleração da gravidade ("para baixo").
Fundamentação teórica
Com base nos requisitos, fica' claro que apenas a altura do piso deve ser especificada, providenciando um elemento, como um nó, com a sua coordenada z acima da altura inferior do piso e com um máximo igual ou superior a altura de piso relevante.
Portanto, o utilizador tem de selecionar uma série de nós que são representativos para a altura do piso. As coordenadas z e, consequentemente, a altura do piso podem ser determinadas através de um ciclo.
A seguir, os pisos são atribuídos a todos os nós através das alturas.
Para as linhas, é possível recorrer aos pisos dos nós. Se uma linha começa num teto e termina no teto que está por cima, deve ser atribuída ao teto superior. Portanto, a linha é atribuída ao piso mais alto que pode ser encontrado nos seus nós.
Uma vez que as barras estão nas linhas e que podem ser atribuídas previamente, a barra tem o mesmo piso que a sua linha.
O mesmo se aplica às superfícies. É determinado o piso mais alto dos nós correspondentes.
Se todos os elementos de um piso tiverem sido atribuídos, as fases de construção podem ser criadas a partir da lista dos pisos.
Implementação
O programa utiliza como base um modelo do pacote NuGet Dlubal.RFEMWebServiceLibrary. Pode descobrir como instalá-lo aqui:
https://github.com/Dlubal-Software/Dlubal_CSharp_ClientPrimeiro, o modelo ativo é ligado da seguinte forma:
...
#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));
...
Para um melhor tratamento de erros, é utilizado um bloco "try-catch" em todo o código-fonte na função principal. Dentro deste bloco, a aplicação é ligada primeiro, o que é feito novamente num blocotry-catch. Após a ligação bem-sucedida da aplicação, o modelo atualmente ativo (em primeiro plano no RFEM 6) é ligado através do método get_active_model. Se ocorrer algum problema, o bloco "try-catch" externo entra em ação.
Uma vez que todos os nós, linhas, barras e superfícies têm de ser analisados, faz sentido obter todos os elementos do RFEM. Para obter o número dos respetivos elementos, são lidos primeiro os números dos objetos de todas as categorias. A seguir, os respetivos objetos podem depois ser transferidos através dos seus números:
...
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[] 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("");
...
Para dar ao utilizador uma visão geral da duração do processo, é escrito um "+" na consola a cada 10 elementos.
Antes de transferir os outros elementos, a altura do piso é determinada no próximo passo. Primeiro, tem de ler os objetos selecionados. A matriz transferida (campo/vetor incluindo elementos) é do tipo local_objeto e contém todos os objetos selecionados com o seu tipo e número. Desta forma, os nós podem ser filtrados num ciclo. Uma vez que todos os nós já estão disponíveis, é possível determinar as coordenadas z desses nós no mesmo ciclo. Segue-se um ciclo através dos objetos selecionados e, assim que um elemento é do tipo de nó, é realizada uma pesquisa entre os nós para este nó e a sua coordenada z é introduzida na matriz andar_alturas. A matriz é aumentada por um elemento para cada coordenada encontrada:
...
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;
...
Uma vez que as coordenadas z não estão na ordem correta, a matriz é primeiro ordenada por tamanho. A coordenada z, no entanto, está na direção negativa e, portanto, a ordenação tem de ser invertida através de Inverter. Inicialmente, foi definido que a fundação e o rés do chão correspondem a um piso, razão pela qual o primeiro elemento na altura dos pisos é removido.
Num próximo passo, os elementos restantes, tais como linhas, barras e superfícies são transferidos:
...
// 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("");
...
Para ordenar os elementos em pisos individuais, são criadas primeiro listas bidimensionais. A lista nds_flow_numbers tem os pisos como primeira dimensão e os números dos nós do piso como segunda dimensão. Em primeiro lugar, o loop seguinte percorre todos os nós e tem um sub-loop que percorre toda a altura dos pisos. Desta forma, é verificado para cada nó a partir da parte inferior no edifício até ao topo se a coordenada Z se encontra dentro da altura do piso. Devido a imprecisões numéricas, foi subtraída uma tolerância de 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;
}
}
}
...
Assim que um nó estiver dentro da região especificada, o número do piso é introduzido como comentário e o número do nó é adicionado à lista. Neste momento, também seria possível transferir o comentário para o RFEM (comentado). A razão pela qual o comentário é utilizado é porque a seguir não são as coordenadas z que devem ser avaliadas, mas sim o número do piso. Isto é evidente quando observamos o ciclo sobre as linhas:
...
// 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;
}
}
}
}
lns[i].comment = floor_max.ToString();
lns_floor_numbers[floor_max].Add(lns[i].no);
//model.set_line(lns[i]);
}
...
O loop passa por todas as linhas e tem um sub-loop, que passa por todos os nós da linha. Dentro deste sub-loop, existe outro loop a percorrer todos os nós para encontrar o nó que corresponde ao número do nó a partir da linha. Fora deste nó, é lido o número do piso a partir do comentário. Conforme já mencionado nas bases teóricas, basta determinar o número do piso mais alto. Este número é então armazenado como número do piso no comentário da linha e o número da linha é inserido na lista.
As barras recebem os mesmos números de piso das linhas nas quais se encontram. Assim, é necessário um ciclo sobre todas as barras com um sub- ciclo sobre todas as linhas. Assim que o número da linha corresponder ao número da barra, a barra obtém a altura do piso como um comentário e o número é adicionado à lista:
...
// 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]);
}
...
O procedimento nas superfícies é idêntico ao das linhas. Existe um ciclo sobre as superfícies com o ciclo parcial sobre as suas linhas de contorno. O piso mais alto a partir da linha de contorno torna-se o número do piso da superfície.
Após ordenar todos os elementos pelos pisos, é necessário criar as fases de construção. É criada uma fase de construção para cada piso, pelo que é feito um loop sobre a altura do piso:
...
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. Se surgirem problemas, um bloco "try-catch" adicional providencia proteção, o qual chama cancel_modification no caso de uma interrupção, terminando assim o processamento corretamente. Uma nota importante sobre a transferência é que elementos a serem transferidos, tais como continue_on_construction_stage, só podem ser transferidos se a correspondente propriedade "Specified" tiver sido definida como "true" ( continue_on_construction_stageSpecified). Nem todos os elementos têm esta propriedade "especificada", mas deve ser considerada, caso esteja disponível.
Conclusão
Devido à versatilidade da interface Serviço web e API, é possível automatizar um número de entradas que seriam muito demoradas se fossem feitas manualmente. No presente exemplo, a entrada para o módulo Análise das fases de construção é automatizada, o que também serve como marcador de posição para muitos outros módulos e opções. Assim sendo, existem apenas alguns limites para a sua criatividade para soluções que poupam tempo e seguirão-se muitos exemplos.