milcapy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. milcapy/__init__.py +29 -0
  2. milcapy/analysis/__init__.py +0 -0
  3. milcapy/analysis/linear_static.py +392 -0
  4. milcapy/analysis/manager.py +101 -0
  5. milcapy/core/__init__.py +0 -0
  6. milcapy/core/node.py +94 -0
  7. milcapy/core/results.py +256 -0
  8. milcapy/elements/CST.py +137 -0
  9. milcapy/elements/MQ6.py +330 -0
  10. milcapy/elements/MQ6I.py +246 -0
  11. milcapy/elements/MQ6IMod.py +40 -0
  12. milcapy/elements/__init__.py +0 -0
  13. milcapy/elements/member.py +390 -0
  14. milcapy/elements/truss.py +133 -0
  15. milcapy/loads/__init__.py +9 -0
  16. milcapy/loads/load.py +1100 -0
  17. milcapy/loads/load_pattern.py +419 -0
  18. milcapy/material/__init__.py +0 -0
  19. milcapy/material/material.py +68 -0
  20. milcapy/model/__init__.py +0 -0
  21. milcapy/model/model.py +1482 -0
  22. milcapy/plotter/UIdisplay.py +926 -0
  23. milcapy/plotter/__init__.py +0 -0
  24. milcapy/plotter/load.py +454 -0
  25. milcapy/plotter/options.py +450 -0
  26. milcapy/plotter/plotter.py +2115 -0
  27. milcapy/plotter/plotter_values.py +219 -0
  28. milcapy/plotter/suports.py +322 -0
  29. milcapy/plotter/utils.py +103 -0
  30. milcapy/plotter/widgets.py +569 -0
  31. milcapy/postprocess/CST_pp.py +55 -0
  32. milcapy/postprocess/__init__.py +0 -0
  33. milcapy/postprocess/membranebQ6.py +7 -0
  34. milcapy/postprocess/post_processing.py +220 -0
  35. milcapy/postprocess/segment_member.py +406 -0
  36. milcapy/report/__init__.py +0 -0
  37. milcapy/report/report.py +26 -0
  38. milcapy/section/__init__.py +0 -0
  39. milcapy/section/section.py +304 -0
  40. milcapy/utils/__init__.py +24 -0
  41. milcapy/utils/element.py +283 -0
  42. milcapy/utils/geometry.py +106 -0
  43. milcapy/utils/types.py +96 -0
  44. milcapy-0.1.0.dist-info/METADATA +627 -0
  45. milcapy-0.1.0.dist-info/RECORD +47 -0
  46. milcapy-0.1.0.dist-info/WHEEL +5 -0
  47. milcapy-0.1.0.dist-info/top_level.txt +1 -0
milcapy/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ from .model.model import SystemMilcaModel
2
+ from .utils.types import (
3
+ BeamTheoriesType,
4
+ CoordinateSystemType,
5
+ DirectionType,
6
+ StateType,
7
+ LoadType,
8
+ FieldTypeMembrane,
9
+ ConstitutiveModel,
10
+ )
11
+
12
+ class SystemModel(SystemMilcaModel):
13
+ """
14
+ Class to create a model for the MILCA software.
15
+
16
+ Attributes:
17
+ model (SystemMilcaModel): Instance of the SystemMilcaModel class.
18
+ """
19
+
20
+ def __init__(self):
21
+ super().__init__()
22
+
23
+ def model_viewer(
24
+ model: SystemMilcaModel
25
+ ):
26
+ """
27
+ Muestra la interfaz gráfica para visualizar el modelo.
28
+ """
29
+ model.show()
File without changes
@@ -0,0 +1,392 @@
1
+ from typing import TYPE_CHECKING, Tuple, Dict
2
+ import numpy as np
3
+ import time
4
+ from milcapy.loads.load import PrescribedDOF
5
+
6
+ if TYPE_CHECKING:
7
+ from milcapy.model.model import SystemMilcaModel
8
+
9
+ class LinearStaticAnalysis:
10
+ """
11
+ Solucionador basado en el Método de Rigidez Directa.
12
+ Resuelve sistemas de ecuaciones estructurales K*u = F usando métodos de solución directa.
13
+ Usa numpy.linalg.solve(K, F)
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ model: "SystemMilcaModel",
19
+ ) -> None:
20
+ """
21
+ Inicializa el solucionador con el modelo y el método de solución.
22
+
23
+ Args:
24
+ model: Modelo estructural que contiene K_global y F_global.
25
+ """
26
+ self.model = model
27
+
28
+ # Rendimiento y diagnóstico
29
+ self.solution_time = 0.0
30
+ self.assembly_time = 0.0
31
+
32
+
33
+ def _dof_map(
34
+ self,
35
+ ) -> Tuple[np.ndarray, np.ndarray, Dict[int, float]]:
36
+ """Mapea los grados de libertad del modelo para trabajar con numpy.
37
+ [0, 1, 2, ] pertenece al nodo 1 y así sucesivamente."""
38
+ nn = len(self.model.nodes)
39
+ restraints = np.zeros(nn * 3, dtype=bool)
40
+
41
+ prescribed_dofs = {} # {dof: value}
42
+
43
+ for node in self.model.nodes.values():
44
+ preDOF: PrescribedDOF = node.prescribed_dofs.get(self.model.current_load_pattern, PrescribedDOF())
45
+
46
+ restraints[node.dofs[0] - 1] = node.restraints[0] #or preDOF.ux is not None
47
+ restraints[node.dofs[1] - 1] = node.restraints[1] #or preDOF.uy is not None
48
+ restraints[node.dofs[2] - 1] = node.restraints[2] #or preDOF.rz is not None
49
+
50
+ if preDOF.ux is not None:
51
+ prescribed_dofs[node.dofs[0] - 1] = preDOF.ux
52
+ if preDOF.uy is not None:
53
+ prescribed_dofs[node.dofs[1] - 1] = preDOF.uy
54
+ if preDOF.rz is not None:
55
+ prescribed_dofs[node.dofs[2] - 1] = preDOF.rz
56
+
57
+ free_dofs = np.where(~restraints)[0]
58
+ restrained_dofs = np.where(restraints)[0]
59
+
60
+ return free_dofs, restrained_dofs, prescribed_dofs
61
+
62
+ def assemble_global_load_vector(
63
+ self,
64
+ ) -> np.ndarray:
65
+ """Calcula el vector de carga global del sistema.
66
+
67
+ Returns:
68
+ np.ndarray: Vector de carga global.
69
+ """
70
+ start_time = time.time()
71
+
72
+ nn = len(self.model.nodes)
73
+ F = np.zeros(nn * 3)
74
+
75
+ # Asignar fuerzas nodales almacenadas en los nodos
76
+ for node in self.model.nodes.values():
77
+ f = node.load_vector()
78
+ dofs = node.dofs
79
+ F[dofs - 1] += f
80
+
81
+ # Agregar el vector de fuerzas globales almacenadas en los miembros
82
+ for member in self.model.members.values():
83
+ f = member.global_load_vector()
84
+ dofs = member.dofs
85
+ F[dofs - 1] += f
86
+
87
+ # Agregar cargas de los elementos CST
88
+ for cst in self.model.csts.values():
89
+ f = cst.get_load_vector()
90
+ dofs = cst.dofs
91
+ F[dofs - 1] += f
92
+
93
+ # Agregar cargas de los elementos Truss
94
+ for truss in self.model.trusses.values():
95
+ f = truss.global_load_vector()
96
+ dofs = truss.dofs
97
+ F[dofs - 1] += f
98
+
99
+ end_time = time.time()
100
+ self.assembly_time += (end_time - start_time)
101
+
102
+ return F
103
+
104
+ def assemble_global_stiffness_matrix(
105
+ self,
106
+ ) -> np.ndarray:
107
+ """Ensamblaje de la matriz de rigidez global.
108
+
109
+ Returns:
110
+ np.ndarray: Matriz de rigidez global.
111
+ """
112
+ start_time = time.time()
113
+ nn = len(self.model.nodes)
114
+ K = np.zeros((nn * 3, nn * 3))
115
+
116
+ # Ensamblar la matriz de rigidez global
117
+ # Rigides de los elmentos 1D
118
+ for member in self.model.members.values():
119
+ k = member.global_stiffness_matrix()
120
+ dofs = member.dofs
121
+
122
+ rows = np.repeat(dofs - 1, 6)
123
+ cols = np.tile(dofs - 1, 6)
124
+ values = k.flatten()
125
+
126
+ for i, j, val in zip(rows, cols, values):
127
+ K[i, j] += val
128
+
129
+ # Rigidez de los elementos CST
130
+ for cst in self.model.csts.values():
131
+ k = cst.global_stiffness_matrix()
132
+ dofs = cst.dofs
133
+
134
+ rows = np.repeat(dofs - 1, 6)
135
+ cols = np.tile(dofs - 1, 6)
136
+ values = k.flatten()
137
+
138
+ for i, j, val in zip(rows, cols, values):
139
+ K[i, j] += val
140
+
141
+ # Rigidez de los elementos Membrane Quad 6
142
+ for membQ6 in self.model.membrane_q6.values():
143
+ k = membQ6.global_stiffness_matrix()
144
+ dofs = membQ6.dofs
145
+
146
+ rows = np.repeat(dofs - 1, 12)
147
+ cols = np.tile(dofs - 1, 12)
148
+ values = k.flatten()
149
+
150
+ for i, j, val in zip(rows, cols, values):
151
+ K[i, j] += val
152
+
153
+
154
+ # Rigidez de los elementos Membrana Quad6I
155
+ for membQ6I in self.model.membrane_q6i.values():
156
+ k = membQ6I.global_stiffness_matrix()
157
+ dofs = membQ6I.dofs
158
+
159
+ rows = np.repeat(dofs - 1, 8) # 8 es el numero de grados de libertad del elemento
160
+ cols = np.tile(dofs - 1, 8)
161
+ values = k.flatten()
162
+
163
+ for i, j, val in zip(rows, cols, values):
164
+ K[i, j] += val
165
+
166
+
167
+ # Rigidez de los elementos Armadura
168
+ for truss in self.model.trusses.values():
169
+ k = truss.global_stiffness_matrix()
170
+ dofs = truss.dofs
171
+
172
+ rows = np.repeat(dofs - 1, 4)
173
+ cols = np.tile(dofs - 1, 4)
174
+ values = k.flatten()
175
+
176
+ for i, j, val in zip(rows, cols, values):
177
+ K[i, j] += val
178
+
179
+
180
+ # aplicar las condiciones de ejes locales de los nodos
181
+ # for node in self.model.nodes.values():
182
+ # if node.local_axis is not None:
183
+ # dofs = node.dofs
184
+ # Tlg = node.local_axis.get_transformation_matrix()
185
+ # kfc = Tlg.T @ K[dofs - 1, dofs - 1] @ Tlg
186
+ # kfila = np.zeros((3, nn * 3))
187
+ # kcolumna = np.zeros((nn * 3, 3))
188
+ # for node in self.model.nodes.values():
189
+ # dofi = node.dofs
190
+ # kfila[:, dofi - 1] += Tlg.T @ K[dofs - 1, dofi - 1]
191
+ # kcolumna[dofi - 1, :] += K[dofi - 1, dofs - 1] @ Tlg
192
+
193
+ # K[dofs - 1, :] = kfila
194
+ # K[:, dofs - 1] = kcolumna
195
+ # K[dofs - 1, dofs - 1] = kfc
196
+
197
+
198
+ K = self.apply_local_axes(K, self.model)
199
+
200
+ # Kold = K.copy()
201
+ # for node in self.model.nodes.values():
202
+ # if node.local_axis is not None:
203
+ # dofs = node.dofs
204
+ # Tlg = node.local_axis.get_transformation_matrix()
205
+
206
+ # # diagonal
207
+ # kfc = Tlg.T @ Kold[dofs - 1, dofs - 1] @ Tlg
208
+
209
+ # # fila y columna
210
+ # kfila = np.zeros((3, nn * 3))
211
+ # kcolumna = np.zeros((nn * 3, 3))
212
+ # for nodej in self.model.nodes.values():
213
+ # dofj = nodej.dofs
214
+ # kfila[:, dofj - 1] = Tlg.T @ Kold[dofs - 1, dofj - 1]
215
+ # kcolumna[dofj - 1, :] = Kold[dofs - 1, dofj - 1] @ Tlg
216
+
217
+ # # escribir en la nueva K
218
+ # K[dofs - 1, :] = kfila
219
+ # K[:, dofs - 1] = kcolumna
220
+ # K[dofs - 1, dofs - 1] = kfc
221
+
222
+ # for i, j, val in zip(rows, cols, values):
223
+ # K[i, j] += val
224
+
225
+ end_time = time.time()
226
+ self.assembly_time += (end_time - start_time)
227
+
228
+ return K
229
+
230
+ @staticmethod
231
+ def apply_local_axes(
232
+ K: np.ndarray,
233
+ model: "SystemMilcaModel"
234
+ ) -> np.ndarray:
235
+ """
236
+ Aplica las transformaciones locales de cada nodo a la matriz de rigidez global K.
237
+ """
238
+ nn = len(model.nodes)
239
+ ndof = nn * 3
240
+ Kold = K.copy()
241
+ Knew = Kold.copy()
242
+
243
+ for node in model.nodes.values():
244
+ if node.local_axis is None:
245
+ continue
246
+
247
+ dofs = node.dofs - 1 # índices base 0
248
+ T = node.local_axis.get_transformation_matrix()
249
+
250
+ # fila
251
+ for nodej in model.nodes.values():
252
+ dofj = nodej.dofs - 1
253
+ Knew[np.ix_(dofs, dofj)] = T.T @ Kold[np.ix_(dofs, dofj)]
254
+
255
+ # columna
256
+ for nodej in model.nodes.values():
257
+ dofj = nodej.dofs - 1
258
+ Knew[np.ix_(dofj, dofs)] = Kold[np.ix_(dofj, dofs)] @ T
259
+
260
+ # diagonal
261
+ Knew[np.ix_(dofs, dofs)] = T.T @ Kold[np.ix_(dofs, dofs)] @ T
262
+
263
+ return Knew
264
+
265
+
266
+
267
+ def apply_boundary_conditions(
268
+ self,
269
+ K: np.ndarray,
270
+ F: np.ndarray,
271
+ free_dofs: np.ndarray,
272
+ restrained_dofs: np.ndarray
273
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
274
+ """Aplica las condiciones de frontera.
275
+
276
+ Args:
277
+ K (np.ndarray): Matriz de rigidez global.
278
+ F (np.ndarray): Vector de fuerzas global.
279
+
280
+ Returns:
281
+ Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
282
+ - K_d: Matriz de rigidez para grados de libertad libres.
283
+ - K_dc: Matriz de rigidez que relaciona GDL libres con restringidos.
284
+ - K_cd: Matriz de rigidez que relaciona GDL restringidos con libres.
285
+ - K_c: Matriz de rigidez para GDL restringidos.
286
+ - F_d: Vector de fuerzas para GDL libres.
287
+ - F_c: Vector de fuerzas para GDL restringidos.
288
+ """
289
+
290
+ # Aplicar las condiciones de apoyos elásticos
291
+ nn = len(self.model.nodes)
292
+ K_kk = np.zeros((nn * 3, nn * 3))
293
+ for node in self.model.nodes.values():
294
+ if node.elastic_supports is not None:
295
+ kx, ky, krz = node.elastic_supports.get_elastic_supports()
296
+ if kx is not None:
297
+ K_kk[node.dofs[0] - 1, node.dofs[0] - 1] += kx
298
+ if ky is not None:
299
+ K_kk[node.dofs[1] - 1, node.dofs[1] - 1] += ky
300
+ if krz is not None:
301
+ K_kk[node.dofs[2] - 1, node.dofs[2] - 1] += krz
302
+ K = K + K_kk
303
+
304
+ # Reducir la matriz de rigidez (d: desconocidos, c: conocidos)
305
+ # | Kd Kdc |
306
+ # K = | |
307
+ # | Kcd Kc |
308
+ K_d = K[np.ix_(free_dofs, free_dofs)]
309
+ K_dc = K[np.ix_(free_dofs, restrained_dofs)]
310
+ K_cd = K[np.ix_(restrained_dofs, free_dofs)]
311
+ K_c = K[np.ix_(restrained_dofs, restrained_dofs)]
312
+
313
+ # Reducir el vector de fuerzas
314
+ # | Fd |
315
+ # F = | |
316
+ # | Fc |
317
+ F_d = F[free_dofs]
318
+ F_c = F[restrained_dofs]
319
+
320
+ return K_d, K_dc, K_cd, K_c, F_d, F_c, K_kk
321
+
322
+ def solve(
323
+ self,
324
+ ) -> Tuple[np.ndarray, np.ndarray]:
325
+ """Resuelve el sistema de ecuaciones F = KU.
326
+
327
+ Returns:
328
+ Tuple[np.ndarray, np.ndarray]:
329
+ - Desplazamientos nodales
330
+ - Reacciones en los apoyos.
331
+ """
332
+
333
+ start_time = time.time()
334
+ # Obtener la matriz de rigidez global y el vector de fuerzas global
335
+ K = self.assemble_global_stiffness_matrix()
336
+ F = self.assemble_global_load_vector()
337
+ self.model.global_stiffness_matrix = K
338
+ self.model.global_load_vector = {}
339
+ self.model.global_load_vector[self.model.current_load_pattern] = F
340
+
341
+ # Aplicar las condiciones de frontera
342
+ free_dofs, restrained_dofs, prescribed_dofs = self._dof_map()
343
+ K_d, K_dc, K_cd, K_c, F_d, F_c, K_kk = self.apply_boundary_conditions(K, F, free_dofs, restrained_dofs)
344
+
345
+ # Resolver el sistema de ecuaciones
346
+ # |Ud|
347
+ # U = | | = displacements
348
+ # |Uc|
349
+
350
+ # Colocar los desplazamientos en los grados de libertad libres
351
+ nn = len(self.model.nodes)
352
+ # completar el vector de desplazamientos
353
+ displacements = np.zeros(nn * 3)
354
+ # Aplicar las condiciones de prescindidos
355
+ displacements[list(prescribed_dofs.keys())] = list(prescribed_dofs.values())
356
+ Uc = np.copy(displacements[restrained_dofs])
357
+ Upc = np.copy(displacements[free_dofs])
358
+
359
+ # resolver el sistema de ecuaciones
360
+ if np.linalg.det(K_d) == 0:
361
+ try:
362
+ factorDeRegularizacion = 1e-7
363
+ Warning(f"La matriz de rigidez global es singular, por lo que para resolver se le sumara un I[ndof. ndof] * {factorDeRegularizacion} a la matriz de rigidez global")
364
+ K_d += np.eye(K_d.shape[0]) * factorDeRegularizacion
365
+ U_d = np.linalg.solve(K_d, F_d - K_dc @ Uc - K_d @ Upc)
366
+ except:
367
+ raise ValueError("La matriz de rigidez global es singular y no se puede resolver")
368
+ else:
369
+ U_d = np.linalg.solve(K_d, F_d - K_dc @ Uc - K_d @ Upc)
370
+
371
+
372
+ displacements[free_dofs] = U_d + Upc
373
+
374
+ # Calcular las reacciones en los apoyos
375
+ R = K_cd @ (U_d + Upc) + K_c @ Uc - F_c
376
+ # Completar el vector de reacciones
377
+ reactions = np.zeros(nn * 3)
378
+ reactions[restrained_dofs] = R
379
+ # corregir las reacciones por las condiciones de apoyos elásticos
380
+ reactions -= K_kk @ displacements
381
+
382
+ # Otra forma de alcular las reacciones en los apoyos, para no estar completando el vector de reacciones
383
+ # R = K_global * U_global - F_global
384
+ # reactions = K @ displacements - F
385
+
386
+ end_time = time.time()
387
+ self.solution_time = (end_time - start_time)
388
+
389
+ ## Tiempo de solucion
390
+ print(f"Tiempo de solucion: {self.solution_time} para el Load Pattern: {self.model.current_load_pattern}")
391
+
392
+ return displacements, reactions
@@ -0,0 +1,101 @@
1
+ from typing import TYPE_CHECKING, ValuesView
2
+ from milcapy.analysis.linear_static import LinearStaticAnalysis
3
+ from milcapy.postprocess.post_processing import PostProcessing
4
+ from milcapy.core.results import Results
5
+ from milcapy.utils.types import StateType
6
+ if TYPE_CHECKING:
7
+ from milcapy.model.model import SystemMilcaModel
8
+ from milcapy.loads.load_pattern import LoadPattern
9
+
10
+
11
+
12
+ class AnalysisManager:
13
+ """Clase mananger para el análisis estructural.
14
+ maneja los tipos de análisis y opciones de análisis estructural.
15
+ realiza el análisis estructural para todos las condiciones de carga."""
16
+
17
+ def __init__(
18
+ self,
19
+ model: "SystemMilcaModel",
20
+ ) -> None:
21
+ """
22
+ Inicializa el análisis estructural.
23
+
24
+ Args:
25
+ model: Sistema estructural a analizar.
26
+ """
27
+ self.model = model
28
+ self.load_patterns = model.load_patterns
29
+ self.results = model.results
30
+ self.current_load_pattern: str | None = None
31
+
32
+
33
+ def notify_all(
34
+ self,
35
+ ) -> None:
36
+ """Notifica a todos los componentes del modelo."""
37
+ # Notificar a todos los miembros del modelo
38
+ for member in self.model.members.values():
39
+ member.set_current_load_pattern(self.current_load_pattern)
40
+ # Notificar a todos los cerchas del modelo
41
+ for truss in self.model.trusses.values():
42
+ truss.set_current_load_pattern(self.current_load_pattern)
43
+ # Notificar a todos los nodos del modelo
44
+ for node in self.model.nodes.values():
45
+ node.set_current_load_pattern(self.current_load_pattern)
46
+ # Notificar al modelo
47
+ self.model.current_load_pattern = self.current_load_pattern
48
+
49
+ def analyze_for_list_load_pattern(
50
+ self,
51
+ load_pattern_name: list[str] | None = None,
52
+ ) -> None:
53
+ """Ejecuta el análisis estructural para todas las condiciones de carga."""
54
+
55
+ patterns_to_analyze: list[LoadPattern] | ValuesView[LoadPattern] = []
56
+
57
+ if load_pattern_name is not None:
58
+ patterns_to_analyze = [self.load_patterns[name] for name in load_pattern_name]
59
+ else:
60
+ patterns_to_analyze = self.load_patterns.values()
61
+
62
+ # solucionar para cada load pattern
63
+ for load_pattern in patterns_to_analyze:
64
+ if load_pattern.state == StateType.INACTIVE:
65
+ continue
66
+ # SYSTEM NOTIFICATION: notificar a todos los componentes del modelo
67
+ self.current_load_pattern = load_pattern.name
68
+ self.notify_all()
69
+
70
+ # Asignar las cargas a los nodos y miembros almacenados en el patrón de carga
71
+ load_pattern.assign_loads_to_nodes()
72
+ load_pattern.assign_loads_to_members()
73
+ load_pattern.assign_prescribed_dofs_to_nodes()
74
+
75
+ # resolver el modelo
76
+ analysis = LinearStaticAnalysis(self.model)
77
+ displacements, reactions = analysis.solve()
78
+
79
+ # Crear un objeto de resultados para almacenar los resultados del análisis
80
+ self.results[load_pattern.name] = Results()
81
+ self.results[load_pattern.name].set_model_displacements(displacements)
82
+ self.results[load_pattern.name].set_model_reactions(reactions)
83
+
84
+
85
+ # crear un objeto de post-procesamiento para procesar los resultados
86
+ post_processing = PostProcessing(self.model, self.results[load_pattern.name], self.model.postprocessing_options, load_pattern.name)
87
+ post_processing.process_displacements_for_nodes()
88
+ post_processing.process_reactions_for_nodes()
89
+ post_processing.process_displacements_for_members()
90
+ post_processing.process_internal_forces_for_members()
91
+ post_processing.process_displacements_for_cst()
92
+ post_processing.post_process_for_cst()
93
+ post_processing.process_displacements_for_membrane_q6()
94
+ post_processing.process_displacements_for_membrane_q6i()
95
+ post_processing.post_process_for_members()
96
+ post_processing.process_displacements_for_trusses()
97
+ post_processing.process_internal_forces_for_trusses()
98
+ post_processing.post_process_for_trusses()
99
+
100
+ # actualizar el estado de analisis en LoadPattern
101
+ load_pattern.analyzed = True
File without changes
milcapy/core/node.py ADDED
@@ -0,0 +1,94 @@
1
+ from typing import TYPE_CHECKING, Dict, Optional
2
+ from milcapy.loads.load import PointLoad, PrescribedDOF, ElasticSupport, LocalAxis
3
+ import numpy as np
4
+
5
+ if TYPE_CHECKING:
6
+ from milcapy.utils.types import Restraints
7
+ from milcapy.utils.geometry import Vertex
8
+
9
+ class Node:
10
+ """
11
+ Representa un nodo en el modelo estructural.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ id: int,
17
+ vertex: "Vertex"
18
+ ) -> None:
19
+ """
20
+ Inicializa un nodo con su identificador y posición.
21
+
22
+ Args:
23
+ id (int): Identificador único del nodo.
24
+ vertex (Vertex): Coordenadas (x, y) del nodo.
25
+ """
26
+ self.id: int = id
27
+ self.vertex: "Vertex" = vertex
28
+ self.restraints: "Restraints" = (
29
+ False, False, False) # Restricciones del nodo
30
+
31
+ # Cálculo de los índices de grados de libertad
32
+ self.dofs: np.ndarray = np.array([
33
+ self.id * 3 - 2, # DOF en x
34
+ self.id * 3 - 1, # DOF en y
35
+ self.id * 3 # DOF en theta
36
+ ], dtype=int)
37
+
38
+ # Cargas aplicadas al nodo
39
+ self.loads: Dict[str, PointLoad] = {} # {pattern_name: PointLoad}
40
+
41
+ # Patrón de carga actual
42
+ self.current_load_pattern: Optional[str] = None
43
+
44
+ # Desplazamientos prescindidos
45
+ self.prescribed_dofs: Dict[str, PrescribedDOF] = {}
46
+
47
+ # apoyos elásticos
48
+ self.elastic_supports: Optional[ElasticSupport] = None
49
+
50
+ # eje local
51
+ self.local_axis: Optional[LocalAxis] = None
52
+
53
+ def load_vector(self) -> np.ndarray:
54
+ """
55
+ Vector de cargas aplicadas al nodo para el patrón de carga actual en sistema global.
56
+ """
57
+ load = self.loads.get(self.current_load_pattern, PointLoad())
58
+ return load.components
59
+
60
+ def set_current_load_pattern(self, load_pattern_name: str) -> None:
61
+ """
62
+ Establece el patrón de carga actual del nodo.
63
+ """
64
+ self.current_load_pattern = load_pattern_name
65
+
66
+ def set_restraints(self, restraints: "Restraints") -> None:
67
+ """
68
+ Establece las restricciones del nodo.
69
+ """
70
+ self.restraints = restraints
71
+
72
+ def set_load(self, load: "PointLoad") -> None:
73
+ """
74
+ Establece una carga puntual en el nodo.
75
+ """
76
+ self.loads[self.current_load_pattern] = load
77
+
78
+ def set_prescribed_dof(self, prescribed_dof: "PrescribedDOF") -> None:
79
+ """
80
+ Establece los desplazamientos prescindidos del nodo.
81
+ """
82
+ self.prescribed_dofs[self.current_load_pattern] = prescribed_dof
83
+
84
+ def set_elastic_support(self, elastic_support: ElasticSupport) -> None:
85
+ """
86
+ Establece los apoyos elásticos del nodo.
87
+ """
88
+ self.elastic_supports = elastic_support
89
+
90
+ def set_local_axis(self, local_axis: LocalAxis) -> None:
91
+ """
92
+ Establece el eje local del nodo. ojo esto hace que el vector de fuerzas aplicados, desplazamientos prescindidos y apoyos elásticos se transformen al sistema local.
93
+ """
94
+ self.local_axis = local_axis