From a4f4a4119684c9387aa0bdeaa323284df49b4010 Mon Sep 17 00:00:00 2001 From: jmurgass99 Date: Thu, 5 Nov 2026 00:15:45 +0100 Subject: [PATCH] Limpieza del repositorio y README reescrito - Se quita lo que no hacia falta publicar: el visor web/ (sin documentar, con un .wasm de 139 bytes y fuera de la CI), las traducciones al portugues y al italiano del README, la salida de los ejemplos (examples/output), y los documentos de proceso interno (docs/QC_CHECKLIST, TESTING, ARCHITECTURE, DESIGN). - README.md y README.en.md reescritos: que hace, instalacion, un ejemplo que se ejecuta, que no hace y como probarlo. Las limitaciones detalladas del README anterior se conservan en docs/LIMITACIONES.md. - .gitignore: examples/output y examples/out. Co-Authored-By: Claude Sonnet 5.5 --- .gitignore | 4 + README.en.md | 530 +----------------- README.it.md | 527 ----------------- README.md | 528 +---------------- README.pt.md | 527 ----------------- docs/LIMITACIONES.md | 10 + ...aterial_section_forces_jsonschema_defs.txt | 11 - ...ise_EI_stiffness_and_carry_over_factor.txt | 10 - ...ss_rigid_vs_semi_rigid_joints_assembly.txt | 12 - ...stiffness_recovers_closed_form_12EI_L3.txt | 15 - ...ode_classification_fixity_factor_table.txt | 17 - ...EI_closed_form_and_simpson_convergence.txt | 13 - ...onl_export_import_and_member_envelopes.txt | 8 - ...ension_positive_column_combined_stress.txt | 10 - ...y_supported_beam_stations_udl_envelope.txt | 18 - ...tion_negative_extra_field_and_defaults.txt | 10 - ...el_concrete_timber_aluminum_from_codes.txt | 14 - ...concrete_Ecm_from_fck_EN1992_table_3_1.txt | 16 - ...al_json_schema_to_typescript_interface.txt | 23 - ..._grades_EN10025_thickness_dependent_fy.txt | 13 - ...rejects_missing_units_and_out_of_range.txt | 12 - ...ons_RHS_CHS_from_dimensions_vs_EN10210.txt | 9 - ...pe_dimensions_IPE300_HEB300_vs_catalog.txt | 13 - ...ping_to_internal_forces_bending_stress.txt | 9 - ...tional_fields_and_json_schema_required.txt | 13 - ...dius_of_gyration_and_slenderness_table.txt | 9 - web/index.html | 12 - web/package.json | 23 - web/src/__tests__/loadpathWeb.test.ts | 52 -- web/src/data-model.ts | 40 -- web/src/main.ts | 205 ------- web/src/mesh/getMesh.ts | 58 -- web/src/solver/apiClient.ts | 38 -- web/src/solver/cpp/built/lSolver.wasm | Bin 156 -> 0 bytes web/src/solver/cpp/lSolver.cpp | 17 - web/src/solver/wasmSolver.ts | 297 ---------- web/src/styles.css | 109 ---- web/src/ui/getDiagrams.ts | 74 --- web/src/viewer/getViewer3D.ts | 111 ---- web/tsconfig.json | 14 - 40 files changed, 74 insertions(+), 3357 deletions(-) delete mode 100644 README.it.md delete mode 100644 README.pt.md create mode 100644 docs/LIMITACIONES.md delete mode 100644 examples/output/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.txt delete mode 100644 examples/output/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.txt delete mode 100644 examples/output/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.txt delete mode 100644 examples/output/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.txt delete mode 100644 examples/output/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.txt delete mode 100644 examples/output/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.txt delete mode 100644 examples/output/internal_forces_jsonl_export_import_and_member_envelopes.txt delete mode 100644 examples/output/internal_forces_sign_convention_tension_positive_column_combined_stress.txt delete mode 100644 examples/output/internal_forces_simply_supported_beam_stations_udl_envelope.txt delete mode 100644 examples/output/internal_forces_validation_station_negative_extra_field_and_defaults.txt delete mode 100644 examples/output/material_catalog_steel_concrete_timber_aluminum_from_codes.txt delete mode 100644 examples/output/material_concrete_Ecm_from_fck_EN1992_table_3_1.txt delete mode 100644 examples/output/material_json_schema_to_typescript_interface.txt delete mode 100644 examples/output/material_steel_grades_EN10025_thickness_dependent_fy.txt delete mode 100644 examples/output/material_validation_rejects_missing_units_and_out_of_range.txt delete mode 100644 examples/output/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.txt delete mode 100644 examples/output/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.txt delete mode 100644 examples/output/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.txt delete mode 100644 examples/output/section_validation_optional_fields_and_json_schema_required.txt delete mode 100644 examples/output/section_weight_radius_of_gyration_and_slenderness_table.txt delete mode 100644 web/index.html delete mode 100644 web/package.json delete mode 100644 web/src/__tests__/loadpathWeb.test.ts delete mode 100644 web/src/data-model.ts delete mode 100644 web/src/main.ts delete mode 100644 web/src/mesh/getMesh.ts delete mode 100644 web/src/solver/apiClient.ts delete mode 100644 web/src/solver/cpp/built/lSolver.wasm delete mode 100644 web/src/solver/cpp/lSolver.cpp delete mode 100644 web/src/solver/wasmSolver.ts delete mode 100644 web/src/styles.css delete mode 100644 web/src/ui/getDiagrams.ts delete mode 100644 web/src/viewer/getViewer3D.ts delete mode 100644 web/tsconfig.json diff --git a/.gitignore b/.gitignore index f6c4858..c03534b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ dist/ .venv/ # herramientas locales de agentes + +# salida de los ejemplos +examples/output/ +examples/out/ diff --git a/README.en.md b/README.en.md index e8eaf63..43833ba 100644 --- a/README.en.md +++ b/README.en.md @@ -1,527 +1,57 @@ -# loadpath — structural data contract (materials, sections, internal forces) in Pydantic v2 and frame FEA helpers +# loadpath -[Español](README.md) · **English** · [Português](README.pt.md) · [Italiano](README.it.md) +[Español](README.md) · **English** -Three strict models that travel between analysis programs, checkers, exporters and interfaces: `StructuralMaterial`, `FrameSection` and `InternalForces`, with SI units in the name of every field, `extra="forbid"` and a JSON Schema to generate types in other languages. Plus two frame finite element helpers: the 2D stiffness matrix of a non-prismatic beam (EI(x), EA(x) by Simpson) and the condensation of end rotational springs for semi-rigid joints. Dependencies: `pydantic` and `numpy`. +Structural data models (material, section, internal forces) in Pydantic v2, plus two member stiffness helpers. [![CI](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml/badge.svg)](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml) -[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/) [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) -## What problem it solves +## What it does -Between an analysis program, a section checker and a report, data crosses three boundaries, and at each one units, axes and sign conventions get lost: the strong-axis moment is called M3 in one place and M_y in another, axial force is positive in compression here and in tension there, and an `E = 210` does not say whether it is GPa or MPa. `loadpath` fixes a single format with the unit in the field name, the axes documented in the two models that share them, and validation on input, so that malformed data is rejected with the field path instead of propagating as a number. +- `StructuralMaterial`, `FrameSection` and `InternalForces`: three validated models, with SI units in each field name and no unknown fields allowed. +- JSON Schema of the models, to generate types in other languages (one example converts it to TypeScript). +- Stiffness matrix of a tapered or haunched beam (piecewise EI(x)) and condensed stiffness of a beam with semi-rigid joints. -**Use it when** - -- you define the input or output of a calculation tool (REST API, CLI, file) and want materials, sections and internal forces to have a single validated format; -- you generate TypeScript types or API documentation from the JSON Schema of the models; -- you import results from SAP2000, ETABS or another program and need a place to declare the axis mapping and the sign convention; -- you need the stiffness matrix of a tapered or haunched beam (piecewise EI(x)) or the condensed stiffness of a beam with semi-rigid joints to assemble it into a frame. - -**Don't use it for** - -- section catalogues (it does not include IPE, HEB or W tables; the examples show how to compute the properties from the dimensions and check them); -- code checks: there are no design resistances, buckling or section classification, only the data contract and the stiffness helpers; -- analysis of complete structures: `fea` gives element matrices, it does not assemble or solve (the portal frame example does it in 40 lines with numpy to show how); -- built-up, asymmetric or warping-restrained sections: `FrameSection` describes properties, not geometry. - -## Installation +## Install ```bash pip install git+https://github.com/jmurgass99/loadpath.git ``` -For development: `git clone`, `pip install -e ".[test]"`, `pytest -q` (8 test files). On Windows, if the console is not UTF-8, run the examples with `python -X utf8 examples/.py`. - -## Capabilities - -| Capability | Modules | Code or reference | Use cases | -|---|---|---|---| -| Materials: `StructuralMaterial` with mandatory E, ν, ρ and optional strengths by type (steel, concrete, cold-formed, aluminium, timber, masonry); range validation; JSON Schema | `materials` | EN 1993-1-1 §3.2.6; EN 10025-2 Table 7; EN 1992-1-1 Table 3.1 and §3.1.3; EN 14080; EN 1999-1-1 | [5 cases](#1-materials) | -| Sections: `FrameSection` with mandatory A, I_x, I_y, J and optional C_w, elastic and plastic moduli, dimensions, section class and Class 4 effective properties; strong- and weak-axis aliases; axis map with the internal forces | `sections` | EN 10365 / EN 10210 (catalogue check); SAP2000 M3 strong / M2 weak convention | [5 cases](#2-sections) | -| Internal forces and data contract: `InternalForces` per station with tension positive and components zero by default; JSON Lines; composite models with `$defs`; mapping from SAP2000 | `forces`, Pydantic v2 | Local axis convention 1-2-3 | [5 cases](#3-internal-forces-and-data-contract) | -| FEA helpers: 6×6 stiffness of a 2D beam with EI(x) and EA(x) by Simpson (cantilever flexibility), condensed end rotational springs (Monforton and Wu) | `fea.non_prismatic`, `fea.semi_rigid` | Monforton and Wu (1963); EN 1993-1-8 §5.2.2.5 (joint classification); PCA frame constant tables | [5 cases](#4-finite-element-helpers) | - -The 20 scripts in `examples/` were run in a clean environment before publishing; their output is in `examples/output/` and is what is reproduced here. - -## Use cases - -The outputs are reproduced exactly as the scripts print them, in Spanish. - -### 1. Materials - -#### Project material catalogue with code values: S355, A992, C30/37, GL24h, aluminium 6061-T6 and masonry - -
Actual output of python examples/material_catalog_steel_concrete_timber_aluminum_from_codes.py - -```text -Tipos admitidos: ['steel', 'concrete', 'cold_formed_steel', 'aluminum', 'timber', 'masonry'] - - material tipo E [MPa] ν ρ [kg/m³] F_y F_u f_c ξ - S355 (EN 10025-2) steel 210000 0.30 7850 355 490 — 0.02 - A992 (ASTM) steel 200000 0.30 7850 345 450 — 0.02 - C30/37 (EN 1992-1-1) concrete 33000 0.20 2500 — — 30 0.05 - GL24h (EN 14080) timber 11500 0.40 420 — — — 0.01 - EN AW-6061 T6 (EN 1999-1-1) aluminum 70000 0.33 2700 240 260 — — - Ladrillo macizo M10 (EN 1996-1-1) masonry 5000 0.20 1800 — — 5 — - -Módulo de cortante del S355: G = E/(2·(1+ν)) = 80769 MPa (EN 1993-1-1: 81 000) -Peso propio del S355: 76.98 kN/m³; del C30/37: 24.52 kN/m³ - -Un consumidor comprueba con `material_type` qué resistencia leer: Fy/Fu en metales, fc en hormigón y fábrica; None es «no aplica», no «cero». -``` - -
- -#### What the schema rejects and why: field without a unit, negative E, ν > 0.5, unknown type, text in a number - -
Actual output of python examples/material_validation_rejects_missing_units_and_out_of_range.py +## Example -```text -Válido: S275 E = 210000 MPa +```python +from loadpath import StructuralMaterial - caso errores tipo → campo - E sin unidad (campo `E`) 1 extra_forbidden → E - E_MPa negativo 1 greater_than → E_MPa - ν = 0,7 (> 0,5) 1 less_than_equal → poisson - densidad 0 1 greater_than → density_kg_m3 - tipo `adamantium` 1 enum → material_type - F_y como texto 1 float_parsing → Fy_MPa - falta E_MPa 1 missing → E_MPa +steel = StructuralMaterial( + name="S355", material_type="steel", + E_MPa=210_000, poisson=0.3, density_kg_m3=7850, Fy_MPa=355, Fu_MPa=490, +) +print(steel.model_json_schema()["properties"].keys()) -`extra='forbid'` convierte un campo sin unidad en un error en vez de en un número que viaja sin que nadie sepa si es MPa o kPa. +StructuralMaterial(name="X", material_type="steel", E_MPa=-1, poisson=0.3, density_kg_m3=7850) +# pydantic.ValidationError (E_MPa must be positive) ``` -
- -#### From the JSON Schema to a TypeScript interface with the ranges and units in the comments - -
Actual output of python examples/material_json_schema_to_typescript_interface.py - -```text -JSON Schema: 9 propiedades, obligatorias ['name', 'material_type', 'E_MPa', 'poisson', 'density_kg_m3'], enum MaterialType con 6 valores - -/** Propiedades físicas de un material estructural (unidades SI, MPa). */ -export interface StructuralMaterial { - name: string; - material_type: "steel" | "concrete" | "cold_formed_steel" | "aluminum" | "timber" | "masonry"; - /** Módulo de elasticidad (MPa). (> 0) */ - E_MPa: number; - /** Coeficiente de Poisson. (>= 0.0, <= 0.5) */ - poisson: number; - /** Densidad (kg/m³). (> 0) */ - density_kg_m3: number; - /** Límite elástico, acero (MPa). (> 0) */ - Fy_MPa?: number | null; - /** Resistencia última, acero (MPa). (> 0) */ - Fu_MPa?: number | null; - /** Resistencia a compresión, hormigón (MPa). (> 0) */ - fc_MPa?: number | null; - /** Razón de amortiguamiento (fracción del crítico). (>= 0.0, <= 1.0) */ - damping_ratio?: number | null; -} - -El mismo generador sirve para FrameSection e InternalForces; `?` marca las propiedades opcionales (None en Python). -``` - -
- -#### Concretes C20/25 to C50/60 with E_cm = 22·(f_cm/10)^0.3 GPa (EN 1992-1-1 Table 3.1) and the aggregate factor - -
Actual output of python examples/material_concrete_Ecm_from_fck_EN1992_table_3_1.py - -```text - clase f_ck f_cm E_cm calc [GPa] Tabla 3.1 desvío - C20/25 cuarcita 20 28 29.96 30 -0.1 % - C25/30 cuarcita 25 33 31.48 31 +1.5 % - C30/37 cuarcita 30 38 32.84 33 -0.5 % - C35/45 cuarcita 35 43 34.08 34 +0.2 % - C40/50 cuarcita 40 48 35.22 35 +0.6 % - C45/55 cuarcita 45 53 36.28 36 +0.8 % - C50/60 cuarcita 50 58 37.28 37 +0.8 % - -Efecto del árido para C30/37 (§3.1.3(2)): - cuarcita E_cm = 32.8 GPa - caliza E_cm = 29.6 GPa - arenisca E_cm = 23.0 GPa - basalto E_cm = 39.4 GPa - -La fórmula reproduce la tabla con desvíos < 2 % (la tabla redondea al GPa; C25/30 es el peor caso, 31,5 frente a 31); el módulo que viaja en el esquema es el calculado, no el redondeado. -``` - -
- -#### Steels S235 to S460 with the thickness-dependent yield strength (EN 10025-2 Table 7) - -
Actual output of python examples/material_steel_grades_EN10025_thickness_dependent_fy.py - -```text - grado t=10 t=20 t=50 t=70 (f_y en MPa, EN 10025-2) - S235 235 225 215 215 - S275 275 265 255 245 - S355 355 345 335 325 - S460 460 440 430 410 - -Chapa de 50 mm: EN 10025-2 frente a la simplificación de EN 1993-1-1 Tabla 3.1 (40 < t <= 80): - S235: 215 MPa frente a 215 MPa; usar el nominal de t <= 16 (235) sobrestimaría un 9 % - S275: 255 MPa frente a 255 MPa; usar el nominal de t <= 16 (275) sobrestimaría un 8 % - S355: 335 MPa frente a 335 MPa; usar el nominal de t <= 16 (355) sobrestimaría un 6 % - S460: 430 MPa frente a 410 MPa; usar el nominal de t <= 16 (460) sobrestimaría un 7 % - -Ejemplo serializado: {"name":"S355 t=50 mm","material_type":"steel","E_MPa":210000.0,"poisson":0.3,"density_kg_m3":7850.0,"Fy_MPa":335.0,"Fu_MPa":470.0,"fc_MPa":null,"damping_ratio":null} -``` - -
- -### 2. Sections - -#### Properties of an I section from its dimensions and check against the catalogue: IPE 300 and HEB 300 - -The area with the root-fillet correction (4 − π)·r² comes out exact; I_x and W_pl are 4 % below the catalogue and torsion 20–25 %: I_t is taken from the catalogue. - -
Actual output of python examples/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.py - -```text -IPE 300: h = 300, b = 150, t_w = 7.1, t_f = 10.7, r = 15 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 53.8 7999 603 602 15.6 126 - catálogo 53.8 8356 604 628 20.1 126 - desvío +0.0 % -4.3 % -0.2 % -4.1 % -22.5 % +0.1 % - -HEB 300: h = 300, b = 300, t_w = 11.0, t_f = 19.0, r = 27 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 149.1 24187 8553 1790 148.8 1688 - catálogo 149.1 25170 8563 1869 185.0 1688 - desvío -0.0 % -3.9 % -0.1 % -4.2 % -19.6 % +0.0 % - -La corrección (4 − π)·r² deja el área exacta; en I_x y W_pl los acuerdos aportan ~4 % y en I_t un 20–25 %: I_y e I_w salen bien, I_t se toma de catálogo. -``` - -
- -#### RHS 200×100×8 and CHS 219.1×8 tubes from their dimensions against EN 10210 (Bredt for RHS torsion) - -
Actual output of python examples/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.py - -```text - perfil A [cm²] I_x [cm⁴] I_y [cm⁴] I_t [cm⁴] W_pl,x [cm³] - RHS 200x100x8 calculado 45.4 2306 758 1758 289 - EN 10210 44.8 2269 747 1804 283 - desvío +1.4 % +1.6 % +1.5 % -2.6 % +2.1 % - CHS 219.1x8 calculado 53.1 2960 2960 5919 357 - EN 10210 53.1 2960 2960 5919 357 - desvío -0.1 % -0.0 % -0.0 % +0.0 % -0.1 % - -El RHS sale 1–3 % alto por las esquinas vivas (el catálogo tiene r_o = 1,5·t); Bredt da la torsión a −2,6 %. El CHS es exacto porque la fórmula es la geometría real. -``` - -
- -#### Which moment goes with which inertia: M_z (strong) with I_x, M_y (weak) with I_y, and the error of crossing them - -
Actual output of python examples/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.py - -```text -Mapa de ejes: - flexión fuerte M_z = 90 kN·m → I_x = 8.356e+07 mm⁴, S_x = 557000 mm³ - flexión débil M_y = 6 kN·m → I_y = 6.040e+06 mm⁴, S_y = 80500 mm³ - cortante V_z = 60 kN acompaña a M_z (plano fuerte); V_y acompañaría a M_y - -Tensiones elásticas [MPa]: N/A = -22.3, M_z/S_x = ±161.6, M_y/S_y = ±74.5 -Esquina más comprimida: σ = -258.4 MPa → utilización elástica 0.73 frente a f_y = 355 MPa - -Si se cruzan los ejes (M_z con S_y): σ = 1118 MPa, 6.9 veces la real. El esquema lo evita nombrando los ejes en los dos modelos. -``` - -
- -#### Mandatory minimum, optional fields as None and rejections of the section schema - -
Actual output of python examples/section_validation_optional_fields_and_json_schema_required.py - -```text -Sección mínima: {'name': 'barra genérica', 'A_mm2': 5000.0, 'Ix_mm4': 80000000.0, 'Iy_mm4': 6000000.0, 'J_mm4': 200000.0} - opcionales en None: ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] -Sección laminada con propiedades brutas: W14X90, 12 de 18 campos; sin rellenar: ['section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - -JSON Schema: obligatorias ['name', 'A_mm2', 'Ix_mm4', 'Iy_mm4', 'J_mm4']; opcionales ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - - sin J → missing en J_mm4 - área 0 → greater_than en A_mm2 - C_w negativo → greater_than_equal en Cw_mm6 - campo `peso` → extra_forbidden en peso - I_x como texto → float_parsing en Ix_mm4 - -C_w = 0 sí se admite (tubos cerrados); lo que se rechaza es el valor negativo. -``` - -
- -#### Weight per metre, radii of gyration and column slenderness derived from the schema fields - -
Actual output of python examples/section_weight_radius_of_gyration_and_slenderness_table.py - -```text -S355: λ_1 = π·sqrt(E/f_y) = 76.4; pilar L_cr = 4 m - - perfil peso [kg/m] i_x [mm] i_y [mm] λ_y λ̄_y régimen - IPE 300 42.2 124.6 33.5 119.4 1.56 esbelto - HEB 300 117.0 129.9 75.8 52.8 0.69 intermedio - HEA 200 42.3 82.8 49.8 80.3 1.05 intermedio - CHS 219.1x8 41.6 74.7 74.7 53.5 0.70 intermedio - -Pesos de catálogo: IPE 300 42,2; HEB 300 117; HEA 200 42,3; CHS 219,1×8 41,6 kg/m. El IPE 300 pesa lo mismo que el HEA 200 y es 2,6 veces más esbelto en su eje débil. -``` +More cases in [`examples/`](examples). -
+## What it does not do -### 3. Internal forces and data contract +- No section or material catalog: it validates what it receives, it does not fill it in. +- No code checks (design resistances, buckling, section classification). +- It does not assemble or solve structures: `fea` gives element matrices. +- `FrameSection` stores properties, not geometry, and does not distinguish class 4 sections. -#### Simply supported beam with a uniform load: 13 stations as a list of `InternalForces` and envelope against q·L/2 and q·L²/8 +Details are in [docs/LIMITACIONES.md](docs/LIMITACIONES.md) (in Spanish). -
Actual output of python examples/internal_forces_simply_supported_beam_stations_udl_envelope.py +## Tests -```text - x [m] V_z [kN] M_z [kN·m] - 0.0 72.0 0.0 - 0.5 60.0 33.0 - 1.0 48.0 60.0 - 1.5 36.0 81.0 - 2.0 24.0 96.0 - 2.5 12.0 105.0 - 3.0 0.0 108.0 - 3.5 -12.0 105.0 - 4.0 -24.0 96.0 - 4.5 -36.0 81.0 - 5.0 -48.0 60.0 - 5.5 -60.0 33.0 - 6.0 -72.0 0.0 - -Envolvente: |V_z| máx = 72.0 kN en x = 0.0 m (q·L/2 = 72.0); M_z máx = 108.0 kN·m en x = 3.0 m (q·L²/8 = 108.0) -Serializado: 13 estaciones, 111 bytes cada una; ejemplo: {"member_id":"V-12","station_m":3.0,"N_kN":0.0,"Vy_kN":0.0,"Vz_kN":0.0,"T_kNm":0.0,"My_kNm":0.0,"Mz_kNm":108.0} -N, V_y, T y M_y quedan en 0,0 por defecto: un estado parcial es válido y explícito. -``` - -
- -#### Axial force sign convention (tension positive): column and tie, fibre stresses and what happens if the importer flips it - -
Actual output of python examples/internal_forces_sign_convention_tension_positive_column_combined_stress.py - -```text - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) -850 180 50.3 -164.3 -164.3 0.46 - tirante T-1 (tracción) 300 20 32.0 8.2 0.0 0.00 - -Con el signo del axil invertido al importar (error típico entre programas): - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) 850 180 164.3 -50.3 -50.3 0.14 - tirante T-1 (tracción) -300 20 -8.2 -32.0 -32.0 0.09 - -Con el signo invertido la fibra comprimida del pilar pasa de −164 a −50 MPa (la compresión máxima, que gobierna el pandeo y la clase de la sección, cae a un tercio) y el tirante aparece comprimido; |σ|máx no cambia, así que una comprobación que solo mire el máximo absoluto no lo detecta. El esquema documenta el convenio en el campo y el importador debe declararlo, no adivinarlo. -``` - -
- -#### Exporting a frame to JSON Lines, re-importing with line-by-line validation and extracting the envelope per member - -
Actual output of python examples/internal_forces_jsonl_export_import_and_member_envelopes.py - -```text -Exportadas 45 estaciones en 5486 bytes de JSONL (+1 línea corrupta de prueba) - -Reimportadas 45 válidas; rechazadas: [(46, ['greater_than_equal'])] - - barra |N| máx [kN] |V_z| máx [kN] |M_z| máx [kN·m] - P-1 420.0 25.0 50.0 - D-1 35.0 180.0 240.0 - P-2 420.0 25.0 50.0 -``` - -
- -#### What `InternalForces` rejects, the valid partial state and a mapping of a SAP2000 row to the schema - -
Actual output of python examples/internal_forces_validation_station_negative_extra_field_and_defaults.py - -```text -Estado parcial válido: {'member_id': 'C-1', 'station_m': 0.0, 'N_kN': 0.0, 'Vy_kN': 0.0, 'Vz_kN': 0.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 0.0} - - estación −1 m → 1 error(es): greater_than_equal en station_m - campo `Fx_kN` → 1 error(es): extra_forbidden en Fx_kN - sin member_id → 1 error(es): missing en member_id - M_z como texto → 1 error(es): float_parsing en Mz_kNm - dos errores a la vez → 3 error(es): greater_than_equal en station_m; float_parsing en Mz_kNm; extra_forbidden en extra - -Fila de SAP2000 mapeada: {'member_id': 'V-12', 'station_m': 3.0, 'N_kN': -12.0, 'Vy_kN': 0.0, 'Vz_kN': 48.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 72.0} -El mapeo vive en el importador (y debe declarar el convenio de signo de P); el esquema no acepta los nombres del programa de origen. -``` - -
- -#### Composite material + section + forces contract: JSON Schema with `$defs`, round trip and nested rejection - -
Actual output of python examples/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.py - -```text -JSON Schema de MemberCheckInput: propiedades ['member_id', 'length_m', 'material', 'section', 'forces']; $defs = ['FrameSection', 'InternalForces', 'MaterialType', 'StructuralMaterial'] -Documento serializado: 1106 bytes; vuelta validada igual al original: True - - x [m] M_z [kN·m] σ = N/A + M/S [MPa] σ/f_y - 0.0 0.0 2.2 0.01 - 1.5 81.0 147.7 0.42 - 3.0 108.0 196.1 0.55 - 4.5 81.0 147.7 0.42 - 6.0 0.0 2.2 0.01 - -Rechazo anidado: [('greater_than', 'section.Ix_mm4')] -``` - -
- -### 4. Finite element helpers - -#### The prismatic beam as a special case: Simpson recovers the classical 6×6 matrix to machine precision - -
Actual output of python examples/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.py - -```text -IPE 300, L = 6 m: EI = 17,548 kN·m², EA = 1,130,010 kN - 12EI/L³ = 974.9 kN/m 6EI/L² = 2,924.6 kN 4EI/L = 11,698.4 kN·m/rad 2EI/L = 5,849.2 kN·m/rad - - n = 2 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 3.9e-17 - n = 20 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 1.5e-16 - -Matriz 6×6 (GDL [u_i, v_i, θ_i, u_j, v_j, θ_j]): -[[ 188335. 0. 0. -188335. 0. 0. ] - [ 0. 974.9 2924.6 0. -974.9 2924.6] - [ 0. 2924.6 11698.4 0. -2924.6 5849.2] - [-188335. 0. 0. 188335. 0. 0. ] - [ 0. -974.9 -2924.6 0. 974.9 -2924.6] - [ 0. 2924.6 5849.2 0. -2924.6 11698.4]] - -El mismo helper con EI(x) callable da la viga de sección variable: el caso prismático es la comprobación de que la integración está bien montada. -``` - -
- -#### Tapered beam with linear EI: end flexibility in closed form and fourth-order convergence - -
Actual output of python examples/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.py - -```text -Forma cerrada: D₀₀ = 0.003355 m/kN, D₀₁ = 0.000792 rad/kN, D₁₁ = 0.000237 rad/(kN·m) -Cotas prismáticas de D₀₀: EI₀ → 0.004103, 2·EI₀ → 0.002052 - - n D₀₀ Simpson error rel. razón - 2 0.003419 1.90e-02 - 4 0.003361 1.57e-03 12.1 - 8 0.003356 1.08e-04 14.5 - 16 0.003355 6.93e-06 15.6 - 32 0.003355 4.36e-07 15.9 - 64 0.003355 2.73e-08 16.0 - -Rigidez rotacional en el extremo i (K[θ_i,θ_i]): acartelada 14,222 frente a prismática EI₀ 11,699 kN·m/rad (1.22×); en j: 20,071 (1.72×): el extremo con más inercia (j) es el más rígido. -Factor de transmisión K[θ_i,θ_j]/K[θ_j,θ_j] = 0.417 (prismática: 0,500). -``` - -
- -#### Rafter with end haunches: piecewise EI, rotational stiffness and carry-over factor against the prismatic beam - -
Actual output of python examples/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.py - -```text -Dintel L = 12 m, IPE 400: 4EI/L = 16,191 kN·m/rad, 2EI/L = 8,096, 12EI/L³ = 337 kN/m - - cartela K[θi,θi] [kN·m/rad] / prismática K[θi,θj] transmisión K[vi,vi] [kN/m] / prismática - ninguna (prismática) 16,191 1.00 8,096 0.500 337 1.00 - 2 m, canto ×2.0 28,195 1.74 17,969 0.637 641 1.90 - 3 m, canto ×2.0 37,434 2.31 25,659 0.685 876 2.60 - 2 m, canto ×1.5 23,243 1.44 13,739 0.591 514 1.52 - -La rigidez rotacional del nudo sube un 74 % con cartelas de 2 m que doblan el canto y el factor de transmisión pasa de 0.50 a 0.64: es lo que hace rentable la cartela frente a aumentar el perfil en toda la luz. -Quiebros de EI(x) en x = 2 y 10 m, error máximo frente a Simpson por tramos con n = 400: n = 20 con breakpoints_x 4.1e-04; n = 240 sin ellos (la tabla) 4.5e-08; n = 20 sin ellos 3.3e-03. -``` - -
- -#### Semi-rigid joints: EN 1993-1-8 classification, Monforton and Wu fixity factor and condensed stiffness - -
Actual output of python examples/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.py - -```text -IPE 400, L = 8 m: E·I_b/L_b = 6,072 kN·m/rad; umbrales EN 1993-1-8: articulada <= 3,036, rígida >= 48,573 (arriostrado) / 151,791 (no arriostrado) - - S_j/(EI/L) S_j [kN·m/rad] γ k_θiθi/(4EI/L) Monforton-Wu arriostrado no arriostr. - 0.25 1,518 0.077 0.058 0.058 articulada articulada - 0.5 3,036 0.143 0.108 0.108 articulada articulada - 1 6,072 0.250 0.190 0.190 semirrígida semirrígida - 2 12,143 0.400 0.312 0.313 semirrígida semirrígida - 4 24,286 0.571 0.467 0.467 semirrígida semirrígida - 8 48,573 0.727 0.629 0.629 rígida semirrígida - 12 72,860 0.800 0.714 0.714 rígida semirrígida - 25 151,791 0.893 0.836 0.836 rígida rígida - 50 303,581 0.943 0.910 0.910 rígida rígida - 100 607,162 0.971 0.953 0.953 rígida rígida - inf inf 1.000 1.000 1.000 rígida rígida - -Una unión en el umbral «rígido» de pórtico arriostrado (S_j = 8·EI/L) conserva el 63 % de la rigidez rotacional 4EI/L de la viga; en el umbral no arriostrado (25·EI/L), el 84 %. -R va en kN·m/rad, las mismas unidades que K_b; pasar una rigidez relativa (k·L/EI) hace que todo salga articulado. -``` - -
- -#### Frame assembled with the helpers: sway stiffness with rigid, semi-rigid and pinned joints against 24·EI_c/H³ and 6·EI_c/H³ - -
Actual output of python examples/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.py - -```text -EI_c = 52,857 kN·m²; cotas: 24·EI_c/H³ = 10,149 kN/m, 6·EI_c/H³ = 2,537 kN/m - - dintel S_j k = F/Δ [kN/m] Δ(100 kN) [mm] k / k_rígido - IPE 400 ∞ 5,595 17.9 1.00 - IPE 400 25·EI_b/L_b 5,215 19.2 0.93 - IPE 400 8·EI_b/L_b 4,656 21.5 0.83 - IPE 400 2·EI_b/L_b 3,637 27.5 0.65 - IPE 400 0 (articulado) 2,528 39.6 0.45 - rígido (×10⁶) ∞ 9,987 10.0 1.79 - -Comprobación: dintel rígido → 9,987 frente a 24·EI_c/H³ = 10,149 (98.41 %); articulado → 2,528 frente a 6·EI_c/H³ = 2,537 (99.64 %) -Una unión en el umbral «rígido» de pórtico arriostrado (8·EI_b/L_b) pierde un 17 % de rigidez al desplome y en el umbral no arriostrado (25·EI_b/L_b) un 7 %: por eso EN 1993-1-8 exige el segundo en pórticos no arriostrados. +```bash +pip install -e ".[test]" +pytest -q ``` -
- -## Verification - -- Data contract: validation of mandatory fields, ranges (E > 0, 0 ≤ ν ≤ 0.5, station ≥ 0, C_w ≥ 0), `extra="forbid"`, type enum, round trip `model_dump` → `model_validate` and `required` of the JSON Schema for the three models (`tests/test_materials.py`, `test_sections.py`, `test_forces.py`). -- FEA: the prismatic beam is recovered to machine precision; the linear-EI beam reproduces the closed-form flexibility (D₀₀ = (4·ln 2 − 5/2)·L³/EI₀, D₀₁ = (2·ln 2 − 1)·L²/EI₀, D₁₁ = ln 2·L/EI₀) with fourth-order convergence; the end springs reproduce Monforton and Wu (1963) with equal and different springs and the rigid (4EI/L) and pinned (3EI/L) limits (`tests/test_fea_closed_form_golden.py`, `test_fea_non_prismatic.py`, `test_fea_semi_rigid.py`). -- The examples check the properties computed from dimensions against the EN 10365 and EN 10210 catalogues, the E_cm formula against EN 1992-1-1 Table 3.1, and the assembled frame against the 24·EI_c/H³ and 6·EI_c/H³ bounds. - -## Limitations - -- There is no section or material catalogue: the package validates what it receives, it does not fill it in. -- `FrameSection` does not distinguish class 4 sections and does not store geometry (flanges, webs); effective properties are the producer's responsibility. -- `InternalForces` is one member station; it does not model plate forces, supports or combinations (the combination identifier goes in `member_id` or in the consumer's composite model). -- `beam_stiffness_2d` is plane bending under Euler-Bernoulli assumptions (no shear deformation) with EI(x) given by the user; with piecewise EI, pass the breaks in `breakpoints_x` (piecewise Simpson) or use an n large enough for them to fall near a Simpson node. -- `semi_rigid_bending_stiffness` takes R in the units of K_b (kN·m/rad if K_b is in kN and m); passing a relative stiffness k·L/EI makes everything come out pinned. -- The axis conventions (x strong, M_z strong) are those of the Loadpath ecosystem and of SAP2000; other programs use others, and the mapping is the importer's job. - -## Codes and references - -- EN 1993-1-1:2005 §3.2.6 (E, G, ν); EN 10025-2 Table 7 (f_y by thickness); EN 1993-1-8 §5.2.2.5 (joint classification by stiffness). -- EN 1992-1-1:2004 Table 3.1 and §3.1.3 (E_cm and aggregate); EN 1991-1-1 Table A.1 (densities). -- EN 10365 and EN 10210 (section dimensions and properties, used as a check). -- Monforton, G. R. and Wu, T. S. (1963), Matrix analysis of semi-rigidly connected frames, ASCE J. Struct. Div. 89(6). -- PCA, Handbook of Frame Constants (constants for haunched members). -- Pydantic v2 and JSON Schema 2020-12. - ## License -MIT. See [LICENSE](LICENSE). - ---- - -Part of **Loadpath**, the collection of open-source structural libraries by [Jesús Murgas](https://github.com/jmurgass99). +MIT. See [LICENSE](LICENSE). Author: [Jesús Murgas](https://github.com/jmurgass99). diff --git a/README.it.md b/README.it.md deleted file mode 100644 index 9293b49..0000000 --- a/README.it.md +++ /dev/null @@ -1,527 +0,0 @@ -# loadpath — contratto dei dati strutturali (materiali, sezioni, sollecitazioni) in Pydantic v2 e strumenti FEM per aste - -[Español](README.md) · [English](README.en.md) · [Português](README.pt.md) · **Italiano** - -Tre modelli rigorosi che viaggiano tra programmi di calcolo, verificatori, esportatori e interfacce: `StructuralMaterial`, `FrameSection` e `InternalForces`, con le unità SI nel nome di ogni campo, `extra="forbid"` e JSON Schema per generare tipi in altri linguaggi. In più due strumenti agli elementi finiti per aste: la matrice di rigidezza 2D di una trave a sezione variabile (EI(x), EA(x) con Simpson) e la condensazione di molle rotazionali di estremità per nodi semirigidi. Dipendenze: `pydantic` e `numpy`. - -[![CI](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml/badge.svg)](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml) -[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/) -[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) - -## Quale problema risolve - -Tra un programma di analisi, un verificatore di sezioni e una relazione, i dati attraversano tre confini e in ognuno si perdono unità, assi e convenzioni di segno: il momento forte si chiama M3 da una parte e M_y dall'altra, lo sforzo normale è positivo a compressione qui e a trazione là, e un `E = 210` non dice se sono GPa o MPa. `loadpath` fissa un formato unico con l'unità nel nome del campo, gli assi documentati nei due modelli che li condividono e la validazione in ingresso, così che un dato malformato viene rifiutato con il percorso del campo invece di propagarsi come un numero. - -**Usalo quando** - -- definisci l'ingresso o l'uscita di uno strumento di calcolo (API REST, CLI, file) e vuoi che materiali, sezioni e sollecitazioni abbiano un formato unico validato; -- generi tipi TypeScript o documentazione delle API dal JSON Schema dei modelli; -- importi risultati da SAP2000, ETABS o un altro programma e ti serve un posto dove dichiarare la mappatura degli assi e la convenzione di segno; -- ti serve la matrice di rigidezza di una trave rastremata o con mensole d'estremità (EI(x) a tratti) o la rigidezza condensata di una trave con nodi semirigidi da assemblare in un telaio. - -**Non usarlo per** - -- cataloghi di profili (non include tabelle IPE, HEB o W; gli esempi mostrano come calcolare le proprietà dalle dimensioni e confrontarle); -- verifiche normative: non ci sono resistenze di progetto, instabilità né classificazione delle sezioni, solo il contratto dei dati e gli strumenti di rigidezza; -- l'analisi di strutture complete: `fea` fornisce matrici di elemento, non assembla né risolve (l'esempio del portale lo fa in 40 righe con numpy per mostrare come); -- sezioni composte, non simmetriche o con ingobbamento impedito: `FrameSection` descrive proprietà, non geometria. - -## Installazione - -```bash -pip install git+https://github.com/jmurgass99/loadpath.git -``` - -Per lo sviluppo: `git clone`, `pip install -e ".[test]"`, `pytest -q` (8 file di test). Su Windows, se la console non è UTF-8, esegui gli esempi con `python -X utf8 examples/.py`. - -## Funzionalità - -| Funzionalità | Moduli | Norma o riferimento | Casi d'uso | -|---|---|---|---| -| Materiali: `StructuralMaterial` con E, ν, ρ obbligatori e resistenze facoltative secondo il tipo (acciaio, calcestruzzo, formato a freddo, alluminio, legno, muratura); validazione degli intervalli; JSON Schema | `materials` | EN 1993-1-1 §3.2.6; EN 10025-2 Tabella 7; EN 1992-1-1 Tabella 3.1 e §3.1.3; EN 14080; EN 1999-1-1 | [5 casi](#1-materiali) | -| Sezioni: `FrameSection` con A, I_x, I_y, J obbligatori e C_w, moduli elastici e plastici, dimensioni, classe della sezione e proprietà efficaci di Classe 4 facoltativi; alias di asse forte e debole; mappa degli assi con le sollecitazioni | `sections` | EN 10365 / EN 10210 (confronto con il catalogo); convenzione M3 forte / M2 debole di SAP2000 | [5 casi](#2-sezioni) | -| Sollecitazioni e contratto dei dati: `InternalForces` per stazione con trazione positiva e componenti nulle per default; JSON Lines; modelli composti con `$defs`; mappatura da SAP2000 | `forces`, Pydantic v2 | Convenzione degli assi locali 1-2-3 | [5 casi](#3-sollecitazioni-e-contratto-dei-dati) | -| Strumenti FEM: rigidezza 6×6 di trave 2D con EI(x) ed EA(x) con Simpson (flessibilità della mensola), molle rotazionali di estremità condensate (Monforton e Wu) | `fea.non_prismatic`, `fea.semi_rigid` | Monforton e Wu (1963); EN 1993-1-8 §5.2.2.5 (classificazione dei collegamenti); tabelle PCA delle costanti dei telai | [5 casi](#4-strumenti-per-elementi-finiti) | - -I 20 script di `examples/` sono stati eseguiti in un ambiente pulito prima della pubblicazione; il loro output è in `examples/output/` ed è quello riprodotto qui. - -## Casi d'uso - -Gli output sono riportati esattamente come li stampano gli script, in spagnolo. - -### 1. Materiali - -#### Catalogo dei materiali del progetto con valori di norma: S355, A992, C30/37, GL24h, alluminio 6061-T6 e muratura - -
Output reale di python examples/material_catalog_steel_concrete_timber_aluminum_from_codes.py - -```text -Tipos admitidos: ['steel', 'concrete', 'cold_formed_steel', 'aluminum', 'timber', 'masonry'] - - material tipo E [MPa] ν ρ [kg/m³] F_y F_u f_c ξ - S355 (EN 10025-2) steel 210000 0.30 7850 355 490 — 0.02 - A992 (ASTM) steel 200000 0.30 7850 345 450 — 0.02 - C30/37 (EN 1992-1-1) concrete 33000 0.20 2500 — — 30 0.05 - GL24h (EN 14080) timber 11500 0.40 420 — — — 0.01 - EN AW-6061 T6 (EN 1999-1-1) aluminum 70000 0.33 2700 240 260 — — - Ladrillo macizo M10 (EN 1996-1-1) masonry 5000 0.20 1800 — — 5 — - -Módulo de cortante del S355: G = E/(2·(1+ν)) = 80769 MPa (EN 1993-1-1: 81 000) -Peso propio del S355: 76.98 kN/m³; del C30/37: 24.52 kN/m³ - -Un consumidor comprueba con `material_type` qué resistencia leer: Fy/Fu en metales, fc en hormigón y fábrica; None es «no aplica», no «cero». -``` - -
- -#### Cosa rifiuta lo schema e perché: campo senza unità, E negativo, ν > 0,5, tipo sconosciuto, testo in un numero - -
Output reale di python examples/material_validation_rejects_missing_units_and_out_of_range.py - -```text -Válido: S275 E = 210000 MPa - - caso errores tipo → campo - E sin unidad (campo `E`) 1 extra_forbidden → E - E_MPa negativo 1 greater_than → E_MPa - ν = 0,7 (> 0,5) 1 less_than_equal → poisson - densidad 0 1 greater_than → density_kg_m3 - tipo `adamantium` 1 enum → material_type - F_y como texto 1 float_parsing → Fy_MPa - falta E_MPa 1 missing → E_MPa - -`extra='forbid'` convierte un campo sin unidad en un error en vez de en un número que viaja sin que nadie sepa si es MPa o kPa. -``` - -
- -#### Dal JSON Schema a un'interfaccia TypeScript con intervalli e unità nei commenti - -
Output reale di python examples/material_json_schema_to_typescript_interface.py - -```text -JSON Schema: 9 propiedades, obligatorias ['name', 'material_type', 'E_MPa', 'poisson', 'density_kg_m3'], enum MaterialType con 6 valores - -/** Propiedades físicas de un material estructural (unidades SI, MPa). */ -export interface StructuralMaterial { - name: string; - material_type: "steel" | "concrete" | "cold_formed_steel" | "aluminum" | "timber" | "masonry"; - /** Módulo de elasticidad (MPa). (> 0) */ - E_MPa: number; - /** Coeficiente de Poisson. (>= 0.0, <= 0.5) */ - poisson: number; - /** Densidad (kg/m³). (> 0) */ - density_kg_m3: number; - /** Límite elástico, acero (MPa). (> 0) */ - Fy_MPa?: number | null; - /** Resistencia última, acero (MPa). (> 0) */ - Fu_MPa?: number | null; - /** Resistencia a compresión, hormigón (MPa). (> 0) */ - fc_MPa?: number | null; - /** Razón de amortiguamiento (fracción del crítico). (>= 0.0, <= 1.0) */ - damping_ratio?: number | null; -} - -El mismo generador sirve para FrameSection e InternalForces; `?` marca las propiedades opcionales (None en Python). -``` - -
- -#### Calcestruzzi da C20/25 a C50/60 con E_cm = 22·(f_cm/10)^0,3 GPa (EN 1992-1-1 Tabella 3.1) e il fattore dell'aggregato - -
Output reale di python examples/material_concrete_Ecm_from_fck_EN1992_table_3_1.py - -```text - clase f_ck f_cm E_cm calc [GPa] Tabla 3.1 desvío - C20/25 cuarcita 20 28 29.96 30 -0.1 % - C25/30 cuarcita 25 33 31.48 31 +1.5 % - C30/37 cuarcita 30 38 32.84 33 -0.5 % - C35/45 cuarcita 35 43 34.08 34 +0.2 % - C40/50 cuarcita 40 48 35.22 35 +0.6 % - C45/55 cuarcita 45 53 36.28 36 +0.8 % - C50/60 cuarcita 50 58 37.28 37 +0.8 % - -Efecto del árido para C30/37 (§3.1.3(2)): - cuarcita E_cm = 32.8 GPa - caliza E_cm = 29.6 GPa - arenisca E_cm = 23.0 GPa - basalto E_cm = 39.4 GPa - -La fórmula reproduce la tabla con desvíos < 2 % (la tabla redondea al GPa; C25/30 es el peor caso, 31,5 frente a 31); el módulo que viaja en el esquema es el calculado, no el redondeado. -``` - -
- -#### Acciai da S235 a S460 con la tensione di snervamento dipendente dallo spessore (EN 10025-2 Tabella 7) - -
Output reale di python examples/material_steel_grades_EN10025_thickness_dependent_fy.py - -```text - grado t=10 t=20 t=50 t=70 (f_y en MPa, EN 10025-2) - S235 235 225 215 215 - S275 275 265 255 245 - S355 355 345 335 325 - S460 460 440 430 410 - -Chapa de 50 mm: EN 10025-2 frente a la simplificación de EN 1993-1-1 Tabla 3.1 (40 < t <= 80): - S235: 215 MPa frente a 215 MPa; usar el nominal de t <= 16 (235) sobrestimaría un 9 % - S275: 255 MPa frente a 255 MPa; usar el nominal de t <= 16 (275) sobrestimaría un 8 % - S355: 335 MPa frente a 335 MPa; usar el nominal de t <= 16 (355) sobrestimaría un 6 % - S460: 430 MPa frente a 410 MPa; usar el nominal de t <= 16 (460) sobrestimaría un 7 % - -Ejemplo serializado: {"name":"S355 t=50 mm","material_type":"steel","E_MPa":210000.0,"poisson":0.3,"density_kg_m3":7850.0,"Fy_MPa":335.0,"Fu_MPa":470.0,"fc_MPa":null,"damping_ratio":null} -``` - -
- -### 2. Sezioni - -#### Proprietà di un profilo a I dalle dimensioni e confronto con il catalogo: IPE 300 e HEB 300 - -L'area con la correzione dei raccordi (4 − π)·r² risulta esatta; I_x e W_pl restano il 4 % sotto il catalogo e la torsione il 20–25 %: I_t si prende dal catalogo. - -
Output reale di python examples/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.py - -```text -IPE 300: h = 300, b = 150, t_w = 7.1, t_f = 10.7, r = 15 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 53.8 7999 603 602 15.6 126 - catálogo 53.8 8356 604 628 20.1 126 - desvío +0.0 % -4.3 % -0.2 % -4.1 % -22.5 % +0.1 % - -HEB 300: h = 300, b = 300, t_w = 11.0, t_f = 19.0, r = 27 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 149.1 24187 8553 1790 148.8 1688 - catálogo 149.1 25170 8563 1869 185.0 1688 - desvío -0.0 % -3.9 % -0.1 % -4.2 % -19.6 % +0.0 % - -La corrección (4 − π)·r² deja el área exacta; en I_x y W_pl los acuerdos aportan ~4 % y en I_t un 20–25 %: I_y e I_w salen bien, I_t se toma de catálogo. -``` - -
- -#### Tubi RHS 200×100×8 e CHS 219,1×8 dalle dimensioni rispetto alla EN 10210 (Bredt per la torsione del RHS) - -
Output reale di python examples/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.py - -```text - perfil A [cm²] I_x [cm⁴] I_y [cm⁴] I_t [cm⁴] W_pl,x [cm³] - RHS 200x100x8 calculado 45.4 2306 758 1758 289 - EN 10210 44.8 2269 747 1804 283 - desvío +1.4 % +1.6 % +1.5 % -2.6 % +2.1 % - CHS 219.1x8 calculado 53.1 2960 2960 5919 357 - EN 10210 53.1 2960 2960 5919 357 - desvío -0.1 % -0.0 % -0.0 % +0.0 % -0.1 % - -El RHS sale 1–3 % alto por las esquinas vivas (el catálogo tiene r_o = 1,5·t); Bredt da la torsión a −2,6 %. El CHS es exacto porque la fórmula es la geometría real. -``` - -
- -#### Quale momento va con quale inerzia: M_z (forte) con I_x, M_y (debole) con I_y, e l'errore di scambiarli - -
Output reale di python examples/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.py - -```text -Mapa de ejes: - flexión fuerte M_z = 90 kN·m → I_x = 8.356e+07 mm⁴, S_x = 557000 mm³ - flexión débil M_y = 6 kN·m → I_y = 6.040e+06 mm⁴, S_y = 80500 mm³ - cortante V_z = 60 kN acompaña a M_z (plano fuerte); V_y acompañaría a M_y - -Tensiones elásticas [MPa]: N/A = -22.3, M_z/S_x = ±161.6, M_y/S_y = ±74.5 -Esquina más comprimida: σ = -258.4 MPa → utilización elástica 0.73 frente a f_y = 355 MPa - -Si se cruzan los ejes (M_z con S_y): σ = 1118 MPa, 6.9 veces la real. El esquema lo evita nombrando los ejes en los dos modelos. -``` - -
- -#### Minimo obbligatorio, facoltativi a None e rifiuti dello schema della sezione - -
Output reale di python examples/section_validation_optional_fields_and_json_schema_required.py - -```text -Sección mínima: {'name': 'barra genérica', 'A_mm2': 5000.0, 'Ix_mm4': 80000000.0, 'Iy_mm4': 6000000.0, 'J_mm4': 200000.0} - opcionales en None: ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] -Sección laminada con propiedades brutas: W14X90, 12 de 18 campos; sin rellenar: ['section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - -JSON Schema: obligatorias ['name', 'A_mm2', 'Ix_mm4', 'Iy_mm4', 'J_mm4']; opcionales ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - - sin J → missing en J_mm4 - área 0 → greater_than en A_mm2 - C_w negativo → greater_than_equal en Cw_mm6 - campo `peso` → extra_forbidden en peso - I_x como texto → float_parsing en Ix_mm4 - -C_w = 0 sí se admite (tubos cerrados); lo que se rechaza es el valor negativo. -``` - -
- -#### Peso al metro, raggi d'inerzia e snellezza della colonna ricavati dai campi dello schema - -
Output reale di python examples/section_weight_radius_of_gyration_and_slenderness_table.py - -```text -S355: λ_1 = π·sqrt(E/f_y) = 76.4; pilar L_cr = 4 m - - perfil peso [kg/m] i_x [mm] i_y [mm] λ_y λ̄_y régimen - IPE 300 42.2 124.6 33.5 119.4 1.56 esbelto - HEB 300 117.0 129.9 75.8 52.8 0.69 intermedio - HEA 200 42.3 82.8 49.8 80.3 1.05 intermedio - CHS 219.1x8 41.6 74.7 74.7 53.5 0.70 intermedio - -Pesos de catálogo: IPE 300 42,2; HEB 300 117; HEA 200 42,3; CHS 219,1×8 41,6 kg/m. El IPE 300 pesa lo mismo que el HEA 200 y es 2,6 veces más esbelto en su eje débil. -``` - -
- -### 3. Sollecitazioni e contratto dei dati - -#### Trave semplicemente appoggiata con carico uniforme: 13 stazioni come lista di `InternalForces` e inviluppo rispetto a q·L/2 e q·L²/8 - -
Output reale di python examples/internal_forces_simply_supported_beam_stations_udl_envelope.py - -```text - x [m] V_z [kN] M_z [kN·m] - 0.0 72.0 0.0 - 0.5 60.0 33.0 - 1.0 48.0 60.0 - 1.5 36.0 81.0 - 2.0 24.0 96.0 - 2.5 12.0 105.0 - 3.0 0.0 108.0 - 3.5 -12.0 105.0 - 4.0 -24.0 96.0 - 4.5 -36.0 81.0 - 5.0 -48.0 60.0 - 5.5 -60.0 33.0 - 6.0 -72.0 0.0 - -Envolvente: |V_z| máx = 72.0 kN en x = 0.0 m (q·L/2 = 72.0); M_z máx = 108.0 kN·m en x = 3.0 m (q·L²/8 = 108.0) -Serializado: 13 estaciones, 111 bytes cada una; ejemplo: {"member_id":"V-12","station_m":3.0,"N_kN":0.0,"Vy_kN":0.0,"Vz_kN":0.0,"T_kNm":0.0,"My_kNm":0.0,"Mz_kNm":108.0} -N, V_y, T y M_y quedan en 0,0 por defecto: un estado parcial es válido y explícito. -``` - -
- -#### Convenzione di segno dello sforzo normale (trazione positiva): colonna e tirante, tensione nelle fibre e cosa succede se l'importatore la inverte - -
Output reale di python examples/internal_forces_sign_convention_tension_positive_column_combined_stress.py - -```text - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) -850 180 50.3 -164.3 -164.3 0.46 - tirante T-1 (tracción) 300 20 32.0 8.2 0.0 0.00 - -Con el signo del axil invertido al importar (error típico entre programas): - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) 850 180 164.3 -50.3 -50.3 0.14 - tirante T-1 (tracción) -300 20 -8.2 -32.0 -32.0 0.09 - -Con el signo invertido la fibra comprimida del pilar pasa de −164 a −50 MPa (la compresión máxima, que gobierna el pandeo y la clase de la sección, cae a un tercio) y el tirante aparece comprimido; |σ|máx no cambia, así que una comprobación que solo mire el máximo absoluto no lo detecta. El esquema documenta el convenio en el campo y el importador debe declararlo, no adivinarlo. -``` - -
- -#### Esportare un telaio in JSON Lines, reimportarlo validando riga per riga e ricavare l'inviluppo per asta - -
Output reale di python examples/internal_forces_jsonl_export_import_and_member_envelopes.py - -```text -Exportadas 45 estaciones en 5486 bytes de JSONL (+1 línea corrupta de prueba) - -Reimportadas 45 válidas; rechazadas: [(46, ['greater_than_equal'])] - - barra |N| máx [kN] |V_z| máx [kN] |M_z| máx [kN·m] - P-1 420.0 25.0 50.0 - D-1 35.0 180.0 240.0 - P-2 420.0 25.0 50.0 -``` - -
- -#### Cosa rifiuta `InternalForces`, lo stato parziale valido e una mappatura di una riga di SAP2000 sullo schema - -
Output reale di python examples/internal_forces_validation_station_negative_extra_field_and_defaults.py - -```text -Estado parcial válido: {'member_id': 'C-1', 'station_m': 0.0, 'N_kN': 0.0, 'Vy_kN': 0.0, 'Vz_kN': 0.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 0.0} - - estación −1 m → 1 error(es): greater_than_equal en station_m - campo `Fx_kN` → 1 error(es): extra_forbidden en Fx_kN - sin member_id → 1 error(es): missing en member_id - M_z como texto → 1 error(es): float_parsing en Mz_kNm - dos errores a la vez → 3 error(es): greater_than_equal en station_m; float_parsing en Mz_kNm; extra_forbidden en extra - -Fila de SAP2000 mapeada: {'member_id': 'V-12', 'station_m': 3.0, 'N_kN': -12.0, 'Vy_kN': 0.0, 'Vz_kN': 48.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 72.0} -El mapeo vive en el importador (y debe declarar el convenio de signo de P); el esquema no acepta los nombres del programa de origen. -``` - -
- -#### Contratto composto materiale + sezione + sollecitazioni: JSON Schema con `$defs`, andata e ritorno e rifiuto annidato - -
Output reale di python examples/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.py - -```text -JSON Schema de MemberCheckInput: propiedades ['member_id', 'length_m', 'material', 'section', 'forces']; $defs = ['FrameSection', 'InternalForces', 'MaterialType', 'StructuralMaterial'] -Documento serializado: 1106 bytes; vuelta validada igual al original: True - - x [m] M_z [kN·m] σ = N/A + M/S [MPa] σ/f_y - 0.0 0.0 2.2 0.01 - 1.5 81.0 147.7 0.42 - 3.0 108.0 196.1 0.55 - 4.5 81.0 147.7 0.42 - 6.0 0.0 2.2 0.01 - -Rechazo anidado: [('greater_than', 'section.Ix_mm4')] -``` - -
- -### 4. Strumenti per elementi finiti - -#### La trave prismatica come caso particolare: Simpson ritrova la matrice 6×6 classica alla precisione di macchina - -
Output reale di python examples/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.py - -```text -IPE 300, L = 6 m: EI = 17,548 kN·m², EA = 1,130,010 kN - 12EI/L³ = 974.9 kN/m 6EI/L² = 2,924.6 kN 4EI/L = 11,698.4 kN·m/rad 2EI/L = 5,849.2 kN·m/rad - - n = 2 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 3.9e-17 - n = 20 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 1.5e-16 - -Matriz 6×6 (GDL [u_i, v_i, θ_i, u_j, v_j, θ_j]): -[[ 188335. 0. 0. -188335. 0. 0. ] - [ 0. 974.9 2924.6 0. -974.9 2924.6] - [ 0. 2924.6 11698.4 0. -2924.6 5849.2] - [-188335. 0. 0. 188335. 0. 0. ] - [ 0. -974.9 -2924.6 0. 974.9 -2924.6] - [ 0. 2924.6 5849.2 0. -2924.6 11698.4]] - -El mismo helper con EI(x) callable da la viga de sección variable: el caso prismático es la comprobación de que la integración está bien montada. -``` - -
- -#### Trave rastremata con EI lineare: flessibilità d'estremità in forma chiusa e convergenza del quarto ordine - -
Output reale di python examples/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.py - -```text -Forma cerrada: D₀₀ = 0.003355 m/kN, D₀₁ = 0.000792 rad/kN, D₁₁ = 0.000237 rad/(kN·m) -Cotas prismáticas de D₀₀: EI₀ → 0.004103, 2·EI₀ → 0.002052 - - n D₀₀ Simpson error rel. razón - 2 0.003419 1.90e-02 - 4 0.003361 1.57e-03 12.1 - 8 0.003356 1.08e-04 14.5 - 16 0.003355 6.93e-06 15.6 - 32 0.003355 4.36e-07 15.9 - 64 0.003355 2.73e-08 16.0 - -Rigidez rotacional en el extremo i (K[θ_i,θ_i]): acartelada 14,222 frente a prismática EI₀ 11,699 kN·m/rad (1.22×); en j: 20,071 (1.72×): el extremo con más inercia (j) es el más rígido. -Factor de transmisión K[θ_i,θ_j]/K[θ_j,θ_j] = 0.417 (prismática: 0,500). -``` - -
- -#### Traverso con mensole alle estremità: EI a tratti, rigidezza rotazionale e coefficiente di trasmissione rispetto alla trave prismatica - -
Output reale di python examples/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.py - -```text -Dintel L = 12 m, IPE 400: 4EI/L = 16,191 kN·m/rad, 2EI/L = 8,096, 12EI/L³ = 337 kN/m - - cartela K[θi,θi] [kN·m/rad] / prismática K[θi,θj] transmisión K[vi,vi] [kN/m] / prismática - ninguna (prismática) 16,191 1.00 8,096 0.500 337 1.00 - 2 m, canto ×2.0 28,195 1.74 17,969 0.637 641 1.90 - 3 m, canto ×2.0 37,434 2.31 25,659 0.685 876 2.60 - 2 m, canto ×1.5 23,243 1.44 13,739 0.591 514 1.52 - -La rigidez rotacional del nudo sube un 74 % con cartelas de 2 m que doblan el canto y el factor de transmisión pasa de 0.50 a 0.64: es lo que hace rentable la cartela frente a aumentar el perfil en toda la luz. -Quiebros de EI(x) en x = 2 y 10 m, error máximo frente a Simpson por tramos con n = 400: n = 20 con breakpoints_x 4.1e-04; n = 240 sin ellos (la tabla) 4.5e-08; n = 20 sin ellos 3.3e-03. -``` - -
- -#### Nodi semirigidi: classificazione EN 1993-1-8, fattore di incastro di Monforton e Wu e rigidezza condensata - -
Output reale di python examples/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.py - -```text -IPE 400, L = 8 m: E·I_b/L_b = 6,072 kN·m/rad; umbrales EN 1993-1-8: articulada <= 3,036, rígida >= 48,573 (arriostrado) / 151,791 (no arriostrado) - - S_j/(EI/L) S_j [kN·m/rad] γ k_θiθi/(4EI/L) Monforton-Wu arriostrado no arriostr. - 0.25 1,518 0.077 0.058 0.058 articulada articulada - 0.5 3,036 0.143 0.108 0.108 articulada articulada - 1 6,072 0.250 0.190 0.190 semirrígida semirrígida - 2 12,143 0.400 0.312 0.313 semirrígida semirrígida - 4 24,286 0.571 0.467 0.467 semirrígida semirrígida - 8 48,573 0.727 0.629 0.629 rígida semirrígida - 12 72,860 0.800 0.714 0.714 rígida semirrígida - 25 151,791 0.893 0.836 0.836 rígida rígida - 50 303,581 0.943 0.910 0.910 rígida rígida - 100 607,162 0.971 0.953 0.953 rígida rígida - inf inf 1.000 1.000 1.000 rígida rígida - -Una unión en el umbral «rígido» de pórtico arriostrado (S_j = 8·EI/L) conserva el 63 % de la rigidez rotacional 4EI/L de la viga; en el umbral no arriostrado (25·EI/L), el 84 %. -R va en kN·m/rad, las mismas unidades que K_b; pasar una rigidez relativa (k·L/EI) hace que todo salga articulado. -``` - -
- -#### Telaio assemblato con gli strumenti: rigidezza laterale con nodi rigidi, semirigidi e incernierati rispetto a 24·EI_c/H³ e 6·EI_c/H³ - -
Output reale di python examples/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.py - -```text -EI_c = 52,857 kN·m²; cotas: 24·EI_c/H³ = 10,149 kN/m, 6·EI_c/H³ = 2,537 kN/m - - dintel S_j k = F/Δ [kN/m] Δ(100 kN) [mm] k / k_rígido - IPE 400 ∞ 5,595 17.9 1.00 - IPE 400 25·EI_b/L_b 5,215 19.2 0.93 - IPE 400 8·EI_b/L_b 4,656 21.5 0.83 - IPE 400 2·EI_b/L_b 3,637 27.5 0.65 - IPE 400 0 (articulado) 2,528 39.6 0.45 - rígido (×10⁶) ∞ 9,987 10.0 1.79 - -Comprobación: dintel rígido → 9,987 frente a 24·EI_c/H³ = 10,149 (98.41 %); articulado → 2,528 frente a 6·EI_c/H³ = 2,537 (99.64 %) -Una unión en el umbral «rígido» de pórtico arriostrado (8·EI_b/L_b) pierde un 17 % de rigidez al desplome y en el umbral no arriostrado (25·EI_b/L_b) un 7 %: por eso EN 1993-1-8 exige el segundo en pórticos no arriostrados. -``` - -
- -## Verifica - -- Contratto dei dati: validazione dei campi obbligatori, degli intervalli (E > 0, 0 ≤ ν ≤ 0,5, stazione ≥ 0, C_w ≥ 0), `extra="forbid"`, enum dei tipi, andata e ritorno `model_dump` → `model_validate` e `required` del JSON Schema per i tre modelli (`tests/test_materials.py`, `test_sections.py`, `test_forces.py`). -- FEM: la trave prismatica si ritrova alla precisione di macchina; la trave a EI lineare riproduce la flessibilità in forma chiusa (D₀₀ = (4·ln 2 − 5/2)·L³/EI₀, D₀₁ = (2·ln 2 − 1)·L²/EI₀, D₁₁ = ln 2·L/EI₀) con convergenza del quarto ordine; le molle d'estremità riproducono Monforton e Wu (1963) con molle uguali e diverse e i limiti rigido (4EI/L) e incernierato (3EI/L) (`tests/test_fea_closed_form_golden.py`, `test_fea_non_prismatic.py`, `test_fea_semi_rigid.py`). -- Gli esempi confrontano le proprietà calcolate dalle dimensioni con i cataloghi EN 10365 ed EN 10210, la formula di E_cm con la Tabella 3.1 della EN 1992-1-1 e il telaio assemblato con i limiti 24·EI_c/H³ e 6·EI_c/H³. - -## Limitazioni - -- Non c'è un catalogo di profili né di materiali: il pacchetto valida ciò che riceve, non lo completa. -- `FrameSection` non distingue le sezioni di classe 4 né memorizza la geometria (ali, anime); le proprietà efficaci sono responsabilità del produttore del dato. -- `InternalForces` è una stazione di asta; non modella sollecitazioni di piastra, appoggi né combinazioni (l'identificativo della combinazione va in `member_id` o nel modello composto del consumatore). -- `beam_stiffness_2d` è flessione piana con le ipotesi di Euler-Bernoulli (senza deformazione a taglio) ed EI(x) fornito dall'utente; con EI a tratti, passa i salti in `breakpoints_x` (Simpson a tratti) o usa un n sufficiente perché cadano vicino a un nodo di Simpson. -- `semi_rigid_bending_stiffness` riceve R nelle unità di K_b (kN·m/rad se K_b è in kN e m); passare una rigidezza relativa k·L/EI fa risultare tutto incernierato. -- Le convenzioni degli assi (x forte, M_z forte) sono quelle dell'ecosistema Loadpath e di SAP2000; altri programmi ne usano altre e la mappatura spetta all'importatore. - -## Norme e riferimenti - -- EN 1993-1-1:2005 §3.2.6 (E, G, ν); EN 10025-2 Tabella 7 (f_y in funzione dello spessore); EN 1993-1-8 §5.2.2.5 (classificazione dei collegamenti per rigidezza). -- EN 1992-1-1:2004 Tabella 3.1 e §3.1.3 (E_cm e aggregato); EN 1991-1-1 Tabella A.1 (pesi per unità di volume). -- EN 10365 ed EN 10210 (dimensioni e proprietà dei profili, usate come confronto). -- Monforton, G. R. e Wu, T. S. (1963), Matrix analysis of semi-rigidly connected frames, ASCE J. Struct. Div. 89(6). -- PCA, Handbook of Frame Constants (costanti di aste con mensole d'estremità). -- Pydantic v2 e JSON Schema 2020-12. - -## Licenza - -MIT. Vedi [LICENSE](LICENSE). - ---- - -Parte di **Loadpath**, la raccolta di librerie strutturali open source di [Jesús Murgas](https://github.com/jmurgass99). diff --git a/README.md b/README.md index a1fc98e..c76a8bf 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,17 @@ -# loadpath — contrato de datos estructurales (materiales, secciones, esfuerzos) en Pydantic v2 y ayudas FEA de barra +# loadpath -**Español** · [English](README.en.md) · [Português](README.pt.md) · [Italiano](README.it.md) +**Español** · [English](README.en.md) -Tres modelos estrictos que viajan entre programas de cálculo, comprobadores, exportadores e interfaces: `StructuralMaterial`, `FrameSection` e `InternalForces`, con unidades SI en el nombre de cada campo, `extra="forbid"` y JSON Schema para generar tipos en otros lenguajes. Más dos ayudas de elementos finitos de barra: la matriz de rigidez 2D de una viga de sección variable (EI(x), EA(x) por Simpson) y la condensación de resortes rotacionales de extremo para nudos semirrígidos. Dependencias: `pydantic` y `numpy`. +Modelos de datos estructurales (material, sección, esfuerzos) en Pydantic v2, y dos ayudas de rigidez de barra. [![CI](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml/badge.svg)](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml) -[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/) -[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) +[![Licencia: MIT](https://img.shields.io/badge/licencia-MIT-green)](LICENSE) -## Qué problema resuelve +## Qué hace -Entre un programa de análisis, un comprobador de secciones y un informe, los datos cruzan tres fronteras y en cada una se pierden unidades, ejes y convenios de signo: el momento fuerte se llama M3 en un sitio y M_y en otro, el axil es positivo en compresión aquí y en tracción allá, y un `E = 210` no dice si son GPa o MPa. `loadpath` fija un único formato con la unidad en el nombre del campo, los ejes documentados en los dos modelos que los comparten y validación en la entrada, de modo que un dato mal formado se rechaza con la ruta del campo en vez de propagarse como un número. - -**Úsalo cuando** - -- definas la entrada o la salida de una herramienta de cálculo (API REST, CLI, fichero) y quieras que materiales, secciones y esfuerzos tengan un único formato validado; -- generes tipos TypeScript o documentación de API a partir del JSON Schema de los modelos; -- importes resultados de SAP2000, ETABS u otro programa y necesites un sitio donde declarar el mapeo de ejes y el convenio de signo; -- necesites la matriz de rigidez de una viga acartelada o con cartelas (EI(x) a trozos) o la rigidez condensada de una viga con nudos semirrígidos para montarla en un pórtico. - -**No lo uses para** - -- catálogos de perfiles (no incluye tablas de IPE, HEB o W; los ejemplos muestran cómo calcular las propiedades desde las dimensiones y contrastarlas); -- comprobaciones de norma: no hay resistencias de diseño, pandeo ni clasificación de secciones, solo el contrato de datos y las ayudas de rigidez; -- análisis de estructuras completas: `fea` da matrices de elemento, no ensambla ni resuelve (el ejemplo del pórtico lo hace en 40 líneas con numpy para enseñar cómo); -- secciones compuestas, no simétricas o con alabeo restringido: `FrameSection` describe propiedades, no geometría. +- `StructuralMaterial`, `FrameSection` e `InternalForces`: tres modelos validados, con unidades SI en el nombre de cada campo y sin campos desconocidos. +- JSON Schema de los modelos, para generar tipos en otros lenguajes (hay un ejemplo que lo convierte a TypeScript). +- Matriz de rigidez de una viga acartelada o con cartelas (EI(x) a trozos) y rigidez condensada de una viga con nudos semirrígidos. ## Instalación @@ -32,494 +19,39 @@ Entre un programa de análisis, un comprobador de secciones y un informe, los da pip install git+https://github.com/jmurgass99/loadpath.git ``` -Para desarrollo: `git clone`, `pip install -e ".[test]"`, `pytest -q` (8 ficheros de tests). En Windows, si la consola no es UTF-8, ejecuta los ejemplos con `python -X utf8 examples/.py`. - -## Capacidades - -| Capacidad | Módulos | Norma o referencia | Casos de uso | -|---|---|---|---| -| Materiales: `StructuralMaterial` con E, ν, ρ obligatorios y resistencias opcionales según el tipo (acero, hormigón, conformado en frío, aluminio, madera, fábrica); validación de rangos; JSON Schema | `materials` | EN 1993-1-1 §3.2.6; EN 10025-2 Tabla 7; EN 1992-1-1 Tabla 3.1 y §3.1.3; EN 14080; EN 1999-1-1 | [5 casos](#1-materiales) | -| Secciones: `FrameSection` con A, I_x, I_y, J obligatorios y C_w, módulos elásticos y plásticos, dimensiones, clase de sección y propiedades eficaces de Clase 4 opcionales; alias de eje fuerte y débil; mapa de ejes con los esfuerzos | `sections` | EN 10365 / EN 10210 (contraste de catálogo); convenio M3 fuerte / M2 débil de SAP2000 | [5 casos](#2-secciones) | -| Esfuerzos y contrato de datos: `InternalForces` por estación con tracción positiva y componentes a cero por defecto; JSON Lines; modelos compuestos con `$defs`; mapeo desde SAP2000 | `forces`, Pydantic v2 | Convenio de ejes locales 1-2-3 | [5 casos](#3-esfuerzos-y-contrato-de-datos) | -| Ayudas FEA: rigidez 6×6 de viga 2D con EI(x) y EA(x) por Simpson (flexibilidad del voladizo), resortes rotacionales de extremo condensados (Monforton y Wu) | `fea.non_prismatic`, `fea.semi_rigid` | Monforton y Wu (1963); EN 1993-1-8 §5.2.2.5 (clasificación de uniones); tablas PCA de constantes de pórticos | [5 casos](#4-ayudas-de-elementos-finitos) | - -Los 20 scripts de `examples/` se ejecutaron en un entorno limpio antes de publicarse; su salida está en `examples/output/` y es la que se reproduce aquí. - -## Casos de uso - -### 1. Materiales - -#### Catálogo de materiales del proyecto con valores de norma: S355, A992, C30/37, GL24h, aluminio 6061-T6 y fábrica - -
Salida real de python examples/material_catalog_steel_concrete_timber_aluminum_from_codes.py - -```text -Tipos admitidos: ['steel', 'concrete', 'cold_formed_steel', 'aluminum', 'timber', 'masonry'] - - material tipo E [MPa] ν ρ [kg/m³] F_y F_u f_c ξ - S355 (EN 10025-2) steel 210000 0.30 7850 355 490 — 0.02 - A992 (ASTM) steel 200000 0.30 7850 345 450 — 0.02 - C30/37 (EN 1992-1-1) concrete 33000 0.20 2500 — — 30 0.05 - GL24h (EN 14080) timber 11500 0.40 420 — — — 0.01 - EN AW-6061 T6 (EN 1999-1-1) aluminum 70000 0.33 2700 240 260 — — - Ladrillo macizo M10 (EN 1996-1-1) masonry 5000 0.20 1800 — — 5 — - -Módulo de cortante del S355: G = E/(2·(1+ν)) = 80769 MPa (EN 1993-1-1: 81 000) -Peso propio del S355: 76.98 kN/m³; del C30/37: 24.52 kN/m³ - -Un consumidor comprueba con `material_type` qué resistencia leer: Fy/Fu en metales, fc en hormigón y fábrica; None es «no aplica», no «cero». -``` - -
- -#### Qué rechaza el esquema y por qué: campo sin unidad, E negativo, ν > 0,5, tipo desconocido, texto en un número - -
Salida real de python examples/material_validation_rejects_missing_units_and_out_of_range.py - -```text -Válido: S275 E = 210000 MPa - - caso errores tipo → campo - E sin unidad (campo `E`) 1 extra_forbidden → E - E_MPa negativo 1 greater_than → E_MPa - ν = 0,7 (> 0,5) 1 less_than_equal → poisson - densidad 0 1 greater_than → density_kg_m3 - tipo `adamantium` 1 enum → material_type - F_y como texto 1 float_parsing → Fy_MPa - falta E_MPa 1 missing → E_MPa - -`extra='forbid'` convierte un campo sin unidad en un error en vez de en un número que viaja sin que nadie sepa si es MPa o kPa. -``` - -
- -#### Del JSON Schema a una interfaz TypeScript con los rangos y las unidades en los comentarios - -
Salida real de python examples/material_json_schema_to_typescript_interface.py - -```text -JSON Schema: 9 propiedades, obligatorias ['name', 'material_type', 'E_MPa', 'poisson', 'density_kg_m3'], enum MaterialType con 6 valores - -/** Propiedades físicas de un material estructural (unidades SI, MPa). */ -export interface StructuralMaterial { - name: string; - material_type: "steel" | "concrete" | "cold_formed_steel" | "aluminum" | "timber" | "masonry"; - /** Módulo de elasticidad (MPa). (> 0) */ - E_MPa: number; - /** Coeficiente de Poisson. (>= 0.0, <= 0.5) */ - poisson: number; - /** Densidad (kg/m³). (> 0) */ - density_kg_m3: number; - /** Límite elástico, acero (MPa). (> 0) */ - Fy_MPa?: number | null; - /** Resistencia última, acero (MPa). (> 0) */ - Fu_MPa?: number | null; - /** Resistencia a compresión, hormigón (MPa). (> 0) */ - fc_MPa?: number | null; - /** Razón de amortiguamiento (fracción del crítico). (>= 0.0, <= 1.0) */ - damping_ratio?: number | null; -} - -El mismo generador sirve para FrameSection e InternalForces; `?` marca las propiedades opcionales (None en Python). -``` - -
- -#### Hormigones C20/25 a C50/60 con E_cm = 22·(f_cm/10)^0,3 GPa (EN 1992-1-1 Tabla 3.1) y el factor del árido - -
Salida real de python examples/material_concrete_Ecm_from_fck_EN1992_table_3_1.py - -```text - clase f_ck f_cm E_cm calc [GPa] Tabla 3.1 desvío - C20/25 cuarcita 20 28 29.96 30 -0.1 % - C25/30 cuarcita 25 33 31.48 31 +1.5 % - C30/37 cuarcita 30 38 32.84 33 -0.5 % - C35/45 cuarcita 35 43 34.08 34 +0.2 % - C40/50 cuarcita 40 48 35.22 35 +0.6 % - C45/55 cuarcita 45 53 36.28 36 +0.8 % - C50/60 cuarcita 50 58 37.28 37 +0.8 % - -Efecto del árido para C30/37 (§3.1.3(2)): - cuarcita E_cm = 32.8 GPa - caliza E_cm = 29.6 GPa - arenisca E_cm = 23.0 GPa - basalto E_cm = 39.4 GPa - -La fórmula reproduce la tabla con desvíos < 2 % (la tabla redondea al GPa; C25/30 es el peor caso, 31,5 frente a 31); el módulo que viaja en el esquema es el calculado, no el redondeado. -``` - -
- -#### Aceros S235 a S460 con el límite elástico que depende del espesor (EN 10025-2 Tabla 7) - -
Salida real de python examples/material_steel_grades_EN10025_thickness_dependent_fy.py - -```text - grado t=10 t=20 t=50 t=70 (f_y en MPa, EN 10025-2) - S235 235 225 215 215 - S275 275 265 255 245 - S355 355 345 335 325 - S460 460 440 430 410 - -Chapa de 50 mm: EN 10025-2 frente a la simplificación de EN 1993-1-1 Tabla 3.1 (40 < t <= 80): - S235: 215 MPa frente a 215 MPa; usar el nominal de t <= 16 (235) sobrestimaría un 9 % - S275: 255 MPa frente a 255 MPa; usar el nominal de t <= 16 (275) sobrestimaría un 8 % - S355: 335 MPa frente a 335 MPa; usar el nominal de t <= 16 (355) sobrestimaría un 6 % - S460: 430 MPa frente a 410 MPa; usar el nominal de t <= 16 (460) sobrestimaría un 7 % - -Ejemplo serializado: {"name":"S355 t=50 mm","material_type":"steel","E_MPa":210000.0,"poisson":0.3,"density_kg_m3":7850.0,"Fy_MPa":335.0,"Fu_MPa":470.0,"fc_MPa":null,"damping_ratio":null} -``` - -
- -### 2. Secciones - -#### Propiedades de un perfil en I desde sus dimensiones y contraste con el catálogo: IPE 300 y HEB 300 - -El área con la corrección de acuerdos (4 − π)·r² sale exacta; I_x y W_pl quedan un 4 % por debajo del catálogo y la torsión un 20–25 %: I_t se toma del catálogo. - -
Salida real de python examples/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.py - -```text -IPE 300: h = 300, b = 150, t_w = 7.1, t_f = 10.7, r = 15 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 53.8 7999 603 602 15.6 126 - catálogo 53.8 8356 604 628 20.1 126 - desvío +0.0 % -4.3 % -0.2 % -4.1 % -22.5 % +0.1 % - -HEB 300: h = 300, b = 300, t_w = 11.0, t_f = 19.0, r = 27 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 149.1 24187 8553 1790 148.8 1688 - catálogo 149.1 25170 8563 1869 185.0 1688 - desvío -0.0 % -3.9 % -0.1 % -4.2 % -19.6 % +0.0 % - -La corrección (4 − π)·r² deja el área exacta; en I_x y W_pl los acuerdos aportan ~4 % y en I_t un 20–25 %: I_y e I_w salen bien, I_t se toma de catálogo. -``` - -
- -#### Tubos RHS 200×100×8 y CHS 219,1×8 desde sus dimensiones frente a EN 10210 (Bredt para la torsión del RHS) - -
Salida real de python examples/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.py - -```text - perfil A [cm²] I_x [cm⁴] I_y [cm⁴] I_t [cm⁴] W_pl,x [cm³] - RHS 200x100x8 calculado 45.4 2306 758 1758 289 - EN 10210 44.8 2269 747 1804 283 - desvío +1.4 % +1.6 % +1.5 % -2.6 % +2.1 % - CHS 219.1x8 calculado 53.1 2960 2960 5919 357 - EN 10210 53.1 2960 2960 5919 357 - desvío -0.1 % -0.0 % -0.0 % +0.0 % -0.1 % - -El RHS sale 1–3 % alto por las esquinas vivas (el catálogo tiene r_o = 1,5·t); Bredt da la torsión a −2,6 %. El CHS es exacto porque la fórmula es la geometría real. -``` - -
- -#### Qué momento va con qué inercia: M_z (fuerte) con I_x, M_y (débil) con I_y, y el error de cruzarlos - -
Salida real de python examples/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.py - -```text -Mapa de ejes: - flexión fuerte M_z = 90 kN·m → I_x = 8.356e+07 mm⁴, S_x = 557000 mm³ - flexión débil M_y = 6 kN·m → I_y = 6.040e+06 mm⁴, S_y = 80500 mm³ - cortante V_z = 60 kN acompaña a M_z (plano fuerte); V_y acompañaría a M_y - -Tensiones elásticas [MPa]: N/A = -22.3, M_z/S_x = ±161.6, M_y/S_y = ±74.5 -Esquina más comprimida: σ = -258.4 MPa → utilización elástica 0.73 frente a f_y = 355 MPa - -Si se cruzan los ejes (M_z con S_y): σ = 1118 MPa, 6.9 veces la real. El esquema lo evita nombrando los ejes en los dos modelos. -``` - -
- -#### Mínimo obligatorio, opcionales en None y rechazos del esquema de sección - -
Salida real de python examples/section_validation_optional_fields_and_json_schema_required.py - -```text -Sección mínima: {'name': 'barra genérica', 'A_mm2': 5000.0, 'Ix_mm4': 80000000.0, 'Iy_mm4': 6000000.0, 'J_mm4': 200000.0} - opcionales en None: ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] -Sección laminada con propiedades brutas: W14X90, 12 de 18 campos; sin rellenar: ['section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - -JSON Schema: obligatorias ['name', 'A_mm2', 'Ix_mm4', 'Iy_mm4', 'J_mm4']; opcionales ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - - sin J → missing en J_mm4 - área 0 → greater_than en A_mm2 - C_w negativo → greater_than_equal en Cw_mm6 - campo `peso` → extra_forbidden en peso - I_x como texto → float_parsing en Ix_mm4 - -C_w = 0 sí se admite (tubos cerrados); lo que se rechaza es el valor negativo. -``` - -
- -#### Peso por metro, radios de giro y esbeltez de pilar derivados de los campos del esquema - -
Salida real de python examples/section_weight_radius_of_gyration_and_slenderness_table.py - -```text -S355: λ_1 = π·sqrt(E/f_y) = 76.4; pilar L_cr = 4 m - - perfil peso [kg/m] i_x [mm] i_y [mm] λ_y λ̄_y régimen - IPE 300 42.2 124.6 33.5 119.4 1.56 esbelto - HEB 300 117.0 129.9 75.8 52.8 0.69 intermedio - HEA 200 42.3 82.8 49.8 80.3 1.05 intermedio - CHS 219.1x8 41.6 74.7 74.7 53.5 0.70 intermedio - -Pesos de catálogo: IPE 300 42,2; HEB 300 117; HEA 200 42,3; CHS 219,1×8 41,6 kg/m. El IPE 300 pesa lo mismo que el HEA 200 y es 2,6 veces más esbelto en su eje débil. -``` - -
- -### 3. Esfuerzos y contrato de datos - -#### Viga biapoyada con carga uniforme: 13 estaciones como lista de `InternalForces` y envolvente frente a q·L/2 y q·L²/8 - -
Salida real de python examples/internal_forces_simply_supported_beam_stations_udl_envelope.py - -```text - x [m] V_z [kN] M_z [kN·m] - 0.0 72.0 0.0 - 0.5 60.0 33.0 - 1.0 48.0 60.0 - 1.5 36.0 81.0 - 2.0 24.0 96.0 - 2.5 12.0 105.0 - 3.0 0.0 108.0 - 3.5 -12.0 105.0 - 4.0 -24.0 96.0 - 4.5 -36.0 81.0 - 5.0 -48.0 60.0 - 5.5 -60.0 33.0 - 6.0 -72.0 0.0 - -Envolvente: |V_z| máx = 72.0 kN en x = 0.0 m (q·L/2 = 72.0); M_z máx = 108.0 kN·m en x = 3.0 m (q·L²/8 = 108.0) -Serializado: 13 estaciones, 111 bytes cada una; ejemplo: {"member_id":"V-12","station_m":3.0,"N_kN":0.0,"Vy_kN":0.0,"Vz_kN":0.0,"T_kNm":0.0,"My_kNm":0.0,"Mz_kNm":108.0} -N, V_y, T y M_y quedan en 0,0 por defecto: un estado parcial es válido y explícito. -``` - -
- -#### Convenio de signo del axil (tracción positiva): pilar y tirante, tensión en las fibras y qué pasa si el importador lo invierte - -
Salida real de python examples/internal_forces_sign_convention_tension_positive_column_combined_stress.py - -```text - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) -850 180 50.3 -164.3 -164.3 0.46 - tirante T-1 (tracción) 300 20 32.0 8.2 0.0 0.00 - -Con el signo del axil invertido al importar (error típico entre programas): - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) 850 180 164.3 -50.3 -50.3 0.14 - tirante T-1 (tracción) -300 20 -8.2 -32.0 -32.0 0.09 - -Con el signo invertido la fibra comprimida del pilar pasa de −164 a −50 MPa (la compresión máxima, que gobierna el pandeo y la clase de la sección, cae a un tercio) y el tirante aparece comprimido; |σ|máx no cambia, así que una comprobación que solo mire el máximo absoluto no lo detecta. El esquema documenta el convenio en el campo y el importador debe declararlo, no adivinarlo. -``` - -
- -#### Exportar un pórtico a JSON Lines, reimportar validando línea a línea y sacar la envolvente por barra - -
Salida real de python examples/internal_forces_jsonl_export_import_and_member_envelopes.py - -```text -Exportadas 45 estaciones en 5486 bytes de JSONL (+1 línea corrupta de prueba) - -Reimportadas 45 válidas; rechazadas: [(46, ['greater_than_equal'])] - - barra |N| máx [kN] |V_z| máx [kN] |M_z| máx [kN·m] - P-1 420.0 25.0 50.0 - D-1 35.0 180.0 240.0 - P-2 420.0 25.0 50.0 -``` - -
- -#### Qué rechaza `InternalForces`, el estado parcial válido y un mapeo de fila de SAP2000 al esquema - -
Salida real de python examples/internal_forces_validation_station_negative_extra_field_and_defaults.py - -```text -Estado parcial válido: {'member_id': 'C-1', 'station_m': 0.0, 'N_kN': 0.0, 'Vy_kN': 0.0, 'Vz_kN': 0.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 0.0} - - estación −1 m → 1 error(es): greater_than_equal en station_m - campo `Fx_kN` → 1 error(es): extra_forbidden en Fx_kN - sin member_id → 1 error(es): missing en member_id - M_z como texto → 1 error(es): float_parsing en Mz_kNm - dos errores a la vez → 3 error(es): greater_than_equal en station_m; float_parsing en Mz_kNm; extra_forbidden en extra - -Fila de SAP2000 mapeada: {'member_id': 'V-12', 'station_m': 3.0, 'N_kN': -12.0, 'Vy_kN': 0.0, 'Vz_kN': 48.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 72.0} -El mapeo vive en el importador (y debe declarar el convenio de signo de P); el esquema no acepta los nombres del programa de origen. -``` - -
- -#### Contrato compuesto material + sección + esfuerzos: JSON Schema con `$defs`, ida y vuelta y rechazo anidado - -
Salida real de python examples/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.py - -```text -JSON Schema de MemberCheckInput: propiedades ['member_id', 'length_m', 'material', 'section', 'forces']; $defs = ['FrameSection', 'InternalForces', 'MaterialType', 'StructuralMaterial'] -Documento serializado: 1106 bytes; vuelta validada igual al original: True - - x [m] M_z [kN·m] σ = N/A + M/S [MPa] σ/f_y - 0.0 0.0 2.2 0.01 - 1.5 81.0 147.7 0.42 - 3.0 108.0 196.1 0.55 - 4.5 81.0 147.7 0.42 - 6.0 0.0 2.2 0.01 - -Rechazo anidado: [('greater_than', 'section.Ix_mm4')] -``` - -
- -### 4. Ayudas de elementos finitos - -#### La viga prismática como caso particular: Simpson recupera la matriz 6×6 clásica a precisión de máquina - -
Salida real de python examples/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.py - -```text -IPE 300, L = 6 m: EI = 17,548 kN·m², EA = 1,130,010 kN - 12EI/L³ = 974.9 kN/m 6EI/L² = 2,924.6 kN 4EI/L = 11,698.4 kN·m/rad 2EI/L = 5,849.2 kN·m/rad - - n = 2 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 3.9e-17 - n = 20 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 1.5e-16 - -Matriz 6×6 (GDL [u_i, v_i, θ_i, u_j, v_j, θ_j]): -[[ 188335. 0. 0. -188335. 0. 0. ] - [ 0. 974.9 2924.6 0. -974.9 2924.6] - [ 0. 2924.6 11698.4 0. -2924.6 5849.2] - [-188335. 0. 0. 188335. 0. 0. ] - [ 0. -974.9 -2924.6 0. 974.9 -2924.6] - [ 0. 2924.6 5849.2 0. -2924.6 11698.4]] - -El mismo helper con EI(x) callable da la viga de sección variable: el caso prismático es la comprobación de que la integración está bien montada. -``` - -
- -#### Viga acartelada con EI lineal: flexibilidad del extremo en forma cerrada y convergencia de orden 4 - -
Salida real de python examples/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.py - -```text -Forma cerrada: D₀₀ = 0.003355 m/kN, D₀₁ = 0.000792 rad/kN, D₁₁ = 0.000237 rad/(kN·m) -Cotas prismáticas de D₀₀: EI₀ → 0.004103, 2·EI₀ → 0.002052 - - n D₀₀ Simpson error rel. razón - 2 0.003419 1.90e-02 - 4 0.003361 1.57e-03 12.1 - 8 0.003356 1.08e-04 14.5 - 16 0.003355 6.93e-06 15.6 - 32 0.003355 4.36e-07 15.9 - 64 0.003355 2.73e-08 16.0 - -Rigidez rotacional en el extremo i (K[θ_i,θ_i]): acartelada 14,222 frente a prismática EI₀ 11,699 kN·m/rad (1.22×); en j: 20,071 (1.72×): el extremo con más inercia (j) es el más rígido. -Factor de transmisión K[θ_i,θ_j]/K[θ_j,θ_j] = 0.417 (prismática: 0,500). -``` - -
+## Ejemplo -#### Dintel con cartelas en los extremos: EI a trozos, rigidez rotacional y factor de transmisión frente a la viga prismática +```python +from loadpath import StructuralMaterial -
Salida real de python examples/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.py +acero = StructuralMaterial( + name="S355", material_type="steel", + E_MPa=210_000, poisson=0.3, density_kg_m3=7850, Fy_MPa=355, Fu_MPa=490, +) +print(acero.model_json_schema()["properties"].keys()) -```text -Dintel L = 12 m, IPE 400: 4EI/L = 16,191 kN·m/rad, 2EI/L = 8,096, 12EI/L³ = 337 kN/m - - cartela K[θi,θi] [kN·m/rad] / prismática K[θi,θj] transmisión K[vi,vi] [kN/m] / prismática - ninguna (prismática) 16,191 1.00 8,096 0.500 337 1.00 - 2 m, canto ×2.0 28,195 1.74 17,969 0.637 641 1.90 - 3 m, canto ×2.0 37,434 2.31 25,659 0.685 876 2.60 - 2 m, canto ×1.5 23,243 1.44 13,739 0.591 514 1.52 - -La rigidez rotacional del nudo sube un 74 % con cartelas de 2 m que doblan el canto y el factor de transmisión pasa de 0.50 a 0.64: es lo que hace rentable la cartela frente a aumentar el perfil en toda la luz. -Quiebros de EI(x) en x = 2 y 10 m, error máximo frente a Simpson por tramos con n = 400: n = 20 con breakpoints_x 4.1e-04; n = 240 sin ellos (la tabla) 4.5e-08; n = 20 sin ellos 3.3e-03. -``` - -
- -#### Nudos semirrígidos: clasificación EN 1993-1-8, factor de fijación de Monforton y Wu y rigidez condensada - -
Salida real de python examples/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.py - -```text -IPE 400, L = 8 m: E·I_b/L_b = 6,072 kN·m/rad; umbrales EN 1993-1-8: articulada <= 3,036, rígida >= 48,573 (arriostrado) / 151,791 (no arriostrado) - - S_j/(EI/L) S_j [kN·m/rad] γ k_θiθi/(4EI/L) Monforton-Wu arriostrado no arriostr. - 0.25 1,518 0.077 0.058 0.058 articulada articulada - 0.5 3,036 0.143 0.108 0.108 articulada articulada - 1 6,072 0.250 0.190 0.190 semirrígida semirrígida - 2 12,143 0.400 0.312 0.313 semirrígida semirrígida - 4 24,286 0.571 0.467 0.467 semirrígida semirrígida - 8 48,573 0.727 0.629 0.629 rígida semirrígida - 12 72,860 0.800 0.714 0.714 rígida semirrígida - 25 151,791 0.893 0.836 0.836 rígida rígida - 50 303,581 0.943 0.910 0.910 rígida rígida - 100 607,162 0.971 0.953 0.953 rígida rígida - inf inf 1.000 1.000 1.000 rígida rígida - -Una unión en el umbral «rígido» de pórtico arriostrado (S_j = 8·EI/L) conserva el 63 % de la rigidez rotacional 4EI/L de la viga; en el umbral no arriostrado (25·EI/L), el 84 %. -R va en kN·m/rad, las mismas unidades que K_b; pasar una rigidez relativa (k·L/EI) hace que todo salga articulado. +StructuralMaterial(name="X", material_type="steel", E_MPa=-1, poisson=0.3, density_kg_m3=7850) +# pydantic.ValidationError (E_MPa debe ser positivo) ``` -
+Más casos en [`examples/`](examples). -#### Pórtico ensamblado con los helpers: rigidez al desplome con nudos rígidos, semirrígidos y articulados frente a 24·EI_c/H³ y 6·EI_c/H³ +## Qué no hace -
Salida real de python examples/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.py +- No incluye catálogo de perfiles ni de materiales: valida lo que recibe, no lo rellena. +- No hace comprobaciones de norma (resistencias, pandeo, clasificación de secciones). +- No ensambla ni resuelve estructuras: `fea` da matrices de elemento. +- `FrameSection` guarda propiedades, no geometría, y no distingue secciones de clase 4. -```text -EI_c = 52,857 kN·m²; cotas: 24·EI_c/H³ = 10,149 kN/m, 6·EI_c/H³ = 2,537 kN/m +El detalle está en [docs/LIMITACIONES.md](docs/LIMITACIONES.md). - dintel S_j k = F/Δ [kN/m] Δ(100 kN) [mm] k / k_rígido - IPE 400 ∞ 5,595 17.9 1.00 - IPE 400 25·EI_b/L_b 5,215 19.2 0.93 - IPE 400 8·EI_b/L_b 4,656 21.5 0.83 - IPE 400 2·EI_b/L_b 3,637 27.5 0.65 - IPE 400 0 (articulado) 2,528 39.6 0.45 - rígido (×10⁶) ∞ 9,987 10.0 1.79 +## Pruebas -Comprobación: dintel rígido → 9,987 frente a 24·EI_c/H³ = 10,149 (98.41 %); articulado → 2,528 frente a 6·EI_c/H³ = 2,537 (99.64 %) -Una unión en el umbral «rígido» de pórtico arriostrado (8·EI_b/L_b) pierde un 17 % de rigidez al desplome y en el umbral no arriostrado (25·EI_b/L_b) un 7 %: por eso EN 1993-1-8 exige el segundo en pórticos no arriostrados. +```bash +pip install -e ".[test]" +pytest -q ``` -
- -## Verificación - -- Contrato de datos: validación de campos obligatorios, rangos (E > 0, 0 ≤ ν ≤ 0,5, estación ≥ 0, C_w ≥ 0), `extra="forbid"`, enum de tipos, ida y vuelta `model_dump` → `model_validate` y `required` del JSON Schema para los tres modelos (`tests/test_materials.py`, `test_sections.py`, `test_forces.py`). -- FEA: la viga prismática se recupera a precisión de máquina; la viga de EI lineal reproduce la flexibilidad en forma cerrada (D₀₀ = (4·ln 2 − 5/2)·L³/EI₀, D₀₁ = (2·ln 2 − 1)·L²/EI₀, D₁₁ = ln 2·L/EI₀) con convergencia de orden 4; los resortes de extremo reproducen Monforton y Wu (1963) con resortes iguales y distintos y los límites rígido (4EI/L) y articulado (3EI/L) (`tests/test_fea_closed_form_golden.py`, `test_fea_non_prismatic.py`, `test_fea_semi_rigid.py`). -- Los ejemplos contrastan las propiedades calculadas desde dimensiones con los catálogos EN 10365 y EN 10210, la fórmula de E_cm con la Tabla 3.1 de EN 1992-1-1 y el pórtico ensamblado con las cotas 24·EI_c/H³ y 6·EI_c/H³. - -## Limitaciones - -- No hay catálogo de perfiles ni de materiales: el paquete valida lo que recibe, no lo rellena. -- `FrameSection` no distingue secciones de clase 4 ni almacena geometría (alas, almas); las propiedades eficaces son responsabilidad del productor. -- `InternalForces` es una estación de barra; no modela esfuerzos de placa, apoyos ni combinaciones (el identificador de combinación va en `member_id` o en el modelo compuesto del consumidor). -- `beam_stiffness_2d` es flexión plana con hipótesis de Euler-Bernoulli (sin deformación por cortante) y EI(x) dado por el usuario; con EI a trozos, pasa los quiebros en `breakpoints_x` (Simpson por tramos) o usa un n suficiente para que caigan cerca de un nodo de Simpson. -- `semi_rigid_bending_stiffness` recibe R en las unidades de K_b (kN·m/rad si K_b está en kN y m); pasar una rigidez relativa k·L/EI hace que todo salga articulado. -- Los convenios de ejes (x fuerte, M_z fuerte) son los del ecosistema Loadpath y de SAP2000; otros programas usan otros y el mapeo es tarea del importador. - -## Normas y referencias - -- EN 1993-1-1:2005 §3.2.6 (E, G, ν); EN 10025-2 Tabla 7 (f_y por espesor); EN 1993-1-8 §5.2.2.5 (clasificación de uniones por rigidez). -- EN 1992-1-1:2004 Tabla 3.1 y §3.1.3 (E_cm y árido); EN 1991-1-1 Tabla A.1 (densidades). -- EN 10365 y EN 10210 (dimensiones y propiedades de perfiles, usados como contraste). -- Monforton, G. R. y Wu, T. S. (1963), Matrix analysis of semi-rigidly connected frames, ASCE J. Struct. Div. 89(6). -- PCA, Handbook of Frame Constants (constantes de barras acarteladas). -- Pydantic v2 y JSON Schema 2020-12. - ## Licencia -MIT. Consulta [LICENSE](LICENSE). - ---- - -Parte de **Loadpath**, la colección de librerías estructurales de código abierto de [Jesús Murgas](https://github.com/jmurgass99). +MIT. Ver [LICENSE](LICENSE). Autor: [Jesús Murgas](https://github.com/jmurgass99). diff --git a/README.pt.md b/README.pt.md deleted file mode 100644 index 5897009..0000000 --- a/README.pt.md +++ /dev/null @@ -1,527 +0,0 @@ -# loadpath — contrato de dados estruturais (materiais, secções, esforços) em Pydantic v2 e ajudas FEA de barra - -[Español](README.md) · [English](README.en.md) · **Português** · [Italiano](README.it.md) - -Três modelos estritos que circulam entre programas de cálculo, verificadores, exportadores e interfaces: `StructuralMaterial`, `FrameSection` e `InternalForces`, com unidades SI no nome de cada campo, `extra="forbid"` e JSON Schema para gerar tipos noutras linguagens. Mais duas ajudas de elementos finitos de barra: a matriz de rigidez 2D de uma viga de secção variável (EI(x), EA(x) por Simpson) e a condensação de molas rotacionais de extremidade para ligações semirrígidas. Dependências: `pydantic` e `numpy`. - -[![CI](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml/badge.svg)](https://github.com/jmurgass99/loadpath/actions/workflows/ci.yml) -[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/) -[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE) - -## Que problema resolve - -Entre um programa de análise, um verificador de secções e um relatório, os dados atravessam três fronteiras e em cada uma perdem-se unidades, eixos e convenções de sinal: o momento forte chama-se M3 num sítio e M_y noutro, o esforço axial é positivo em compressão aqui e em tração ali, e um `E = 210` não diz se são GPa ou MPa. O `loadpath` fixa um formato único com a unidade no nome do campo, os eixos documentados nos dois modelos que os partilham e validação na entrada, de modo que um dado mal formado é rejeitado com o caminho do campo em vez de se propagar como um número. - -**Use quando** - -- definir a entrada ou a saída de uma ferramenta de cálculo (API REST, CLI, ficheiro) e quiser que materiais, secções e esforços tenham um formato único validado; -- gerar tipos TypeScript ou documentação de API a partir do JSON Schema dos modelos; -- importar resultados do SAP2000, ETABS ou outro programa e precisar de um sítio onde declarar o mapeamento de eixos e a convenção de sinal; -- precisar da matriz de rigidez de uma viga de altura variável ou com esquadros (EI(x) por troços) ou da rigidez condensada de uma viga com ligações semirrígidas para a montar num pórtico. - -**Não use para** - -- catálogos de perfis (não inclui tabelas de IPE, HEB ou W; os exemplos mostram como calcular as propriedades a partir das dimensões e contrastá-las); -- verificações de norma: não há resistências de cálculo, encurvadura nem classificação de secções, apenas o contrato de dados e as ajudas de rigidez; -- análise de estruturas completas: `fea` dá matrizes de elemento, não monta nem resolve (o exemplo do pórtico fá-lo em 40 linhas com numpy para mostrar como); -- secções compostas, não simétricas ou com empenamento restringido: `FrameSection` descreve propriedades, não geometria. - -## Instalação - -```bash -pip install git+https://github.com/jmurgass99/loadpath.git -``` - -Para desenvolvimento: `git clone`, `pip install -e ".[test]"`, `pytest -q` (8 ficheiros de testes). No Windows, se a consola não for UTF-8, execute os exemplos com `python -X utf8 examples/.py`. - -## Capacidades - -| Capacidade | Módulos | Norma ou referência | Casos de uso | -|---|---|---|---| -| Materiais: `StructuralMaterial` com E, ν, ρ obrigatórios e resistências opcionais conforme o tipo (aço, betão, enformado a frio, alumínio, madeira, alvenaria); validação de intervalos; JSON Schema | `materials` | EN 1993-1-1 §3.2.6; EN 10025-2 Tabela 7; EN 1992-1-1 Tabela 3.1 e §3.1.3; EN 14080; EN 1999-1-1 | [5 casos](#1-materiais) | -| Secções: `FrameSection` com A, I_x, I_y, J obrigatórios e C_w, módulos elásticos e plásticos, dimensões, classe de secção e propriedades eficazes de Classe 4 opcionais; aliases de eixo forte e fraco; mapa de eixos com os esforços | `sections` | EN 10365 / EN 10210 (contraste de catálogo); convenção M3 forte / M2 fraco do SAP2000 | [5 casos](#2-secções) | -| Esforços e contrato de dados: `InternalForces` por estação com tração positiva e componentes a zero por defeito; JSON Lines; modelos compostos com `$defs`; mapeamento a partir do SAP2000 | `forces`, Pydantic v2 | Convenção de eixos locais 1-2-3 | [5 casos](#3-esforços-e-contrato-de-dados) | -| Ajudas FEA: rigidez 6×6 de viga 2D com EI(x) e EA(x) por Simpson (flexibilidade da consola), molas rotacionais de extremidade condensadas (Monforton e Wu) | `fea.non_prismatic`, `fea.semi_rigid` | Monforton e Wu (1963); EN 1993-1-8 §5.2.2.5 (classificação de ligações); tabelas PCA de constantes de pórticos | [5 casos](#4-ajudas-de-elementos-finitos) | - -Os 20 scripts de `examples/` foram executados num ambiente limpo antes de serem publicados; a saída está em `examples/output/` e é a que se reproduz aqui. - -## Casos de uso - -As saídas reproduzem-se exatamente como os scripts as imprimem, em espanhol. - -### 1. Materiais - -#### Catálogo de materiais do projeto com valores de norma: S355, A992, C30/37, GL24h, alumínio 6061-T6 e alvenaria - -
Saída real de python examples/material_catalog_steel_concrete_timber_aluminum_from_codes.py - -```text -Tipos admitidos: ['steel', 'concrete', 'cold_formed_steel', 'aluminum', 'timber', 'masonry'] - - material tipo E [MPa] ν ρ [kg/m³] F_y F_u f_c ξ - S355 (EN 10025-2) steel 210000 0.30 7850 355 490 — 0.02 - A992 (ASTM) steel 200000 0.30 7850 345 450 — 0.02 - C30/37 (EN 1992-1-1) concrete 33000 0.20 2500 — — 30 0.05 - GL24h (EN 14080) timber 11500 0.40 420 — — — 0.01 - EN AW-6061 T6 (EN 1999-1-1) aluminum 70000 0.33 2700 240 260 — — - Ladrillo macizo M10 (EN 1996-1-1) masonry 5000 0.20 1800 — — 5 — - -Módulo de cortante del S355: G = E/(2·(1+ν)) = 80769 MPa (EN 1993-1-1: 81 000) -Peso propio del S355: 76.98 kN/m³; del C30/37: 24.52 kN/m³ - -Un consumidor comprueba con `material_type` qué resistencia leer: Fy/Fu en metales, fc en hormigón y fábrica; None es «no aplica», no «cero». -``` - -
- -#### O que o esquema rejeita e porquê: campo sem unidade, E negativo, ν > 0,5, tipo desconhecido, texto num número - -
Saída real de python examples/material_validation_rejects_missing_units_and_out_of_range.py - -```text -Válido: S275 E = 210000 MPa - - caso errores tipo → campo - E sin unidad (campo `E`) 1 extra_forbidden → E - E_MPa negativo 1 greater_than → E_MPa - ν = 0,7 (> 0,5) 1 less_than_equal → poisson - densidad 0 1 greater_than → density_kg_m3 - tipo `adamantium` 1 enum → material_type - F_y como texto 1 float_parsing → Fy_MPa - falta E_MPa 1 missing → E_MPa - -`extra='forbid'` convierte un campo sin unidad en un error en vez de en un número que viaja sin que nadie sepa si es MPa o kPa. -``` - -
- -#### Do JSON Schema a uma interface TypeScript com os intervalos e as unidades nos comentários - -
Saída real de python examples/material_json_schema_to_typescript_interface.py - -```text -JSON Schema: 9 propiedades, obligatorias ['name', 'material_type', 'E_MPa', 'poisson', 'density_kg_m3'], enum MaterialType con 6 valores - -/** Propiedades físicas de un material estructural (unidades SI, MPa). */ -export interface StructuralMaterial { - name: string; - material_type: "steel" | "concrete" | "cold_formed_steel" | "aluminum" | "timber" | "masonry"; - /** Módulo de elasticidad (MPa). (> 0) */ - E_MPa: number; - /** Coeficiente de Poisson. (>= 0.0, <= 0.5) */ - poisson: number; - /** Densidad (kg/m³). (> 0) */ - density_kg_m3: number; - /** Límite elástico, acero (MPa). (> 0) */ - Fy_MPa?: number | null; - /** Resistencia última, acero (MPa). (> 0) */ - Fu_MPa?: number | null; - /** Resistencia a compresión, hormigón (MPa). (> 0) */ - fc_MPa?: number | null; - /** Razón de amortiguamiento (fracción del crítico). (>= 0.0, <= 1.0) */ - damping_ratio?: number | null; -} - -El mismo generador sirve para FrameSection e InternalForces; `?` marca las propiedades opcionales (None en Python). -``` - -
- -#### Betões C20/25 a C50/60 com E_cm = 22·(f_cm/10)^0,3 GPa (EN 1992-1-1 Tabela 3.1) e o fator do agregado - -
Saída real de python examples/material_concrete_Ecm_from_fck_EN1992_table_3_1.py - -```text - clase f_ck f_cm E_cm calc [GPa] Tabla 3.1 desvío - C20/25 cuarcita 20 28 29.96 30 -0.1 % - C25/30 cuarcita 25 33 31.48 31 +1.5 % - C30/37 cuarcita 30 38 32.84 33 -0.5 % - C35/45 cuarcita 35 43 34.08 34 +0.2 % - C40/50 cuarcita 40 48 35.22 35 +0.6 % - C45/55 cuarcita 45 53 36.28 36 +0.8 % - C50/60 cuarcita 50 58 37.28 37 +0.8 % - -Efecto del árido para C30/37 (§3.1.3(2)): - cuarcita E_cm = 32.8 GPa - caliza E_cm = 29.6 GPa - arenisca E_cm = 23.0 GPa - basalto E_cm = 39.4 GPa - -La fórmula reproduce la tabla con desvíos < 2 % (la tabla redondea al GPa; C25/30 es el peor caso, 31,5 frente a 31); el módulo que viaja en el esquema es el calculado, no el redondeado. -``` - -
- -#### Aços S235 a S460 com a tensão de cedência dependente da espessura (EN 10025-2 Tabela 7) - -
Saída real de python examples/material_steel_grades_EN10025_thickness_dependent_fy.py - -```text - grado t=10 t=20 t=50 t=70 (f_y en MPa, EN 10025-2) - S235 235 225 215 215 - S275 275 265 255 245 - S355 355 345 335 325 - S460 460 440 430 410 - -Chapa de 50 mm: EN 10025-2 frente a la simplificación de EN 1993-1-1 Tabla 3.1 (40 < t <= 80): - S235: 215 MPa frente a 215 MPa; usar el nominal de t <= 16 (235) sobrestimaría un 9 % - S275: 255 MPa frente a 255 MPa; usar el nominal de t <= 16 (275) sobrestimaría un 8 % - S355: 335 MPa frente a 335 MPa; usar el nominal de t <= 16 (355) sobrestimaría un 6 % - S460: 430 MPa frente a 410 MPa; usar el nominal de t <= 16 (460) sobrestimaría un 7 % - -Ejemplo serializado: {"name":"S355 t=50 mm","material_type":"steel","E_MPa":210000.0,"poisson":0.3,"density_kg_m3":7850.0,"Fy_MPa":335.0,"Fu_MPa":470.0,"fc_MPa":null,"damping_ratio":null} -``` - -
- -### 2. Secções - -#### Propriedades de um perfil em I a partir das dimensões e contraste com o catálogo: IPE 300 e HEB 300 - -A área com a correção das concordâncias (4 − π)·r² sai exata; I_x e W_pl ficam 4 % abaixo do catálogo e a torção 20–25 %: I_t toma-se do catálogo. - -
Saída real de python examples/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.py - -```text -IPE 300: h = 300, b = 150, t_w = 7.1, t_f = 10.7, r = 15 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 53.8 7999 603 602 15.6 126 - catálogo 53.8 8356 604 628 20.1 126 - desvío +0.0 % -4.3 % -0.2 % -4.1 % -22.5 % +0.1 % - -HEB 300: h = 300, b = 300, t_w = 11.0, t_f = 19.0, r = 27 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 149.1 24187 8553 1790 148.8 1688 - catálogo 149.1 25170 8563 1869 185.0 1688 - desvío -0.0 % -3.9 % -0.1 % -4.2 % -19.6 % +0.0 % - -La corrección (4 − π)·r² deja el área exacta; en I_x y W_pl los acuerdos aportan ~4 % y en I_t un 20–25 %: I_y e I_w salen bien, I_t se toma de catálogo. -``` - -
- -#### Tubos RHS 200×100×8 e CHS 219,1×8 a partir das dimensões face à EN 10210 (Bredt para a torção do RHS) - -
Saída real de python examples/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.py - -```text - perfil A [cm²] I_x [cm⁴] I_y [cm⁴] I_t [cm⁴] W_pl,x [cm³] - RHS 200x100x8 calculado 45.4 2306 758 1758 289 - EN 10210 44.8 2269 747 1804 283 - desvío +1.4 % +1.6 % +1.5 % -2.6 % +2.1 % - CHS 219.1x8 calculado 53.1 2960 2960 5919 357 - EN 10210 53.1 2960 2960 5919 357 - desvío -0.1 % -0.0 % -0.0 % +0.0 % -0.1 % - -El RHS sale 1–3 % alto por las esquinas vivas (el catálogo tiene r_o = 1,5·t); Bredt da la torsión a −2,6 %. El CHS es exacto porque la fórmula es la geometría real. -``` - -
- -#### Que momento vai com que inércia: M_z (forte) com I_x, M_y (fraco) com I_y, e o erro de os cruzar - -
Saída real de python examples/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.py - -```text -Mapa de ejes: - flexión fuerte M_z = 90 kN·m → I_x = 8.356e+07 mm⁴, S_x = 557000 mm³ - flexión débil M_y = 6 kN·m → I_y = 6.040e+06 mm⁴, S_y = 80500 mm³ - cortante V_z = 60 kN acompaña a M_z (plano fuerte); V_y acompañaría a M_y - -Tensiones elásticas [MPa]: N/A = -22.3, M_z/S_x = ±161.6, M_y/S_y = ±74.5 -Esquina más comprimida: σ = -258.4 MPa → utilización elástica 0.73 frente a f_y = 355 MPa - -Si se cruzan los ejes (M_z con S_y): σ = 1118 MPa, 6.9 veces la real. El esquema lo evita nombrando los ejes en los dos modelos. -``` - -
- -#### Mínimo obrigatório, opcionais em None e rejeições do esquema de secção - -
Saída real de python examples/section_validation_optional_fields_and_json_schema_required.py - -```text -Sección mínima: {'name': 'barra genérica', 'A_mm2': 5000.0, 'Ix_mm4': 80000000.0, 'Iy_mm4': 6000000.0, 'J_mm4': 200000.0} - opcionales en None: ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] -Sección laminada con propiedades brutas: W14X90, 12 de 18 campos; sin rellenar: ['section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - -JSON Schema: obligatorias ['name', 'A_mm2', 'Ix_mm4', 'Iy_mm4', 'J_mm4']; opcionales ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - - sin J → missing en J_mm4 - área 0 → greater_than en A_mm2 - C_w negativo → greater_than_equal en Cw_mm6 - campo `peso` → extra_forbidden en peso - I_x como texto → float_parsing en Ix_mm4 - -C_w = 0 sí se admite (tubos cerrados); lo que se rechaza es el valor negativo. -``` - -
- -#### Peso por metro, raios de giração e esbelteza de pilar derivados dos campos do esquema - -
Saída real de python examples/section_weight_radius_of_gyration_and_slenderness_table.py - -```text -S355: λ_1 = π·sqrt(E/f_y) = 76.4; pilar L_cr = 4 m - - perfil peso [kg/m] i_x [mm] i_y [mm] λ_y λ̄_y régimen - IPE 300 42.2 124.6 33.5 119.4 1.56 esbelto - HEB 300 117.0 129.9 75.8 52.8 0.69 intermedio - HEA 200 42.3 82.8 49.8 80.3 1.05 intermedio - CHS 219.1x8 41.6 74.7 74.7 53.5 0.70 intermedio - -Pesos de catálogo: IPE 300 42,2; HEB 300 117; HEA 200 42,3; CHS 219,1×8 41,6 kg/m. El IPE 300 pesa lo mismo que el HEA 200 y es 2,6 veces más esbelto en su eje débil. -``` - -
- -### 3. Esforços e contrato de dados - -#### Viga simplesmente apoiada com carga uniforme: 13 estações como lista de `InternalForces` e envolvente face a q·L/2 e q·L²/8 - -
Saída real de python examples/internal_forces_simply_supported_beam_stations_udl_envelope.py - -```text - x [m] V_z [kN] M_z [kN·m] - 0.0 72.0 0.0 - 0.5 60.0 33.0 - 1.0 48.0 60.0 - 1.5 36.0 81.0 - 2.0 24.0 96.0 - 2.5 12.0 105.0 - 3.0 0.0 108.0 - 3.5 -12.0 105.0 - 4.0 -24.0 96.0 - 4.5 -36.0 81.0 - 5.0 -48.0 60.0 - 5.5 -60.0 33.0 - 6.0 -72.0 0.0 - -Envolvente: |V_z| máx = 72.0 kN en x = 0.0 m (q·L/2 = 72.0); M_z máx = 108.0 kN·m en x = 3.0 m (q·L²/8 = 108.0) -Serializado: 13 estaciones, 111 bytes cada una; ejemplo: {"member_id":"V-12","station_m":3.0,"N_kN":0.0,"Vy_kN":0.0,"Vz_kN":0.0,"T_kNm":0.0,"My_kNm":0.0,"Mz_kNm":108.0} -N, V_y, T y M_y quedan en 0,0 por defecto: un estado parcial es válido y explícito. -``` - -
- -#### Convenção de sinal do esforço axial (tração positiva): pilar e tirante, tensão nas fibras e o que acontece se o importador a inverter - -
Saída real de python examples/internal_forces_sign_convention_tension_positive_column_combined_stress.py - -```text - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) -850 180 50.3 -164.3 -164.3 0.46 - tirante T-1 (tracción) 300 20 32.0 8.2 0.0 0.00 - -Con el signo del axil invertido al importar (error típico entre programas): - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) 850 180 164.3 -50.3 -50.3 0.14 - tirante T-1 (tracción) -300 20 -8.2 -32.0 -32.0 0.09 - -Con el signo invertido la fibra comprimida del pilar pasa de −164 a −50 MPa (la compresión máxima, que gobierna el pandeo y la clase de la sección, cae a un tercio) y el tirante aparece comprimido; |σ|máx no cambia, así que una comprobación que solo mire el máximo absoluto no lo detecta. El esquema documenta el convenio en el campo y el importador debe declararlo, no adivinarlo. -``` - -
- -#### Exportar um pórtico para JSON Lines, reimportar validando linha a linha e obter a envolvente por barra - -
Saída real de python examples/internal_forces_jsonl_export_import_and_member_envelopes.py - -```text -Exportadas 45 estaciones en 5486 bytes de JSONL (+1 línea corrupta de prueba) - -Reimportadas 45 válidas; rechazadas: [(46, ['greater_than_equal'])] - - barra |N| máx [kN] |V_z| máx [kN] |M_z| máx [kN·m] - P-1 420.0 25.0 50.0 - D-1 35.0 180.0 240.0 - P-2 420.0 25.0 50.0 -``` - -
- -#### O que `InternalForces` rejeita, o estado parcial válido e um mapeamento de linha do SAP2000 para o esquema - -
Saída real de python examples/internal_forces_validation_station_negative_extra_field_and_defaults.py - -```text -Estado parcial válido: {'member_id': 'C-1', 'station_m': 0.0, 'N_kN': 0.0, 'Vy_kN': 0.0, 'Vz_kN': 0.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 0.0} - - estación −1 m → 1 error(es): greater_than_equal en station_m - campo `Fx_kN` → 1 error(es): extra_forbidden en Fx_kN - sin member_id → 1 error(es): missing en member_id - M_z como texto → 1 error(es): float_parsing en Mz_kNm - dos errores a la vez → 3 error(es): greater_than_equal en station_m; float_parsing en Mz_kNm; extra_forbidden en extra - -Fila de SAP2000 mapeada: {'member_id': 'V-12', 'station_m': 3.0, 'N_kN': -12.0, 'Vy_kN': 0.0, 'Vz_kN': 48.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 72.0} -El mapeo vive en el importador (y debe declarar el convenio de signo de P); el esquema no acepta los nombres del programa de origen. -``` - -
- -#### Contrato composto material + secção + esforços: JSON Schema com `$defs`, ida e volta e rejeição aninhada - -
Saída real de python examples/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.py - -```text -JSON Schema de MemberCheckInput: propiedades ['member_id', 'length_m', 'material', 'section', 'forces']; $defs = ['FrameSection', 'InternalForces', 'MaterialType', 'StructuralMaterial'] -Documento serializado: 1106 bytes; vuelta validada igual al original: True - - x [m] M_z [kN·m] σ = N/A + M/S [MPa] σ/f_y - 0.0 0.0 2.2 0.01 - 1.5 81.0 147.7 0.42 - 3.0 108.0 196.1 0.55 - 4.5 81.0 147.7 0.42 - 6.0 0.0 2.2 0.01 - -Rechazo anidado: [('greater_than', 'section.Ix_mm4')] -``` - -
- -### 4. Ajudas de elementos finitos - -#### A viga prismática como caso particular: Simpson recupera a matriz 6×6 clássica à precisão da máquina - -
Saída real de python examples/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.py - -```text -IPE 300, L = 6 m: EI = 17,548 kN·m², EA = 1,130,010 kN - 12EI/L³ = 974.9 kN/m 6EI/L² = 2,924.6 kN 4EI/L = 11,698.4 kN·m/rad 2EI/L = 5,849.2 kN·m/rad - - n = 2 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 3.9e-17 - n = 20 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 1.5e-16 - -Matriz 6×6 (GDL [u_i, v_i, θ_i, u_j, v_j, θ_j]): -[[ 188335. 0. 0. -188335. 0. 0. ] - [ 0. 974.9 2924.6 0. -974.9 2924.6] - [ 0. 2924.6 11698.4 0. -2924.6 5849.2] - [-188335. 0. 0. 188335. 0. 0. ] - [ 0. -974.9 -2924.6 0. 974.9 -2924.6] - [ 0. 2924.6 5849.2 0. -2924.6 11698.4]] - -El mismo helper con EI(x) callable da la viga de sección variable: el caso prismático es la comprobación de que la integración está bien montada. -``` - -
- -#### Viga de altura variável com EI linear: flexibilidade da extremidade em forma fechada e convergência de ordem 4 - -
Saída real de python examples/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.py - -```text -Forma cerrada: D₀₀ = 0.003355 m/kN, D₀₁ = 0.000792 rad/kN, D₁₁ = 0.000237 rad/(kN·m) -Cotas prismáticas de D₀₀: EI₀ → 0.004103, 2·EI₀ → 0.002052 - - n D₀₀ Simpson error rel. razón - 2 0.003419 1.90e-02 - 4 0.003361 1.57e-03 12.1 - 8 0.003356 1.08e-04 14.5 - 16 0.003355 6.93e-06 15.6 - 32 0.003355 4.36e-07 15.9 - 64 0.003355 2.73e-08 16.0 - -Rigidez rotacional en el extremo i (K[θ_i,θ_i]): acartelada 14,222 frente a prismática EI₀ 11,699 kN·m/rad (1.22×); en j: 20,071 (1.72×): el extremo con más inercia (j) es el más rígido. -Factor de transmisión K[θ_i,θ_j]/K[θ_j,θ_j] = 0.417 (prismática: 0,500). -``` - -
- -#### Travessa com esquadros nas extremidades: EI por troços, rigidez rotacional e fator de transmissão face à viga prismática - -
Saída real de python examples/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.py - -```text -Dintel L = 12 m, IPE 400: 4EI/L = 16,191 kN·m/rad, 2EI/L = 8,096, 12EI/L³ = 337 kN/m - - cartela K[θi,θi] [kN·m/rad] / prismática K[θi,θj] transmisión K[vi,vi] [kN/m] / prismática - ninguna (prismática) 16,191 1.00 8,096 0.500 337 1.00 - 2 m, canto ×2.0 28,195 1.74 17,969 0.637 641 1.90 - 3 m, canto ×2.0 37,434 2.31 25,659 0.685 876 2.60 - 2 m, canto ×1.5 23,243 1.44 13,739 0.591 514 1.52 - -La rigidez rotacional del nudo sube un 74 % con cartelas de 2 m que doblan el canto y el factor de transmisión pasa de 0.50 a 0.64: es lo que hace rentable la cartela frente a aumentar el perfil en toda la luz. -Quiebros de EI(x) en x = 2 y 10 m, error máximo frente a Simpson por tramos con n = 400: n = 20 con breakpoints_x 4.1e-04; n = 240 sin ellos (la tabla) 4.5e-08; n = 20 sin ellos 3.3e-03. -``` - -
- -#### Ligações semirrígidas: classificação EN 1993-1-8, fator de fixação de Monforton e Wu e rigidez condensada - -
Saída real de python examples/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.py - -```text -IPE 400, L = 8 m: E·I_b/L_b = 6,072 kN·m/rad; umbrales EN 1993-1-8: articulada <= 3,036, rígida >= 48,573 (arriostrado) / 151,791 (no arriostrado) - - S_j/(EI/L) S_j [kN·m/rad] γ k_θiθi/(4EI/L) Monforton-Wu arriostrado no arriostr. - 0.25 1,518 0.077 0.058 0.058 articulada articulada - 0.5 3,036 0.143 0.108 0.108 articulada articulada - 1 6,072 0.250 0.190 0.190 semirrígida semirrígida - 2 12,143 0.400 0.312 0.313 semirrígida semirrígida - 4 24,286 0.571 0.467 0.467 semirrígida semirrígida - 8 48,573 0.727 0.629 0.629 rígida semirrígida - 12 72,860 0.800 0.714 0.714 rígida semirrígida - 25 151,791 0.893 0.836 0.836 rígida rígida - 50 303,581 0.943 0.910 0.910 rígida rígida - 100 607,162 0.971 0.953 0.953 rígida rígida - inf inf 1.000 1.000 1.000 rígida rígida - -Una unión en el umbral «rígido» de pórtico arriostrado (S_j = 8·EI/L) conserva el 63 % de la rigidez rotacional 4EI/L de la viga; en el umbral no arriostrado (25·EI/L), el 84 %. -R va en kN·m/rad, las mismas unidades que K_b; pasar una rigidez relativa (k·L/EI) hace que todo salga articulado. -``` - -
- -#### Pórtico montado com as ajudas: rigidez lateral com ligações rígidas, semirrígidas e articuladas face a 24·EI_c/H³ e 6·EI_c/H³ - -
Saída real de python examples/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.py - -```text -EI_c = 52,857 kN·m²; cotas: 24·EI_c/H³ = 10,149 kN/m, 6·EI_c/H³ = 2,537 kN/m - - dintel S_j k = F/Δ [kN/m] Δ(100 kN) [mm] k / k_rígido - IPE 400 ∞ 5,595 17.9 1.00 - IPE 400 25·EI_b/L_b 5,215 19.2 0.93 - IPE 400 8·EI_b/L_b 4,656 21.5 0.83 - IPE 400 2·EI_b/L_b 3,637 27.5 0.65 - IPE 400 0 (articulado) 2,528 39.6 0.45 - rígido (×10⁶) ∞ 9,987 10.0 1.79 - -Comprobación: dintel rígido → 9,987 frente a 24·EI_c/H³ = 10,149 (98.41 %); articulado → 2,528 frente a 6·EI_c/H³ = 2,537 (99.64 %) -Una unión en el umbral «rígido» de pórtico arriostrado (8·EI_b/L_b) pierde un 17 % de rigidez al desplome y en el umbral no arriostrado (25·EI_b/L_b) un 7 %: por eso EN 1993-1-8 exige el segundo en pórticos no arriostrados. -``` - -
- -## Verificação - -- Contrato de dados: validação de campos obrigatórios, intervalos (E > 0, 0 ≤ ν ≤ 0,5, estação ≥ 0, C_w ≥ 0), `extra="forbid"`, enum de tipos, ida e volta `model_dump` → `model_validate` e `required` do JSON Schema para os três modelos (`tests/test_materials.py`, `test_sections.py`, `test_forces.py`). -- FEA: a viga prismática recupera-se à precisão da máquina; a viga de EI linear reproduz a flexibilidade em forma fechada (D₀₀ = (4·ln 2 − 5/2)·L³/EI₀, D₀₁ = (2·ln 2 − 1)·L²/EI₀, D₁₁ = ln 2·L/EI₀) com convergência de ordem 4; as molas de extremidade reproduzem Monforton e Wu (1963) com molas iguais e diferentes e os limites rígido (4EI/L) e articulado (3EI/L) (`tests/test_fea_closed_form_golden.py`, `test_fea_non_prismatic.py`, `test_fea_semi_rigid.py`). -- Os exemplos contrastam as propriedades calculadas a partir das dimensões com os catálogos EN 10365 e EN 10210, a fórmula de E_cm com a Tabela 3.1 da EN 1992-1-1 e o pórtico montado com os limites 24·EI_c/H³ e 6·EI_c/H³. - -## Limitações - -- Não há catálogo de perfis nem de materiais: o pacote valida o que recebe, não o preenche. -- `FrameSection` não distingue secções de classe 4 nem guarda geometria (banzos, almas); as propriedades efetivas são responsabilidade do produtor. -- `InternalForces` é uma estação de barra; não modela esforços de placa, apoios nem combinações (o identificador da combinação vai em `member_id` ou no modelo composto do consumidor). -- `beam_stiffness_2d` é flexão plana com as hipóteses de Euler-Bernoulli (sem deformação por esforço transverso) e EI(x) dado pelo utilizador; com EI por troços, passe as quebras em `breakpoints_x` (Simpson por troços) ou use um n suficiente para que caiam perto de um nó de Simpson. -- `semi_rigid_bending_stiffness` recebe R nas unidades de K_b (kN·m/rad se K_b estiver em kN e m); passar uma rigidez relativa k·L/EI faz com que tudo saia articulado. -- As convenções de eixos (x forte, M_z forte) são as do ecossistema Loadpath e do SAP2000; outros programas usam outras e o mapeamento é tarefa do importador. - -## Normas e referências - -- EN 1993-1-1:2005 §3.2.6 (E, G, ν); EN 10025-2 Tabela 7 (f_y por espessura); EN 1993-1-8 §5.2.2.5 (classificação de ligações por rigidez). -- EN 1992-1-1:2004 Tabela 3.1 e §3.1.3 (E_cm e agregado); EN 1991-1-1 Tabela A.1 (massas volúmicas). -- EN 10365 e EN 10210 (dimensões e propriedades de perfis, usadas como contraste). -- Monforton, G. R. e Wu, T. S. (1963), Matrix analysis of semi-rigidly connected frames, ASCE J. Struct. Div. 89(6). -- PCA, Handbook of Frame Constants (constantes de barras com esquadros). -- Pydantic v2 e JSON Schema 2020-12. - -## Licença - -MIT. Consulte [LICENSE](LICENSE). - ---- - -Parte do **Loadpath**, a coleção de bibliotecas estruturais de código aberto de [Jesús Murgas](https://github.com/jmurgass99). diff --git a/docs/LIMITACIONES.md b/docs/LIMITACIONES.md new file mode 100644 index 0000000..d5e9b03 --- /dev/null +++ b/docs/LIMITACIONES.md @@ -0,0 +1,10 @@ +# Limitaciones conocidas de loadpath + +Lo que el paquete no hace o hace de forma simplificada. + +- No hay catálogo de perfiles ni de materiales: el paquete valida lo que recibe, no lo rellena. +- `FrameSection` no distingue secciones de clase 4 ni almacena geometría (alas, almas); las propiedades eficaces son responsabilidad del productor. +- `InternalForces` es una estación de barra; no modela esfuerzos de placa, apoyos ni combinaciones (el identificador de combinación va en `member_id` o en el modelo compuesto del consumidor). +- `beam_stiffness_2d` es flexión plana con hipótesis de Euler-Bernoulli (sin deformación por cortante) y EI(x) dado por el usuario; con EI a trozos, pasa los quiebros en `breakpoints_x` (Simpson por tramos) o usa un n suficiente para que caigan cerca de un nodo de Simpson. +- `semi_rigid_bending_stiffness` recibe R en las unidades de K_b (kN·m/rad si K_b está en kN y m); pasar una rigidez relativa k·L/EI hace que todo salga articulado. +- Los convenios de ejes (x fuerte, M_z fuerte) son los del ecosistema Loadpath y de SAP2000; otros programas usan otros y el mapeo es tarea del importador. diff --git a/examples/output/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.txt b/examples/output/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.txt deleted file mode 100644 index 046cbde..0000000 --- a/examples/output/data_contract_member_check_bundle_material_section_forces_jsonschema_defs.txt +++ /dev/null @@ -1,11 +0,0 @@ -JSON Schema de MemberCheckInput: propiedades ['member_id', 'length_m', 'material', 'section', 'forces']; $defs = ['FrameSection', 'InternalForces', 'MaterialType', 'StructuralMaterial'] -Documento serializado: 1106 bytes; vuelta validada igual al original: True - - x [m] M_z [kN·m] σ = N/A + M/S [MPa] σ/f_y - 0.0 0.0 2.2 0.01 - 1.5 81.0 147.7 0.42 - 3.0 108.0 196.1 0.55 - 4.5 81.0 147.7 0.42 - 6.0 0.0 2.2 0.01 - -Rechazo anidado: [('greater_than', 'section.Ix_mm4')] diff --git a/examples/output/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.txt b/examples/output/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.txt deleted file mode 100644 index c5260ae..0000000 --- a/examples/output/fea_haunched_rafter_piecewise_EI_stiffness_and_carry_over_factor.txt +++ /dev/null @@ -1,10 +0,0 @@ -Dintel L = 12 m, IPE 400: 4EI/L = 16,191 kN·m/rad, 2EI/L = 8,096, 12EI/L³ = 337 kN/m - - cartela K[θi,θi] [kN·m/rad] / prismática K[θi,θj] transmisión K[vi,vi] [kN/m] / prismática - ninguna (prismática) 16,191 1.00 8,096 0.500 337 1.00 - 2 m, canto ×2.0 28,195 1.74 17,969 0.637 641 1.90 - 3 m, canto ×2.0 37,434 2.31 25,659 0.685 876 2.60 - 2 m, canto ×1.5 23,243 1.44 13,739 0.591 514 1.52 - -La rigidez rotacional del nudo sube un 74 % con cartelas de 2 m que doblan el canto y el factor de transmisión pasa de 0.50 a 0.64: es lo que hace rentable la cartela frente a aumentar el perfil en toda la luz. -Quiebros de EI(x) en x = 2 y 10 m, error máximo frente a Simpson por tramos con n = 400: n = 20 con breakpoints_x 4.1e-04; n = 240 sin ellos (la tabla) 4.5e-08; n = 20 sin ellos 3.3e-03. diff --git a/examples/output/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.txt b/examples/output/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.txt deleted file mode 100644 index d895f42..0000000 --- a/examples/output/fea_portal_frame_sway_stiffness_rigid_vs_semi_rigid_joints_assembly.txt +++ /dev/null @@ -1,12 +0,0 @@ -EI_c = 52,857 kN·m²; cotas: 24·EI_c/H³ = 10,149 kN/m, 6·EI_c/H³ = 2,537 kN/m - - dintel S_j k = F/Δ [kN/m] Δ(100 kN) [mm] k / k_rígido - IPE 400 ∞ 5,595 17.9 1.00 - IPE 400 25·EI_b/L_b 5,215 19.2 0.93 - IPE 400 8·EI_b/L_b 4,656 21.5 0.83 - IPE 400 2·EI_b/L_b 3,637 27.5 0.65 - IPE 400 0 (articulado) 2,528 39.6 0.45 - rígido (×10⁶) ∞ 9,987 10.0 1.79 - -Comprobación: dintel rígido → 9,987 frente a 24·EI_c/H³ = 10,149 (98.41 %); articulado → 2,528 frente a 6·EI_c/H³ = 2,537 (99.64 %) -Una unión en el umbral «rígido» de pórtico arriostrado (8·EI_b/L_b) pierde un 17 % de rigidez al desplome y en el umbral no arriostrado (25·EI_b/L_b) un 7 %: por eso EN 1993-1-8 exige el segundo en pórticos no arriostrados. diff --git a/examples/output/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.txt b/examples/output/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.txt deleted file mode 100644 index 0401519..0000000 --- a/examples/output/fea_prismatic_beam_stiffness_recovers_closed_form_12EI_L3.txt +++ /dev/null @@ -1,15 +0,0 @@ -IPE 300, L = 6 m: EI = 17,548 kN·m², EA = 1,130,010 kN - 12EI/L³ = 974.9 kN/m 6EI/L² = 2,924.6 kN 4EI/L = 11,698.4 kN·m/rad 2EI/L = 5,849.2 kN·m/rad - - n = 2 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 3.9e-17 - n = 20 intervalos de Simpson: error relativo máximo frente a la forma cerrada = 1.5e-16 - -Matriz 6×6 (GDL [u_i, v_i, θ_i, u_j, v_j, θ_j]): -[[ 188335. 0. 0. -188335. 0. 0. ] - [ 0. 974.9 2924.6 0. -974.9 2924.6] - [ 0. 2924.6 11698.4 0. -2924.6 5849.2] - [-188335. 0. 0. 188335. 0. 0. ] - [ 0. -974.9 -2924.6 0. 974.9 -2924.6] - [ 0. 2924.6 5849.2 0. -2924.6 11698.4]] - -El mismo helper con EI(x) callable da la viga de sección variable: el caso prismático es la comprobación de que la integración está bien montada. diff --git a/examples/output/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.txt b/examples/output/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.txt deleted file mode 100644 index 178e8ed..0000000 --- a/examples/output/fea_semi_rigid_joint_eurocode_classification_fixity_factor_table.txt +++ /dev/null @@ -1,17 +0,0 @@ -IPE 400, L = 8 m: E·I_b/L_b = 6,072 kN·m/rad; umbrales EN 1993-1-8: articulada <= 3,036, rígida >= 48,573 (arriostrado) / 151,791 (no arriostrado) - - S_j/(EI/L) S_j [kN·m/rad] γ k_θiθi/(4EI/L) Monforton-Wu arriostrado no arriostr. - 0.25 1,518 0.077 0.058 0.058 articulada articulada - 0.5 3,036 0.143 0.108 0.108 articulada articulada - 1 6,072 0.250 0.190 0.190 semirrígida semirrígida - 2 12,143 0.400 0.312 0.313 semirrígida semirrígida - 4 24,286 0.571 0.467 0.467 semirrígida semirrígida - 8 48,573 0.727 0.629 0.629 rígida semirrígida - 12 72,860 0.800 0.714 0.714 rígida semirrígida - 25 151,791 0.893 0.836 0.836 rígida rígida - 50 303,581 0.943 0.910 0.910 rígida rígida - 100 607,162 0.971 0.953 0.953 rígida rígida - inf inf 1.000 1.000 1.000 rígida rígida - -Una unión en el umbral «rígido» de pórtico arriostrado (S_j = 8·EI/L) conserva el 63 % de la rigidez rotacional 4EI/L de la viga; en el umbral no arriostrado (25·EI/L), el 84 %. -R va en kN·m/rad, las mismas unidades que K_b; pasar una rigidez relativa (k·L/EI) hace que todo salga articulado. diff --git a/examples/output/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.txt b/examples/output/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.txt deleted file mode 100644 index c501bec..0000000 --- a/examples/output/fea_tapered_beam_linear_EI_closed_form_and_simpson_convergence.txt +++ /dev/null @@ -1,13 +0,0 @@ -Forma cerrada: D₀₀ = 0.003355 m/kN, D₀₁ = 0.000792 rad/kN, D₁₁ = 0.000237 rad/(kN·m) -Cotas prismáticas de D₀₀: EI₀ → 0.004103, 2·EI₀ → 0.002052 - - n D₀₀ Simpson error rel. razón - 2 0.003419 1.90e-02 - 4 0.003361 1.57e-03 12.1 - 8 0.003356 1.08e-04 14.5 - 16 0.003355 6.93e-06 15.6 - 32 0.003355 4.36e-07 15.9 - 64 0.003355 2.73e-08 16.0 - -Rigidez rotacional en el extremo i (K[θ_i,θ_i]): acartelada 14,222 frente a prismática EI₀ 11,699 kN·m/rad (1.22×); en j: 20,071 (1.72×): el extremo con más inercia (j) es el más rígido. -Factor de transmisión K[θ_i,θ_j]/K[θ_j,θ_j] = 0.417 (prismática: 0,500). diff --git a/examples/output/internal_forces_jsonl_export_import_and_member_envelopes.txt b/examples/output/internal_forces_jsonl_export_import_and_member_envelopes.txt deleted file mode 100644 index ec36ba7..0000000 --- a/examples/output/internal_forces_jsonl_export_import_and_member_envelopes.txt +++ /dev/null @@ -1,8 +0,0 @@ -Exportadas 45 estaciones en 5486 bytes de JSONL (+1 línea corrupta de prueba) - -Reimportadas 45 válidas; rechazadas: [(46, ['greater_than_equal'])] - - barra |N| máx [kN] |V_z| máx [kN] |M_z| máx [kN·m] - P-1 420.0 25.0 50.0 - D-1 35.0 180.0 240.0 - P-2 420.0 25.0 50.0 diff --git a/examples/output/internal_forces_sign_convention_tension_positive_column_combined_stress.txt b/examples/output/internal_forces_sign_convention_tension_positive_column_combined_stress.txt deleted file mode 100644 index 7dc8a69..0000000 --- a/examples/output/internal_forces_sign_convention_tension_positive_column_combined_stress.txt +++ /dev/null @@ -1,10 +0,0 @@ - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) -850 180 50.3 -164.3 -164.3 0.46 - tirante T-1 (tracción) 300 20 32.0 8.2 0.0 0.00 - -Con el signo del axil invertido al importar (error típico entre programas): - barra N [kN] M_z [kN·m] σ fibra 1 [MPa] σ fibra 2 [MPa] σ_c,máx [MPa] |σ_c|/f_y - pilar P-3 (compresión) 850 180 164.3 -50.3 -50.3 0.14 - tirante T-1 (tracción) -300 20 -8.2 -32.0 -32.0 0.09 - -Con el signo invertido la fibra comprimida del pilar pasa de −164 a −50 MPa (la compresión máxima, que gobierna el pandeo y la clase de la sección, cae a un tercio) y el tirante aparece comprimido; |σ|máx no cambia, así que una comprobación que solo mire el máximo absoluto no lo detecta. El esquema documenta el convenio en el campo y el importador debe declararlo, no adivinarlo. diff --git a/examples/output/internal_forces_simply_supported_beam_stations_udl_envelope.txt b/examples/output/internal_forces_simply_supported_beam_stations_udl_envelope.txt deleted file mode 100644 index 3f88a21..0000000 --- a/examples/output/internal_forces_simply_supported_beam_stations_udl_envelope.txt +++ /dev/null @@ -1,18 +0,0 @@ - x [m] V_z [kN] M_z [kN·m] - 0.0 72.0 0.0 - 0.5 60.0 33.0 - 1.0 48.0 60.0 - 1.5 36.0 81.0 - 2.0 24.0 96.0 - 2.5 12.0 105.0 - 3.0 0.0 108.0 - 3.5 -12.0 105.0 - 4.0 -24.0 96.0 - 4.5 -36.0 81.0 - 5.0 -48.0 60.0 - 5.5 -60.0 33.0 - 6.0 -72.0 0.0 - -Envolvente: |V_z| máx = 72.0 kN en x = 0.0 m (q·L/2 = 72.0); M_z máx = 108.0 kN·m en x = 3.0 m (q·L²/8 = 108.0) -Serializado: 13 estaciones, 111 bytes cada una; ejemplo: {"member_id":"V-12","station_m":3.0,"N_kN":0.0,"Vy_kN":0.0,"Vz_kN":0.0,"T_kNm":0.0,"My_kNm":0.0,"Mz_kNm":108.0} -N, V_y, T y M_y quedan en 0,0 por defecto: un estado parcial es válido y explícito. diff --git a/examples/output/internal_forces_validation_station_negative_extra_field_and_defaults.txt b/examples/output/internal_forces_validation_station_negative_extra_field_and_defaults.txt deleted file mode 100644 index 5842e6f..0000000 --- a/examples/output/internal_forces_validation_station_negative_extra_field_and_defaults.txt +++ /dev/null @@ -1,10 +0,0 @@ -Estado parcial válido: {'member_id': 'C-1', 'station_m': 0.0, 'N_kN': 0.0, 'Vy_kN': 0.0, 'Vz_kN': 0.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 0.0} - - estación −1 m → 1 error(es): greater_than_equal en station_m - campo `Fx_kN` → 1 error(es): extra_forbidden en Fx_kN - sin member_id → 1 error(es): missing en member_id - M_z como texto → 1 error(es): float_parsing en Mz_kNm - dos errores a la vez → 3 error(es): greater_than_equal en station_m; float_parsing en Mz_kNm; extra_forbidden en extra - -Fila de SAP2000 mapeada: {'member_id': 'V-12', 'station_m': 3.0, 'N_kN': -12.0, 'Vy_kN': 0.0, 'Vz_kN': 48.0, 'T_kNm': 0.0, 'My_kNm': 0.0, 'Mz_kNm': 72.0} -El mapeo vive en el importador (y debe declarar el convenio de signo de P); el esquema no acepta los nombres del programa de origen. diff --git a/examples/output/material_catalog_steel_concrete_timber_aluminum_from_codes.txt b/examples/output/material_catalog_steel_concrete_timber_aluminum_from_codes.txt deleted file mode 100644 index 5e504a3..0000000 --- a/examples/output/material_catalog_steel_concrete_timber_aluminum_from_codes.txt +++ /dev/null @@ -1,14 +0,0 @@ -Tipos admitidos: ['steel', 'concrete', 'cold_formed_steel', 'aluminum', 'timber', 'masonry'] - - material tipo E [MPa] ν ρ [kg/m³] F_y F_u f_c ξ - S355 (EN 10025-2) steel 210000 0.30 7850 355 490 — 0.02 - A992 (ASTM) steel 200000 0.30 7850 345 450 — 0.02 - C30/37 (EN 1992-1-1) concrete 33000 0.20 2500 — — 30 0.05 - GL24h (EN 14080) timber 11500 0.40 420 — — — 0.01 - EN AW-6061 T6 (EN 1999-1-1) aluminum 70000 0.33 2700 240 260 — — - Ladrillo macizo M10 (EN 1996-1-1) masonry 5000 0.20 1800 — — 5 — - -Módulo de cortante del S355: G = E/(2·(1+ν)) = 80769 MPa (EN 1993-1-1: 81 000) -Peso propio del S355: 76.98 kN/m³; del C30/37: 24.52 kN/m³ - -Un consumidor comprueba con `material_type` qué resistencia leer: Fy/Fu en metales, fc en hormigón y fábrica; None es «no aplica», no «cero». diff --git a/examples/output/material_concrete_Ecm_from_fck_EN1992_table_3_1.txt b/examples/output/material_concrete_Ecm_from_fck_EN1992_table_3_1.txt deleted file mode 100644 index 73a23b9..0000000 --- a/examples/output/material_concrete_Ecm_from_fck_EN1992_table_3_1.txt +++ /dev/null @@ -1,16 +0,0 @@ - clase f_ck f_cm E_cm calc [GPa] Tabla 3.1 desvío - C20/25 cuarcita 20 28 29.96 30 -0.1 % - C25/30 cuarcita 25 33 31.48 31 +1.5 % - C30/37 cuarcita 30 38 32.84 33 -0.5 % - C35/45 cuarcita 35 43 34.08 34 +0.2 % - C40/50 cuarcita 40 48 35.22 35 +0.6 % - C45/55 cuarcita 45 53 36.28 36 +0.8 % - C50/60 cuarcita 50 58 37.28 37 +0.8 % - -Efecto del árido para C30/37 (§3.1.3(2)): - cuarcita E_cm = 32.8 GPa - caliza E_cm = 29.6 GPa - arenisca E_cm = 23.0 GPa - basalto E_cm = 39.4 GPa - -La fórmula reproduce la tabla con desvíos < 2 % (la tabla redondea al GPa; C25/30 es el peor caso, 31,5 frente a 31); el módulo que viaja en el esquema es el calculado, no el redondeado. diff --git a/examples/output/material_json_schema_to_typescript_interface.txt b/examples/output/material_json_schema_to_typescript_interface.txt deleted file mode 100644 index 0088301..0000000 --- a/examples/output/material_json_schema_to_typescript_interface.txt +++ /dev/null @@ -1,23 +0,0 @@ -JSON Schema: 9 propiedades, obligatorias ['name', 'material_type', 'E_MPa', 'poisson', 'density_kg_m3'], enum MaterialType con 6 valores - -/** Propiedades físicas de un material estructural (unidades SI, MPa). */ -export interface StructuralMaterial { - name: string; - material_type: "steel" | "concrete" | "cold_formed_steel" | "aluminum" | "timber" | "masonry"; - /** Módulo de elasticidad (MPa). (> 0) */ - E_MPa: number; - /** Coeficiente de Poisson. (>= 0.0, <= 0.5) */ - poisson: number; - /** Densidad (kg/m³). (> 0) */ - density_kg_m3: number; - /** Límite elástico, acero (MPa). (> 0) */ - Fy_MPa?: number | null; - /** Resistencia última, acero (MPa). (> 0) */ - Fu_MPa?: number | null; - /** Resistencia a compresión, hormigón (MPa). (> 0) */ - fc_MPa?: number | null; - /** Razón de amortiguamiento (fracción del crítico). (>= 0.0, <= 1.0) */ - damping_ratio?: number | null; -} - -El mismo generador sirve para FrameSection e InternalForces; `?` marca las propiedades opcionales (None en Python). diff --git a/examples/output/material_steel_grades_EN10025_thickness_dependent_fy.txt b/examples/output/material_steel_grades_EN10025_thickness_dependent_fy.txt deleted file mode 100644 index 6eeb46f..0000000 --- a/examples/output/material_steel_grades_EN10025_thickness_dependent_fy.txt +++ /dev/null @@ -1,13 +0,0 @@ - grado t=10 t=20 t=50 t=70 (f_y en MPa, EN 10025-2) - S235 235 225 215 215 - S275 275 265 255 245 - S355 355 345 335 325 - S460 460 440 430 410 - -Chapa de 50 mm: EN 10025-2 frente a la simplificación de EN 1993-1-1 Tabla 3.1 (40 < t <= 80): - S235: 215 MPa frente a 215 MPa; usar el nominal de t <= 16 (235) sobrestimaría un 9 % - S275: 255 MPa frente a 255 MPa; usar el nominal de t <= 16 (275) sobrestimaría un 8 % - S355: 335 MPa frente a 335 MPa; usar el nominal de t <= 16 (355) sobrestimaría un 6 % - S460: 430 MPa frente a 410 MPa; usar el nominal de t <= 16 (460) sobrestimaría un 7 % - -Ejemplo serializado: {"name":"S355 t=50 mm","material_type":"steel","E_MPa":210000.0,"poisson":0.3,"density_kg_m3":7850.0,"Fy_MPa":335.0,"Fu_MPa":470.0,"fc_MPa":null,"damping_ratio":null} diff --git a/examples/output/material_validation_rejects_missing_units_and_out_of_range.txt b/examples/output/material_validation_rejects_missing_units_and_out_of_range.txt deleted file mode 100644 index 37c70f9..0000000 --- a/examples/output/material_validation_rejects_missing_units_and_out_of_range.txt +++ /dev/null @@ -1,12 +0,0 @@ -Válido: S275 E = 210000 MPa - - caso errores tipo → campo - E sin unidad (campo `E`) 1 extra_forbidden → E - E_MPa negativo 1 greater_than → E_MPa - ν = 0,7 (> 0,5) 1 less_than_equal → poisson - densidad 0 1 greater_than → density_kg_m3 - tipo `adamantium` 1 enum → material_type - F_y como texto 1 float_parsing → Fy_MPa - falta E_MPa 1 missing → E_MPa - -`extra='forbid'` convierte un campo sin unidad en un error en vez de en un número que viaja sin que nadie sepa si es MPa o kPa. diff --git a/examples/output/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.txt b/examples/output/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.txt deleted file mode 100644 index 72f86b4..0000000 --- a/examples/output/section_hollow_sections_RHS_CHS_from_dimensions_vs_EN10210.txt +++ /dev/null @@ -1,9 +0,0 @@ - perfil A [cm²] I_x [cm⁴] I_y [cm⁴] I_t [cm⁴] W_pl,x [cm³] - RHS 200x100x8 calculado 45.4 2306 758 1758 289 - EN 10210 44.8 2269 747 1804 283 - desvío +1.4 % +1.6 % +1.5 % -2.6 % +2.1 % - CHS 219.1x8 calculado 53.1 2960 2960 5919 357 - EN 10210 53.1 2960 2960 5919 357 - desvío -0.1 % -0.0 % -0.0 % +0.0 % -0.1 % - -El RHS sale 1–3 % alto por las esquinas vivas (el catálogo tiene r_o = 1,5·t); Bredt da la torsión a −2,6 %. El CHS es exacto porque la fórmula es la geometría real. diff --git a/examples/output/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.txt b/examples/output/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.txt deleted file mode 100644 index e93d4a8..0000000 --- a/examples/output/section_properties_from_I_shape_dimensions_IPE300_HEB300_vs_catalog.txt +++ /dev/null @@ -1,13 +0,0 @@ -IPE 300: h = 300, b = 150, t_w = 7.1, t_f = 10.7, r = 15 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 53.8 7999 603 602 15.6 126 - catálogo 53.8 8356 604 628 20.1 126 - desvío +0.0 % -4.3 % -0.2 % -4.1 % -22.5 % +0.1 % - -HEB 300: h = 300, b = 300, t_w = 11.0, t_f = 19.0, r = 27 mm - A [cm²] I_x [cm⁴] I_y [cm⁴] W_pl,x [cm³] I_t [cm⁴] I_w [10³ cm⁶] - calculado 149.1 24187 8553 1790 148.8 1688 - catálogo 149.1 25170 8563 1869 185.0 1688 - desvío -0.0 % -3.9 % -0.1 % -4.2 % -19.6 % +0.0 % - -La corrección (4 − π)·r² deja el área exacta; en I_x y W_pl los acuerdos aportan ~4 % y en I_t un 20–25 %: I_y e I_w salen bien, I_t se toma de catálogo. diff --git a/examples/output/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.txt b/examples/output/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.txt deleted file mode 100644 index 9845abf..0000000 --- a/examples/output/section_strong_weak_axis_mapping_to_internal_forces_bending_stress.txt +++ /dev/null @@ -1,9 +0,0 @@ -Mapa de ejes: - flexión fuerte M_z = 90 kN·m → I_x = 8.356e+07 mm⁴, S_x = 557000 mm³ - flexión débil M_y = 6 kN·m → I_y = 6.040e+06 mm⁴, S_y = 80500 mm³ - cortante V_z = 60 kN acompaña a M_z (plano fuerte); V_y acompañaría a M_y - -Tensiones elásticas [MPa]: N/A = -22.3, M_z/S_x = ±161.6, M_y/S_y = ±74.5 -Esquina más comprimida: σ = -258.4 MPa → utilización elástica 0.73 frente a f_y = 355 MPa - -Si se cruzan los ejes (M_z con S_y): σ = 1118 MPa, 6.9 veces la real. El esquema lo evita nombrando los ejes en los dos modelos. diff --git a/examples/output/section_validation_optional_fields_and_json_schema_required.txt b/examples/output/section_validation_optional_fields_and_json_schema_required.txt deleted file mode 100644 index 1f6052c..0000000 --- a/examples/output/section_validation_optional_fields_and_json_schema_required.txt +++ /dev/null @@ -1,13 +0,0 @@ -Sección mínima: {'name': 'barra genérica', 'A_mm2': 5000.0, 'Ix_mm4': 80000000.0, 'Iy_mm4': 6000000.0, 'J_mm4': 200000.0} - opcionales en None: ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] -Sección laminada con propiedades brutas: W14X90, 12 de 18 campos; sin rellenar: ['section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - -JSON Schema: obligatorias ['name', 'A_mm2', 'Ix_mm4', 'Iy_mm4', 'J_mm4']; opcionales ['Cw_mm6', 'Zx_mm3', 'Zy_mm3', 'Sx_mm3', 'Sy_mm3', 'depth_mm', 'width_mm', 'section_class', 'A_eff_mm2', 'Ix_eff_mm4', 'Iy_eff_mm4', 'Sx_eff_mm3', 'Sy_eff_mm3'] - - sin J → missing en J_mm4 - área 0 → greater_than en A_mm2 - C_w negativo → greater_than_equal en Cw_mm6 - campo `peso` → extra_forbidden en peso - I_x como texto → float_parsing en Ix_mm4 - -C_w = 0 sí se admite (tubos cerrados); lo que se rechaza es el valor negativo. diff --git a/examples/output/section_weight_radius_of_gyration_and_slenderness_table.txt b/examples/output/section_weight_radius_of_gyration_and_slenderness_table.txt deleted file mode 100644 index 1217f58..0000000 --- a/examples/output/section_weight_radius_of_gyration_and_slenderness_table.txt +++ /dev/null @@ -1,9 +0,0 @@ -S355: λ_1 = π·sqrt(E/f_y) = 76.4; pilar L_cr = 4 m - - perfil peso [kg/m] i_x [mm] i_y [mm] λ_y λ̄_y régimen - IPE 300 42.2 124.6 33.5 119.4 1.56 esbelto - HEB 300 117.0 129.9 75.8 52.8 0.69 intermedio - HEA 200 42.3 82.8 49.8 80.3 1.05 intermedio - CHS 219.1x8 41.6 74.7 74.7 53.5 0.70 intermedio - -Pesos de catálogo: IPE 300 42,2; HEB 300 117; HEA 200 42,3; CHS 219,1×8 41,6 kg/m. El IPE 300 pesa lo mismo que el HEA 200 y es 2,6 veces más esbelto en su eje débil. diff --git a/web/index.html b/web/index.html deleted file mode 100644 index df01ecc..0000000 --- a/web/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Loadpath — Pórtico 3D con Uniones Semirrígidas (VanJS + Three.js + WASM) - - - - - - diff --git a/web/package.json b/web/package.json deleted file mode 100644 index 7a08550..0000000 --- a/web/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@loadpath/web", - "version": "0.1.1", - "description": "Ultralight VanJS + Three.js + WebAssembly 3D frame and semi-rigid connection analyzer with GeometryMapping", - "type": "module", - "scripts": { - "dev": "vite --open", - "build": "vite build", - "preview": "vite preview --open", - "test": "node src/__tests__/loadpathWeb.test.ts", - "build:wasm": "emcc ./src/solver/cpp/lSolver.cpp -o ./src/solver/cpp/built/lSolver.wasm -O3 -s STANDALONE_WASM --no-entry -s EXPORTED_FUNCTIONS=_monfortonFixityFactor,_beamFlexuralStiffness" - }, - "dependencies": { - "three": "^0.170.0", - "vanjs-core": "^1.5.2" - }, - "devDependencies": { - "@types/three": "^0.170.0", - "typescript": "^5.6.3", - "vite": "^6.0.0", - "vitest": "^2.1.5" - } -} diff --git a/web/src/__tests__/loadpathWeb.test.ts b/web/src/__tests__/loadpathWeb.test.ts deleted file mode 100644 index 58556d6..0000000 --- a/web/src/__tests__/loadpathWeb.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import assert from "node:assert/strict"; -import type { FrameComponents, SemanticGeometry } from "../data-model.ts"; -import { getMesh } from "../mesh/getMesh.ts"; -import { initLsolverWasm, solveFrameWasm } from "../solver/wasmSolver.ts"; - -const geom: SemanticGeometry = { - points: new Map([ - [1, [0, 0, 0]], - [2, [8, 0, 0]], - [3, [0, 0, 4.5]], - [4, [8, 0, 4.5]], - ]), - lines: new Map([ - [1, [1, 3]], - [2, [2, 4]], - [3, [3, 4]], - ]), -}; - -const comps: FrameComponents = { - supports: new Map([ - [1, [true, true, true]], - [2, [true, true, true]], - ]), - pointLoadsKN: new Map([[3, [30, 0, 0]]]), - distributedLoadsKNm: new Map([[3, -20]]), - endSpringsKNmRad: new Map([[3, [15000, 15000]]]), - E_MPa: 210000, - A_mm2: 5380, - I_mm4: 8.356e7, - divisions: 5, -}; - -// 1. Mesh mapping -const m = getMesh(geom, 5); -assert.equal(m.nodes.length, 4 + 3 * 4); -assert.equal(m.elements.length, 15); -assert.equal(m.geometryMapping.lineToElements.get(3)?.length, 5); - -// 2. Monforton fixity factor in WASM -const w = await initLsolverWasm(); -const g = w.monfortonFixityFactor(15e6, 210e9, 8.356e-5, 8.0); -assert.ok(g > 0.5 && g < 1.0); - -// 3. Static solve -const mesh = getMesh(geom, 5); -const res = solveFrameWasm({ geometry: geom, mesh, components: comps }); -assert.ok(res.maxDisplacementMm > 0.5); - -console.log( - `LoadPath WASM test OK: gamma_fixity=${g.toFixed(3)}, max_disp=${res.maxDisplacementMm.toFixed(2)} mm` -); diff --git a/web/src/data-model.ts b/web/src/data-model.ts deleted file mode 100644 index aafa8f0..0000000 --- a/web/src/data-model.ts +++ /dev/null @@ -1,40 +0,0 @@ -export type Coord3D = [number, number, number]; -export type LineSegment = [number, number]; -export type SupportFlags = [boolean, boolean, boolean]; // [ux, uz, ry] - -export interface SemanticGeometry { - points: Map; - lines: Map; -} - -export interface FrameComponents { - supports: Map; - pointLoadsKN: Map; - distributedLoadsKNm: Map; - endSpringsKNmRad: Map; - E_MPa: number; - A_mm2: number; - I_mm4: number; - divisions: number; -} - -export interface GeometryMapping { - pointToNodes: Map; - lineToElements: Map; -} - -export interface DiscreteMesh { - nodes: Coord3D[]; - elements: [number, number][]; - geometryMapping: GeometryMapping; -} - -export interface FrameAnalysisResult { - engine: "wasm-local" | "fastapi-python"; - deformedPositions: number[]; - maxDisplacementMm: number; - lineMaxMomentKNm: Map; - lineMaxShearKN: Map; - connectionClasses: Map; - fixityFactorGamma: number; -} diff --git a/web/src/main.ts b/web/src/main.ts deleted file mode 100644 index 16b9c6c..0000000 --- a/web/src/main.ts +++ /dev/null @@ -1,205 +0,0 @@ -import van from "vanjs-core"; -import type { - DiscreteMesh, - FrameComponents, - SemanticGeometry, -} from "./data-model"; -import { getMesh } from "./mesh/getMesh"; -import { runHybridFrameAnalysis } from "./solver/apiClient"; -import { initLsolverWasm } from "./solver/wasmSolver"; -import { getDiagrams } from "./ui/getDiagrams"; -import { getViewer3D } from "./viewer/getViewer3D"; - -const { div, h2, h3, input, label, option, select, span } = van.tags; - -await initLsolverWasm(); - -const geometry = van.state({ - points: new Map([ - [1, [0, 0, 0]], - [2, [8, 0, 0]], - [3, [0, 0, 4.5]], - [4, [8, 0, 4.5]], - ]), - lines: new Map([ - [1, [1, 3]], // Left column - [2, [2, 4]], // Right column - [3, [3, 4]], // Girder with rotational end springs - ]), -}); - -const divisions = van.state(6); -const springStiffnessKNmRad = van.state(18000); -const distributedLoadKNm = van.state(-24); -const lateralLoadKN = van.state(35); -const deformationScale = van.state(40); -const solverMode = van.state<"wasm-local" | "fastapi-python">("wasm-local"); - -const components = van.state({ - supports: new Map([ - [1, [true, true, true]], - [2, [true, true, true]], - ]), - pointLoadsKN: new Map([[3, [35, 0, 0]]]), - distributedLoadsKNm: new Map([[3, -24]]), - endSpringsKNmRad: new Map([[3, [18000, 18000]]]), - E_MPa: 210000, - A_mm2: 5380, - I_mm4: 8.356e7, - divisions: 6, -}); - -const mesh = van.state(getMesh(geometry.val, divisions.val)); -const deformedPositions = van.state([]); -const maxDispMm = van.state(0); -const lineMaxMomentKNm = van.state>(new Map()); -const lineMaxShearKN = van.state>(new Map()); -const connectionClass = van.state("semi_rigid"); -const fixityFactorGamma = van.state(0.75); - -van.derive(() => { - const nDiv = divisions.val; - const sj = springStiffnessKNmRad.val; - const q = distributedLoadKNm.val; - const fx = lateralLoadKN.val; - - components.val = { - ...components.val, - divisions: nDiv, - pointLoadsKN: new Map([[3, [fx, 0, 0]]]), - distributedLoadsKNm: new Map([[3, q]]), - endSpringsKNmRad: new Map([[3, [sj, sj]]]), - }; - mesh.val = getMesh(geometry.val, nDiv); -}); - -van.derive(() => { - runHybridFrameAnalysis({ - mode: solverMode.val, - apiUrl: "http://127.0.0.1:8000", - geometry: geometry.val, - mesh: mesh.val, - components: components.val, - }).then((res) => { - deformedPositions.val = res.deformedPositions; - maxDispMm.val = res.maxDisplacementMm; - lineMaxMomentKNm.val = res.lineMaxMomentKNm; - lineMaxShearKN.val = res.lineMaxShearKN; - connectionClass.val = res.connectionClasses.get(3) ?? "semi_rigid"; - fixityFactorGamma.val = res.fixityFactorGamma; - }); -}); - -const leftPanel = div( - { class: "lp-panel" }, - h2({ style: "font-size: 1.05rem;" }, "Loadpath — Pórtico Semirrígido 3D"), - div( - { class: "lp-card" }, - h3("Malla Semántica (GeometryMapping)"), - div( - { class: "lp-row" }, - label("Divisiones por barra:"), - input({ - type: "number", - min: "1", - max: "12", - value: () => String(divisions.val), - oninput: (e: Event) => - (divisions.val = Math.max(1, Number((e.target as HTMLInputElement).value) || 1)), - }), - ), - div( - { class: "lp-row" }, - label("Nudos / Elementos:"), - span(() => `${mesh.val.nodes.length} / ${mesh.val.elements.length}`), - ), - ), - div( - { class: "lp-card" }, - h3("Uniones Semirrígidas (EN 1993-1-8)"), - div( - { class: "lp-row" }, - label("Sj,ini [kN·m/rad]:"), - input({ - type: "number", - step: "2000", - min: "100", - value: () => String(springStiffnessKNmRad.val), - oninput: (e: Event) => - (springStiffnessKNmRad.val = Math.max(100, Number((e.target as HTMLInputElement).value) || 1000)), - }), - ), - div( - { class: "lp-row" }, - label("Clase EC3:"), - span({ class: "lp-badge" }, () => connectionClass.val.toUpperCase()), - ), - div( - { class: "lp-row" }, - label("Carga q viga [kN/m]:"), - input({ - type: "number", - step: "2", - value: () => String(distributedLoadKNm.val), - oninput: (e: Event) => - (distributedLoadKNm.val = Number((e.target as HTMLInputElement).value) || 0), - }), - ), - div( - { class: "lp-row" }, - label("Carga H nudo 3 [kN]:"), - input({ - type: "number", - step: "5", - value: () => String(lateralLoadKN.val), - oninput: (e: Event) => - (lateralLoadKN.val = Number((e.target as HTMLInputElement).value) || 0), - }), - ), - ), - div( - { class: "lp-card" }, - h3("Motor Híbrido (WASM / FastAPI)"), - div( - { class: "lp-row" }, - label("Solver:"), - select( - { - onchange: (e: Event) => - (solverMode.val = (e.target as HTMLSelectElement).value as "wasm-local" | "fastapi-python"), - }, - option({ value: "wasm-local" }, "WASM + Monforton-Wu"), - option({ value: "fastapi-python" }, "FastAPI Python"), - ), - ), - div( - { class: "lp-row" }, - label("Desplazamiento máx:"), - span(() => `${maxDispMm.val.toFixed(2)} mm`), - ), - ), -); - -const rightPanel = div( - { class: "lp-panel right" }, - getDiagrams({ - lineMaxMomentKNm, - lineMaxShearKN, - fixityFactorGamma, - }), -); - -document.body.appendChild( - div( - { class: "lp-layout" }, - leftPanel, - getViewer3D({ - mesh, - components, - deformedPositions, - lineMaxMomentKNm, - deformationScale, - }), - rightPanel, - ), -); diff --git a/web/src/mesh/getMesh.ts b/web/src/mesh/getMesh.ts deleted file mode 100644 index cac77e1..0000000 --- a/web/src/mesh/getMesh.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { - Coord3D, - DiscreteMesh, - GeometryMapping, - SemanticGeometry, -} from "../data-model.ts"; - -export function getMesh( - geometry: SemanticGeometry, - divisions: number, -): DiscreteMesh { - const nDiv = Math.max(1, Math.floor(divisions)); - const pointToNodes = new Map(); - const lineToElements = new Map(); - const nodes: Coord3D[] = []; - const elements: [number, number][] = []; - - for (const ptId of [...geometry.points.keys()].sort((a, b) => a - b)) { - const c = geometry.points.get(ptId)!; - pointToNodes.set(ptId, nodes.length); - nodes.push([c[0], c[1], c[2]]); - } - - for (const lineId of [...geometry.lines.keys()].sort((a, b) => a - b)) { - const [p1, p2] = geometry.lines.get(lineId)!; - const n1 = pointToNodes.get(p1); - const n2 = pointToNodes.get(p2); - if (n1 === undefined || n2 === undefined) continue; - - const c1 = nodes[n1]; - const c2 = nodes[n2]; - const chain: number[] = [n1]; - - for (let k = 1; k < nDiv; k++) { - const t = k / nDiv; - chain.push(nodes.length); - nodes.push([ - c1[0] + t * (c2[0] - c1[0]), - c1[1] + t * (c2[1] - c1[1]), - c1[2] + t * (c2[2] - c1[2]), - ]); - } - chain.push(n2); - - const elemIds: number[] = []; - for (let k = 0; k < nDiv; k++) { - elemIds.push(elements.length); - elements.push([chain[k], chain[k + 1]]); - } - lineToElements.set(lineId, elemIds); - } - - const geometryMapping: GeometryMapping = { - pointToNodes, - lineToElements, - }; - return { nodes, elements, geometryMapping }; -} diff --git a/web/src/solver/apiClient.ts b/web/src/solver/apiClient.ts deleted file mode 100644 index 5c07091..0000000 --- a/web/src/solver/apiClient.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { - DiscreteMesh, - FrameAnalysisResult, - FrameComponents, - SemanticGeometry, -} from "../data-model"; -import { solveFrameWasm } from "./wasmSolver"; - -export async function runHybridFrameAnalysis({ - mode, - apiUrl, - geometry, - mesh, - components, -}: { - mode: "wasm-local" | "fastapi-python"; - apiUrl: string; - geometry: SemanticGeometry; - mesh: DiscreteMesh; - components: FrameComponents; -}): Promise { - if (mode === "fastapi-python") { - try { - const resp = await fetch(`${apiUrl.replace(/\/$/, "")}/api/fea/frame`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ divisions: components.divisions }), - }); - if (resp.ok) { - const res = solveFrameWasm({ geometry, mesh, components }); - return { ...res, engine: "fastapi-python" }; - } - } catch { - // Fallback to local WASM solver - } - } - return solveFrameWasm({ geometry, mesh, components }); -} diff --git a/web/src/solver/cpp/built/lSolver.wasm b/web/src/solver/cpp/built/lSolver.wasm deleted file mode 100644 index 8f8ee698c301806cb8d8a1b775f909ab25522007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmZQbEY4+QU|?VrU`k-A0RqOF1ZFVB%*@2V$Zp6anwy`OmS0qopXZiYky%pdmY7_U zU&O#5nv|ND>z0#RQCgIk6I_y+mX?=VT+G18<-w%Q-~u!f2tL@OGdLU+7#ArpEmB}m bU|zI%!D4PHm|6`71%^dnNsu(tqQ%?*ATuQ; diff --git a/web/src/solver/cpp/lSolver.cpp b/web/src/solver/cpp/lSolver.cpp deleted file mode 100644 index f96bf61..0000000 --- a/web/src/solver/cpp/lSolver.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// lSolver.cpp — C++ WebAssembly kernel for Loadpath 2D/3D frame stiffness and -// Monforton-Wu (1963) semi-rigid connection fixity factor evaluation. - -extern "C" { - -// Computes Monforton-Wu rotational fixity factor: -// gamma = 1 / (1 + 3 * E * I / (Sj * L)) -double monfortonFixityFactor(double Sj, double E, double I, double L) { - return 1.0 / (1.0 + (3.0 * E * I) / (Sj * L)); -} - -// Computes Euler-Bernoulli shear stiffness 12 * E * I / L^3 -double beamFlexuralStiffness(double E, double I, double L) { - return (12.0 * E * I) / (L * L * L); -} - -} diff --git a/web/src/solver/wasmSolver.ts b/web/src/solver/wasmSolver.ts deleted file mode 100644 index 08cf885..0000000 --- a/web/src/solver/wasmSolver.ts +++ /dev/null @@ -1,297 +0,0 @@ -import type { - DiscreteMesh, - FrameAnalysisResult, - FrameComponents, - SemanticGeometry, -} from "../data-model.ts"; - -const LSOLVER_WASM_BASE64 = - "AGFzbQEAAAABEAJgBHx8fHwBfGADfHx8AXwDAwIAAQcxAhVtb25mb3J0b25GaXhpdHlGYWN0b3IAABViZWFtRmxleHVyYWxTdGlmZm5lc3MAAQpIAisARAAAAAAAAPA/RAAAAAAAAPA/RAAAAAAAAAhAIAGiIAKiIAAgA6KjoKMLGgBEAAAAAAAAKEAgAKIgAaIgAiACoiACoqML"; - -export interface LsolverWasmExports { - monfortonFixityFactor: (Sj: number, E: number, I: number, L: number) => number; - beamFlexuralStiffness: (E: number, I: number, L: number) => number; -} - -let cachedExports: LsolverWasmExports | null = null; - -function decodeBase64(b64: string): Uint8Array { - if (typeof Buffer !== "undefined") { - return new Uint8Array(Buffer.from(b64, "base64")); - } - const bin = atob(b64); - const out = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); - return out; -} - -export async function initLsolverWasm(): Promise { - if (cachedExports) return cachedExports; - const bytes = decodeBase64(LSOLVER_WASM_BASE64); - const { instance } = await WebAssembly.instantiate(bytes.buffer as ArrayBuffer); - cachedExports = instance.exports as unknown as LsolverWasmExports; - return cachedExports; -} - -export function getLsolverWasmSync(): LsolverWasmExports { - if (cachedExports) return cachedExports; - const bytes = decodeBase64(LSOLVER_WASM_BASE64); - const mod = new WebAssembly.Module(bytes.buffer as ArrayBuffer); - const instance = new WebAssembly.Instance(mod); - cachedExports = instance.exports as unknown as LsolverWasmExports; - return cachedExports; -} - -export function classifyConnectionEC3( - Sj_Nmm_rad: number, - E_MPa: number, - I_mm4: number, - L_mm: number, -): string { - const eiL = (E_MPa * I_mm4) / Math.max(L_mm, 1.0); - if (Sj_Nmm_rad < 0.5 * eiL) return "pinned"; - if (Sj_Nmm_rad >= 8.0 * eiL) return "rigid"; - return "semi_rigid"; -} - -function solveGaussian(A: number[][], b: number[]): number[] { - const n = b.length; - const M = A.map((row, i) => [...row, b[i]]); - for (let col = 0; col < n; col++) { - let pivot = col; - for (let row = col + 1; row < n; row++) { - if (Math.abs(M[row][col]) > Math.abs(M[pivot][col])) pivot = row; - } - if (pivot !== col) { - const tmp = M[col]; - M[col] = M[pivot]; - M[pivot] = tmp; - } - const diag = M[col][col]; - if (Math.abs(diag) < 1e-14) continue; - for (let row = col + 1; row < n; row++) { - const f = M[row][col] / diag; - for (let j = col; j <= n; j++) M[row][j] -= f * M[col][j]; - } - } - const x = new Array(n).fill(0); - for (let i = n - 1; i >= 0; i--) { - let s = M[i][n]; - for (let j = i + 1; j < n; j++) s -= M[i][j] * x[j]; - x[i] = Math.abs(M[i][i]) > 1e-14 ? s / M[i][i] : 0; - } - return x; -} - -export function solveFrameWasm({ - geometry, - mesh, - components, -}: { - geometry: SemanticGeometry; - mesh: DiscreteMesh; - components: FrameComponents; -}): FrameAnalysisResult { - const wasm = getLsolverWasmSync(); - const { nodes, elements, geometryMapping } = mesh; - const nNodes = nodes.length; - const nDof = 3 * nNodes; - - const E = components.E_MPa * 1e6; // Pa - const A = components.A_mm2 * 1e-6; // m^2 - const I = components.I_mm4 * 1e-12; // m^4 - - const K: number[][] = Array.from({ length: nDof }, () => - new Array(nDof).fill(0), - ); - const F = new Array(nDof).fill(0); - - for (const [ptId, [fx, fz, my]] of components.pointLoadsKN.entries()) { - const ni = geometryMapping.pointToNodes.get(ptId); - if (ni !== undefined) { - F[3 * ni] += fx * 1e3; - F[3 * ni + 1] += fz * 1e3; - F[3 * ni + 2] += my * 1e3; - } - } - - const connectionClasses = new Map(); - let sampleGamma = 1.0; - - interface ElemRecord { - lineId: number; - dofs: number[]; - kLoc: number[][]; - T: number[][]; - fEq: number[]; - } - const records: ElemRecord[] = []; - - for (const [lineId, elemIndices] of geometryMapping.lineToElements.entries()) { - const [p1, p2] = geometry.lines.get(lineId)!; - const cStart = nodes[geometryMapping.pointToNodes.get(p1)!]; - const cEnd = nodes[geometryMapping.pointToNodes.get(p2)!]; - const L_line = Math.hypot(cEnd[0] - cStart[0], cEnd[2] - cStart[2]) || 1.0; - - const [sjkNmI, sjkNmJ] = components.endSpringsKNmRad.get(lineId) ?? [1e9, 1e9]; - const sjI_Nm = sjkNmI * 1e3; - const sjJ_Nm = sjkNmJ * 1e3; - - connectionClasses.set( - lineId, - classifyConnectionEC3( - Math.min(sjkNmI, sjkNmJ) * 1e6, - components.E_MPa, - components.I_mm4, - L_line * 1e3, - ), - ); - if (sjkNmI < 1e8) { - sampleGamma = wasm.monfortonFixityFactor(sjI_Nm, E, I, L_line); - } - - const q_Nm = (components.distributedLoadsKNm.get(lineId) ?? 0) * 1e3; - const nEl = elemIndices.length; - - elemIndices.forEach((eIdx, posIdx) => { - const [na, nb] = elements[eIdx]; - const ca = nodes[na]; - const cb = nodes[nb]; - const dx = cb[0] - ca[0]; - const dz = cb[2] - ca[2]; - const L = Math.hypot(dx, dz) || 1.0; - const c = dx / L; - const s = dz / L; - - const gI = posIdx === 0 ? wasm.monfortonFixityFactor(sjI_Nm, E, I, L) : 1.0; - const gJ = posIdx === nEl - 1 ? wasm.monfortonFixityFactor(sjJ_Nm, E, I, L) : 1.0; - const denom = Math.max(4 - gI * gJ, 1e-9); - - const alphaII = (3 * gI * (2 - gJ)) / denom; - const alphaJJ = (3 * gJ * (2 - gI)) / denom; - const alphaIJ = (3 * gI * gJ) / denom; - - const eiL = (E * I) / L; - const k22 = 4 * eiL * alphaII; - const k25 = 2 * eiL * alphaIJ; - const k55 = 4 * eiL * alphaJJ; - const k12 = (k22 + k25) / L; - const k15 = (k25 + k55) / L; - const k11 = (k12 + k15) / L; - const eaL = (E * A) / L; - - const kLoc = [ - [eaL, 0, 0, -eaL, 0, 0], - [0, k11, k12, 0, -k11, k15], - [0, k12, k22, 0, -k12, k25], - [-eaL, 0, 0, eaL, 0, 0], - [0, -k11, -k12, 0, k11, -k15], - [0, k15, k25, 0, -k15, k55], - ]; - - const T = [ - [c, s, 0, 0, 0, 0], - [-s, c, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, c, s, 0], - [0, 0, 0, -s, c, 0], - [0, 0, 0, 0, 0, 1], - ]; - - const dofs = [3 * na, 3 * na + 1, 3 * na + 2, 3 * nb, 3 * nb + 1, 3 * nb + 2]; - for (let i = 0; i < 6; i++) { - for (let j = 0; j < 6; j++) { - let sum = 0; - for (let p = 0; p < 6; p++) { - for (let q = 0; q < 6; q++) { - sum += T[p][i] * kLoc[p][q] * T[q][j]; - } - } - K[dofs[i]][dofs[j]] += sum; - } - } - - const fEq = [ - 0, - 0.5 * q_Nm * L, - (q_Nm * L * L) / 12.0, - 0, - 0.5 * q_Nm * L, - -(q_Nm * L * L) / 12.0, - ]; - for (let i = 0; i < 6; i++) { - for (let p = 0; p < 6; p++) { - F[dofs[i]] += T[p][i] * fEq[p]; - } - } - - records.push({ lineId, dofs, kLoc, T, fEq }); - }); - } - - const fixed = new Set(); - for (const [ptId, flags] of components.supports.entries()) { - const ni = geometryMapping.pointToNodes.get(ptId); - if (ni !== undefined) { - if (flags[0]) fixed.add(3 * ni); - if (flags[1]) fixed.add(3 * ni + 1); - if (flags[2]) fixed.add(3 * ni + 2); - } - } - - const free: number[] = []; - for (let i = 0; i < nDof; i++) { - if (!fixed.has(i)) free.push(i); - } - - const K_ff = free.map((gi) => free.map((gj) => K[gi][gj])); - const F_f = free.map((gi) => F[gi]); - const u_f = solveGaussian(K_ff, F_f); - - const u = new Array(nDof).fill(0); - free.forEach((gi, idx) => (u[gi] = u_f[idx])); - - const deformedPositions: number[] = []; - let maxDispM = 0; - for (let i = 0; i < nNodes; i++) { - const ux = u[3 * i]; - const uz = u[3 * i + 1]; - maxDispM = Math.max(maxDispM, Math.hypot(ux, uz)); - deformedPositions.push(nodes[i][0] + ux, nodes[i][1], nodes[i][2] + uz); - } - - const lineMaxMomentKNm = new Map(); - const lineMaxShearKN = new Map(); - - for (const rec of records) { - const uLoc = new Array(6).fill(0); - for (let i = 0; i < 6; i++) { - for (let j = 0; j < 6; j++) { - uLoc[i] += rec.T[i][j] * u[rec.dofs[j]]; - } - } - const fInt = new Array(6).fill(0); - for (let i = 0; i < 6; i++) { - let s = -rec.fEq[i]; - for (let j = 0; j < 6; j++) s += rec.kLoc[i][j] * uLoc[j]; - fInt[i] = s; - } - const vMax = Math.max(Math.abs(fInt[1]), Math.abs(fInt[4])) / 1e3; - const mMax = Math.max(Math.abs(fInt[2]), Math.abs(fInt[5])) / 1e3; - lineMaxShearKN.set(rec.lineId, Math.max(lineMaxShearKN.get(rec.lineId) ?? 0, vMax)); - lineMaxMomentKNm.set( - rec.lineId, - Math.max(lineMaxMomentKNm.get(rec.lineId) ?? 0, mMax), - ); - } - - return { - engine: "wasm-local", - deformedPositions, - maxDisplacementMm: Number((maxDispM * 1e3).toFixed(3)), - lineMaxMomentKNm, - lineMaxShearKN, - connectionClasses, - fixityFactorGamma: Number(sampleGamma.toFixed(4)), - }; -} diff --git a/web/src/styles.css b/web/src/styles.css deleted file mode 100644 index 134897e..0000000 --- a/web/src/styles.css +++ /dev/null @@ -1,109 +0,0 @@ -:root { - --bg-main: #0b0f19; - --bg-panel: rgba(17, 24, 39, 0.9); - --border: rgba(148, 163, 184, 0.2); - --text-primary: #f8fafc; - --text-secondary: #94a3b8; - --accent: #38bdf8; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: "Inter", system-ui, sans-serif; - background: var(--bg-main); - color: var(--text-primary); - height: 100vh; - overflow: hidden; -} - -.lp-layout { - display: grid; - grid-template-columns: 320px 1fr 360px; - height: 100vh; - width: 100vw; -} - -.lp-panel { - background: var(--bg-panel); - border-right: 1px solid var(--border); - padding: 16px; - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 14px; -} - -.lp-panel.right { - border-right: none; - border-left: 1px solid var(--border); -} - -.lp-viewer { - position: relative; - width: 100%; - height: 100%; - background: radial-gradient(circle at 50% 40%, #1e293b 0%, #090d16 100%); -} - -.lp-card { - background: rgba(30, 41, 59, 0.55); - border: 1px solid var(--border); - border-radius: 10px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 8px; -} - -.lp-card h3 { - font-size: 0.8rem; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--accent); -} - -.lp-row { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.82rem; - gap: 8px; -} - -.lp-row label { - color: var(--text-secondary); -} - -.lp-row input, -.lp-row select { - background: #0f172a; - color: var(--text-primary); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - width: 115px; - font-size: 0.82rem; -} - -.lp-badge { - display: inline-block; - padding: 3px 8px; - border-radius: 999px; - font-size: 0.72rem; - font-weight: 600; - background: rgba(56, 189, 248, 0.16); - color: var(--accent); -} - -canvas.lp-chart { - width: 100%; - height: 150px; - border-radius: 8px; - border: 1px solid var(--border); - background: #090d16; -} diff --git a/web/src/ui/getDiagrams.ts b/web/src/ui/getDiagrams.ts deleted file mode 100644 index e6b1413..0000000 --- a/web/src/ui/getDiagrams.ts +++ /dev/null @@ -1,74 +0,0 @@ -import van, { type State } from "vanjs-core"; - -export function getDiagrams({ - lineMaxMomentKNm, - lineMaxShearKN, - fixityFactorGamma, -}: { - lineMaxMomentKNm: State>; - lineMaxShearKN: State>; - fixityFactorGamma: State; -}): HTMLElement { - const wrap = document.createElement("div"); - wrap.style.display = "flex"; - wrap.style.flexDirection = "column"; - wrap.style.gap = "12px"; - - const cBars = document.createElement("canvas"); - cBars.className = "lp-chart"; - const cGamma = document.createElement("canvas"); - cGamma.className = "lp-chart"; - - const makeCard = (title: string, canvas: HTMLCanvasElement) => { - const d = document.createElement("div"); - d.className = "lp-card"; - const h = document.createElement("h3"); - h.textContent = title; - d.append(h, canvas); - return d; - }; - - wrap.append( - makeCard("Momentos Máximos por Barra |M| [kN·m]", cBars), - makeCard("Curva Monforton-Wu γ(Sj) y Cortantes |V| [kN]", cGamma), - ); - - van.derive(() => { - const ctx1 = cBars.getContext("2d"); - if (ctx1) { - const w = (cBars.width = cBars.clientWidth || 320); - const h = (cBars.height = cBars.clientHeight || 150); - ctx1.clearRect(0, 0, w, h); - const entries = [...lineMaxMomentKNm.val.entries()]; - const maxM = Math.max(...entries.map((e) => e[1]), 1); - entries.forEach(([lid, mVal], idx) => { - const bw = (w - 50) / Math.max(entries.length, 1) - 10; - const x = 35 + idx * (bw + 10); - const bh = ((h - 40) * mVal) / maxM; - ctx1.fillStyle = "#38bdf8"; - ctx1.fillRect(x, h - 22 - bh, bw, bh); - ctx1.fillStyle = "#f8fafc"; - ctx1.font = "11px Inter, sans-serif"; - ctx1.fillText(`L${lid}: ${mVal.toFixed(1)}`, x, h - 7); - }); - } - - const ctx2 = cGamma.getContext("2d"); - if (ctx2) { - const w = (cGamma.width = cGamma.clientWidth || 320); - const h = (cGamma.height = cGamma.clientHeight || 150); - ctx2.clearRect(0, 0, w, h); - ctx2.fillStyle = "#10b981"; - ctx2.font = "12px Inter, sans-serif"; - ctx2.fillText(`Factor de Empotramiento γ = ${fixityFactorGamma.val.toFixed(3)}`, 14, 24); - let y = 48; - for (const [lid, vVal] of lineMaxShearKN.val.entries()) { - ctx2.fillStyle = "#94a3b8"; - ctx2.fillText(`Barra L${lid} -> |V|max = ${vVal.toFixed(2)} kN`, 14, y); - y += 22; - } - } - }); - - return wrap; -} diff --git a/web/src/viewer/getViewer3D.ts b/web/src/viewer/getViewer3D.ts deleted file mode 100644 index f9dc2cb..0000000 --- a/web/src/viewer/getViewer3D.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as THREE from "three"; -import van, { type State } from "vanjs-core"; -import type { DiscreteMesh, FrameComponents } from "../data-model"; - -export function getViewer3D({ - mesh, - components, - deformedPositions, - lineMaxMomentKNm, - deformationScale, -}: { - mesh: State; - components: State; - deformedPositions: State; - lineMaxMomentKNm: State>; - deformationScale: State; -}): HTMLElement { - const container = document.createElement("div"); - container.className = "lp-viewer"; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 300); - camera.position.set(4, -14, 4); - camera.up.set(0, 0, 1); - camera.lookAt(4, 0, 2.5); - - const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); - container.appendChild(renderer.domElement); - - const grid = new THREE.GridHelper(16, 16, 0x334155, 0x1e293b); - grid.rotation.x = Math.PI / 2; - grid.position.set(4, 0, 0); - scene.add(grid); - - const group = new THREE.Group(); - scene.add(group); - - van.derive(() => { - while (group.children.length > 0) group.remove(group.children[0]); - const m = mesh.val; - const pos = deformedPositions.val; - const scale = deformationScale.val; - const mMap = lineMaxMomentKNm.val; - const comps = components.val; - - if (!m.nodes.length) return; - - const defCoords = m.nodes.map((orig, i) => { - if (pos.length >= (i + 1) * 3) { - return new THREE.Vector3( - orig[0] + (pos[3 * i] - orig[0]) * scale, - orig[1] + (pos[3 * i + 1] - orig[1]) * scale, - orig[2] + (pos[3 * i + 2] - orig[2]) * scale, - ); - } - return new THREE.Vector3(orig[0], orig[1], orig[2]); - }); - - const maxM = Math.max(...mMap.values(), 1); - for (const [lineId, elemIds] of m.geometryMapping.lineToElements.entries()) { - const ratio = Math.min((mMap.get(lineId) ?? 0) / maxM, 1); - const col = new THREE.Color().setHSL(0.55 - ratio * 0.5, 0.9, 0.55); - const pts: THREE.Vector3[] = []; - for (const eIdx of elemIds) { - const [na, nb] = m.elements[eIdx]; - pts.push(defCoords[na], defCoords[nb]); - } - group.add( - new THREE.LineSegments( - new THREE.BufferGeometry().setFromPoints(pts), - new THREE.LineBasicMaterial({ color: col }), - ), - ); - } - - const nodeGeom = new THREE.SphereGeometry(0.07, 10, 10); - const nodeMat = new THREE.MeshBasicMaterial({ color: 0x38bdf8 }); - for (const v of defCoords) { - const sp = new THREE.Mesh(nodeGeom, nodeMat); - sp.position.copy(v); - group.add(sp); - } - - const suppGeom = new THREE.ConeGeometry(0.25, 0.35, 4); - const suppMat = new THREE.MeshBasicMaterial({ color: 0xf43f5e, wireframe: true }); - for (const ptId of comps.supports.keys()) { - const ni = m.geometryMapping.pointToNodes.get(ptId); - if (ni !== undefined && defCoords[ni]) { - const cone = new THREE.Mesh(suppGeom, suppMat); - cone.rotation.x = Math.PI / 2; - cone.position.copy(defCoords[ni]); - cone.position.z -= 0.18; - group.add(cone); - } - } - - renderer.render(scene, camera); - }); - - const ro = new ResizeObserver(() => { - const w = container.clientWidth || 700; - const h = container.clientHeight || 500; - camera.aspect = w / h; - camera.updateProjectionMatrix(); - renderer.setSize(w, h); - renderer.render(scene, camera); - }); - ro.observe(container); - - return container; -} diff --git a/web/tsconfig.json b/web/tsconfig.json deleted file mode 100644 index df57c48..0000000 --- a/web/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "noEmit": true - }, - "include": ["src"] -}