From 0dbc2a45188cdc5d79367bd89c674e042e38ab85 Mon Sep 17 00:00:00 2001 From: alro1965 Date: Tue, 26 May 2026 22:10:18 -0400 Subject: [PATCH] v0.1-sprint0: Esqueleto completo AR-ShipDesign MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Estructura completa de carpetas (236 módulos stub + implementados) - pyproject.toml, requirements, .gitignore, LICENSE (propietario) - core/project.py: serialización .arsd (ZIP con JSON) - core/units.py: conversiones SI <-> imperial completas - ui/main_window.py: layout DELFTship-style con todos los paneles - Árbol de proyecto (dock izquierda) - Tabs de módulos (centro) - Panel de propiedades (dock derecha) - Panel hidrostáticos en vivo (inferior, fijo) - ui/i18n: español e inglés - ui/themes: tema claro y oscuro - utils/logger.py, settings.py, validation.py - data/liquids.json: 15 líquidos navales - data/stability_criteria.json: IMO IS Code 2008, A.749(18), USCG - tests/test_startup.py: 12 tests, todos PASSED - Módulo scantling/ ISO 12215 (stubs Sprint 2.5) - Módulo fabrication/molds/ para moldes FRP (stubs Sprint 13B) - Módulo fabrication/ para CNC plasma/router/laser (stubs Sprint 13) --- .claude/settings.json | 24 + .claude/settings.local.json | 15 + .env.example | 11 + .gitignore | 49 + AR_ShipDesign_prompt.md | 1063 +++++++++++++++++ CHANGELOG.md | 34 + LICENSE.txt | 10 + README.md | 49 + arshipdesign/__init__.py | 9 + arshipdesign/core/__init__.py | 1 + arshipdesign/core/appendage.py | 2 + arshipdesign/core/battery.py | 2 + arshipdesign/core/compartment.py | 2 + arshipdesign/core/electrical_load.py | 2 + arshipdesign/core/engine.py | 2 + arshipdesign/core/gear.py | 2 + arshipdesign/core/generator.py | 2 + arshipdesign/core/hull.py | 2 + arshipdesign/core/lightship.py | 2 + arshipdesign/core/liquid.py | 2 + arshipdesign/core/loadcase.py | 2 + arshipdesign/core/offsets.py | 2 + arshipdesign/core/pipe.py | 2 + arshipdesign/core/project.py | 273 +++++ arshipdesign/core/propeller.py | 2 + arshipdesign/core/pump.py | 2 + arshipdesign/core/rig.py | 2 + arshipdesign/core/sail.py | 2 + arshipdesign/core/section.py | 2 + arshipdesign/core/ship.py | 3 + arshipdesign/core/superstructure.py | 2 + arshipdesign/core/system.py | 2 + arshipdesign/core/tank.py | 2 + arshipdesign/core/units.py | 285 +++++ arshipdesign/core/valve.py | 2 + arshipdesign/core/waterplane.py | 2 + arshipdesign/core/weight_item.py | 2 + arshipdesign/engines/__init__.py | 1 + arshipdesign/engines/engine_curve.py | 2 + arshipdesign/engines/engine_db.py | 2 + arshipdesign/engines/engine_selection.py | 2 + arshipdesign/engines/fuel_consumption.py | 2 + arshipdesign/engines/nox_emissions.py | 2 + arshipdesign/fabrication/__init__.py | 1 + arshipdesign/fabrication/bom.py | 2 + .../fabrication/construction_sequence.py | 2 + arshipdesign/fabrication/details/__init__.py | 1 + .../fabrication/details/alignment_marks.py | 2 + arshipdesign/fabrication/details/dogbone.py | 2 + .../fabrication/details/grain_direction.py | 2 + .../fabrication/details/lightening_holes.py | 2 + .../fabrication/details/limber_holes.py | 2 + arshipdesign/fabrication/details/slot_tab.py | 2 + .../fabrication/fabrication_report.py | 2 + arshipdesign/fabrication/gcode_generator.py | 2 + arshipdesign/fabrication/joints/__init__.py | 1 + arshipdesign/fabrication/joints/butt_joint.py | 2 + arshipdesign/fabrication/joints/epoxy_bond.py | 2 + .../fabrication/joints/scarf_joint.py | 2 + arshipdesign/fabrication/joints/weld_prep.py | 2 + .../fabrication/material_estimator.py | 2 + .../fabrication/materials/__init__.py | 1 + arshipdesign/fabrication/molds/__init__.py | 1 + .../fabrication/molds/hull_laminate.py | 2 + .../fabrication/molds/laminate_schedule.py | 2 + arshipdesign/fabrication/molds/lofting.py | 2 + arshipdesign/fabrication/molds/mold_female.py | 2 + arshipdesign/fabrication/molds/mold_report.py | 2 + .../fabrication/molds/mold_structure.py | 2 + .../fabrication/molds/parting_line.py | 2 + arshipdesign/fabrication/molds/plug.py | 2 + .../fabrication/molds/resin_calculator.py | 2 + .../fabrication/molds/station_molds.py | 2 + arshipdesign/fabrication/nesting.py | 2 + arshipdesign/fabrication/part_splitter.py | 2 + arshipdesign/fabrication/sheet.py | 2 + .../fabrication/standards/__init__.py | 1 + arshipdesign/fabrication/standards/abyc.py | 2 + .../fabrication/standards/iso_12215.py | 2 + arshipdesign/geometry/__init__.py | 1 + arshipdesign/geometry/boolean.py | 2 + arshipdesign/geometry/fairing.py | 2 + arshipdesign/geometry/fitting.py | 2 + arshipdesign/geometry/intersection.py | 2 + arshipdesign/geometry/mesh.py | 2 + arshipdesign/geometry/nurbs_curve.py | 2 + arshipdesign/geometry/nurbs_surface.py | 2 + arshipdesign/geometry/slicer.py | 2 + arshipdesign/hydrostatics/__init__.py | 1 + arshipdesign/hydrostatics/bonjean.py | 2 + arshipdesign/hydrostatics/coefficients.py | 2 + arshipdesign/hydrostatics/curves_of_form.py | 2 + arshipdesign/hydrostatics/free_floating.py | 2 + arshipdesign/hydrostatics/heeled.py | 2 + arshipdesign/hydrostatics/integrator.py | 2 + arshipdesign/hydrostatics/trim.py | 2 + arshipdesign/hydrostatics/upright.py | 2 + arshipdesign/io/__init__.py | 1 + arshipdesign/io/dxf_reader.py | 2 + arshipdesign/io/dxf_writer.py | 2 + arshipdesign/io/obj_export.py | 2 + arshipdesign/io/offsets_csv.py | 2 + arshipdesign/io/offsets_excel.py | 2 + arshipdesign/io/pdf_export.py | 2 + arshipdesign/io/stl_export.py | 2 + arshipdesign/parametric/__init__.py | 1 + arshipdesign/parametric/lackenby_transform.py | 2 + arshipdesign/parametric/series60.py | 2 + arshipdesign/parametric/wigley.py | 2 + arshipdesign/parametric/wizard_cruiser.py | 2 + arshipdesign/parametric/wizard_planing.py | 2 + .../parametric/wizard_sailing_mono.py | 2 + arshipdesign/parametric/wizard_workboat.py | 2 + arshipdesign/propulsion/__init__.py | 1 + arshipdesign/propulsion/cavitation.py | 2 + arshipdesign/propulsion/electric_drive.py | 2 + arshipdesign/propulsion/pod.py | 2 + arshipdesign/propulsion/propeller_design.py | 2 + arshipdesign/propulsion/propeller_matching.py | 2 + arshipdesign/propulsion/wageningen_b.py | 2 + arshipdesign/propulsion/waterjet.py | 2 + arshipdesign/reports/__init__.py | 1 + arshipdesign/reports/hydrostatic_report.py | 2 + arshipdesign/reports/lines_plan.py | 2 + arshipdesign/reports/pdf_builder.py | 2 + arshipdesign/reports/resistance_powering.py | 2 + arshipdesign/reports/stability_booklet.py | 2 + arshipdesign/resistance/__init__.py | 1 + arshipdesign/resistance/added_resistance.py | 2 + arshipdesign/resistance/compton.py | 2 + arshipdesign/resistance/dsyhs.py | 2 + arshipdesign/resistance/effective_power.py | 2 + arshipdesign/resistance/holtrop_mennen.py | 2 + arshipdesign/resistance/savitsky.py | 2 + arshipdesign/resistance/van_oortmerssen.py | 2 + arshipdesign/sailing/__init__.py | 1 + arshipdesign/sailing/aero_model.py | 2 + arshipdesign/sailing/hydro_model.py | 2 + arshipdesign/sailing/multihull.py | 2 + arshipdesign/sailing/polar_diagram.py | 2 + arshipdesign/sailing/sail_coefficients.py | 2 + arshipdesign/sailing/vpp.py | 2 + arshipdesign/scantling/__init__.py | 1 + arshipdesign/scantling/bulkheads.py | 2 + arshipdesign/scantling/deck_beams.py | 2 + arshipdesign/scantling/design_pressures.py | 2 + arshipdesign/scantling/engine_beds.py | 2 + arshipdesign/scantling/frames.py | 2 + arshipdesign/scantling/framing_system.py | 2 + arshipdesign/scantling/keel_bolts.py | 2 + arshipdesign/scantling/keel_structure.py | 2 + arshipdesign/scantling/longitudinals.py | 2 + arshipdesign/scantling/mast_step.py | 2 + arshipdesign/scantling/materials/__init__.py | 1 + .../scantling/materials/aluminum_marine.py | 2 + .../scantling/materials/frp_laminates.py | 2 + .../scantling/materials/plywood_marine.py | 2 + .../scantling/materials/steel_structural.py | 2 + arshipdesign/scantling/optimizer.py | 2 + arshipdesign/scantling/plating.py | 2 + arshipdesign/scantling/scantling_report.py | 2 + arshipdesign/scantling/section_library.py | 2 + arshipdesign/scantling/standards/__init__.py | 1 + .../scantling/standards/iso_12215_5.py | 2 + .../scantling/standards/iso_12215_6.py | 2 + .../scantling/standards/iso_12215_9.py | 2 + .../scantling/standards/lloyds_small_craft.py | 2 + arshipdesign/seakeeping/__init__.py | 1 + arshipdesign/seakeeping/motion_sickness.py | 2 + arshipdesign/seakeeping/rao.py | 2 + arshipdesign/seakeeping/short_term.py | 2 + arshipdesign/seakeeping/slamming.py | 2 + arshipdesign/seakeeping/spectra.py | 2 + arshipdesign/seakeeping/strip_theory.py | 2 + arshipdesign/stability/__init__.py | 1 + arshipdesign/stability/criteria.py | 2 + arshipdesign/stability/damage.py | 2 + arshipdesign/stability/floodable_length.py | 2 + arshipdesign/stability/intact.py | 2 + arshipdesign/stability/weather.py | 2 + arshipdesign/systems/__init__.py | 1 + arshipdesign/systems/anchoring/__init__.py | 1 + .../systems/anchoring/anchor_selection.py | 2 + arshipdesign/systems/anchoring/windlass.py | 2 + arshipdesign/systems/ballast/__init__.py | 1 + .../systems/ballast/ballast_system.py | 2 + arshipdesign/systems/bilge/__init__.py | 1 + arshipdesign/systems/bilge/bilge_system.py | 2 + .../systems/bilge/oily_water_separator.py | 2 + arshipdesign/systems/electrical/__init__.py | 1 + .../systems/electrical/battery_sizing.py | 2 + .../systems/electrical/cable_sizing.py | 2 + .../systems/electrical/generator_sizing.py | 2 + .../systems/electrical/load_analysis.py | 2 + .../systems/electrical/one_line_diagram.py | 2 + arshipdesign/systems/firefighting/__init__.py | 1 + .../systems/firefighting/co2_system.py | 2 + .../systems/firefighting/detection.py | 2 + .../systems/firefighting/fire_main.py | 2 + arshipdesign/systems/freshwater/__init__.py | 1 + arshipdesign/systems/freshwater/fw_system.py | 2 + arshipdesign/systems/freshwater/watermaker.py | 2 + arshipdesign/systems/fuel/__init__.py | 1 + arshipdesign/systems/fuel/autonomy.py | 2 + arshipdesign/systems/fuel/day_tank.py | 2 + arshipdesign/systems/fuel/fuel_system.py | 2 + arshipdesign/systems/hvac/__init__.py | 1 + arshipdesign/systems/hvac/chiller_sizing.py | 2 + arshipdesign/systems/hvac/heat_balance.py | 2 + arshipdesign/systems/steering/__init__.py | 1 + .../systems/steering/rudder_design.py | 2 + .../systems/steering/steering_gear.py | 2 + arshipdesign/systems/steering/thrusters.py | 2 + arshipdesign/tanks/__init__.py | 1 + arshipdesign/tanks/capacity_plan.py | 2 + arshipdesign/tanks/fsm.py | 2 + arshipdesign/tanks/sounding_table.py | 2 + arshipdesign/tanks/tank_definition.py | 2 + arshipdesign/ui/__init__.py | 1 + arshipdesign/ui/commands/__init__.py | 1 + arshipdesign/ui/commands/add_tank.py | 2 + arshipdesign/ui/commands/command.py | 2 + arshipdesign/ui/commands/modify_hull.py | 2 + arshipdesign/ui/dialogs/__init__.py | 1 + arshipdesign/ui/dialogs/liquid_picker.py | 2 + arshipdesign/ui/dialogs/preferences.py | 2 + .../ui/dialogs/stability_criteria_picker.py | 2 + arshipdesign/ui/dialogs/wizards.py | 2 + arshipdesign/ui/i18n/en.json | 80 ++ arshipdesign/ui/i18n/es.json | 80 ++ arshipdesign/ui/main_window.py | 802 +++++++++++++ arshipdesign/ui/shortcuts.py | 1 + arshipdesign/ui/themes/dark.qss | 162 +++ arshipdesign/ui/themes/light.qss | 49 + arshipdesign/ui/widgets/__init__.py | 1 + arshipdesign/ui/widgets/curve_plotter.py | 2 + .../ui/widgets/electrical_load_table.py | 2 + arshipdesign/ui/widgets/engine_picker.py | 2 + arshipdesign/ui/widgets/loadcase_editor.py | 2 + arshipdesign/ui/widgets/offsets_editor.py | 2 + arshipdesign/ui/widgets/propeller_designer.py | 2 + arshipdesign/ui/widgets/rig_editor.py | 2 + .../ui/widgets/system_diagram_canvas.py | 2 + arshipdesign/ui/widgets/tank_editor.py | 2 + arshipdesign/ui/widgets/viewer_3d.py | 2 + arshipdesign/ui/widgets/viewer_lines.py | 2 + arshipdesign/ui/widgets/viewer_polar.py | 2 + arshipdesign/utils/__init__.py | 1 + arshipdesign/utils/async_runner.py | 20 + arshipdesign/utils/logger.py | 67 ++ arshipdesign/utils/settings.py | 89 ++ arshipdesign/utils/validation.py | 19 + create_stubs.py | 253 ++++ data/benchmarks/README.md | 3 + data/liquids.json | 17 + data/stability_criteria.json | 29 + docs/developer_guide.md | 3 + docs/theory_manual.md | 5 + docs/user_manual.md | 3 + docs/validation_report.md | 3 + main.py | 67 ++ pyproject.toml | 54 + requirements-dev.txt | 7 + requirements.txt | 13 + tests/conftest.py | 9 + tests/test_startup.py | 166 +++ 266 files changed, 4249 insertions(+) create mode 100644 .claude/settings.json create mode 100644 .claude/settings.local.json create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 AR_ShipDesign_prompt.md create mode 100644 CHANGELOG.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 arshipdesign/__init__.py create mode 100644 arshipdesign/core/__init__.py create mode 100644 arshipdesign/core/appendage.py create mode 100644 arshipdesign/core/battery.py create mode 100644 arshipdesign/core/compartment.py create mode 100644 arshipdesign/core/electrical_load.py create mode 100644 arshipdesign/core/engine.py create mode 100644 arshipdesign/core/gear.py create mode 100644 arshipdesign/core/generator.py create mode 100644 arshipdesign/core/hull.py create mode 100644 arshipdesign/core/lightship.py create mode 100644 arshipdesign/core/liquid.py create mode 100644 arshipdesign/core/loadcase.py create mode 100644 arshipdesign/core/offsets.py create mode 100644 arshipdesign/core/pipe.py create mode 100644 arshipdesign/core/project.py create mode 100644 arshipdesign/core/propeller.py create mode 100644 arshipdesign/core/pump.py create mode 100644 arshipdesign/core/rig.py create mode 100644 arshipdesign/core/sail.py create mode 100644 arshipdesign/core/section.py create mode 100644 arshipdesign/core/ship.py create mode 100644 arshipdesign/core/superstructure.py create mode 100644 arshipdesign/core/system.py create mode 100644 arshipdesign/core/tank.py create mode 100644 arshipdesign/core/units.py create mode 100644 arshipdesign/core/valve.py create mode 100644 arshipdesign/core/waterplane.py create mode 100644 arshipdesign/core/weight_item.py create mode 100644 arshipdesign/engines/__init__.py create mode 100644 arshipdesign/engines/engine_curve.py create mode 100644 arshipdesign/engines/engine_db.py create mode 100644 arshipdesign/engines/engine_selection.py create mode 100644 arshipdesign/engines/fuel_consumption.py create mode 100644 arshipdesign/engines/nox_emissions.py create mode 100644 arshipdesign/fabrication/__init__.py create mode 100644 arshipdesign/fabrication/bom.py create mode 100644 arshipdesign/fabrication/construction_sequence.py create mode 100644 arshipdesign/fabrication/details/__init__.py create mode 100644 arshipdesign/fabrication/details/alignment_marks.py create mode 100644 arshipdesign/fabrication/details/dogbone.py create mode 100644 arshipdesign/fabrication/details/grain_direction.py create mode 100644 arshipdesign/fabrication/details/lightening_holes.py create mode 100644 arshipdesign/fabrication/details/limber_holes.py create mode 100644 arshipdesign/fabrication/details/slot_tab.py create mode 100644 arshipdesign/fabrication/fabrication_report.py create mode 100644 arshipdesign/fabrication/gcode_generator.py create mode 100644 arshipdesign/fabrication/joints/__init__.py create mode 100644 arshipdesign/fabrication/joints/butt_joint.py create mode 100644 arshipdesign/fabrication/joints/epoxy_bond.py create mode 100644 arshipdesign/fabrication/joints/scarf_joint.py create mode 100644 arshipdesign/fabrication/joints/weld_prep.py create mode 100644 arshipdesign/fabrication/material_estimator.py create mode 100644 arshipdesign/fabrication/materials/__init__.py create mode 100644 arshipdesign/fabrication/molds/__init__.py create mode 100644 arshipdesign/fabrication/molds/hull_laminate.py create mode 100644 arshipdesign/fabrication/molds/laminate_schedule.py create mode 100644 arshipdesign/fabrication/molds/lofting.py create mode 100644 arshipdesign/fabrication/molds/mold_female.py create mode 100644 arshipdesign/fabrication/molds/mold_report.py create mode 100644 arshipdesign/fabrication/molds/mold_structure.py create mode 100644 arshipdesign/fabrication/molds/parting_line.py create mode 100644 arshipdesign/fabrication/molds/plug.py create mode 100644 arshipdesign/fabrication/molds/resin_calculator.py create mode 100644 arshipdesign/fabrication/molds/station_molds.py create mode 100644 arshipdesign/fabrication/nesting.py create mode 100644 arshipdesign/fabrication/part_splitter.py create mode 100644 arshipdesign/fabrication/sheet.py create mode 100644 arshipdesign/fabrication/standards/__init__.py create mode 100644 arshipdesign/fabrication/standards/abyc.py create mode 100644 arshipdesign/fabrication/standards/iso_12215.py create mode 100644 arshipdesign/geometry/__init__.py create mode 100644 arshipdesign/geometry/boolean.py create mode 100644 arshipdesign/geometry/fairing.py create mode 100644 arshipdesign/geometry/fitting.py create mode 100644 arshipdesign/geometry/intersection.py create mode 100644 arshipdesign/geometry/mesh.py create mode 100644 arshipdesign/geometry/nurbs_curve.py create mode 100644 arshipdesign/geometry/nurbs_surface.py create mode 100644 arshipdesign/geometry/slicer.py create mode 100644 arshipdesign/hydrostatics/__init__.py create mode 100644 arshipdesign/hydrostatics/bonjean.py create mode 100644 arshipdesign/hydrostatics/coefficients.py create mode 100644 arshipdesign/hydrostatics/curves_of_form.py create mode 100644 arshipdesign/hydrostatics/free_floating.py create mode 100644 arshipdesign/hydrostatics/heeled.py create mode 100644 arshipdesign/hydrostatics/integrator.py create mode 100644 arshipdesign/hydrostatics/trim.py create mode 100644 arshipdesign/hydrostatics/upright.py create mode 100644 arshipdesign/io/__init__.py create mode 100644 arshipdesign/io/dxf_reader.py create mode 100644 arshipdesign/io/dxf_writer.py create mode 100644 arshipdesign/io/obj_export.py create mode 100644 arshipdesign/io/offsets_csv.py create mode 100644 arshipdesign/io/offsets_excel.py create mode 100644 arshipdesign/io/pdf_export.py create mode 100644 arshipdesign/io/stl_export.py create mode 100644 arshipdesign/parametric/__init__.py create mode 100644 arshipdesign/parametric/lackenby_transform.py create mode 100644 arshipdesign/parametric/series60.py create mode 100644 arshipdesign/parametric/wigley.py create mode 100644 arshipdesign/parametric/wizard_cruiser.py create mode 100644 arshipdesign/parametric/wizard_planing.py create mode 100644 arshipdesign/parametric/wizard_sailing_mono.py create mode 100644 arshipdesign/parametric/wizard_workboat.py create mode 100644 arshipdesign/propulsion/__init__.py create mode 100644 arshipdesign/propulsion/cavitation.py create mode 100644 arshipdesign/propulsion/electric_drive.py create mode 100644 arshipdesign/propulsion/pod.py create mode 100644 arshipdesign/propulsion/propeller_design.py create mode 100644 arshipdesign/propulsion/propeller_matching.py create mode 100644 arshipdesign/propulsion/wageningen_b.py create mode 100644 arshipdesign/propulsion/waterjet.py create mode 100644 arshipdesign/reports/__init__.py create mode 100644 arshipdesign/reports/hydrostatic_report.py create mode 100644 arshipdesign/reports/lines_plan.py create mode 100644 arshipdesign/reports/pdf_builder.py create mode 100644 arshipdesign/reports/resistance_powering.py create mode 100644 arshipdesign/reports/stability_booklet.py create mode 100644 arshipdesign/resistance/__init__.py create mode 100644 arshipdesign/resistance/added_resistance.py create mode 100644 arshipdesign/resistance/compton.py create mode 100644 arshipdesign/resistance/dsyhs.py create mode 100644 arshipdesign/resistance/effective_power.py create mode 100644 arshipdesign/resistance/holtrop_mennen.py create mode 100644 arshipdesign/resistance/savitsky.py create mode 100644 arshipdesign/resistance/van_oortmerssen.py create mode 100644 arshipdesign/sailing/__init__.py create mode 100644 arshipdesign/sailing/aero_model.py create mode 100644 arshipdesign/sailing/hydro_model.py create mode 100644 arshipdesign/sailing/multihull.py create mode 100644 arshipdesign/sailing/polar_diagram.py create mode 100644 arshipdesign/sailing/sail_coefficients.py create mode 100644 arshipdesign/sailing/vpp.py create mode 100644 arshipdesign/scantling/__init__.py create mode 100644 arshipdesign/scantling/bulkheads.py create mode 100644 arshipdesign/scantling/deck_beams.py create mode 100644 arshipdesign/scantling/design_pressures.py create mode 100644 arshipdesign/scantling/engine_beds.py create mode 100644 arshipdesign/scantling/frames.py create mode 100644 arshipdesign/scantling/framing_system.py create mode 100644 arshipdesign/scantling/keel_bolts.py create mode 100644 arshipdesign/scantling/keel_structure.py create mode 100644 arshipdesign/scantling/longitudinals.py create mode 100644 arshipdesign/scantling/mast_step.py create mode 100644 arshipdesign/scantling/materials/__init__.py create mode 100644 arshipdesign/scantling/materials/aluminum_marine.py create mode 100644 arshipdesign/scantling/materials/frp_laminates.py create mode 100644 arshipdesign/scantling/materials/plywood_marine.py create mode 100644 arshipdesign/scantling/materials/steel_structural.py create mode 100644 arshipdesign/scantling/optimizer.py create mode 100644 arshipdesign/scantling/plating.py create mode 100644 arshipdesign/scantling/scantling_report.py create mode 100644 arshipdesign/scantling/section_library.py create mode 100644 arshipdesign/scantling/standards/__init__.py create mode 100644 arshipdesign/scantling/standards/iso_12215_5.py create mode 100644 arshipdesign/scantling/standards/iso_12215_6.py create mode 100644 arshipdesign/scantling/standards/iso_12215_9.py create mode 100644 arshipdesign/scantling/standards/lloyds_small_craft.py create mode 100644 arshipdesign/seakeeping/__init__.py create mode 100644 arshipdesign/seakeeping/motion_sickness.py create mode 100644 arshipdesign/seakeeping/rao.py create mode 100644 arshipdesign/seakeeping/short_term.py create mode 100644 arshipdesign/seakeeping/slamming.py create mode 100644 arshipdesign/seakeeping/spectra.py create mode 100644 arshipdesign/seakeeping/strip_theory.py create mode 100644 arshipdesign/stability/__init__.py create mode 100644 arshipdesign/stability/criteria.py create mode 100644 arshipdesign/stability/damage.py create mode 100644 arshipdesign/stability/floodable_length.py create mode 100644 arshipdesign/stability/intact.py create mode 100644 arshipdesign/stability/weather.py create mode 100644 arshipdesign/systems/__init__.py create mode 100644 arshipdesign/systems/anchoring/__init__.py create mode 100644 arshipdesign/systems/anchoring/anchor_selection.py create mode 100644 arshipdesign/systems/anchoring/windlass.py create mode 100644 arshipdesign/systems/ballast/__init__.py create mode 100644 arshipdesign/systems/ballast/ballast_system.py create mode 100644 arshipdesign/systems/bilge/__init__.py create mode 100644 arshipdesign/systems/bilge/bilge_system.py create mode 100644 arshipdesign/systems/bilge/oily_water_separator.py create mode 100644 arshipdesign/systems/electrical/__init__.py create mode 100644 arshipdesign/systems/electrical/battery_sizing.py create mode 100644 arshipdesign/systems/electrical/cable_sizing.py create mode 100644 arshipdesign/systems/electrical/generator_sizing.py create mode 100644 arshipdesign/systems/electrical/load_analysis.py create mode 100644 arshipdesign/systems/electrical/one_line_diagram.py create mode 100644 arshipdesign/systems/firefighting/__init__.py create mode 100644 arshipdesign/systems/firefighting/co2_system.py create mode 100644 arshipdesign/systems/firefighting/detection.py create mode 100644 arshipdesign/systems/firefighting/fire_main.py create mode 100644 arshipdesign/systems/freshwater/__init__.py create mode 100644 arshipdesign/systems/freshwater/fw_system.py create mode 100644 arshipdesign/systems/freshwater/watermaker.py create mode 100644 arshipdesign/systems/fuel/__init__.py create mode 100644 arshipdesign/systems/fuel/autonomy.py create mode 100644 arshipdesign/systems/fuel/day_tank.py create mode 100644 arshipdesign/systems/fuel/fuel_system.py create mode 100644 arshipdesign/systems/hvac/__init__.py create mode 100644 arshipdesign/systems/hvac/chiller_sizing.py create mode 100644 arshipdesign/systems/hvac/heat_balance.py create mode 100644 arshipdesign/systems/steering/__init__.py create mode 100644 arshipdesign/systems/steering/rudder_design.py create mode 100644 arshipdesign/systems/steering/steering_gear.py create mode 100644 arshipdesign/systems/steering/thrusters.py create mode 100644 arshipdesign/tanks/__init__.py create mode 100644 arshipdesign/tanks/capacity_plan.py create mode 100644 arshipdesign/tanks/fsm.py create mode 100644 arshipdesign/tanks/sounding_table.py create mode 100644 arshipdesign/tanks/tank_definition.py create mode 100644 arshipdesign/ui/__init__.py create mode 100644 arshipdesign/ui/commands/__init__.py create mode 100644 arshipdesign/ui/commands/add_tank.py create mode 100644 arshipdesign/ui/commands/command.py create mode 100644 arshipdesign/ui/commands/modify_hull.py create mode 100644 arshipdesign/ui/dialogs/__init__.py create mode 100644 arshipdesign/ui/dialogs/liquid_picker.py create mode 100644 arshipdesign/ui/dialogs/preferences.py create mode 100644 arshipdesign/ui/dialogs/stability_criteria_picker.py create mode 100644 arshipdesign/ui/dialogs/wizards.py create mode 100644 arshipdesign/ui/i18n/en.json create mode 100644 arshipdesign/ui/i18n/es.json create mode 100644 arshipdesign/ui/main_window.py create mode 100644 arshipdesign/ui/shortcuts.py create mode 100644 arshipdesign/ui/themes/dark.qss create mode 100644 arshipdesign/ui/themes/light.qss create mode 100644 arshipdesign/ui/widgets/__init__.py create mode 100644 arshipdesign/ui/widgets/curve_plotter.py create mode 100644 arshipdesign/ui/widgets/electrical_load_table.py create mode 100644 arshipdesign/ui/widgets/engine_picker.py create mode 100644 arshipdesign/ui/widgets/loadcase_editor.py create mode 100644 arshipdesign/ui/widgets/offsets_editor.py create mode 100644 arshipdesign/ui/widgets/propeller_designer.py create mode 100644 arshipdesign/ui/widgets/rig_editor.py create mode 100644 arshipdesign/ui/widgets/system_diagram_canvas.py create mode 100644 arshipdesign/ui/widgets/tank_editor.py create mode 100644 arshipdesign/ui/widgets/viewer_3d.py create mode 100644 arshipdesign/ui/widgets/viewer_lines.py create mode 100644 arshipdesign/ui/widgets/viewer_polar.py create mode 100644 arshipdesign/utils/__init__.py create mode 100644 arshipdesign/utils/async_runner.py create mode 100644 arshipdesign/utils/logger.py create mode 100644 arshipdesign/utils/settings.py create mode 100644 arshipdesign/utils/validation.py create mode 100644 create_stubs.py create mode 100644 data/benchmarks/README.md create mode 100644 data/liquids.json create mode 100644 data/stability_criteria.json create mode 100644 docs/developer_guide.md create mode 100644 docs/theory_manual.md create mode 100644 docs/user_manual.md create mode 100644 docs/validation_report.md create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 requirements.txt create mode 100644 tests/conftest.py create mode 100644 tests/test_startup.py diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..5c7867e --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,24 @@ +{ + "permissions": { + "allow": [ + "Bash(*)", + "mcp__ccd_session__mark_chapter", + "mcp__ccd_session__spawn_task", + "mcp__Claude_in_Chrome__*", + "mcp__Claude_Preview__*", + "mcp__ccd_session_mgmt__*", + "mcp__ccd_directory__*", + "mcp__scheduled-tasks__*", + "mcp__mcp-registry__*" + ], + "deny": [ + "Bash(rm -rf /*)", + "Bash(format *)", + "Bash(del /f /s /q C:\\*)", + "Bash(git push --force origin main)", + "Bash(git push --force origin master)", + "Bash(DROP DATABASE*)", + "Bash(mkfs*)" + ] + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..10385a0 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "allow": [ + "Bash(ls ~/.claude/projects/ | head -20)", + "Bash(awk '{print $2}')", + "Bash(xargs -I{} sh -c 'grep -o \"\\\\\"name\\\\\":\\\\\"Bash\\\\\",\\\\\"input\\\\\":{\\\\\"command\\\\\":\\\\\"[^\\\\\"]*\\\\\"\" \"{}\" 2>/dev/null || true')", + "Bash(awk '{print $1, $2}')", + "Bash(xargs '-I{}' python3 -c ' *)", + "Bash(sort -t/ -k1)", + "Bash(xargs -I{} wc -l {})", + "Bash(mkdir -p \"D:/Proyectos Software/AR-Shipdesign/.claude\")", + "Bash(python create_stubs.py)" + ] + } +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..88be5dd --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# AR-ShipDesign — Variables de entorno de ejemplo +# Copiar a .env y ajustar según el entorno local + +# Nivel de logging: DEBUG, INFO, WARNING, ERROR +LOG_LEVEL=INFO + +# Idioma por defecto: es, en +ARSD_LANG=es + +# Tema por defecto: light, dark +ARSD_THEME=light diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6008af3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +dist/ +*.egg-info/ +.eggs/ +*.egg +.venv/ +venv/ +env/ +.env + +# PyInstaller +*.spec +build/ +dist/ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +tests/benchmark_results/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db +desktop.ini + +# Logs +logs/ +*.log + +# Proyecto +*.arsd +*.arsd.bak + +# Datos sensibles +.env.local +secrets.json diff --git a/AR_ShipDesign_prompt.md b/AR_ShipDesign_prompt.md new file mode 100644 index 0000000..a0158d4 --- /dev/null +++ b/AR_ShipDesign_prompt.md @@ -0,0 +1,1063 @@ +# Prompt para Claude Code — Proyecto **AR-ShipDesign** + +> **Instrucciones para Álvaro (no para Code):** +> Copia este documento ENTERO y pégalo como **primer mensaje** en una nueva conversación de Claude Code. Antes de pegarlo, abre Code en una carpeta neutra (no dentro de `D:/AR-ShipDesign` — él la usará). Si tienes Python 3.11+ instalado en Windows, mejor. + +--- + +## 1. Quién eres y qué vas a construir + +Eres mi co-arquitecto naval y desarrollador senior en Python. Vamos a construir **AR-ShipDesign**, una aplicación de escritorio para Windows que sea la herramienta de diseño naval que siempre quise tener: **lo mejor de Maxsurf (rigurosidad técnica, suite de módulos integrados, transformación paramétrica) con la UX amigable de DELFTship (árbol de capas a la izquierda, vista 3D al centro, propiedades a la derecha, hidrostáticos siempre visibles abajo, wizards paso a paso).** + +El software debe cubrir el ciclo de diseño preliminar completo de un buque, abarcando tanto **embarcaciones a motor (desplazamiento, semi-planeo, planeo)** como **veleros (monocascos y multicascos)**, e incluyendo todos los sistemas del buque: propulsión y potencia, eléctrico, combustible y lubricación, agua dulce y sanitario, lastre y achique, gobierno, contraincendios, HVAC. + +**Es un proyecto a largo plazo. No tienes que terminar todo en una sesión.** Trabajaremos por hitos (sprints). Tu primera tarea es entender este documento, prepararme el entorno, y luego ejecutar el **Sprint 0** (esqueleto del proyecto + ventana principal vacía + sistema de proyecto funcional). Después, sprint por sprint según el orden que está al final. + +--- + +## 2. Ubicación del proyecto + +**Todo va en `D:/AR-ShipDesign/`.** + +- La carpeta ya existe vacía. Úsala. +- Si tiene contenido previo, **pregúntame antes de tocar nada.** +- Inicializa Git desde el primer commit. Cada sprint termina con un commit etiquetado (`v0.1-sprint0`, `v0.2-sprint1`, etc.). + +--- + +## 3. Pila tecnológica + +| Capa | Herramienta | Por qué | +|------|-------------|---------| +| Lenguaje | Python 3.11+ | Ecosistema científico maduro | +| GUI | **PySide6** (Qt 6) | Profesional, gratis (LGPL), look nativo Windows | +| Visualización 3D | **PyVista + VTK** embebido en Qt | Calidad pro sin escribir OpenGL crudo | +| Gráficos 2D | **pyqtgraph** | Más rápido que matplotlib para interactivo | +| NURBS / B-splines | **geomdl** (NURBS-Python) | Pura Python, sin dependencias compiladas | +| Geometría avanzada / IGES-STEP | **pythonocc-core** | Opcional (si está instalado), behind feature flag | +| Cálculo numérico | **numpy**, **scipy** | Estándar | +| Tabular | **pandas** | Estándar | +| Excel | **openpyxl** | Lectura/escritura | +| DXF | **ezdxf** | Lectura/escritura | +| Mallas / STL / OBJ | **trimesh** | Operaciones booleanas, cierre de mallas | +| PDF | **reportlab** | Reportes técnicos | +| Persistencia proyecto | Formato propio **`.arsd`** | ZIP con JSONs + meshes binarios | +| Empaquetado final | **PyInstaller** | `.exe` distribuible | +| Tests | **pytest** + benchmarks | Validación contra cascos canónicos | + +**Reglas de implementación:** +- **SI estricto internamente** (m, kg, s, N, Pa, W). Conversión a unidades imperiales solo en la UI cuando el usuario lo pida. +- **Idioma**: español por defecto, inglés como segunda opción. Strings centralizados en `arshipdesign/ui/i18n/es.json` y `en.json`. +- **Type hints obligatorios** en todos los módulos del core. +- **Docstrings estilo NumPy** con referencia bibliográfica al final cuando se implementa una fórmula del estado del arte. +- **Sin red, sin telemetría, sin licencias online.** El software corre offline. +- **Logging** con `logging` estándar, niveles configurables, archivo rotativo en `%APPDATA%/ARShipDesign/logs/`. +- **Configuración del usuario** vía `QSettings` (preferencias, recientes, idioma, tema). +- **Soporta proyectos con archivos relativos** (que el `.arsd` se pueda mover entre PCs sin romper referencias). + +--- + +## 4. Estructura de carpetas del proyecto + +Crea exactamente esta estructura. Cuando un módulo aún no esté implementado, crea el archivo con un docstring que diga "Stub. Implementación en Sprint X" y un `raise NotImplementedError`. Esto deja la navegación del proyecto coherente desde el día 1. + +``` +D:/AR-ShipDesign/ +├── README.md +├── LICENSE.txt +├── CHANGELOG.md +├── pyproject.toml +├── requirements.txt +├── requirements-dev.txt +├── .gitignore +├── .env.example +├── main.py # entry point +│ +├── arshipdesign/ +│ ├── __init__.py # versión: __version__ +│ │ +│ ├── core/ # MODELO DE DATOS (sin UI) +│ │ ├── project.py # raíz del proyecto, serialización .arsd +│ │ ├── ship.py # clase Ship (raíz del modelo) +│ │ ├── hull.py # geometría del casco (NURBS) +│ │ ├── appendage.py # quilla, timón, bulbo, skeg, foils +│ │ ├── superstructure.py +│ │ ├── offsets.py # tabla de offsets +│ │ ├── waterplane.py +│ │ ├── section.py +│ │ ├── compartment.py # bodegas, espacios secos +│ │ ├── tank.py # tanques con líquido +│ │ ├── liquid.py # base de datos de líquidos +│ │ ├── weight_item.py # ítem de peso puntual +│ │ ├── lightship.py # peso en rosca +│ │ ├── loadcase.py # condiciones de carga +│ │ ├── rig.py # aparejo del velero (mástil, jarcia, velas) +│ │ ├── sail.py # velas individuales (main, jib, spi…) +│ │ ├── engine.py # motor (datos del fabricante, curva) +│ │ ├── propeller.py # hélice +│ │ ├── gear.py # reductora +│ │ ├── generator.py # genset +│ │ ├── battery.py # banco de baterías +│ │ ├── electrical_load.py # consumidor eléctrico +│ │ ├── pipe.py # tubería de un sistema +│ │ ├── valve.py +│ │ ├── pump.py +│ │ ├── system.py # sistema del buque (combustible, fw, etc.) +│ │ └── units.py # conversiones SI ↔ imperial +│ │ +│ ├── geometry/ # OPERACIONES GEOMÉTRICAS +│ │ ├── nurbs_surface.py # wrapper sobre geomdl +│ │ ├── nurbs_curve.py +│ │ ├── mesh.py # malla triangular para cálculos +│ │ ├── slicer.py # corte por planos +│ │ ├── intersection.py +│ │ ├── fairing.py # análisis de curvatura +│ │ ├── boolean.py # operaciones booleanas con trimesh +│ │ └── fitting.py # ajuste de NURBS a offsets +│ │ +│ ├── hydrostatics/ # MOTOR DE CÁLCULO PRINCIPAL +│ │ ├── integrator.py # Simpson 1/3, 3/8, trapezoidal, Gauss +│ │ ├── upright.py # hidrostáticos en posición vertical +│ │ ├── heeled.py # equilibrio escorado +│ │ ├── trim.py # equilibrio con asiento +│ │ ├── free_floating.py # equilibrio libre 3DOF +│ │ ├── coefficients.py # Cb, Cp, Cw, Cm, Cws +│ │ ├── curves_of_form.py # las "Curvas Hidrostáticas" +│ │ └── bonjean.py # curvas de Bonjean +│ │ +│ ├── stability/ +│ │ ├── intact.py # estabilidad intacta, GZ vs heel +│ │ ├── damage.py # estabilidad en avería +│ │ ├── criteria.py # IMO IS Code 2008, MSC.267(85) +│ │ ├── floodable_length.py +│ │ ├── downflooding.py +│ │ └── weather.py # criterio meteorológico +│ │ +│ ├── tanks/ # MÓDULO DE TANQUES (DELFTship-style) +│ │ ├── tank_definition.py +│ │ ├── sounding_table.py # tablas de sondaje +│ │ ├── ullage.py # tablas de vacío +│ │ ├── fsm.py # Free Surface Moment +│ │ ├── capacity_plan.py # plan de capacidades +│ │ ├── trim_correction.py # corrección por trim +│ │ ├── temperature_correction.py +│ │ └── tank_calibration.py +│ │ +│ ├── resistance/ +│ │ ├── holtrop_mennen.py # Holtrop & Mennen 1984 (motor) +│ │ ├── savitsky.py # planeadores +│ │ ├── van_oortmerssen.py # embarcaciones pequeñas +│ │ ├── compton.py # semi-planeo +│ │ ├── dsyhs.py # Delft Systematic Yacht Hull Series (veleros) +│ │ ├── effective_power.py # PE = RT · V +│ │ └── added_resistance.py # resistencia añadida en olas +│ │ +│ ├── propulsion/ +│ │ ├── wageningen_b.py # Serie B Wageningen (KT, KQ, η) +│ │ ├── propeller_design.py # diseño/selección de hélice +│ │ ├── propeller_matching.py # matching motor-hélice +│ │ ├── cavitation.py # criterios de cavitación (Burrill, Keller) +│ │ ├── shaft_line.py # eje, soportes, alineación +│ │ ├── waterjet.py # propulsión por chorro +│ │ ├── pod.py # propulsión azimutal +│ │ └── electric_drive.py # propulsión eléctrica/híbrida +│ │ +│ ├── engines/ # CATÁLOGO DE MOTORES Y SELECCIÓN +│ │ ├── engine_db.py # catálogo (datos genéricos por familia) +│ │ ├── engine_curve.py # curva BHP vs RPM, SFOC vs carga +│ │ ├── engine_selection.py # algoritmo de selección por potencia +│ │ ├── fuel_consumption.py # consumo en cada modo operativo +│ │ ├── nox_emissions.py # emisiones IMO Tier I/II/III +│ │ └── operating_profile.py # perfil operativo (% tiempo a cada velocidad) +│ │ +│ ├── sailing/ # MÓDULO PARA VELEROS +│ │ ├── vpp.py # Velocity Prediction Program (núcleo) +│ │ ├── aero_model.py # modelo aerodinámico (ORC-style) +│ │ ├── hydro_model.py # hidrodinámica del velero (DSYHS) +│ │ ├── sail_coefficients.py # CL, CD por tipo de vela y AWA +│ │ ├── rig_geometry.py # geometría del aparejo +│ │ ├── stability_sailing.py # momento adrizante, ángulo de escora +│ │ ├── polar_diagram.py # diagrama polar de velocidad +│ │ ├── handicap_orc.py # cálculo de rating ORC simplificado +│ │ └── multihull.py # catamarán/trimarán específico +│ │ +│ ├── seakeeping/ +│ │ ├── strip_theory.py # Salvesen-Tuck-Faltinsen +│ │ ├── rao.py # Response Amplitude Operators +│ │ ├── spectra.py # ITTC, JONSWAP, Pierson-Moskowitz +│ │ ├── short_term.py # respuestas en mar corto +│ │ ├── slamming.py # probabilidad de pantocazo +│ │ ├── deck_wetness.py +│ │ └── motion_sickness.py # MSI según ISO 2631 +│ │ +│ ├── systems/ # SISTEMAS DEL BUQUE +│ │ ├── electrical/ +│ │ │ ├── load_analysis.py # EPLA por modo operativo +│ │ │ ├── generator_sizing.py # selección de gensets +│ │ │ ├── battery_sizing.py # banco de baterías +│ │ │ ├── one_line_diagram.py # diagrama unifilar +│ │ │ ├── cable_sizing.py # dimensionamiento de cables +│ │ │ ├── short_circuit.py # cortocircuito básico +│ │ │ └── shore_power.py +│ │ ├── fuel/ +│ │ │ ├── fuel_system.py # diagrama, capacidades, autonomía +│ │ │ ├── autonomy.py # cálculo de autonomía (rango) +│ │ │ ├── transfer_pumps.py +│ │ │ └── day_tank.py +│ │ ├── lub_oil/ +│ │ │ └── lub_oil_system.py +│ │ ├── freshwater/ +│ │ │ ├── fw_system.py # consumo, tanques, hidróforo +│ │ │ ├── hot_water.py +│ │ │ └── watermaker.py # osmosis inversa +│ │ ├── sanitary/ +│ │ │ ├── greywater.py +│ │ │ ├── blackwater.py +│ │ │ └── stp.py # planta de tratamiento +│ │ ├── ballast/ +│ │ │ ├── ballast_system.py +│ │ │ └── ballast_pumps.py +│ │ ├── bilge/ +│ │ │ ├── bilge_system.py # achique +│ │ │ ├── oily_water_separator.py +│ │ │ └── bilge_pumps.py +│ │ ├── firefighting/ +│ │ │ ├── fire_main.py # agua salada presurizada +│ │ │ ├── foam_system.py +│ │ │ ├── co2_system.py +│ │ │ ├── sprinkler.py +│ │ │ ├── detection.py +│ │ │ └── classification.py # zonas A60, B15, etc. +│ │ ├── hvac/ +│ │ │ ├── heat_balance.py # cargas térmicas por espacio +│ │ │ ├── duct_sizing.py +│ │ │ ├── chiller_sizing.py +│ │ │ └── ventilation.py +│ │ ├── steering/ +│ │ │ ├── rudder_design.py # área, perfil, fuerza +│ │ │ ├── steering_gear.py # potencia del servo +│ │ │ └── thrusters.py # impulsores transversales +│ │ ├── anchoring/ +│ │ │ ├── anchor_selection.py +│ │ │ ├── chain_locker.py +│ │ │ └── windlass.py +│ │ └── compressed_air/ +│ │ └── compressed_air.py +│ │ +│ ├── io/ # IMPORT / EXPORT +│ │ ├── offsets_csv.py +│ │ ├── offsets_excel.py +│ │ ├── dxf_reader.py +│ │ ├── dxf_writer.py +│ │ ├── iges_reader.py # con pythonocc, opcional +│ │ ├── iges_writer.py +│ │ ├── step_io.py +│ │ ├── stl_export.py +│ │ ├── obj_export.py +│ │ ├── rhino_3dm.py # opcional (rhino3dm-py) +│ │ ├── delftship_fbm.py # importar .fbm de DELFTship +│ │ ├── maxsurf_msd.py # importar .msd si es posible (puede ser parcial) +│ │ ├── project_serializer.py # formato .arsd +│ │ └── pdf_export.py +│ │ +│ ├── parametric/ # GENERACIÓN PARAMÉTRICA +│ │ ├── wizard_workboat.py # casco con espejo +│ │ ├── wizard_cruiser.py # crucero a motor +│ │ ├── wizard_sailing_mono.py # velero monocasco +│ │ ├── wizard_sailing_cat.py # catamarán +│ │ ├── wizard_planing.py # planeador +│ │ ├── series60.py # generador serie 60 +│ │ ├── wigley.py # casco matemático Wigley +│ │ ├── kcs.py # KRISO Container Ship +│ │ ├── dtmb5415.py # DTMB 5415 (caso militar) +│ │ └── lackenby_transform.py # transformación paramétrica +│ │ +│ ├── reports/ +│ │ ├── hydrostatic_report.py +│ │ ├── stability_booklet.py # booklet completo +│ │ ├── capacity_plan_report.py +│ │ ├── lines_plan.py # plano de líneas (PDF y DXF) +│ │ ├── general_arrangement.py # plano de disposición general +│ │ ├── resistance_powering.py +│ │ ├── electrical_balance.py # balance eléctrico +│ │ ├── system_diagrams.py # P&IDs simplificados +│ │ ├── vpp_report.py # polar diagram + tabla para velero +│ │ └── pdf_builder.py # constructor común +│ │ +│ ├── ui/ # TODA LA UI Qt +│ │ ├── main_window.py +│ │ ├── i18n/ +│ │ │ ├── es.json +│ │ │ └── en.json +│ │ ├── themes/ +│ │ │ ├── light.qss +│ │ │ └── dark.qss +│ │ ├── icons/ # SVG +│ │ ├── widgets/ +│ │ │ ├── viewer_3d.py +│ │ │ ├── viewer_lines.py # plano de líneas 2D +│ │ │ ├── viewer_polar.py # polar diagram velero +│ │ │ ├── tree_panel.py +│ │ │ ├── properties_panel.py +│ │ │ ├── hydrostatics_panel.py # panel fijo abajo, siempre visible +│ │ │ ├── offsets_editor.py +│ │ │ ├── tank_editor.py +│ │ │ ├── loadcase_editor.py +│ │ │ ├── rig_editor.py +│ │ │ ├── engine_picker.py +│ │ │ ├── propeller_designer.py +│ │ │ ├── electrical_load_table.py +│ │ │ ├── system_diagram_canvas.py # canvas para diagramas unifilares y P&ID +│ │ │ └── curve_plotter.py +│ │ ├── dialogs/ +│ │ │ ├── new_project.py +│ │ │ ├── wizards.py +│ │ │ ├── stability_criteria_picker.py +│ │ │ ├── liquid_picker.py +│ │ │ ├── preferences.py +│ │ │ └── about.py +│ │ ├── commands/ # patrón Command para Undo/Redo +│ │ │ ├── command.py +│ │ │ ├── modify_hull.py +│ │ │ ├── add_tank.py +│ │ │ └── ... +│ │ └── shortcuts.py # atajos de teclado centralizados +│ │ +│ └── utils/ +│ ├── logger.py +│ ├── settings.py +│ ├── async_runner.py # QThreadPool para cálculos largos +│ └── validation.py +│ +├── data/ +│ ├── liquids.json +│ ├── stability_criteria.json +│ ├── engine_catalog.json # catálogo genérico de motores +│ ├── propeller_series/ +│ │ └── wageningen_b.json +│ ├── steel_grades.json +│ ├── benchmarks/ # cascos de validación +│ │ ├── wigley_hull.csv +│ │ ├── series60_cb070.csv +│ │ ├── kcs.csv +│ │ ├── dtmb5415.csv +│ │ ├── delft_372.csv # Delft Systematic Yacht Series +│ │ └── README.md +│ ├── icons_class/ # iconos para diagramas (bombas, válvulas, etc.) +│ └── templates/ +│ ├── lightship_workboat.json +│ ├── lightship_yacht.json +│ └── default_loadcases.json +│ +├── tests/ +│ ├── conftest.py +│ ├── test_integrator.py +│ ├── test_hydrostatics_wigley.py +│ ├── test_hydrostatics_series60.py +│ ├── test_gz_curve_imo.py +│ ├── test_holtrop.py +│ ├── test_savitsky.py +│ ├── test_dsyhs.py +│ ├── test_vpp.py +│ ├── test_tank_calculations.py +│ ├── test_capacity_plan.py +│ ├── test_offsets_io.py +│ ├── test_dxf_io.py +│ ├── test_project_serialization.py +│ ├── test_propeller_matching.py +│ ├── test_electrical_load.py +│ └── test_engine_selection.py +│ +└── docs/ + ├── user_manual.md + ├── theory_manual.md # FÓRMULAS + REFERENCIAS BIBLIOGRÁFICAS + ├── validation_report.md # validación contra cascos canónicos + ├── developer_guide.md + └── changelog_internal.md +``` + +--- + +## 5. Layout de la ventana principal (UX inspirada en DELFTship) + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ Archivo | Editar | Ver | Modelo | Análisis | Sistemas | Reportes | Ayuda │ +├──────────────────────────────────────────────────────────────────────────┤ +│ 🗎 💾 ↩ ↪ | 🔍 🚢 wizard | ⚙ unidades | ☀/🌙 tema | español ▾ │ +├────────────┬──────────────────────────────────────┬──────────────────────┤ +│ │ │ │ +│ 📁 Project │ │ Propiedades del │ +│ ▾ Ship │ │ ítem seleccionado │ +│ ▾ Casco │ │ │ +│ Surf 1 │ Vista 3D principal (PyVista) │ Loa : 24.50 m │ +│ Surf 2 │ │ Lpp : 23.40 m │ +│ ▾ Apend. │ • orbit / zoom / pan │ B : 6.00 m │ +│ Quilla │ • toggle waterlines │ T : 3.20 m │ +│ Timón │ • toggle sections │ D : 3.80 m │ +│ ▾ Tanques│ • shaded / wireframe / hidden │ ... │ +│ FO 1 P │ • measure tool │ │ +│ FO 1 S │ • clipping plane │ │ +│ FW 1 │ │ │ +│ ▾ Bodeg. │ │ │ +│ ▾ Casos │ │ │ +│ Light │ │ │ +│ Full │ │ │ +│ Ballast │ │ │ +│ ▾ Aparejo│ │ │ +│ Mástil │ │ │ +│ Mayor │ │ │ +│ ▾ Motor │ │ │ +│ ▾ Sist. │ │ │ +│ Eléct. │ │ │ +│ Combust │ │ │ +│ F.W. │ │ │ +│ Achique │ │ │ +│ Lastre │ │ │ +│ CI │ │ │ +│ HVAC │ │ │ +├────────────┴──────────────────────────────────────┴──────────────────────┤ +│ HIDROSTÁTICOS EN VIVO (siempre visible): │ +│ T=[3.20 m ▾] Δ=2845 t LCB=12.30 KB=1.85 KMT=4.20 GMT=1.05 │ +│ TPC=8.2 t/cm MCT=42.5 Cb=0.682 Cw=0.821 Cm=0.985 ⚠ IMO OK │ +├──────────────────────────────────────────────────────────────────────────┤ +│ Tabs: [3D] [Líneas] [Offsets] [Curvas] [Tanques] [Capacidad] │ +│ [Estabilidad GZ] [Resistencia] [Propulsión] [VPP] [Movimientos] │ +│ [Eléctrico] [Combustible] [F.W.] [Achique] [CI] [HVAC] [Reporte] │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +**Reglas de UX irrenunciables:** + +1. **Auto-recálculo** del panel hidrostático en <200 ms al cambiar el calado o el modelo. +2. **Undo/Redo robusto** (Ctrl+Z hasta 50 pasos, historial visible). +3. **Unidades configurables en UI**, SI internamente. +4. **Tooltips con la fórmula** en hover de cualquier campo calculado (p.ej. KMT → "KMT = KB + IT/∇"). +5. **Drag & drop** de offsets, IGES, DXF sobre la ventana. +6. **Wizards paso a paso** para cada tipo de embarcación. +7. **Validación visual** del modelo: panel rojo con problemas concretos + botón "intentar corregir". +8. **Hidrostáticos en vivo** abajo, siempre visible, no se puede ocultar (en lateral si la pantalla es chica). +9. **Indicador "⚠ IMO"** en verde/amarillo/rojo según cumplimiento del último caso de carga activo. + +--- + +## 6. Módulo HIDROSTÁTICOS (el motor que alimenta todo) + +Para cualquier calado/asiento/escora, sobre la malla triangular del casco con **regla de Simpson 1/3** (fallback 3/8 cuando estaciones no son impares, trapezoidal para mallas irregulares): + +| Símbolo | Nombre | Fórmula | +|---------|--------|---------| +| ∇ | Volumen desplazado | ∫∫∫ dV | +| Δ | Desplazamiento | ρ · ∇ | +| Aw | Área del plano de flotación | ∫∫ dA en z=T | +| Cw | Coef. plano flotación | Aw / (Lwl · B) | +| Am | Área cuaderna maestra | ∫ y dz en x=Lpp/2 | +| Cm | Coef. cuaderna maestra | Am / (B · T) | +| Cb | Coef. de bloque | ∇ / (Lwl · B · T) | +| Cp | Coef. prismático | Cb / Cm | +| S | Superficie mojada | ∫∫ dS | +| LCB | Centro long. carena | ∫∫∫ x dV / ∇ | +| KB (VCB) | Centro vert. carena | ∫∫∫ z dV / ∇ | +| LCF | Centro long. flotación | ∫∫ x dA / Aw | +| IT | Inercia transv. de Aw | ∫∫ y² dA | +| IL | Inercia long. de Aw | ∫∫ (x−xF)² dA | +| BMT | Radio metac. transv. | IT / ∇ | +| BML | Radio metac. long. | IL / ∇ | +| KMT | Altura metac. transv. | KB + BMT | +| KML | Altura metac. long. | KB + BML | +| TPC | Toneladas por cm inmersión | Aw · ρ / 100 | +| MCT (MTC) | Momento para cambiar asiento 1 cm | Δ · GML / (100 · Lpp) | +| WSA | Wetted Surface Area | S | +| Cws | Coef. superficie mojada | S / √(∇ · Lwl) | + +**Salidas obligatorias del módulo:** +- **Curvas de Forma** (los 12-15 parámetros vs calado en una gráfica multi-escala). +- **Curvas de Bonjean** (área seccional vs calado, por estación). +- **Resolutor de equilibrio libre 3 GDL** (heave + trim + heel) con `scipy.optimize.fsolve` o Newton-Raphson, tolerancia configurable. + +--- + +## 7. Estabilidad y curva GZ + +- **GZ vs escora** de 0° a 90° en pasos de 5° (configurable). En cada ángulo: mantener Δ constante, reequilibrar trim, calcular B', GZ = brazo horizontal G→vertical de B'. +- Salidas: curva GZ, GZmax y ángulo, rango de estabilidad positiva, ángulo de inundación progresiva, área bajo curva. +- **Criterios IMO IS Code 2008 (MSC.267(85))**: + - A(0→30°) ≥ 0.055 m·rad + - A(0→40°) ≥ 0.090 m·rad + - A(30→40°) ≥ 0.030 m·rad + - GZ(30°) ≥ 0.20 m + - Ángulo de GZmax ≥ 25° + - GMT inicial ≥ 0.15 m +- **Criterio meteorológico** A.749(18). +- Criterios extra: USCG, ABS, RINA — configurables en `data/stability_criteria.json`. +- **Estabilidad en avería** (determinística): por compartimento, permeabilidad configurable, método de pérdida de flotabilidad. +- **Floodable length curve**. + +--- + +## 8. TANQUES (lo bueno de DELFTship) + +Esta es la parte donde el software gratuito típicamente flojea. Hacerla bien. + +### 8.1 Definición de tanque + +- **Geometría**: + - **Prismático** (6 planos): caso fácil. + - **Con forma de casco** (límite exterior = superficie del casco): wing tanks, double bottom. + - **Arbitrario** (malla triangular cerrada): casos complejos. +- **Tipo**: HFO, MGO, LO, FW, SW lastre, agua gris, agua negra, sentina, carga líquida, vacío. +- **Líquido** desde `data/liquids.json` (ver §8.4), override permitido. +- **Capacidad 100%** (volumen geométrico calculado). +- **Capacidad operativa** (98% combustibles, 95% agua, 100% carga estiba). +- **Nivel actual** (intercambiable: % / m³ / t / sondaje). +- **Posición de boca de medición** (para que las tablas de sondaje reflejen el sensor real). +- **Permeabilidad** (avería): 0.95 líquidos, 0.85 carga seca, 0.95 máquinas, 0.60 acomodación. + +### 8.2 Cálculos por tanque + +1. **Tabla de sondaje**: cada cm (config) desde el fondo: V, peso, LCG, TCG, VCG del contenido, FSM, IT del líquido. +2. **Tabla de ullage** (equivalente desde el techo). +3. **Curva de FSM vs nivel**. +4. **Corrección por trim** (las tablas dependen del asiento). +5. **Corrección por temperatura** (para combustibles, expansión). +6. **Validación**: detección de intersecciones entre tanques, tanques fuera del casco, mal cerrados. + +### 8.3 Integración con estabilidad + +En cada `LoadCase`: +- Sumar pesos (lightship + cargas + líquidos). +- Calcular LCG/TCG/VCG combinado. +- **GG' (corrección FSM)** = Σ(FSMi · ρi) / Δ. +- **GMT corregido** = GMT − GG'. +- Reportar equilibrio + estabilidad + cumplimiento criterios. + +### 8.4 `data/liquids.json` precarga + +```json +{ + "HFO": {"name": "Heavy Fuel Oil", "density_kg_m3": 991, "viscosity_cSt": 380}, + "MGO": {"name": "Marine Gas Oil", "density_kg_m3": 890, "viscosity_cSt": 6}, + "MDO": {"name": "Marine Diesel Oil", "density_kg_m3": 870, "viscosity_cSt": 11}, + "LSFO": {"name": "Low Sulphur Fuel Oil", "density_kg_m3": 985, "viscosity_cSt": 180}, + "LO": {"name": "Lubricating Oil", "density_kg_m3": 920, "viscosity_cSt": 100}, + "HO": {"name": "Hydraulic Oil", "density_kg_m3": 875, "viscosity_cSt": 46}, + "FW": {"name": "Fresh Water", "density_kg_m3": 1000, "viscosity_cSt": 1.0}, + "SW": {"name": "Sea Water (ballast)", "density_kg_m3": 1025, "viscosity_cSt": 1.05}, + "DW": {"name": "Distilled Water", "density_kg_m3": 999, "viscosity_cSt": 1.0}, + "SLP": {"name": "Slop / Bilge", "density_kg_m3": 1010, "viscosity_cSt": 5}, + "SEW": {"name": "Sewage", "density_kg_m3": 1010, "viscosity_cSt": 3}, + "URE": {"name": "Urea 40% (SCR)", "density_kg_m3": 1090, "viscosity_cSt": 1.4} +} +``` + +--- + +## 9. RESISTENCIA — múltiples métodos según tipo de buque + +Selección automática del método según parámetros (con override manual): + +| Método | Aplicabilidad | +|--------|--------------| +| **Holtrop & Mennen 1984** | Cascos de desplazamiento convencionales, 0.05 < Fr < 0.45 | +| **Van Oortmerssen 1971** | Embarcaciones pequeñas Lwl 8–80 m, desplazamiento | +| **Compton 1986** | Semi-planeo 0.4 < Fr < 0.9 | +| **Savitsky 1964** | Planeadores prismáticos, Fr_∇ > 1.0 | +| **Delft Systematic Yacht Hull Series (DSYHS)** | Veleros (Keuning–Sonnenberg) | +| **Mercier–Savitsky** | Round bilge planing | + +### 9.1 Holtrop & Mennen 1984 (detalle obligatorio) + +Implementar con todos los componentes: +- **RF** (ITTC-57): CF = 0.075 / (log₁₀ Rn − 2)² +- **(1+k)** factor de forma (fórmula de Holtrop, c14) +- **RAPP** apéndices (con factores 1+k2 por apéndice) +- **RW** olas (con c1…c17 según Fr y Cp) +- **RB** bulbo de proa +- **RTR** espejo de popa +- **RA** corrección modelo-buque (CA) +- **RT = (1+k)·RF + RAPP + RW + RB + RTR + RA** +- **PE = RT · V** + +Inputs derivados automáticamente del modelo: L, B, T, Cb, Cp, Cm, Cw, ∇, S, LCB%, AT (transom), ABT y hB (bulbo). + +Salida: curva RT y PE vs V (Fr 0.05 → 0.45 o rango definido). + +Referencia: Holtrop J., 1984, *"A statistical re-analysis of resistance and propulsion data"*, ISP Vol. 31. + +### 9.2 DSYHS (veleros) + +Para veleros usar la regresión de Delft (Keuning–Sonnenberg 1998 y actualizaciones): +- Resistencia residual del casco desnudo (upright). +- Incremento por escora (heel drag). +- Resistencia inducida por sideforce (apéndices). +- Resistencia añadida en olas. + +Parámetros geométricos requeridos: Lwl, Bwl, Tc (canoe body), ∇c, LCB%, LCF%, Cp, Aw, S, BTR (beam-to-draft ratio). + +--- + +## 10. PROPULSIÓN + +### 10.1 Serie B Wageningen + +Implementar coeficientes KT, KQ vs J para: +- Número de palas Z = 2, 3, 4, 5, 6, 7 +- Relación área expandida/disco AE/A0 = 0.30 → 1.05 +- Relación paso/diámetro P/D = 0.5 → 1.4 + +Fórmulas polinómicas Oosterveld–van Oossanen (con corrección de Reynolds). + +### 10.2 Matching motor-hélice + +Algoritmo: +1. Dado RT(V) y selección de hélice (D, P/D, AE/A0, Z). +2. Calcular wake fraction w y thrust deduction t (Holtrop). +3. Resolver punto de operación: T(1−t) = RT, encontrar n (rpm de hélice). +4. Calcular Q, PD (delivered power), aplicar eficiencias (η_S eje, η_R relativo rotacional, η_G reductora). +5. **PB (brake power)** = PD / (η_S · η_G). +6. Comparar con curva del motor a ese rpm. + +Salida: punto de operación óptimo, curvas de KT, KQ, η, demanda vs disponible. + +### 10.3 Cavitación + +- **Criterio de Burrill** (gráfico σ vs τc). +- **Criterio de Keller** (mínimo AE/A0 para evitar cavitación). +- Alerta visual cuando el diseño viola alguno. + +### 10.4 Otros propulsores + +- **Waterjet**: cálculo simplificado de empuje vs caudal y velocidad. +- **Pod azimutal**: con factor de degradación frente a hélice convencional. +- **Propulsión eléctrica/híbrida**: cálculo de potencia eléctrica requerida, factor de carga, eficiencia de motor eléctrico, gestión de batería. + +--- + +## 11. MOTORES y dimensionamiento + +### 11.1 Catálogo `data/engine_catalog.json` + +Catálogo **genérico por familia** (no fabricante específico para evitar IP). Datos típicos: + +```json +{ + "high_speed_diesel": { + "rpm_range": [1500, 2400], + "bhp_range_kw": [50, 1500], + "sfoc_at_85_kw_kwh": 215, + "weight_kg_per_kw": 3.5, + "applications": ["yates", "workboats", "patrulleras"] + }, + "medium_speed_diesel": { + "rpm_range": [600, 1200], + "bhp_range_kw": [500, 8000], + "sfoc_at_85_kw_kwh": 195, + "weight_kg_per_kw": 12, + "applications": ["ferries", "OSV", "cargo pequeño"] + }, + "low_speed_two_stroke": { + "rpm_range": [70, 180], + "bhp_range_kw": [3000, 80000], + "sfoc_at_85_kw_kwh": 165, + "weight_kg_per_kw": 38, + "applications": ["bulk carrier", "tanker", "container"] + }, + "outboard_petrol": {...}, + "outboard_electric": {...}, + "gas_turbine": {...}, + "dual_fuel": {...}, + "fuel_cell_h2": {...} +} +``` + +### 11.2 Algoritmo de selección + +Inputs: PB requerida al MCR, velocidad de servicio, perfil operativo (% del tiempo a cada V). + +Salida ranked: +1. Familia recomendada. +2. Cantidad de motores (1, 2, 4). +3. Rango de rpm óptimo para matching con hélice. +4. SFOC promedio sobre el perfil operativo → **consumo en t/día** y **rango / autonomía**. +5. Peso seco estimado. + +### 11.3 Curva de consumo + +SFOC vs % carga del motor (curva en U típica con mínimo cerca del 80%). Salidas: +- Consumo en cada modo (en puerto, maniobra, navegación, full ahead). +- **Autonomía**: rango_nm = (capacidad_FO · 1000 / consumo_horario · ηtransfer) · V_servicio +- **CO₂ emitido por viaje** (factor 3.114 kg CO₂ / kg HFO). +- **NOx**: comparación con límite IMO Tier I/II/III según año y zona. + +--- + +## 12. VELEROS — Módulo VPP completo + +### 12.1 Arquitectura del VPP + +Sigue el esquema clásico (ORC y Maxsurf VPP): + +**Loop de equilibrio para cada (TWS, TWA):** +1. Estimar velocidad inicial Vboat. +2. Calcular AWA y AWS (viento aparente) a partir de TWS, TWA, Vboat. +3. **Modelo aerodinámico** → fuerza propulsiva, fuerza lateral (heeling force), momento escorante. +4. **Modelo hidrodinámico** → resistencia total (upright + heel + induced + added wave) y sideforce de apéndices. +5. **Balance de fuerzas**: + - ΣFlongitudinal = 0 → F_drive = R_total + - ΣFlateral = 0 → F_side_sails = F_side_apéndices (define leeway angle β) + - ΣMroll = 0 → Heeling moment = Righting moment (define φ ángulo de escora) +6. Si no balancea, ajustar Vboat y volver al paso 2. +7. Para optimizar VMG, iterar también sobre reef (rizado) y flat (planitud de vela). + +### 12.2 Modelo aerodinámico (estilo ORC) + +Coeficientes CL y CD por tipo de vela vs AWA: +- **Main + jib** (upwind) +- **Main + genoa** +- **Main + spinnaker** (downwind asimétrico) +- **Main + code zero** + +Cada vela: `Cl_max`, `Cd_min`, curvas tabuladas. Compatible con la documentación pública del ORC VPP 2023. + +Escalado con altura del centro de esfuerzo (CE), corrección por gradiente de viento (perfil logarítmico atmosférico). + +### 12.3 Modelo hidrodinámico + +- **DSYHS** (Keuning–Sonnenberg) para resistencia upright del canoe body. +- **Heel drag** por incremento de WSA y cambio de forma de carena al escorar. +- **Induced drag** del keel y timón (teoría de Glauert). +- **Added wave resistance** (regresión de Gerritsma). + +### 12.4 Estabilidad del velero + +Momento adrizante con corrección por: +- Velocidad (loss of stability with speed). +- Movimiento de tripulación (crew on rail). +- Vela arrizada / flat factor. + +### 12.5 Salidas + +- **Polar diagram** interactivo (TWA radial, V boat radial, TWS curvas). +- **Tabla de velocidades** TWS × TWA (igual que ORC). +- **VMG upwind y downwind** con TWA óptimo. +- **Curvas de target speed** para regata. +- **Rating ORC simplificado** (estimación, no certificado). + +### 12.6 Multicascos + +Catamarán/trimarán: balance lateral diferente (no escora, levanta casco a barlovento). Hidrodinámica por casco individual + interferencia. Momento de vuelco (capsize moment) en lugar de adrizamiento. + +--- + +## 13. SEAKEEPING (predicción de movimientos) + +**Strip theory Salvesen–Tuck–Faltinsen (1970)** para los 6 GDL. + +Outputs: +- **RAOs** por modo, vs ω y rumbo respecto a olas. +- Espectros: ITTC, Pierson–Moskowitz, JONSWAP (parametrizable Hs, Tp, γ). +- Respuestas a corto plazo: significant motion, máximo esperado en N ciclos. +- Probabilidad de **slamming**, **deck wetness**, **propeller emergence**. +- **MSI** (Motion Sickness Incidence) según ISO 2631. +- Gráficas polares de movimientos vs rumbo. + +--- + +## 14. SISTEMAS DEL BUQUE + +Cada sistema tiene su propia tab en la UI, su propio modelo de datos en `core/`, su propio motor de cálculo en `systems/`, y su propio reporte en `reports/`. + +### 14.1 Sistema ELÉCTRICO + +**Electrical Power Load Analysis (EPLA)** clásico: + +- Lista de consumidores eléctricos (luces, bombas, motores auxiliares, electrónica, HVAC, cocina, etc.) con: + - Potencia conectada (kW) + - Tensión (24V DC / 230V AC / 400V AC / 690V AC) + - Factor de carga (LF) por modo operativo + - Factor de simultaneidad +- **Modos operativos** configurables: + - En puerto, sin carga + - En puerto, cargando/descargando + - Maniobra + - Navegación normal + - Navegación con mal tiempo + - Emergencia +- **Cálculo automático** por modo: ΣP · LF · KS = demanda eléctrica del modo. +- **Selección de generadores**: número, potencia, redundancia (uno fuera de servicio debe cubrir el modo crítico). +- **Banco de baterías** (yates, eléctricos): capacidad Ah, autonomía a hotel load, recarga. +- **Diagrama unifilar** dibujado en canvas Qt: barras principal/emergencia, breakers, transformadores, consumidores agrupados. +- **Cable sizing** simplificado por corriente de carga, longitud y caída de tensión (≤3% en main, ≤1% en lighting). +- **Cortocircuito** básico (Icc en barras principales). +- **Reporte** "Electrical Load Balance" estilo clasificadora. + +### 14.2 Sistema de COMBUSTIBLE + +- Tanques de almacenamiento + tanque de servicio diario (day tank) + tanque de sedimentación. +- Bombas de trasiego (capacidad mínima: llenar day tank en 30 min). +- Separadoras (purificadoras) — capacidad según caudal de motor + margen. +- Tubería con diámetros calculados por velocidad de fluido (~1 m/s succión, ~2 m/s descarga). +- **Autonomía**: con perfil operativo y consumo de motor → días de navegación, rango en mn. +- Diagrama P&ID simplificado. + +### 14.3 Sistema de LUBRICACIÓN + +- Carter del motor + tanque de almacenamiento + tanque de lodos. +- Bomba de lubricación, enfriador, filtros. +- Cálculo de tiempo entre cambios según horas de operación. + +### 14.4 Sistema de AGUA DULCE + +- Consumo per cápita: 120-200 L/persona/día (configurable según tipo de buque). +- Tanques + hidróforo + calentador. +- **Watermaker (osmosis inversa)**: caudal vs consumo + margen, energía consumida (~3-4 kWh/m³). +- Dimensionamiento de tanques para autonomía con/sin watermaker. + +### 14.5 Sistema SANITARIO (aguas grises y negras) + +- Tanques de retención (capacidad mínima según MARPOL Anexo IV). +- STP (Sewage Treatment Plant) o tanque de retención + descarga en puerto. +- Conexiones a tierra. + +### 14.6 Sistema de LASTRE + +- Tanques de lastre + bombas de lastre. +- **Diagrama de lastre** para transición entre casos de carga. +- Cumplimiento Ballast Water Management Convention (D-1/D-2) — solo nota informativa. + +### 14.7 Sistema de ACHIQUE (BILGE) + +- Bomba principal + emergencia. +- **Capacidad** mínima según clasificadora (típico: vaciar el compartimento más grande en 2 horas). +- Separador de sentinas (OWS) — 15 ppm IMO. +- Diagrama de tuberías por compartimento. + +### 14.8 Sistema CONTRAINCENDIOS + +- **Fire main** (agua salada presurizada): bombas principales + emergencia, hidrantes, mangueras, lanzas, presión mínima en hidrante más alejado (típico 3.5 bar). +- **Sistema fijo**: CO₂ para cámara de máquinas (5-7% volumen), espuma para tanques de combustible. +- **Sprinklers** en acomodaciones (clase B). +- **Detección**: humo, calor, llama; zonas A60/A30/B15. +- Esquema de clasificación de mamparos por zona. +- **Cálculo**: capacidad de bomba CI = mínimo 2 chorros simultáneos en hidrante más desfavorable. + +### 14.9 Sistema HVAC + +- **Heat balance** por espacio: cargas sensibles + latentes + ganancia/pérdida por mamparos. +- Condiciones ambientales: ASHRAE (verano 35°C/27°C húmedo, invierno -10°C) — configurables. +- Caudal de aire por espacio (renovaciones/hora según uso). +- **Dimensionamiento de chiller**: kW frigoríficos + margen 15%. +- **Ductos**: velocidades 4-6 m/s en main, 2-3 m/s en ramificaciones. +- Reporte de cargas por compartimento. + +### 14.10 Sistema de GOBIERNO + +- **Diseño del timón**: área (regla típica A_R = c · L · T donde c = 1.5–2.5% para mercante, 4-6% para velero). +- **Fuerza en el timón** a velocidad máxima (fórmula de Joessel modificada). +- **Steering gear**: torque requerido, potencia hidráulica, tiempo de banda-a-banda (≤28 s a max ahead según SOLAS). +- **Thrusters** transversales: empuje requerido para vientos cruzados típicos. + +--- + +## 15. IMPORTACIÓN / EXPORTACIÓN + +### 15.1 Tabla de offsets + +Aceptar CSV/Excel en dos formatos: +1. **Largo** (3 columnas: Station, Waterline, HalfBreadth). +2. **Matricial** (filas = estaciones, columnas = WL, celdas = semi-mangas). + +Wizard: previsualización con detección automática del formato; si falla, asistente paso a paso. + +Ajustar superficies NURBS por mínimos cuadrados con `geomdl.fitting.approximate_surface`. + +### 15.2 Otros formatos + +- **IGES**: lectura/escritura con pythonocc-core (opcional). +- **STEP**: lectura/escritura idem. +- **DXF**: polylines 3D que representan WL/sections/buttocks → reconstrucción por skinning. +- **STL/OBJ**: malla triangular (export para CFD externo o impresión). +- **Rhino 3DM**: con `rhino3dm-py` (opcional). +- **DELFTship .fbm**: lectura best-effort (formato binario propio). +- **Maxsurf .msd**: lectura parcial best-effort. + +### 15.3 Wizards paramétricos + +- Casco con espejo (workboat). +- Crucero a motor (yate de desplazamiento). +- Planeador (semi-V, hard chine). +- Velero monocasco moderno. +- Catamarán. +- Serie 60 (Cb objetivo). +- Wigley (validación). +- DTMB 5415 (validación militar). + +### 15.4 Exportación + +- DXF 2D (plano de líneas con cajetín, escala, cotas, estilo astillero). +- DXF 3D (wireframe). +- IGES, STEP, STL, OBJ, CSV. +- PDF de reportes. + +--- + +## 16. CASOS DE CARGA (`LoadCase`) + +Cada `LoadCase` agrega: +- **Lightship**: peso en rosca con LCG/TCG/VCG, radios de giro (kxx, kyy, kzz). +- **Deadweight items**: lista de pesos (tripulación, provisiones, equipo, stores) con posición. +- **Cargo**: bodegas con peso y CG o densidad y nivel. +- **Tanques**: nivel de cada uno. +- **Trim correctors / constants**. + +Motor → equilibrio + reporte: +- Calado en proa, popa, medio, en perpendiculares. +- Trim, heel. +- Δ, GMT, GMT corregido por FSM. +- Reserva de flotabilidad. +- Cumplimiento criterios IMO (verde/amarillo/rojo). + +Plantillas predefinidas: Lightship, Departure full, Arrival full, Departure ballast, Arrival ballast. + +--- + +## 17. VALIDACIÓN CONTRA BENCHMARKS + +Antes de marcar un módulo como completo, validar contra cascos con valores publicados: + +| Casco | Origen | Validar | +|-------|--------|---------| +| **Wigley** | Casco matemático | ∇, S, Cb, Cp analíticos | +| **Series 60 Cb 0.70** | Todd 1963 | Hidrostáticos + Holtrop | +| **KCS (KRISO Container Ship)** | Tokyo 2015 workshop | Holtrop + CFD ref | +| **DTMB 5415** | Combatant naval | Hidrostáticos + seakeeping | +| **Delft 372** | DSYHS (velero) | Resistencia upright + heel | + +Cada test guarda el resultado en `tests/benchmark_results/` y compara con tolerancia (±2% típicamente). + +--- + +## 18. PLAN DE SPRINTS (ejecuta en este orden) + +Cada sprint termina con: tests pasando, commit etiquetado, entrada en `CHANGELOG.md`, sección añadida a `docs/user_manual.md`. + +### Sprint 0 — Setup (esta sesión, esperable 1-2 horas) + +- Crear estructura completa de carpetas con stubs. +- `pyproject.toml`, `requirements.txt`, `.gitignore`, `LICENSE` (MIT por defecto, pregúntame si quieres otra). +- `main.py` que abra una ventana Qt vacía con menú base. +- `core/project.py` con clase `Project` y serialización `.arsd` (vacía pero funcional). +- `core/units.py` con conversiones SI ↔ imperial. +- `ui/main_window.py` con el layout descrito en §5 (paneles vacíos pero presentes). +- `tests/conftest.py` y un test trivial que valide que arranca. +- Git init + primer commit `v0.1-sprint0`. +- **Entregable**: la app abre, se ve el layout, archivo nuevo / guardar / abrir funcionan con `.arsd` vacío. + +### Sprint 1 — Geometría y offsets + +- `geometry/nurbs_surface.py`, `geometry/mesh.py`, `geometry/slicer.py`. +- `io/offsets_csv.py`, `io/offsets_excel.py`. +- `ui/widgets/offsets_editor.py`, `ui/widgets/viewer_3d.py`. +- Wizard Wigley (caso de validación inmediata). +- **Entregable**: cargar tabla de offsets, ver casco en 3D, ver waterlines/sections. + +### Sprint 2 — Hidrostáticos básicos + +- `hydrostatics/integrator.py` (Simpson 1/3, 3/8, trap). +- `hydrostatics/upright.py`, `coefficients.py`, `curves_of_form.py`. +- `ui/widgets/hydrostatics_panel.py` (panel inferior siempre visible). +- Tests contra Wigley analítico y Series 60. +- **Entregable**: panel hidrostático abajo recalcula al cambiar calado. + +### Sprint 3 — Equilibrio libre y estabilidad GZ + +- `hydrostatics/free_floating.py`, `heeled.py`, `trim.py`. +- `stability/intact.py`, `criteria.py`. +- `ui/widgets/curve_plotter.py` (pyqtgraph). +- **Entregable**: curva GZ + cumplimiento IMO con semáforo. + +### Sprint 4 — Tanques completo + +- `core/tank.py`, `core/liquid.py`. +- `tanks/*` (todos los módulos). +- `ui/widgets/tank_editor.py`. +- `core/loadcase.py` + integración con estabilidad. +- **Entregable**: definir tanques, generar tablas de sondaje, ver capacity plan, estabilidad con FSM. + +### Sprint 5 — Resistencia y propulsión + +- `resistance/holtrop_mennen.py`, `savitsky.py`, `van_oortmerssen.py`. +- `propulsion/wageningen_b.py`, `propeller_matching.py`. +- `engines/engine_db.py`, `engine_selection.py`. +- **Entregable**: curva RT/V, selección hélice, selección motor con consumo. + +### Sprint 6 — Veleros y VPP + +- `core/rig.py`, `core/sail.py`. +- `sailing/vpp.py`, `aero_model.py`, `hydro_model.py`. +- `resistance/dsyhs.py`. +- `ui/widgets/viewer_polar.py`. +- **Entregable**: polar diagram completo, tabla de velocidades estilo ORC. + +### Sprint 7 — Sistemas del buque + +- `systems/electrical/*` con EPLA y diagrama unifilar. +- `systems/fuel/*` con autonomía. +- `systems/freshwater/*`, `bilge/*`, `ballast/*`. +- `ui/widgets/system_diagram_canvas.py`. +- **Entregable**: balance eléctrico completo, autonomía calculada, diagramas básicos. + +### Sprint 8 — CI, HVAC, gobierno + +- `systems/firefighting/*`, `hvac/*`, `steering/*`, `anchoring/*`. +- **Entregable**: dimensionamiento de timón, servo, CI, HVAC. + +### Sprint 9 — Seakeeping + +- `seakeeping/strip_theory.py`, `rao.py`, `spectra.py`. +- **Entregable**: RAOs, espectros, MSI. + +### Sprint 10 — Reportes profesionales + +- Todos los `reports/*` con plantillas PDF estilo astillero. +- Stability Booklet completo. +- Capacity Plan. +- Lines Plan en PDF y DXF. + +### Sprint 11 — IGES/STEP, paramétricos avanzados + +- `io/iges_*`, `step_io.py` con pythonocc. +- `parametric/lackenby_transform.py`. +- Wizards completos para todos los tipos. + +### Sprint 12 — Empaquetado y distribución + +- `PyInstaller` config para `.exe`. +- Splash screen, icono, installer (NSIS). +- Manual de usuario PDF. + +--- + +## 19. REGLAS DE TRABAJO ENTRE TÚ Y YO + +1. **Antes de cada sprint**, resúmeme en 3-5 líneas qué vas a hacer y pídeme go/no-go. +2. **Si una decisión técnica afecta el roadmap** (cambiar librería, refactor grande), pregúntame antes. +3. **Si encuentras que algo de este documento es ambiguo o contradictorio**, dilo. No asumas. +4. **Si una fórmula del estado del arte tiene varias versiones publicadas**, escoge la más citada o la más reciente (preferentemente Holtrop 1984 sobre Holtrop 1982; ORC VPP 2023 sobre versiones anteriores) y deja referencia explícita en el docstring. +5. **No instales dependencias pesadas** (pythonocc, rhino3dm) hasta el sprint que las necesite. +6. **Commits frecuentes**, mensajes descriptivos, en español. +7. **Tests obligatorios** para cualquier fórmula numérica antes de declarar el sprint terminado. +8. **Documenta a medida que avanzas**: `docs/theory_manual.md` se llena en paralelo al código, no al final. +9. Si un cálculo es lento (>2 s) en un caso típico, **muévelo a un QThreadPool** para no congelar la UI. +10. **Validación visual**: cualquier resultado numérico que se muestre en la UI debe poder graficarse al lado, o tener una vista que ayude a interpretarlo (el usuario debe saber leer una curva, no solo un número). + +--- + +## 20. Empezamos + +Cuando hayas leído este documento por completo: + +1. Confírmame en 5 líneas máximo qué entendiste, qué dudas tienes (si las hay), y qué vas a entregar al final del **Sprint 0**. +2. Cuando te diga "go", ejecuta Sprint 0 completo y muéstrame al final cómo abrir la app y qué deberíamos ver. + +¿Listo? Empecemos. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7fe035c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog — AR-ShipDesign + +Todos los cambios notables se documentan en este archivo. +Formato basado en [Keep a Changelog](https://keepachangelog.com/es/1.0.0/). + +--- + +## [0.1.0] — Sprint 0 — 2025-05-26 + +### Añadido +- Estructura completa de carpetas del proyecto con stubs documentados +- `pyproject.toml`, `requirements.txt`, `.gitignore`, `LICENSE.txt` +- `main.py` — punto de entrada de la aplicación +- `arshipdesign/core/project.py` — clase `Project` con serialización `.arsd` +- `arshipdesign/core/units.py` — conversiones SI ↔ imperial completas +- `arshipdesign/ui/main_window.py` — ventana principal con layout DELFTship-style + - Panel árbol de proyecto (izquierda) + - Vista 3D central (PyVista placeholder) + - Panel de propiedades (derecha) + - Panel hidrostáticos en vivo (inferior, siempre visible) + - Barra de tabs de módulos +- `arshipdesign/ui/i18n/es.json` y `en.json` — internacionalización +- `arshipdesign/ui/themes/light.qss` y `dark.qss` — temas visual +- `arshipdesign/utils/logger.py` — logging rotativo en %APPDATA% +- `arshipdesign/utils/settings.py` — configuración con QSettings +- `data/liquids.json` — base de datos de líquidos navales +- `data/stability_criteria.json` — criterios IMO IS Code 2008 +- `tests/conftest.py` + `tests/test_startup.py` — tests básicos + +### Arquitectura +- NURBS (geomdl) como motor geométrico principal +- Stubs de todos los módulos futuros con referencia al sprint de implementación +- Módulo `scantling/` para diseño estructural según ISO 12215 +- Módulo `fabrication/` para CNC (plasma/router/laser) y moldes FRP diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..bec4483 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,10 @@ +AR-ShipDesign — Software de Diseño Naval +Copyright (c) 2025 Álvaro Rodríguez. Todos los derechos reservados. + +LICENCIA PROPIETARIA + +Este software y su código fuente son propiedad exclusiva de Álvaro Rodríguez. +Queda prohibida su copia, distribución, modificación, ingeniería inversa o +uso comercial sin autorización expresa y por escrito del titular. + +Se reservan todos los derechos hasta que se defina una licencia de distribución. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b42715c --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# AR-ShipDesign + +Software profesional de diseño naval para Windows. + +**Lo mejor de Maxsurf** (rigor técnico, NURBS, suite completa) con **la UX de DELFTship** +(árbol de capas, vista 3D, hidrostáticos siempre visibles, wizards paso a paso). + +## Características principales + +- Diseño de cascos por superficies NURBS (buques a motor y veleros) +- Hidrostáticos en tiempo real +- Estabilidad intacta y en avería — criterios IMO IS Code 2008 +- Escantillado estructural según ISO 12215 +- Resistencia: Holtrop & Mennen, Savitsky, DSYHS y más +- VPP completo para veleros (polar diagram estilo ORC) +- Seakeeping por strip theory (Salvesen-Tuck-Faltinsen) +- Sistemas del buque: eléctrico, combustible, FW, CI, HVAC, gobierno +- Fabricación CNC: nesting + G-code (plasma / router / laser) +- Construcción de moldes en fibra de vidrio con schedule de laminado + +## Instalación rápida + +```bash +python -m venv .venv +.venv\Scripts\activate +pip install -r requirements.txt +python main.py +``` + +## Requisitos + +- Windows 10/11 (64-bit) +- Python 3.11+ +- 4 GB RAM mínimo, 8 GB recomendado + +## Estado del proyecto + +| Sprint | Módulo | Estado | +|--------|--------|--------| +| 0 | Esqueleto + UI base + proyecto .arsd | ✅ Completo | +| 1 | Geometría NURBS + offsets + viewer 3D | 🔜 Próximo | +| 2 | Hidrostáticos básicos | ⏳ Pendiente | +| 3 | Equilibrio libre + estabilidad GZ | ⏳ Pendiente | +| ... | ... | ... | + +## Licencia + +Copyright © 2025 Álvaro Rodríguez. Todos los derechos reservados. +Ver `LICENSE.txt` para detalles. diff --git a/arshipdesign/__init__.py b/arshipdesign/__init__.py new file mode 100644 index 0000000..96db99b --- /dev/null +++ b/arshipdesign/__init__.py @@ -0,0 +1,9 @@ +""" +AR-ShipDesign — Software profesional de diseño naval. + +Copyright (c) 2025 Álvaro Rodríguez. Todos los derechos reservados. +""" + +__version__ = "0.1.0" +__author__ = "Álvaro Rodríguez" +__license__ = "Propietario" diff --git a/arshipdesign/core/__init__.py b/arshipdesign/core/__init__.py new file mode 100644 index 0000000..1a8dc8c --- /dev/null +++ b/arshipdesign/core/__init__.py @@ -0,0 +1 @@ +# arshipdesign/core diff --git a/arshipdesign/core/appendage.py b/arshipdesign/core/appendage.py new file mode 100644 index 0000000..fa5d594 --- /dev/null +++ b/arshipdesign/core/appendage.py @@ -0,0 +1,2 @@ +"""Apéndices. Stub — Sprint 1.""" +raise NotImplementedError("appendage — Sprint 1") diff --git a/arshipdesign/core/battery.py b/arshipdesign/core/battery.py new file mode 100644 index 0000000..6c5c605 --- /dev/null +++ b/arshipdesign/core/battery.py @@ -0,0 +1,2 @@ +"""Baterías. Stub — Sprint 7.""" +raise NotImplementedError("battery — Sprint 7") diff --git a/arshipdesign/core/compartment.py b/arshipdesign/core/compartment.py new file mode 100644 index 0000000..147dc02 --- /dev/null +++ b/arshipdesign/core/compartment.py @@ -0,0 +1,2 @@ +"""Compartimento. Stub — Sprint 4.""" +raise NotImplementedError("compartment — Sprint 4") diff --git a/arshipdesign/core/electrical_load.py b/arshipdesign/core/electrical_load.py new file mode 100644 index 0000000..b5c71e0 --- /dev/null +++ b/arshipdesign/core/electrical_load.py @@ -0,0 +1,2 @@ +"""Carga eléctrica. Stub — Sprint 7.""" +raise NotImplementedError("electrical_load — Sprint 7") diff --git a/arshipdesign/core/engine.py b/arshipdesign/core/engine.py new file mode 100644 index 0000000..e20e326 --- /dev/null +++ b/arshipdesign/core/engine.py @@ -0,0 +1,2 @@ +"""Motor. Stub — Sprint 5.""" +raise NotImplementedError("engine — Sprint 5") diff --git a/arshipdesign/core/gear.py b/arshipdesign/core/gear.py new file mode 100644 index 0000000..c48cc91 --- /dev/null +++ b/arshipdesign/core/gear.py @@ -0,0 +1,2 @@ +"""Reductora. Stub — Sprint 5.""" +raise NotImplementedError("gear — Sprint 5") diff --git a/arshipdesign/core/generator.py b/arshipdesign/core/generator.py new file mode 100644 index 0000000..bedcf4f --- /dev/null +++ b/arshipdesign/core/generator.py @@ -0,0 +1,2 @@ +"""Genset. Stub — Sprint 7.""" +raise NotImplementedError("generator — Sprint 7") diff --git a/arshipdesign/core/hull.py b/arshipdesign/core/hull.py new file mode 100644 index 0000000..59210a6 --- /dev/null +++ b/arshipdesign/core/hull.py @@ -0,0 +1,2 @@ +"""Geometría del casco NURBS. Stub — Sprint 1.""" +raise NotImplementedError("hull — Sprint 1") diff --git a/arshipdesign/core/lightship.py b/arshipdesign/core/lightship.py new file mode 100644 index 0000000..5212376 --- /dev/null +++ b/arshipdesign/core/lightship.py @@ -0,0 +1,2 @@ +"""Peso en rosca. Stub — Sprint 4.""" +raise NotImplementedError("lightship — Sprint 4") diff --git a/arshipdesign/core/liquid.py b/arshipdesign/core/liquid.py new file mode 100644 index 0000000..32f65c0 --- /dev/null +++ b/arshipdesign/core/liquid.py @@ -0,0 +1,2 @@ +"""Líquidos. Stub — Sprint 4.""" +raise NotImplementedError("liquid — Sprint 4") diff --git a/arshipdesign/core/loadcase.py b/arshipdesign/core/loadcase.py new file mode 100644 index 0000000..5971e99 --- /dev/null +++ b/arshipdesign/core/loadcase.py @@ -0,0 +1,2 @@ +"""Condiciones de carga. Stub — Sprint 4.""" +raise NotImplementedError("loadcase — Sprint 4") diff --git a/arshipdesign/core/offsets.py b/arshipdesign/core/offsets.py new file mode 100644 index 0000000..dd8623d --- /dev/null +++ b/arshipdesign/core/offsets.py @@ -0,0 +1,2 @@ +"""Tabla de offsets. Stub — Sprint 1.""" +raise NotImplementedError("offsets — Sprint 1") diff --git a/arshipdesign/core/pipe.py b/arshipdesign/core/pipe.py new file mode 100644 index 0000000..608b717 --- /dev/null +++ b/arshipdesign/core/pipe.py @@ -0,0 +1,2 @@ +"""Tubería. Stub — Sprint 7.""" +raise NotImplementedError("pipe — Sprint 7") diff --git a/arshipdesign/core/project.py b/arshipdesign/core/project.py new file mode 100644 index 0000000..9e61680 --- /dev/null +++ b/arshipdesign/core/project.py @@ -0,0 +1,273 @@ +""" +Clase Project — raíz del modelo de datos de AR-ShipDesign. + +Gestiona la serialización/deserialización del formato .arsd +(ZIP con JSONs + meshes binarios). + +El archivo .arsd es un ZIP que contiene: + manifest.json → metadatos del proyecto + ship.json → datos del buque + geometry/ → meshes binarios (.npz) + loadcases/ → condiciones de carga +""" + +from __future__ import annotations + +import json +import shutil +import zipfile +from dataclasses import dataclass, field +from datetime import datetime +from pathlib import Path +from typing import Any + +from arshipdesign.utils.logger import get_logger + +logger = get_logger("core.project") + +ARSD_EXTENSION = ".arsd" +MANIFEST_FILE = "manifest.json" +SHIP_FILE = "ship.json" +FORMAT_VERSION = "1.0" + + +@dataclass +class ProjectMetadata: + """Metadatos del proyecto.""" + name: str = "Proyecto sin título" + description: str = "" + author: str = "" + created_at: str = field(default_factory=lambda: datetime.now().isoformat()) + modified_at: str = field(default_factory=lambda: datetime.now().isoformat()) + format_version: str = FORMAT_VERSION + arshipdesign_version: str = "0.1.0" + + def to_dict(self) -> dict[str, Any]: + return { + "name": self.name, + "description": self.description, + "author": self.author, + "created_at": self.created_at, + "modified_at": self.modified_at, + "format_version": self.format_version, + "arshipdesign_version": self.arshipdesign_version, + } + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "ProjectMetadata": + return cls( + name=data.get("name", "Sin título"), + description=data.get("description", ""), + author=data.get("author", ""), + created_at=data.get("created_at", datetime.now().isoformat()), + modified_at=data.get("modified_at", datetime.now().isoformat()), + format_version=data.get("format_version", FORMAT_VERSION), + arshipdesign_version=data.get("arshipdesign_version", "0.1.0"), + ) + + +class Project: + """ + Raíz del modelo de datos de AR-ShipDesign. + + Gestiona el ciclo de vida del proyecto: nuevo, abrir, guardar, guardar como. + El formato .arsd es un archivo ZIP con estructura interna definida. + + Parameters + ---------- + path : Path | None + Ruta al archivo .arsd. None para proyecto nuevo sin guardar. + + Examples + -------- + >>> project = Project.new("Mi velero") + >>> project.save(Path("D:/proyectos/mi_velero.arsd")) + >>> project2 = Project.load(Path("D:/proyectos/mi_velero.arsd")) + """ + + def __init__(self, path: Path | None = None) -> None: + self.path: Path | None = path + self.metadata: ProjectMetadata = ProjectMetadata() + self.ship_data: dict[str, Any] = {} + self._is_modified: bool = False + + # ────────────────────────────────────────────── + # CONSTRUCTORES + # ────────────────────────────────────────────── + + @classmethod + def new(cls, name: str = "Proyecto sin título", author: str = "") -> "Project": + """Crea un proyecto nuevo vacío.""" + project = cls(path=None) + project.metadata = ProjectMetadata(name=name, author=author) + project.ship_data = _default_ship_data() + project._is_modified = True + logger.info("Nuevo proyecto creado: %s", name) + return project + + @classmethod + def load(cls, path: Path) -> "Project": + """ + Carga un proyecto desde un archivo .arsd. + + Parameters + ---------- + path : Path + Ruta al archivo .arsd. + + Raises + ------ + FileNotFoundError + Si el archivo no existe. + ValueError + Si el archivo no es un .arsd válido. + """ + path = Path(path) + if not path.exists(): + raise FileNotFoundError(f"Archivo no encontrado: {path}") + if path.suffix.lower() != ARSD_EXTENSION: + raise ValueError(f"El archivo no tiene extensión {ARSD_EXTENSION}: {path}") + + logger.info("Cargando proyecto: %s", path) + + try: + with zipfile.ZipFile(path, "r") as zf: + names = zf.namelist() + + if MANIFEST_FILE not in names: + raise ValueError("Archivo .arsd corrupto: falta manifest.json") + + manifest_raw = zf.read(MANIFEST_FILE).decode("utf-8") + manifest = json.loads(manifest_raw) + metadata = ProjectMetadata.from_dict(manifest) + + ship_data: dict[str, Any] = {} + if SHIP_FILE in names: + ship_raw = zf.read(SHIP_FILE).decode("utf-8") + ship_data = json.loads(ship_raw) + + except zipfile.BadZipFile as e: + raise ValueError(f"Archivo .arsd inválido o corrupto: {e}") from e + + project = cls(path=path) + project.metadata = metadata + project.ship_data = ship_data + project._is_modified = False + + logger.info("Proyecto cargado: %s (v%s)", metadata.name, metadata.format_version) + return project + + # ────────────────────────────────────────────── + # GUARDAR + # ────────────────────────────────────────────── + + def save(self, path: Path | None = None) -> None: + """ + Guarda el proyecto en un archivo .arsd. + + Parameters + ---------- + path : Path | None + Si se especifica, guarda en esa ruta (guardar como). + Si es None, guarda en self.path. + + Raises + ------ + ValueError + Si no hay ruta definida. + """ + save_path = path or self.path + if save_path is None: + raise ValueError("No hay ruta definida. Use save(path) para guardar.") + + save_path = Path(save_path) + if save_path.suffix.lower() != ARSD_EXTENSION: + save_path = save_path.with_suffix(ARSD_EXTENSION) + + # Actualizar fecha de modificación + self.metadata.modified_at = datetime.now().isoformat() + + # Guardar en un temporal primero (escritura atómica) + tmp_path = save_path.with_suffix(".arsd.tmp") + + try: + with zipfile.ZipFile(tmp_path, "w", compression=zipfile.ZIP_DEFLATED) as zf: + # manifest.json + zf.writestr( + MANIFEST_FILE, + json.dumps(self.metadata.to_dict(), indent=2, ensure_ascii=False), + ) + # ship.json + zf.writestr( + SHIP_FILE, + json.dumps(self.ship_data, indent=2, ensure_ascii=False), + ) + + # Reemplazar el archivo final + shutil.move(str(tmp_path), str(save_path)) + + except Exception: + if tmp_path.exists(): + tmp_path.unlink() + raise + + self.path = save_path + self._is_modified = False + logger.info("Proyecto guardado: %s", save_path) + + def save_as(self, path: Path) -> None: + """Guarda el proyecto en una nueva ruta.""" + self.save(path) + + # ────────────────────────────────────────────── + # PROPIEDADES + # ────────────────────────────────────────────── + + @property + def name(self) -> str: + return self.metadata.name + + @name.setter + def name(self, value: str) -> None: + self.metadata.name = value + self._is_modified = True + + @property + def is_modified(self) -> bool: + return self._is_modified + + @property + def display_name(self) -> str: + """Nombre para mostrar en la barra de título.""" + base = self.metadata.name + if self.path: + base = self.path.stem + return f"{base}{'*' if self._is_modified else ''}" + + def mark_modified(self) -> None: + """Marca el proyecto como modificado.""" + self._is_modified = True + + def __repr__(self) -> str: + return f"Project(name={self.name!r}, path={self.path})" + + +# ────────────────────────────────────────────── +# HELPERS PRIVADOS +# ────────────────────────────────────────────── + +def _default_ship_data() -> dict[str, Any]: + """Estructura de datos inicial de un buque vacío.""" + return { + "type": "motor", # motor | sailing_mono | sailing_cat | planing + "hull": {}, + "appendages": [], + "superstructure": {}, + "tanks": [], + "compartments": [], + "loadcases": [], + "rig": None, + "propulsion": {}, + "systems": {}, + "scantling": {}, + } diff --git a/arshipdesign/core/propeller.py b/arshipdesign/core/propeller.py new file mode 100644 index 0000000..e9125ba --- /dev/null +++ b/arshipdesign/core/propeller.py @@ -0,0 +1,2 @@ +"""Hélice. Stub — Sprint 5.""" +raise NotImplementedError("propeller — Sprint 5") diff --git a/arshipdesign/core/pump.py b/arshipdesign/core/pump.py new file mode 100644 index 0000000..0a01313 --- /dev/null +++ b/arshipdesign/core/pump.py @@ -0,0 +1,2 @@ +"""Bomba. Stub — Sprint 7.""" +raise NotImplementedError("pump — Sprint 7") diff --git a/arshipdesign/core/rig.py b/arshipdesign/core/rig.py new file mode 100644 index 0000000..895eda9 --- /dev/null +++ b/arshipdesign/core/rig.py @@ -0,0 +1,2 @@ +"""Aparejo velero. Stub — Sprint 6.""" +raise NotImplementedError("rig — Sprint 6") diff --git a/arshipdesign/core/sail.py b/arshipdesign/core/sail.py new file mode 100644 index 0000000..0de1437 --- /dev/null +++ b/arshipdesign/core/sail.py @@ -0,0 +1,2 @@ +"""Velas. Stub — Sprint 6.""" +raise NotImplementedError("sail — Sprint 6") diff --git a/arshipdesign/core/section.py b/arshipdesign/core/section.py new file mode 100644 index 0000000..1ef1e28 --- /dev/null +++ b/arshipdesign/core/section.py @@ -0,0 +1,2 @@ +"""Sección transversal. Stub — Sprint 1.""" +raise NotImplementedError("section — Sprint 1") diff --git a/arshipdesign/core/ship.py b/arshipdesign/core/ship.py new file mode 100644 index 0000000..89eae02 --- /dev/null +++ b/arshipdesign/core/ship.py @@ -0,0 +1,3 @@ +"""Clase Ship. Stub — Sprint 1.""" +class Ship: + def __init__(self): raise NotImplementedError("Ship — Sprint 1") diff --git a/arshipdesign/core/superstructure.py b/arshipdesign/core/superstructure.py new file mode 100644 index 0000000..9f45aa6 --- /dev/null +++ b/arshipdesign/core/superstructure.py @@ -0,0 +1,2 @@ +"""Superestructura. Stub — Sprint 1.""" +raise NotImplementedError("superstructure — Sprint 1") diff --git a/arshipdesign/core/system.py b/arshipdesign/core/system.py new file mode 100644 index 0000000..31e6e76 --- /dev/null +++ b/arshipdesign/core/system.py @@ -0,0 +1,2 @@ +"""Sistema del buque. Stub — Sprint 7.""" +raise NotImplementedError("system — Sprint 7") diff --git a/arshipdesign/core/tank.py b/arshipdesign/core/tank.py new file mode 100644 index 0000000..21287e5 --- /dev/null +++ b/arshipdesign/core/tank.py @@ -0,0 +1,2 @@ +"""Tanque. Stub — Sprint 4.""" +raise NotImplementedError("tank — Sprint 4") diff --git a/arshipdesign/core/units.py b/arshipdesign/core/units.py new file mode 100644 index 0000000..9807c02 --- /dev/null +++ b/arshipdesign/core/units.py @@ -0,0 +1,285 @@ +""" +Conversiones de unidades SI ↔ Imperial para AR-ShipDesign. + +Internamente todo se almacena en SI (m, kg, s, N, Pa, W). +Este módulo convierte para visualización en UI cuando el usuario +selecciona unidades imperiales. + +Unidades SI base: + Longitud → metro (m) + Masa → kilogramo (kg) + Fuerza → Newton (N) + Presión → Pascal (Pa) + Potencia → Watt (W) + Velocidad → metro/segundo (m/s) + Volumen → metro cúbico (m³) + Área → metro cuadrado (m²) + Temperatura → Celsius (°C) +""" + +from __future__ import annotations +from enum import Enum + + +class UnitSystem(Enum): + SI = "si" + IMPERIAL = "imperial" + + +# ────────────────────────────────────────────── +# LONGITUD +# ────────────────────────────────────────────── + +def m_to_ft(m: float) -> float: + """Metros → Pies.""" + return m * 3.280839895 + +def ft_to_m(ft: float) -> float: + """Pies → Metros.""" + return ft / 3.280839895 + +def m_to_in(m: float) -> float: + """Metros → Pulgadas.""" + return m * 39.3700787 + +def in_to_m(inches: float) -> float: + """Pulgadas → Metros.""" + return inches / 39.3700787 + +def mm_to_in(mm: float) -> float: + """Milímetros → Pulgadas.""" + return mm / 25.4 + +def in_to_mm(inches: float) -> float: + """Pulgadas → Milímetros.""" + return inches * 25.4 + +def nm_to_km(nm: float) -> float: + """Millas náuticas → Kilómetros.""" + return nm * 1.852 + +def km_to_nm(km: float) -> float: + """Kilómetros → Millas náuticas.""" + return km / 1.852 + + +# ────────────────────────────────────────────── +# VELOCIDAD +# ────────────────────────────────────────────── + +def ms_to_kn(ms: float) -> float: + """m/s → Nudos.""" + return ms * 1.943844 + +def kn_to_ms(kn: float) -> float: + """Nudos → m/s.""" + return kn / 1.943844 + +def kn_to_kmh(kn: float) -> float: + """Nudos → km/h.""" + return kn * 1.852 + +def kmh_to_kn(kmh: float) -> float: + """km/h → Nudos.""" + return kmh / 1.852 + + +# ────────────────────────────────────────────── +# MASA Y FUERZA +# ────────────────────────────────────────────── + +def kg_to_lb(kg: float) -> float: + """Kilogramos → Libras.""" + return kg * 2.204622622 + +def lb_to_kg(lb: float) -> float: + """Libras → Kilogramos.""" + return lb / 2.204622622 + +def kg_to_ton_metric(kg: float) -> float: + """Kilogramos → Toneladas métricas.""" + return kg / 1000.0 + +def ton_metric_to_kg(t: float) -> float: + """Toneladas métricas → Kilogramos.""" + return t * 1000.0 + +def kg_to_ton_long(kg: float) -> float: + """Kilogramos → Toneladas largas (UK).""" + return kg / 1016.0469088 + +def ton_long_to_kg(t: float) -> float: + """Toneladas largas → Kilogramos.""" + return t * 1016.0469088 + +def n_to_lbf(n: float) -> float: + """Newton → Libra-fuerza.""" + return n * 0.224808943 + +def lbf_to_n(lbf: float) -> float: + """Libra-fuerza → Newton.""" + return lbf / 0.224808943 + + +# ────────────────────────────────────────────── +# PRESIÓN +# ────────────────────────────────────────────── + +def pa_to_psi(pa: float) -> float: + """Pascal → PSI.""" + return pa * 0.000145038 + +def psi_to_pa(psi: float) -> float: + """PSI → Pascal.""" + return psi / 0.000145038 + +def pa_to_bar(pa: float) -> float: + """Pascal → Bar.""" + return pa / 100000.0 + +def bar_to_pa(bar: float) -> float: + """Bar → Pascal.""" + return bar * 100000.0 + +def pa_to_kpa(pa: float) -> float: + """Pascal → Kilopascal.""" + return pa / 1000.0 + +def kpa_to_pa(kpa: float) -> float: + """Kilopascal → Pascal.""" + return kpa * 1000.0 + + +# ────────────────────────────────────────────── +# POTENCIA +# ────────────────────────────────────────────── + +def w_to_hp(w: float) -> float: + """Watt → Caballos de vapor (metric HP).""" + return w / 735.49875 + +def hp_to_w(hp: float) -> float: + """HP métrico → Watt.""" + return hp * 735.49875 + +def w_to_bhp(w: float) -> float: + """Watt → BHP (brake horsepower, SAE).""" + return w / 745.69987 + +def bhp_to_w(bhp: float) -> float: + """BHP → Watt.""" + return bhp * 745.69987 + +def w_to_kw(w: float) -> float: + """Watt → Kilowatt.""" + return w / 1000.0 + +def kw_to_w(kw: float) -> float: + """Kilowatt → Watt.""" + return kw * 1000.0 + + +# ────────────────────────────────────────────── +# VOLUMEN +# ────────────────────────────────────────────── + +def m3_to_ft3(m3: float) -> float: + """m³ → ft³.""" + return m3 * 35.3146667 + +def ft3_to_m3(ft3: float) -> float: + """ft³ → m³.""" + return ft3 / 35.3146667 + +def m3_to_liters(m3: float) -> float: + """m³ → Litros.""" + return m3 * 1000.0 + +def liters_to_m3(liters: float) -> float: + """Litros → m³.""" + return liters / 1000.0 + +def m3_to_usgal(m3: float) -> float: + """m³ → Galones US.""" + return m3 * 264.172052 + +def usgal_to_m3(gal: float) -> float: + """Galones US → m³.""" + return gal / 264.172052 + + +# ────────────────────────────────────────────── +# ÁREA +# ────────────────────────────────────────────── + +def m2_to_ft2(m2: float) -> float: + """m² → ft².""" + return m2 * 10.7639104 + +def ft2_to_m2(ft2: float) -> float: + """ft² → m².""" + return ft2 / 10.7639104 + + +# ────────────────────────────────────────────── +# TEMPERATURA +# ────────────────────────────────────────────── + +def c_to_f(celsius: float) -> float: + """Celsius → Fahrenheit.""" + return celsius * 9 / 5 + 32 + +def f_to_c(fahrenheit: float) -> float: + """Fahrenheit → Celsius.""" + return (fahrenheit - 32) * 5 / 9 + + +# ────────────────────────────────────────────── +# DENSIDAD +# ────────────────────────────────────────────── + +def kg_m3_to_lb_ft3(rho: float) -> float: + """kg/m³ → lb/ft³.""" + return rho * 0.062427961 + +def lb_ft3_to_kg_m3(rho: float) -> float: + """lb/ft³ → kg/m³.""" + return rho / 0.062427961 + + +# ────────────────────────────────────────────── +# MOMENTO +# ────────────────────────────────────────────── + +def nm_to_ft_lbf(nm: float) -> float: + """Newton·metro → ft·lbf.""" + return nm * 0.737562149 + +def ft_lbf_to_nm(ft_lbf: float) -> float: + """ft·lbf → Newton·metro.""" + return ft_lbf / 0.737562149 + + +# ────────────────────────────────────────────── +# HELPER GENÉRICO para la UI +# ────────────────────────────────────────────── + +UNIT_LABELS: dict[str, dict[str, str]] = { + "length": {"si": "m", "imperial": "ft"}, + "length_small": {"si": "mm", "imperial": "in"}, + "speed": {"si": "m/s", "imperial": "kn"}, + "speed_nav": {"si": "kn", "imperial": "kn"}, + "mass": {"si": "kg", "imperial": "lb"}, + "displacement": {"si": "t", "imperial": "LT"}, + "power": {"si": "kW", "imperial": "BHP"}, + "pressure": {"si": "kPa", "imperial": "psi"}, + "volume": {"si": "m³", "imperial": "ft³"}, + "area": {"si": "m²", "imperial": "ft²"}, + "temperature": {"si": "°C", "imperial": "°F"}, + "density": {"si": "kg/m³", "imperial": "lb/ft³"}, +} + + +def unit_label(quantity: str, system: str = "si") -> str: + """Retorna la etiqueta de unidad para una cantidad dada.""" + return UNIT_LABELS.get(quantity, {}).get(system, "—") diff --git a/arshipdesign/core/valve.py b/arshipdesign/core/valve.py new file mode 100644 index 0000000..15fa14e --- /dev/null +++ b/arshipdesign/core/valve.py @@ -0,0 +1,2 @@ +"""Válvula. Stub — Sprint 7.""" +raise NotImplementedError("valve — Sprint 7") diff --git a/arshipdesign/core/waterplane.py b/arshipdesign/core/waterplane.py new file mode 100644 index 0000000..abf91c4 --- /dev/null +++ b/arshipdesign/core/waterplane.py @@ -0,0 +1,2 @@ +"""Plano de flotación. Stub — Sprint 2.""" +raise NotImplementedError("waterplane — Sprint 2") diff --git a/arshipdesign/core/weight_item.py b/arshipdesign/core/weight_item.py new file mode 100644 index 0000000..6644b5d --- /dev/null +++ b/arshipdesign/core/weight_item.py @@ -0,0 +1,2 @@ +"""Peso puntual. Stub — Sprint 4.""" +raise NotImplementedError("weight_item — Sprint 4") diff --git a/arshipdesign/engines/__init__.py b/arshipdesign/engines/__init__.py new file mode 100644 index 0000000..e182990 --- /dev/null +++ b/arshipdesign/engines/__init__.py @@ -0,0 +1 @@ +# arshipdesign/engines diff --git a/arshipdesign/engines/engine_curve.py b/arshipdesign/engines/engine_curve.py new file mode 100644 index 0000000..9b75a04 --- /dev/null +++ b/arshipdesign/engines/engine_curve.py @@ -0,0 +1,2 @@ +"""Curva BHP RPM. Stub — Sprint 5.""" +raise NotImplementedError("engine_curve — Sprint 5") diff --git a/arshipdesign/engines/engine_db.py b/arshipdesign/engines/engine_db.py new file mode 100644 index 0000000..41678c6 --- /dev/null +++ b/arshipdesign/engines/engine_db.py @@ -0,0 +1,2 @@ +"""Catálogo motores. Stub — Sprint 5.""" +raise NotImplementedError("engine_db — Sprint 5") diff --git a/arshipdesign/engines/engine_selection.py b/arshipdesign/engines/engine_selection.py new file mode 100644 index 0000000..e243157 --- /dev/null +++ b/arshipdesign/engines/engine_selection.py @@ -0,0 +1,2 @@ +"""Selección motor. Stub — Sprint 5.""" +raise NotImplementedError("engine_selection — Sprint 5") diff --git a/arshipdesign/engines/fuel_consumption.py b/arshipdesign/engines/fuel_consumption.py new file mode 100644 index 0000000..2e0d71c --- /dev/null +++ b/arshipdesign/engines/fuel_consumption.py @@ -0,0 +1,2 @@ +"""Consumo combustible. Stub — Sprint 5.""" +raise NotImplementedError("fuel_consumption — Sprint 5") diff --git a/arshipdesign/engines/nox_emissions.py b/arshipdesign/engines/nox_emissions.py new file mode 100644 index 0000000..c57ec55 --- /dev/null +++ b/arshipdesign/engines/nox_emissions.py @@ -0,0 +1,2 @@ +"""Emisiones NOx IMO. Stub — Sprint 5.""" +raise NotImplementedError("nox_emissions — Sprint 5") diff --git a/arshipdesign/fabrication/__init__.py b/arshipdesign/fabrication/__init__.py new file mode 100644 index 0000000..6efb6c7 --- /dev/null +++ b/arshipdesign/fabrication/__init__.py @@ -0,0 +1 @@ +# arshipdesign/fabrication diff --git a/arshipdesign/fabrication/bom.py b/arshipdesign/fabrication/bom.py new file mode 100644 index 0000000..ef1f2a2 --- /dev/null +++ b/arshipdesign/fabrication/bom.py @@ -0,0 +1,2 @@ +"""BOM lista de materiales. Stub — Sprint 13.""" +raise NotImplementedError("bom — Sprint 13") diff --git a/arshipdesign/fabrication/construction_sequence.py b/arshipdesign/fabrication/construction_sequence.py new file mode 100644 index 0000000..7dbc915 --- /dev/null +++ b/arshipdesign/fabrication/construction_sequence.py @@ -0,0 +1,2 @@ +"""Secuencia de montaje. Stub — Sprint 13.""" +raise NotImplementedError("construction_sequence — Sprint 13") diff --git a/arshipdesign/fabrication/details/__init__.py b/arshipdesign/fabrication/details/__init__.py new file mode 100644 index 0000000..61859b7 --- /dev/null +++ b/arshipdesign/fabrication/details/__init__.py @@ -0,0 +1 @@ +# fabrication/details diff --git a/arshipdesign/fabrication/details/alignment_marks.py b/arshipdesign/fabrication/details/alignment_marks.py new file mode 100644 index 0000000..fe7c081 --- /dev/null +++ b/arshipdesign/fabrication/details/alignment_marks.py @@ -0,0 +1,2 @@ +"""Marcas alineación. Stub — Sprint 13.""" +raise NotImplementedError("alignment_marks — Sprint 13") diff --git a/arshipdesign/fabrication/details/dogbone.py b/arshipdesign/fabrication/details/dogbone.py new file mode 100644 index 0000000..481ec2c --- /dev/null +++ b/arshipdesign/fabrication/details/dogbone.py @@ -0,0 +1,2 @@ +"""Dog-bones CNC. Stub — Sprint 13.""" +raise NotImplementedError("dogbone — Sprint 13") diff --git a/arshipdesign/fabrication/details/grain_direction.py b/arshipdesign/fabrication/details/grain_direction.py new file mode 100644 index 0000000..6e7c856 --- /dev/null +++ b/arshipdesign/fabrication/details/grain_direction.py @@ -0,0 +1,2 @@ +"""Dirección fibra plywood. Stub — Sprint 13.""" +raise NotImplementedError("grain_direction — Sprint 13") diff --git a/arshipdesign/fabrication/details/lightening_holes.py b/arshipdesign/fabrication/details/lightening_holes.py new file mode 100644 index 0000000..81cf400 --- /dev/null +++ b/arshipdesign/fabrication/details/lightening_holes.py @@ -0,0 +1,2 @@ +"""Vaciados aligeramiento. Stub — Sprint 13.""" +raise NotImplementedError("lightening_holes — Sprint 13") diff --git a/arshipdesign/fabrication/details/limber_holes.py b/arshipdesign/fabrication/details/limber_holes.py new file mode 100644 index 0000000..c0d08b5 --- /dev/null +++ b/arshipdesign/fabrication/details/limber_holes.py @@ -0,0 +1,2 @@ +"""Agujeros achique. Stub — Sprint 13.""" +raise NotImplementedError("limber_holes — Sprint 13") diff --git a/arshipdesign/fabrication/details/slot_tab.py b/arshipdesign/fabrication/details/slot_tab.py new file mode 100644 index 0000000..a656877 --- /dev/null +++ b/arshipdesign/fabrication/details/slot_tab.py @@ -0,0 +1,2 @@ +"""Slots y tabs. Stub — Sprint 13.""" +raise NotImplementedError("slot_tab — Sprint 13") diff --git a/arshipdesign/fabrication/fabrication_report.py b/arshipdesign/fabrication/fabrication_report.py new file mode 100644 index 0000000..577e38e --- /dev/null +++ b/arshipdesign/fabrication/fabrication_report.py @@ -0,0 +1,2 @@ +"""Reporte fabricación. Stub — Sprint 13.""" +raise NotImplementedError("fabrication_report — Sprint 13") diff --git a/arshipdesign/fabrication/gcode_generator.py b/arshipdesign/fabrication/gcode_generator.py new file mode 100644 index 0000000..86020e1 --- /dev/null +++ b/arshipdesign/fabrication/gcode_generator.py @@ -0,0 +1,2 @@ +"""G-code CNC plasma/router/laser. Stub — Sprint 13.""" +raise NotImplementedError("gcode_generator — Sprint 13") diff --git a/arshipdesign/fabrication/joints/__init__.py b/arshipdesign/fabrication/joints/__init__.py new file mode 100644 index 0000000..f20364c --- /dev/null +++ b/arshipdesign/fabrication/joints/__init__.py @@ -0,0 +1 @@ +# fabrication/joints diff --git a/arshipdesign/fabrication/joints/butt_joint.py b/arshipdesign/fabrication/joints/butt_joint.py new file mode 100644 index 0000000..d522f18 --- /dev/null +++ b/arshipdesign/fabrication/joints/butt_joint.py @@ -0,0 +1,2 @@ +"""Butt joint. Stub — Sprint 13.""" +raise NotImplementedError("butt_joint — Sprint 13") diff --git a/arshipdesign/fabrication/joints/epoxy_bond.py b/arshipdesign/fabrication/joints/epoxy_bond.py new file mode 100644 index 0000000..c45ac87 --- /dev/null +++ b/arshipdesign/fabrication/joints/epoxy_bond.py @@ -0,0 +1,2 @@ +"""Unión epóxica. Stub — Sprint 13.""" +raise NotImplementedError("epoxy_bond — Sprint 13") diff --git a/arshipdesign/fabrication/joints/scarf_joint.py b/arshipdesign/fabrication/joints/scarf_joint.py new file mode 100644 index 0000000..e7c7411 --- /dev/null +++ b/arshipdesign/fabrication/joints/scarf_joint.py @@ -0,0 +1,2 @@ +"""Scarf joint. Stub — Sprint 13.""" +raise NotImplementedError("scarf_joint — Sprint 13") diff --git a/arshipdesign/fabrication/joints/weld_prep.py b/arshipdesign/fabrication/joints/weld_prep.py new file mode 100644 index 0000000..10e970e --- /dev/null +++ b/arshipdesign/fabrication/joints/weld_prep.py @@ -0,0 +1,2 @@ +"""Preparación soldadura. Stub — Sprint 13.""" +raise NotImplementedError("weld_prep — Sprint 13") diff --git a/arshipdesign/fabrication/material_estimator.py b/arshipdesign/fabrication/material_estimator.py new file mode 100644 index 0000000..78a0a0a --- /dev/null +++ b/arshipdesign/fabrication/material_estimator.py @@ -0,0 +1,2 @@ +"""Estimación de material. Stub — Sprint 13.""" +raise NotImplementedError("material_estimator — Sprint 13") diff --git a/arshipdesign/fabrication/materials/__init__.py b/arshipdesign/fabrication/materials/__init__.py new file mode 100644 index 0000000..b77a2a7 --- /dev/null +++ b/arshipdesign/fabrication/materials/__init__.py @@ -0,0 +1 @@ +# fabrication/materials diff --git a/arshipdesign/fabrication/molds/__init__.py b/arshipdesign/fabrication/molds/__init__.py new file mode 100644 index 0000000..427d9df --- /dev/null +++ b/arshipdesign/fabrication/molds/__init__.py @@ -0,0 +1 @@ +# fabrication/molds — Moldes FRP diff --git a/arshipdesign/fabrication/molds/hull_laminate.py b/arshipdesign/fabrication/molds/hull_laminate.py new file mode 100644 index 0000000..fde9bf5 --- /dev/null +++ b/arshipdesign/fabrication/molds/hull_laminate.py @@ -0,0 +1,2 @@ +"""Schedule laminado casco FRP. Stub — Sprint 13B.""" +raise NotImplementedError("hull_laminate — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/laminate_schedule.py b/arshipdesign/fabrication/molds/laminate_schedule.py new file mode 100644 index 0000000..b81df47 --- /dev/null +++ b/arshipdesign/fabrication/molds/laminate_schedule.py @@ -0,0 +1,2 @@ +"""Schedule laminado molde. Stub — Sprint 13B.""" +raise NotImplementedError("laminate_schedule — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/lofting.py b/arshipdesign/fabrication/molds/lofting.py new file mode 100644 index 0000000..fe5b60e --- /dev/null +++ b/arshipdesign/fabrication/molds/lofting.py @@ -0,0 +1,2 @@ +"""Lofting tabla offsets. Stub — Sprint 13B.""" +raise NotImplementedError("lofting — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/mold_female.py b/arshipdesign/fabrication/molds/mold_female.py new file mode 100644 index 0000000..086f348 --- /dev/null +++ b/arshipdesign/fabrication/molds/mold_female.py @@ -0,0 +1,2 @@ +"""Molde hembra. Stub — Sprint 13B.""" +raise NotImplementedError("mold_female — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/mold_report.py b/arshipdesign/fabrication/molds/mold_report.py new file mode 100644 index 0000000..ee75d46 --- /dev/null +++ b/arshipdesign/fabrication/molds/mold_report.py @@ -0,0 +1,2 @@ +"""Reporte molde FRP. Stub — Sprint 13B.""" +raise NotImplementedError("mold_report — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/mold_structure.py b/arshipdesign/fabrication/molds/mold_structure.py new file mode 100644 index 0000000..446953b --- /dev/null +++ b/arshipdesign/fabrication/molds/mold_structure.py @@ -0,0 +1,2 @@ +"""Estructura del molde. Stub — Sprint 13B.""" +raise NotImplementedError("mold_structure — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/parting_line.py b/arshipdesign/fabrication/molds/parting_line.py new file mode 100644 index 0000000..185f55f --- /dev/null +++ b/arshipdesign/fabrication/molds/parting_line.py @@ -0,0 +1,2 @@ +"""Línea partición. Stub — Sprint 13B.""" +raise NotImplementedError("parting_line — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/plug.py b/arshipdesign/fabrication/molds/plug.py new file mode 100644 index 0000000..687627e --- /dev/null +++ b/arshipdesign/fabrication/molds/plug.py @@ -0,0 +1,2 @@ +"""Plug macho FRP. Stub — Sprint 13B.""" +raise NotImplementedError("plug — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/resin_calculator.py b/arshipdesign/fabrication/molds/resin_calculator.py new file mode 100644 index 0000000..36a86e6 --- /dev/null +++ b/arshipdesign/fabrication/molds/resin_calculator.py @@ -0,0 +1,2 @@ +"""Cálculo materiales FRP. Stub — Sprint 13B.""" +raise NotImplementedError("resin_calculator — Sprint 13B") diff --git a/arshipdesign/fabrication/molds/station_molds.py b/arshipdesign/fabrication/molds/station_molds.py new file mode 100644 index 0000000..4248d73 --- /dev/null +++ b/arshipdesign/fabrication/molds/station_molds.py @@ -0,0 +1,2 @@ +"""Cuadernas del molde con offsets. Stub — Sprint 13B.""" +raise NotImplementedError("station_molds — Sprint 13B") diff --git a/arshipdesign/fabrication/nesting.py b/arshipdesign/fabrication/nesting.py new file mode 100644 index 0000000..1f828e5 --- /dev/null +++ b/arshipdesign/fabrication/nesting.py @@ -0,0 +1,2 @@ +"""Nesting 2D. Stub — Sprint 13.""" +raise NotImplementedError("nesting — Sprint 13") diff --git a/arshipdesign/fabrication/part_splitter.py b/arshipdesign/fabrication/part_splitter.py new file mode 100644 index 0000000..6bb140a --- /dev/null +++ b/arshipdesign/fabrication/part_splitter.py @@ -0,0 +1,2 @@ +"""División de piezas. Stub — Sprint 13.""" +raise NotImplementedError("part_splitter — Sprint 13") diff --git a/arshipdesign/fabrication/sheet.py b/arshipdesign/fabrication/sheet.py new file mode 100644 index 0000000..07c2d9a --- /dev/null +++ b/arshipdesign/fabrication/sheet.py @@ -0,0 +1,2 @@ +"""Lámina de material. Stub — Sprint 13.""" +raise NotImplementedError("sheet — Sprint 13") diff --git a/arshipdesign/fabrication/standards/__init__.py b/arshipdesign/fabrication/standards/__init__.py new file mode 100644 index 0000000..d0dc77b --- /dev/null +++ b/arshipdesign/fabrication/standards/__init__.py @@ -0,0 +1 @@ +# fabrication/standards diff --git a/arshipdesign/fabrication/standards/abyc.py b/arshipdesign/fabrication/standards/abyc.py new file mode 100644 index 0000000..f3bd9f2 --- /dev/null +++ b/arshipdesign/fabrication/standards/abyc.py @@ -0,0 +1,2 @@ +"""ABYC. Stub — Sprint 13B.""" +raise NotImplementedError("abyc — Sprint 13B") diff --git a/arshipdesign/fabrication/standards/iso_12215.py b/arshipdesign/fabrication/standards/iso_12215.py new file mode 100644 index 0000000..9f88dc0 --- /dev/null +++ b/arshipdesign/fabrication/standards/iso_12215.py @@ -0,0 +1,2 @@ +"""ISO 12215 FRP construcción. Stub — Sprint 13B.""" +raise NotImplementedError("iso_12215 fabrication — Sprint 13B") diff --git a/arshipdesign/geometry/__init__.py b/arshipdesign/geometry/__init__.py new file mode 100644 index 0000000..05fcc70 --- /dev/null +++ b/arshipdesign/geometry/__init__.py @@ -0,0 +1 @@ +# arshipdesign/geometry diff --git a/arshipdesign/geometry/boolean.py b/arshipdesign/geometry/boolean.py new file mode 100644 index 0000000..e6eaca1 --- /dev/null +++ b/arshipdesign/geometry/boolean.py @@ -0,0 +1,2 @@ +"""Booleanas trimesh. Stub — Sprint 1.""" +raise NotImplementedError("boolean — Sprint 1") diff --git a/arshipdesign/geometry/fairing.py b/arshipdesign/geometry/fairing.py new file mode 100644 index 0000000..fc9971b --- /dev/null +++ b/arshipdesign/geometry/fairing.py @@ -0,0 +1,2 @@ +"""Análisis curvatura. Stub — Sprint 1.""" +raise NotImplementedError("fairing — Sprint 1") diff --git a/arshipdesign/geometry/fitting.py b/arshipdesign/geometry/fitting.py new file mode 100644 index 0000000..f6d5591 --- /dev/null +++ b/arshipdesign/geometry/fitting.py @@ -0,0 +1,2 @@ +"""Ajuste NURBS. Stub — Sprint 1.""" +raise NotImplementedError("fitting — Sprint 1") diff --git a/arshipdesign/geometry/intersection.py b/arshipdesign/geometry/intersection.py new file mode 100644 index 0000000..62b8f79 --- /dev/null +++ b/arshipdesign/geometry/intersection.py @@ -0,0 +1,2 @@ +"""Intersecciones. Stub — Sprint 1.""" +raise NotImplementedError("intersection — Sprint 1") diff --git a/arshipdesign/geometry/mesh.py b/arshipdesign/geometry/mesh.py new file mode 100644 index 0000000..6994c9d --- /dev/null +++ b/arshipdesign/geometry/mesh.py @@ -0,0 +1,2 @@ +"""Malla triangular. Stub — Sprint 1.""" +raise NotImplementedError("mesh — Sprint 1") diff --git a/arshipdesign/geometry/nurbs_curve.py b/arshipdesign/geometry/nurbs_curve.py new file mode 100644 index 0000000..dd714a8 --- /dev/null +++ b/arshipdesign/geometry/nurbs_curve.py @@ -0,0 +1,2 @@ +"""Curva NURBS. Stub — Sprint 1.""" +raise NotImplementedError("nurbs_curve — Sprint 1") diff --git a/arshipdesign/geometry/nurbs_surface.py b/arshipdesign/geometry/nurbs_surface.py new file mode 100644 index 0000000..6444cd4 --- /dev/null +++ b/arshipdesign/geometry/nurbs_surface.py @@ -0,0 +1,2 @@ +"""Superficie NURBS geomdl. Stub — Sprint 1.""" +raise NotImplementedError("nurbs_surface — Sprint 1") diff --git a/arshipdesign/geometry/slicer.py b/arshipdesign/geometry/slicer.py new file mode 100644 index 0000000..4d25ffe --- /dev/null +++ b/arshipdesign/geometry/slicer.py @@ -0,0 +1,2 @@ +"""Corte por planos. Stub — Sprint 1.""" +raise NotImplementedError("slicer — Sprint 1") diff --git a/arshipdesign/hydrostatics/__init__.py b/arshipdesign/hydrostatics/__init__.py new file mode 100644 index 0000000..2b3318d --- /dev/null +++ b/arshipdesign/hydrostatics/__init__.py @@ -0,0 +1 @@ +# arshipdesign/hydrostatics diff --git a/arshipdesign/hydrostatics/bonjean.py b/arshipdesign/hydrostatics/bonjean.py new file mode 100644 index 0000000..3d5445c --- /dev/null +++ b/arshipdesign/hydrostatics/bonjean.py @@ -0,0 +1,2 @@ +"""Curvas Bonjean. Stub — Sprint 2.""" +raise NotImplementedError("bonjean — Sprint 2") diff --git a/arshipdesign/hydrostatics/coefficients.py b/arshipdesign/hydrostatics/coefficients.py new file mode 100644 index 0000000..f205b66 --- /dev/null +++ b/arshipdesign/hydrostatics/coefficients.py @@ -0,0 +1,2 @@ +"""Coeficientes de forma. Stub — Sprint 2.""" +raise NotImplementedError("coefficients — Sprint 2") diff --git a/arshipdesign/hydrostatics/curves_of_form.py b/arshipdesign/hydrostatics/curves_of_form.py new file mode 100644 index 0000000..18d1d5f --- /dev/null +++ b/arshipdesign/hydrostatics/curves_of_form.py @@ -0,0 +1,2 @@ +"""Curvas hidrostáticas. Stub — Sprint 2.""" +raise NotImplementedError("curves_of_form — Sprint 2") diff --git a/arshipdesign/hydrostatics/free_floating.py b/arshipdesign/hydrostatics/free_floating.py new file mode 100644 index 0000000..416cdaf --- /dev/null +++ b/arshipdesign/hydrostatics/free_floating.py @@ -0,0 +1,2 @@ +"""Equilibrio libre 3DOF. Stub — Sprint 3.""" +raise NotImplementedError("free_floating — Sprint 3") diff --git a/arshipdesign/hydrostatics/heeled.py b/arshipdesign/hydrostatics/heeled.py new file mode 100644 index 0000000..9209df2 --- /dev/null +++ b/arshipdesign/hydrostatics/heeled.py @@ -0,0 +1,2 @@ +"""Equilibrio escorado. Stub — Sprint 3.""" +raise NotImplementedError("heeled — Sprint 3") diff --git a/arshipdesign/hydrostatics/integrator.py b/arshipdesign/hydrostatics/integrator.py new file mode 100644 index 0000000..f04bf6a --- /dev/null +++ b/arshipdesign/hydrostatics/integrator.py @@ -0,0 +1,2 @@ +"""Integradores Simpson. Stub — Sprint 2.""" +raise NotImplementedError("integrator — Sprint 2") diff --git a/arshipdesign/hydrostatics/trim.py b/arshipdesign/hydrostatics/trim.py new file mode 100644 index 0000000..121b128 --- /dev/null +++ b/arshipdesign/hydrostatics/trim.py @@ -0,0 +1,2 @@ +"""Equilibrio asiento. Stub — Sprint 3.""" +raise NotImplementedError("trim — Sprint 3") diff --git a/arshipdesign/hydrostatics/upright.py b/arshipdesign/hydrostatics/upright.py new file mode 100644 index 0000000..10870d7 --- /dev/null +++ b/arshipdesign/hydrostatics/upright.py @@ -0,0 +1,2 @@ +"""Hidrostáticos vertical. Stub — Sprint 2.""" +raise NotImplementedError("upright — Sprint 2") diff --git a/arshipdesign/io/__init__.py b/arshipdesign/io/__init__.py new file mode 100644 index 0000000..31a796a --- /dev/null +++ b/arshipdesign/io/__init__.py @@ -0,0 +1 @@ +# arshipdesign/io diff --git a/arshipdesign/io/dxf_reader.py b/arshipdesign/io/dxf_reader.py new file mode 100644 index 0000000..312862b --- /dev/null +++ b/arshipdesign/io/dxf_reader.py @@ -0,0 +1,2 @@ +"""Lectura DXF. Stub — Sprint 1.""" +raise NotImplementedError("dxf_reader — Sprint 1") diff --git a/arshipdesign/io/dxf_writer.py b/arshipdesign/io/dxf_writer.py new file mode 100644 index 0000000..317abe3 --- /dev/null +++ b/arshipdesign/io/dxf_writer.py @@ -0,0 +1,2 @@ +"""Escritura DXF. Stub — Sprint 10.""" +raise NotImplementedError("dxf_writer — Sprint 10") diff --git a/arshipdesign/io/obj_export.py b/arshipdesign/io/obj_export.py new file mode 100644 index 0000000..f920c47 --- /dev/null +++ b/arshipdesign/io/obj_export.py @@ -0,0 +1,2 @@ +"""Exportación OBJ. Stub — Sprint 1.""" +raise NotImplementedError("obj_export — Sprint 1") diff --git a/arshipdesign/io/offsets_csv.py b/arshipdesign/io/offsets_csv.py new file mode 100644 index 0000000..f41a2ec --- /dev/null +++ b/arshipdesign/io/offsets_csv.py @@ -0,0 +1,2 @@ +"""Offsets CSV. Stub — Sprint 1.""" +raise NotImplementedError("offsets_csv — Sprint 1") diff --git a/arshipdesign/io/offsets_excel.py b/arshipdesign/io/offsets_excel.py new file mode 100644 index 0000000..e0ea44a --- /dev/null +++ b/arshipdesign/io/offsets_excel.py @@ -0,0 +1,2 @@ +"""Offsets Excel. Stub — Sprint 1.""" +raise NotImplementedError("offsets_excel — Sprint 1") diff --git a/arshipdesign/io/pdf_export.py b/arshipdesign/io/pdf_export.py new file mode 100644 index 0000000..8fe4481 --- /dev/null +++ b/arshipdesign/io/pdf_export.py @@ -0,0 +1,2 @@ +"""Exportación PDF. Stub — Sprint 10.""" +raise NotImplementedError("pdf_export — Sprint 10") diff --git a/arshipdesign/io/stl_export.py b/arshipdesign/io/stl_export.py new file mode 100644 index 0000000..74d5a7f --- /dev/null +++ b/arshipdesign/io/stl_export.py @@ -0,0 +1,2 @@ +"""Exportación STL. Stub — Sprint 1.""" +raise NotImplementedError("stl_export — Sprint 1") diff --git a/arshipdesign/parametric/__init__.py b/arshipdesign/parametric/__init__.py new file mode 100644 index 0000000..1a760cc --- /dev/null +++ b/arshipdesign/parametric/__init__.py @@ -0,0 +1 @@ +# arshipdesign/parametric diff --git a/arshipdesign/parametric/lackenby_transform.py b/arshipdesign/parametric/lackenby_transform.py new file mode 100644 index 0000000..c2c64d7 --- /dev/null +++ b/arshipdesign/parametric/lackenby_transform.py @@ -0,0 +1,2 @@ +"""Lackenby. Stub — Sprint 11.""" +raise NotImplementedError("lackenby_transform — Sprint 11") diff --git a/arshipdesign/parametric/series60.py b/arshipdesign/parametric/series60.py new file mode 100644 index 0000000..d10bf3a --- /dev/null +++ b/arshipdesign/parametric/series60.py @@ -0,0 +1,2 @@ +"""Serie 60. Stub — Sprint 11.""" +raise NotImplementedError("series60 — Sprint 11") diff --git a/arshipdesign/parametric/wigley.py b/arshipdesign/parametric/wigley.py new file mode 100644 index 0000000..6e31ffe --- /dev/null +++ b/arshipdesign/parametric/wigley.py @@ -0,0 +1,2 @@ +"""Wigley validación. Stub — Sprint 1.""" +raise NotImplementedError("wigley — Sprint 1") diff --git a/arshipdesign/parametric/wizard_cruiser.py b/arshipdesign/parametric/wizard_cruiser.py new file mode 100644 index 0000000..5298ade --- /dev/null +++ b/arshipdesign/parametric/wizard_cruiser.py @@ -0,0 +1,2 @@ +"""Wizard crucero. Stub — Sprint 11.""" +raise NotImplementedError("wizard_cruiser — Sprint 11") diff --git a/arshipdesign/parametric/wizard_planing.py b/arshipdesign/parametric/wizard_planing.py new file mode 100644 index 0000000..4c500cb --- /dev/null +++ b/arshipdesign/parametric/wizard_planing.py @@ -0,0 +1,2 @@ +"""Wizard planeador. Stub — Sprint 11.""" +raise NotImplementedError("wizard_planing — Sprint 11") diff --git a/arshipdesign/parametric/wizard_sailing_mono.py b/arshipdesign/parametric/wizard_sailing_mono.py new file mode 100644 index 0000000..de8a6dc --- /dev/null +++ b/arshipdesign/parametric/wizard_sailing_mono.py @@ -0,0 +1,2 @@ +"""Wizard velero mono. Stub — Sprint 11.""" +raise NotImplementedError("wizard_sailing_mono — Sprint 11") diff --git a/arshipdesign/parametric/wizard_workboat.py b/arshipdesign/parametric/wizard_workboat.py new file mode 100644 index 0000000..3486020 --- /dev/null +++ b/arshipdesign/parametric/wizard_workboat.py @@ -0,0 +1,2 @@ +"""Wizard workboat. Stub — Sprint 11.""" +raise NotImplementedError("wizard_workboat — Sprint 11") diff --git a/arshipdesign/propulsion/__init__.py b/arshipdesign/propulsion/__init__.py new file mode 100644 index 0000000..ea64b1c --- /dev/null +++ b/arshipdesign/propulsion/__init__.py @@ -0,0 +1 @@ +# arshipdesign/propulsion diff --git a/arshipdesign/propulsion/cavitation.py b/arshipdesign/propulsion/cavitation.py new file mode 100644 index 0000000..e2da1eb --- /dev/null +++ b/arshipdesign/propulsion/cavitation.py @@ -0,0 +1,2 @@ +"""Cavitación. Stub — Sprint 5.""" +raise NotImplementedError("cavitation — Sprint 5") diff --git a/arshipdesign/propulsion/electric_drive.py b/arshipdesign/propulsion/electric_drive.py new file mode 100644 index 0000000..bb201f8 --- /dev/null +++ b/arshipdesign/propulsion/electric_drive.py @@ -0,0 +1,2 @@ +"""Propulsión eléctrica. Stub — Sprint 5.""" +raise NotImplementedError("electric_drive — Sprint 5") diff --git a/arshipdesign/propulsion/pod.py b/arshipdesign/propulsion/pod.py new file mode 100644 index 0000000..cbe35e3 --- /dev/null +++ b/arshipdesign/propulsion/pod.py @@ -0,0 +1,2 @@ +"""Pod azimutal. Stub — Sprint 5.""" +raise NotImplementedError("pod — Sprint 5") diff --git a/arshipdesign/propulsion/propeller_design.py b/arshipdesign/propulsion/propeller_design.py new file mode 100644 index 0000000..566a692 --- /dev/null +++ b/arshipdesign/propulsion/propeller_design.py @@ -0,0 +1,2 @@ +"""Diseño hélice. Stub — Sprint 5.""" +raise NotImplementedError("propeller_design — Sprint 5") diff --git a/arshipdesign/propulsion/propeller_matching.py b/arshipdesign/propulsion/propeller_matching.py new file mode 100644 index 0000000..5d0c47c --- /dev/null +++ b/arshipdesign/propulsion/propeller_matching.py @@ -0,0 +1,2 @@ +"""Matching motor-hélice. Stub — Sprint 5.""" +raise NotImplementedError("propeller_matching — Sprint 5") diff --git a/arshipdesign/propulsion/wageningen_b.py b/arshipdesign/propulsion/wageningen_b.py new file mode 100644 index 0000000..ddec1ac --- /dev/null +++ b/arshipdesign/propulsion/wageningen_b.py @@ -0,0 +1,2 @@ +"""Serie B Wageningen. Stub — Sprint 5.""" +raise NotImplementedError("wageningen_b — Sprint 5") diff --git a/arshipdesign/propulsion/waterjet.py b/arshipdesign/propulsion/waterjet.py new file mode 100644 index 0000000..0607cb2 --- /dev/null +++ b/arshipdesign/propulsion/waterjet.py @@ -0,0 +1,2 @@ +"""Waterjet. Stub — Sprint 5.""" +raise NotImplementedError("waterjet — Sprint 5") diff --git a/arshipdesign/reports/__init__.py b/arshipdesign/reports/__init__.py new file mode 100644 index 0000000..2786ab5 --- /dev/null +++ b/arshipdesign/reports/__init__.py @@ -0,0 +1 @@ +# arshipdesign/reports diff --git a/arshipdesign/reports/hydrostatic_report.py b/arshipdesign/reports/hydrostatic_report.py new file mode 100644 index 0000000..a9fb717 --- /dev/null +++ b/arshipdesign/reports/hydrostatic_report.py @@ -0,0 +1,2 @@ +"""Reporte hidrostático. Stub — Sprint 10.""" +raise NotImplementedError("hydrostatic_report — Sprint 10") diff --git a/arshipdesign/reports/lines_plan.py b/arshipdesign/reports/lines_plan.py new file mode 100644 index 0000000..a0d825c --- /dev/null +++ b/arshipdesign/reports/lines_plan.py @@ -0,0 +1,2 @@ +"""Plano de líneas. Stub — Sprint 10.""" +raise NotImplementedError("lines_plan — Sprint 10") diff --git a/arshipdesign/reports/pdf_builder.py b/arshipdesign/reports/pdf_builder.py new file mode 100644 index 0000000..c115e73 --- /dev/null +++ b/arshipdesign/reports/pdf_builder.py @@ -0,0 +1,2 @@ +"""Constructor PDFs. Stub — Sprint 10.""" +raise NotImplementedError("pdf_builder — Sprint 10") diff --git a/arshipdesign/reports/resistance_powering.py b/arshipdesign/reports/resistance_powering.py new file mode 100644 index 0000000..082b634 --- /dev/null +++ b/arshipdesign/reports/resistance_powering.py @@ -0,0 +1,2 @@ +"""Reporte resistencia. Stub — Sprint 10.""" +raise NotImplementedError("resistance_powering — Sprint 10") diff --git a/arshipdesign/reports/stability_booklet.py b/arshipdesign/reports/stability_booklet.py new file mode 100644 index 0000000..f52bb63 --- /dev/null +++ b/arshipdesign/reports/stability_booklet.py @@ -0,0 +1,2 @@ +"""Cuaderno estabilidad. Stub — Sprint 10.""" +raise NotImplementedError("stability_booklet — Sprint 10") diff --git a/arshipdesign/resistance/__init__.py b/arshipdesign/resistance/__init__.py new file mode 100644 index 0000000..154e1b9 --- /dev/null +++ b/arshipdesign/resistance/__init__.py @@ -0,0 +1 @@ +# arshipdesign/resistance diff --git a/arshipdesign/resistance/added_resistance.py b/arshipdesign/resistance/added_resistance.py new file mode 100644 index 0000000..dbd06f8 --- /dev/null +++ b/arshipdesign/resistance/added_resistance.py @@ -0,0 +1,2 @@ +"""Resistencia en olas. Stub — Sprint 9.""" +raise NotImplementedError("added_resistance — Sprint 9") diff --git a/arshipdesign/resistance/compton.py b/arshipdesign/resistance/compton.py new file mode 100644 index 0000000..e9926b2 --- /dev/null +++ b/arshipdesign/resistance/compton.py @@ -0,0 +1,2 @@ +"""Compton 1986. Stub — Sprint 5.""" +raise NotImplementedError("compton — Sprint 5") diff --git a/arshipdesign/resistance/dsyhs.py b/arshipdesign/resistance/dsyhs.py new file mode 100644 index 0000000..eecd295 --- /dev/null +++ b/arshipdesign/resistance/dsyhs.py @@ -0,0 +1,2 @@ +"""DSYHS veleros. Stub — Sprint 6.""" +raise NotImplementedError("dsyhs — Sprint 6") diff --git a/arshipdesign/resistance/effective_power.py b/arshipdesign/resistance/effective_power.py new file mode 100644 index 0000000..7dc3a93 --- /dev/null +++ b/arshipdesign/resistance/effective_power.py @@ -0,0 +1,2 @@ +"""PE = RT x V. Stub — Sprint 5.""" +raise NotImplementedError("effective_power — Sprint 5") diff --git a/arshipdesign/resistance/holtrop_mennen.py b/arshipdesign/resistance/holtrop_mennen.py new file mode 100644 index 0000000..57d0be9 --- /dev/null +++ b/arshipdesign/resistance/holtrop_mennen.py @@ -0,0 +1,2 @@ +"""Holtrop Mennen 1984. Stub — Sprint 5.""" +raise NotImplementedError("holtrop_mennen — Sprint 5") diff --git a/arshipdesign/resistance/savitsky.py b/arshipdesign/resistance/savitsky.py new file mode 100644 index 0000000..16645ef --- /dev/null +++ b/arshipdesign/resistance/savitsky.py @@ -0,0 +1,2 @@ +"""Savitsky 1964. Stub — Sprint 5.""" +raise NotImplementedError("savitsky — Sprint 5") diff --git a/arshipdesign/resistance/van_oortmerssen.py b/arshipdesign/resistance/van_oortmerssen.py new file mode 100644 index 0000000..e4b57f0 --- /dev/null +++ b/arshipdesign/resistance/van_oortmerssen.py @@ -0,0 +1,2 @@ +"""Van Oortmerssen. Stub — Sprint 5.""" +raise NotImplementedError("van_oortmerssen — Sprint 5") diff --git a/arshipdesign/sailing/__init__.py b/arshipdesign/sailing/__init__.py new file mode 100644 index 0000000..dc39a6c --- /dev/null +++ b/arshipdesign/sailing/__init__.py @@ -0,0 +1 @@ +# arshipdesign/sailing diff --git a/arshipdesign/sailing/aero_model.py b/arshipdesign/sailing/aero_model.py new file mode 100644 index 0000000..a33fa25 --- /dev/null +++ b/arshipdesign/sailing/aero_model.py @@ -0,0 +1,2 @@ +"""Modelo aerodinámico. Stub — Sprint 6.""" +raise NotImplementedError("aero_model — Sprint 6") diff --git a/arshipdesign/sailing/hydro_model.py b/arshipdesign/sailing/hydro_model.py new file mode 100644 index 0000000..897e054 --- /dev/null +++ b/arshipdesign/sailing/hydro_model.py @@ -0,0 +1,2 @@ +"""Hidrodinámica velero. Stub — Sprint 6.""" +raise NotImplementedError("hydro_model — Sprint 6") diff --git a/arshipdesign/sailing/multihull.py b/arshipdesign/sailing/multihull.py new file mode 100644 index 0000000..41876be --- /dev/null +++ b/arshipdesign/sailing/multihull.py @@ -0,0 +1,2 @@ +"""Multicasco. Stub — Sprint 6.""" +raise NotImplementedError("multihull — Sprint 6") diff --git a/arshipdesign/sailing/polar_diagram.py b/arshipdesign/sailing/polar_diagram.py new file mode 100644 index 0000000..2441444 --- /dev/null +++ b/arshipdesign/sailing/polar_diagram.py @@ -0,0 +1,2 @@ +"""Diagrama polar. Stub — Sprint 6.""" +raise NotImplementedError("polar_diagram — Sprint 6") diff --git a/arshipdesign/sailing/sail_coefficients.py b/arshipdesign/sailing/sail_coefficients.py new file mode 100644 index 0000000..cd46643 --- /dev/null +++ b/arshipdesign/sailing/sail_coefficients.py @@ -0,0 +1,2 @@ +"""CL CD velas. Stub — Sprint 6.""" +raise NotImplementedError("sail_coefficients — Sprint 6") diff --git a/arshipdesign/sailing/vpp.py b/arshipdesign/sailing/vpp.py new file mode 100644 index 0000000..ff8c83e --- /dev/null +++ b/arshipdesign/sailing/vpp.py @@ -0,0 +1,2 @@ +"""VPP. Stub — Sprint 6.""" +raise NotImplementedError("vpp — Sprint 6") diff --git a/arshipdesign/scantling/__init__.py b/arshipdesign/scantling/__init__.py new file mode 100644 index 0000000..0a06061 --- /dev/null +++ b/arshipdesign/scantling/__init__.py @@ -0,0 +1 @@ +# arshipdesign/scantling diff --git a/arshipdesign/scantling/bulkheads.py b/arshipdesign/scantling/bulkheads.py new file mode 100644 index 0000000..13ec0de --- /dev/null +++ b/arshipdesign/scantling/bulkheads.py @@ -0,0 +1,2 @@ +"""Mamparos. Stub — Sprint 2.5.""" +raise NotImplementedError("bulkheads — Sprint 2.5") diff --git a/arshipdesign/scantling/deck_beams.py b/arshipdesign/scantling/deck_beams.py new file mode 100644 index 0000000..b721e46 --- /dev/null +++ b/arshipdesign/scantling/deck_beams.py @@ -0,0 +1,2 @@ +"""Baos cubierta. Stub — Sprint 2.5.""" +raise NotImplementedError("deck_beams — Sprint 2.5") diff --git a/arshipdesign/scantling/design_pressures.py b/arshipdesign/scantling/design_pressures.py new file mode 100644 index 0000000..3ca31ba --- /dev/null +++ b/arshipdesign/scantling/design_pressures.py @@ -0,0 +1,2 @@ +"""Presiones diseño ISO 12215-5. Stub — Sprint 2.5.""" +raise NotImplementedError("design_pressures — Sprint 2.5") diff --git a/arshipdesign/scantling/engine_beds.py b/arshipdesign/scantling/engine_beds.py new file mode 100644 index 0000000..72abc4a --- /dev/null +++ b/arshipdesign/scantling/engine_beds.py @@ -0,0 +1,2 @@ +"""Bancadas motor. Stub — Sprint 2.5.""" +raise NotImplementedError("engine_beds — Sprint 2.5") diff --git a/arshipdesign/scantling/frames.py b/arshipdesign/scantling/frames.py new file mode 100644 index 0000000..f5a4b9a --- /dev/null +++ b/arshipdesign/scantling/frames.py @@ -0,0 +1,2 @@ +"""Cuadernas módulo sección. Stub — Sprint 2.5.""" +raise NotImplementedError("frames — Sprint 2.5") diff --git a/arshipdesign/scantling/framing_system.py b/arshipdesign/scantling/framing_system.py new file mode 100644 index 0000000..36ddd1c --- /dev/null +++ b/arshipdesign/scantling/framing_system.py @@ -0,0 +1,2 @@ +"""Sistema de cuadernas. Stub — Sprint 2.5.""" +raise NotImplementedError("framing_system — Sprint 2.5") diff --git a/arshipdesign/scantling/keel_bolts.py b/arshipdesign/scantling/keel_bolts.py new file mode 100644 index 0000000..14c5084 --- /dev/null +++ b/arshipdesign/scantling/keel_bolts.py @@ -0,0 +1,2 @@ +"""Pernos quilla. Stub — Sprint 2.5.""" +raise NotImplementedError("keel_bolts — Sprint 2.5") diff --git a/arshipdesign/scantling/keel_structure.py b/arshipdesign/scantling/keel_structure.py new file mode 100644 index 0000000..46de096 --- /dev/null +++ b/arshipdesign/scantling/keel_structure.py @@ -0,0 +1,2 @@ +"""Quilla y varengas. Stub — Sprint 2.5.""" +raise NotImplementedError("keel_structure — Sprint 2.5") diff --git a/arshipdesign/scantling/longitudinals.py b/arshipdesign/scantling/longitudinals.py new file mode 100644 index 0000000..6e7309f --- /dev/null +++ b/arshipdesign/scantling/longitudinals.py @@ -0,0 +1,2 @@ +"""Refuerzos longitudinales. Stub — Sprint 2.5.""" +raise NotImplementedError("longitudinals — Sprint 2.5") diff --git a/arshipdesign/scantling/mast_step.py b/arshipdesign/scantling/mast_step.py new file mode 100644 index 0000000..eda05f6 --- /dev/null +++ b/arshipdesign/scantling/mast_step.py @@ -0,0 +1,2 @@ +"""Paso de mástil. Stub — Sprint 2.5.""" +raise NotImplementedError("mast_step — Sprint 2.5") diff --git a/arshipdesign/scantling/materials/__init__.py b/arshipdesign/scantling/materials/__init__.py new file mode 100644 index 0000000..2e805f9 --- /dev/null +++ b/arshipdesign/scantling/materials/__init__.py @@ -0,0 +1 @@ +# scantling/materials diff --git a/arshipdesign/scantling/materials/aluminum_marine.py b/arshipdesign/scantling/materials/aluminum_marine.py new file mode 100644 index 0000000..9a66805 --- /dev/null +++ b/arshipdesign/scantling/materials/aluminum_marine.py @@ -0,0 +1,2 @@ +"""Aluminio marino. Stub — Sprint 2.5.""" +raise NotImplementedError("aluminum_marine — Sprint 2.5") diff --git a/arshipdesign/scantling/materials/frp_laminates.py b/arshipdesign/scantling/materials/frp_laminates.py new file mode 100644 index 0000000..9329776 --- /dev/null +++ b/arshipdesign/scantling/materials/frp_laminates.py @@ -0,0 +1,2 @@ +"""Laminados FRP. Stub — Sprint 2.5.""" +raise NotImplementedError("frp_laminates — Sprint 2.5") diff --git a/arshipdesign/scantling/materials/plywood_marine.py b/arshipdesign/scantling/materials/plywood_marine.py new file mode 100644 index 0000000..ee557dc --- /dev/null +++ b/arshipdesign/scantling/materials/plywood_marine.py @@ -0,0 +1,2 @@ +"""Plywood marino BS1088. Stub — Sprint 2.5.""" +raise NotImplementedError("plywood_marine — Sprint 2.5") diff --git a/arshipdesign/scantling/materials/steel_structural.py b/arshipdesign/scantling/materials/steel_structural.py new file mode 100644 index 0000000..efb1b17 --- /dev/null +++ b/arshipdesign/scantling/materials/steel_structural.py @@ -0,0 +1,2 @@ +"""Acero naval. Stub — Sprint 2.5.""" +raise NotImplementedError("steel_structural — Sprint 2.5") diff --git a/arshipdesign/scantling/optimizer.py b/arshipdesign/scantling/optimizer.py new file mode 100644 index 0000000..ac67584 --- /dev/null +++ b/arshipdesign/scantling/optimizer.py @@ -0,0 +1,2 @@ +"""Optimización peso/resistencia. Stub — Sprint 2.5.""" +raise NotImplementedError("optimizer — Sprint 2.5") diff --git a/arshipdesign/scantling/plating.py b/arshipdesign/scantling/plating.py new file mode 100644 index 0000000..0918d50 --- /dev/null +++ b/arshipdesign/scantling/plating.py @@ -0,0 +1,2 @@ +"""Espesor mínimo forros. Stub — Sprint 2.5.""" +raise NotImplementedError("plating — Sprint 2.5") diff --git a/arshipdesign/scantling/scantling_report.py b/arshipdesign/scantling/scantling_report.py new file mode 100644 index 0000000..e05dd07 --- /dev/null +++ b/arshipdesign/scantling/scantling_report.py @@ -0,0 +1,2 @@ +"""Reporte escantillado. Stub — Sprint 2.5.""" +raise NotImplementedError("scantling_report — Sprint 2.5") diff --git a/arshipdesign/scantling/section_library.py b/arshipdesign/scantling/section_library.py new file mode 100644 index 0000000..e817133 --- /dev/null +++ b/arshipdesign/scantling/section_library.py @@ -0,0 +1,2 @@ +"""Perfiles estructurales. Stub — Sprint 2.5.""" +raise NotImplementedError("section_library — Sprint 2.5") diff --git a/arshipdesign/scantling/standards/__init__.py b/arshipdesign/scantling/standards/__init__.py new file mode 100644 index 0000000..f9ecd97 --- /dev/null +++ b/arshipdesign/scantling/standards/__init__.py @@ -0,0 +1 @@ +# scantling/standards diff --git a/arshipdesign/scantling/standards/iso_12215_5.py b/arshipdesign/scantling/standards/iso_12215_5.py new file mode 100644 index 0000000..0644165 --- /dev/null +++ b/arshipdesign/scantling/standards/iso_12215_5.py @@ -0,0 +1,2 @@ +"""ISO 12215-5. Stub — Sprint 2.5.""" +raise NotImplementedError("iso_12215_5 — Sprint 2.5") diff --git a/arshipdesign/scantling/standards/iso_12215_6.py b/arshipdesign/scantling/standards/iso_12215_6.py new file mode 100644 index 0000000..1f6d575 --- /dev/null +++ b/arshipdesign/scantling/standards/iso_12215_6.py @@ -0,0 +1,2 @@ +"""ISO 12215-6. Stub — Sprint 2.5.""" +raise NotImplementedError("iso_12215_6 — Sprint 2.5") diff --git a/arshipdesign/scantling/standards/iso_12215_9.py b/arshipdesign/scantling/standards/iso_12215_9.py new file mode 100644 index 0000000..77afc43 --- /dev/null +++ b/arshipdesign/scantling/standards/iso_12215_9.py @@ -0,0 +1,2 @@ +"""ISO 12215-9 veleros. Stub — Sprint 2.5.""" +raise NotImplementedError("iso_12215_9 — Sprint 2.5") diff --git a/arshipdesign/scantling/standards/lloyds_small_craft.py b/arshipdesign/scantling/standards/lloyds_small_craft.py new file mode 100644 index 0000000..e817e7c --- /dev/null +++ b/arshipdesign/scantling/standards/lloyds_small_craft.py @@ -0,0 +1,2 @@ +"""Lloyds Small Craft. Stub — Sprint 2.5.""" +raise NotImplementedError("lloyds_small_craft — Sprint 2.5") diff --git a/arshipdesign/seakeeping/__init__.py b/arshipdesign/seakeeping/__init__.py new file mode 100644 index 0000000..8e7833b --- /dev/null +++ b/arshipdesign/seakeeping/__init__.py @@ -0,0 +1 @@ +# arshipdesign/seakeeping diff --git a/arshipdesign/seakeeping/motion_sickness.py b/arshipdesign/seakeeping/motion_sickness.py new file mode 100644 index 0000000..4d792d5 --- /dev/null +++ b/arshipdesign/seakeeping/motion_sickness.py @@ -0,0 +1,2 @@ +"""MSI ISO 2631. Stub — Sprint 9.""" +raise NotImplementedError("motion_sickness — Sprint 9") diff --git a/arshipdesign/seakeeping/rao.py b/arshipdesign/seakeeping/rao.py new file mode 100644 index 0000000..2de4751 --- /dev/null +++ b/arshipdesign/seakeeping/rao.py @@ -0,0 +1,2 @@ +"""RAOs. Stub — Sprint 9.""" +raise NotImplementedError("rao — Sprint 9") diff --git a/arshipdesign/seakeeping/short_term.py b/arshipdesign/seakeeping/short_term.py new file mode 100644 index 0000000..aeea5f6 --- /dev/null +++ b/arshipdesign/seakeeping/short_term.py @@ -0,0 +1,2 @@ +"""Mar corto. Stub — Sprint 9.""" +raise NotImplementedError("short_term — Sprint 9") diff --git a/arshipdesign/seakeeping/slamming.py b/arshipdesign/seakeeping/slamming.py new file mode 100644 index 0000000..9a0272b --- /dev/null +++ b/arshipdesign/seakeeping/slamming.py @@ -0,0 +1,2 @@ +"""Pantocazo. Stub — Sprint 9.""" +raise NotImplementedError("slamming — Sprint 9") diff --git a/arshipdesign/seakeeping/spectra.py b/arshipdesign/seakeeping/spectra.py new file mode 100644 index 0000000..14f4c25 --- /dev/null +++ b/arshipdesign/seakeeping/spectra.py @@ -0,0 +1,2 @@ +"""Espectros olas. Stub — Sprint 9.""" +raise NotImplementedError("spectra — Sprint 9") diff --git a/arshipdesign/seakeeping/strip_theory.py b/arshipdesign/seakeeping/strip_theory.py new file mode 100644 index 0000000..9c41662 --- /dev/null +++ b/arshipdesign/seakeeping/strip_theory.py @@ -0,0 +1,2 @@ +"""Strip theory STF. Stub — Sprint 9.""" +raise NotImplementedError("strip_theory — Sprint 9") diff --git a/arshipdesign/stability/__init__.py b/arshipdesign/stability/__init__.py new file mode 100644 index 0000000..b53549b --- /dev/null +++ b/arshipdesign/stability/__init__.py @@ -0,0 +1 @@ +# arshipdesign/stability diff --git a/arshipdesign/stability/criteria.py b/arshipdesign/stability/criteria.py new file mode 100644 index 0000000..b503292 --- /dev/null +++ b/arshipdesign/stability/criteria.py @@ -0,0 +1,2 @@ +"""Criterios IMO. Stub — Sprint 3.""" +raise NotImplementedError("criteria — Sprint 3") diff --git a/arshipdesign/stability/damage.py b/arshipdesign/stability/damage.py new file mode 100644 index 0000000..ebe5e2f --- /dev/null +++ b/arshipdesign/stability/damage.py @@ -0,0 +1,2 @@ +"""Estabilidad avería. Stub — Sprint 3.""" +raise NotImplementedError("damage — Sprint 3") diff --git a/arshipdesign/stability/floodable_length.py b/arshipdesign/stability/floodable_length.py new file mode 100644 index 0000000..71a3d5d --- /dev/null +++ b/arshipdesign/stability/floodable_length.py @@ -0,0 +1,2 @@ +"""Longitud inundable. Stub — Sprint 3.""" +raise NotImplementedError("floodable_length — Sprint 3") diff --git a/arshipdesign/stability/intact.py b/arshipdesign/stability/intact.py new file mode 100644 index 0000000..9b92ed1 --- /dev/null +++ b/arshipdesign/stability/intact.py @@ -0,0 +1,2 @@ +"""Estabilidad intacta GZ. Stub — Sprint 3.""" +raise NotImplementedError("intact — Sprint 3") diff --git a/arshipdesign/stability/weather.py b/arshipdesign/stability/weather.py new file mode 100644 index 0000000..6f9cf7d --- /dev/null +++ b/arshipdesign/stability/weather.py @@ -0,0 +1,2 @@ +"""Criterio meteorológico. Stub — Sprint 3.""" +raise NotImplementedError("weather — Sprint 3") diff --git a/arshipdesign/systems/__init__.py b/arshipdesign/systems/__init__.py new file mode 100644 index 0000000..624642d --- /dev/null +++ b/arshipdesign/systems/__init__.py @@ -0,0 +1 @@ +# arshipdesign/systems diff --git a/arshipdesign/systems/anchoring/__init__.py b/arshipdesign/systems/anchoring/__init__.py new file mode 100644 index 0000000..e449d33 --- /dev/null +++ b/arshipdesign/systems/anchoring/__init__.py @@ -0,0 +1 @@ +# systems/anchoring diff --git a/arshipdesign/systems/anchoring/anchor_selection.py b/arshipdesign/systems/anchoring/anchor_selection.py new file mode 100644 index 0000000..0940fcf --- /dev/null +++ b/arshipdesign/systems/anchoring/anchor_selection.py @@ -0,0 +1,2 @@ +"""Ancla. Stub — Sprint 8.""" +raise NotImplementedError("anchor_selection — Sprint 8") diff --git a/arshipdesign/systems/anchoring/windlass.py b/arshipdesign/systems/anchoring/windlass.py new file mode 100644 index 0000000..835bd35 --- /dev/null +++ b/arshipdesign/systems/anchoring/windlass.py @@ -0,0 +1,2 @@ +"""Molinete. Stub — Sprint 8.""" +raise NotImplementedError("windlass — Sprint 8") diff --git a/arshipdesign/systems/ballast/__init__.py b/arshipdesign/systems/ballast/__init__.py new file mode 100644 index 0000000..5567c07 --- /dev/null +++ b/arshipdesign/systems/ballast/__init__.py @@ -0,0 +1 @@ +# systems/ballast diff --git a/arshipdesign/systems/ballast/ballast_system.py b/arshipdesign/systems/ballast/ballast_system.py new file mode 100644 index 0000000..b5f638c --- /dev/null +++ b/arshipdesign/systems/ballast/ballast_system.py @@ -0,0 +1,2 @@ +"""Lastre. Stub — Sprint 7.""" +raise NotImplementedError("ballast_system — Sprint 7") diff --git a/arshipdesign/systems/bilge/__init__.py b/arshipdesign/systems/bilge/__init__.py new file mode 100644 index 0000000..7b26e2d --- /dev/null +++ b/arshipdesign/systems/bilge/__init__.py @@ -0,0 +1 @@ +# systems/bilge diff --git a/arshipdesign/systems/bilge/bilge_system.py b/arshipdesign/systems/bilge/bilge_system.py new file mode 100644 index 0000000..914517c --- /dev/null +++ b/arshipdesign/systems/bilge/bilge_system.py @@ -0,0 +1,2 @@ +"""Achique. Stub — Sprint 7.""" +raise NotImplementedError("bilge_system — Sprint 7") diff --git a/arshipdesign/systems/bilge/oily_water_separator.py b/arshipdesign/systems/bilge/oily_water_separator.py new file mode 100644 index 0000000..d34bae1 --- /dev/null +++ b/arshipdesign/systems/bilge/oily_water_separator.py @@ -0,0 +1,2 @@ +"""Separador sentinas OWS. Stub — Sprint 7.""" +raise NotImplementedError("oily_water_separator — Sprint 7") diff --git a/arshipdesign/systems/electrical/__init__.py b/arshipdesign/systems/electrical/__init__.py new file mode 100644 index 0000000..8429827 --- /dev/null +++ b/arshipdesign/systems/electrical/__init__.py @@ -0,0 +1 @@ +# systems/electrical diff --git a/arshipdesign/systems/electrical/battery_sizing.py b/arshipdesign/systems/electrical/battery_sizing.py new file mode 100644 index 0000000..c1d002f --- /dev/null +++ b/arshipdesign/systems/electrical/battery_sizing.py @@ -0,0 +1,2 @@ +"""Baterías. Stub — Sprint 7.""" +raise NotImplementedError("battery_sizing — Sprint 7") diff --git a/arshipdesign/systems/electrical/cable_sizing.py b/arshipdesign/systems/electrical/cable_sizing.py new file mode 100644 index 0000000..de303eb --- /dev/null +++ b/arshipdesign/systems/electrical/cable_sizing.py @@ -0,0 +1,2 @@ +"""Cables. Stub — Sprint 7.""" +raise NotImplementedError("cable_sizing — Sprint 7") diff --git a/arshipdesign/systems/electrical/generator_sizing.py b/arshipdesign/systems/electrical/generator_sizing.py new file mode 100644 index 0000000..95176d2 --- /dev/null +++ b/arshipdesign/systems/electrical/generator_sizing.py @@ -0,0 +1,2 @@ +"""Generadores. Stub — Sprint 7.""" +raise NotImplementedError("generator_sizing — Sprint 7") diff --git a/arshipdesign/systems/electrical/load_analysis.py b/arshipdesign/systems/electrical/load_analysis.py new file mode 100644 index 0000000..6769794 --- /dev/null +++ b/arshipdesign/systems/electrical/load_analysis.py @@ -0,0 +1,2 @@ +"""EPLA. Stub — Sprint 7.""" +raise NotImplementedError("load_analysis — Sprint 7") diff --git a/arshipdesign/systems/electrical/one_line_diagram.py b/arshipdesign/systems/electrical/one_line_diagram.py new file mode 100644 index 0000000..70576ab --- /dev/null +++ b/arshipdesign/systems/electrical/one_line_diagram.py @@ -0,0 +1,2 @@ +"""Diagrama unifilar. Stub — Sprint 7.""" +raise NotImplementedError("one_line_diagram — Sprint 7") diff --git a/arshipdesign/systems/firefighting/__init__.py b/arshipdesign/systems/firefighting/__init__.py new file mode 100644 index 0000000..f5c069e --- /dev/null +++ b/arshipdesign/systems/firefighting/__init__.py @@ -0,0 +1 @@ +# systems/firefighting diff --git a/arshipdesign/systems/firefighting/co2_system.py b/arshipdesign/systems/firefighting/co2_system.py new file mode 100644 index 0000000..b86a23c --- /dev/null +++ b/arshipdesign/systems/firefighting/co2_system.py @@ -0,0 +1,2 @@ +"""CO2. Stub — Sprint 8.""" +raise NotImplementedError("co2_system — Sprint 8") diff --git a/arshipdesign/systems/firefighting/detection.py b/arshipdesign/systems/firefighting/detection.py new file mode 100644 index 0000000..b70e9b9 --- /dev/null +++ b/arshipdesign/systems/firefighting/detection.py @@ -0,0 +1,2 @@ +"""Detección incendios. Stub — Sprint 8.""" +raise NotImplementedError("detection — Sprint 8") diff --git a/arshipdesign/systems/firefighting/fire_main.py b/arshipdesign/systems/firefighting/fire_main.py new file mode 100644 index 0000000..9f09043 --- /dev/null +++ b/arshipdesign/systems/firefighting/fire_main.py @@ -0,0 +1,2 @@ +"""CI agua salada. Stub — Sprint 8.""" +raise NotImplementedError("fire_main — Sprint 8") diff --git a/arshipdesign/systems/freshwater/__init__.py b/arshipdesign/systems/freshwater/__init__.py new file mode 100644 index 0000000..0389d3c --- /dev/null +++ b/arshipdesign/systems/freshwater/__init__.py @@ -0,0 +1 @@ +# systems/freshwater diff --git a/arshipdesign/systems/freshwater/fw_system.py b/arshipdesign/systems/freshwater/fw_system.py new file mode 100644 index 0000000..4456191 --- /dev/null +++ b/arshipdesign/systems/freshwater/fw_system.py @@ -0,0 +1,2 @@ +"""Agua dulce. Stub — Sprint 7.""" +raise NotImplementedError("fw_system — Sprint 7") diff --git a/arshipdesign/systems/freshwater/watermaker.py b/arshipdesign/systems/freshwater/watermaker.py new file mode 100644 index 0000000..fdb48d1 --- /dev/null +++ b/arshipdesign/systems/freshwater/watermaker.py @@ -0,0 +1,2 @@ +"""Osmosis inversa. Stub — Sprint 7.""" +raise NotImplementedError("watermaker — Sprint 7") diff --git a/arshipdesign/systems/fuel/__init__.py b/arshipdesign/systems/fuel/__init__.py new file mode 100644 index 0000000..8a5067b --- /dev/null +++ b/arshipdesign/systems/fuel/__init__.py @@ -0,0 +1 @@ +# systems/fuel diff --git a/arshipdesign/systems/fuel/autonomy.py b/arshipdesign/systems/fuel/autonomy.py new file mode 100644 index 0000000..8f0d7fc --- /dev/null +++ b/arshipdesign/systems/fuel/autonomy.py @@ -0,0 +1,2 @@ +"""Autonomía. Stub — Sprint 7.""" +raise NotImplementedError("autonomy — Sprint 7") diff --git a/arshipdesign/systems/fuel/day_tank.py b/arshipdesign/systems/fuel/day_tank.py new file mode 100644 index 0000000..6e60d20 --- /dev/null +++ b/arshipdesign/systems/fuel/day_tank.py @@ -0,0 +1,2 @@ +"""Tanque diario. Stub — Sprint 7.""" +raise NotImplementedError("day_tank — Sprint 7") diff --git a/arshipdesign/systems/fuel/fuel_system.py b/arshipdesign/systems/fuel/fuel_system.py new file mode 100644 index 0000000..78c9069 --- /dev/null +++ b/arshipdesign/systems/fuel/fuel_system.py @@ -0,0 +1,2 @@ +"""Combustible. Stub — Sprint 7.""" +raise NotImplementedError("fuel_system — Sprint 7") diff --git a/arshipdesign/systems/hvac/__init__.py b/arshipdesign/systems/hvac/__init__.py new file mode 100644 index 0000000..1e836ec --- /dev/null +++ b/arshipdesign/systems/hvac/__init__.py @@ -0,0 +1 @@ +# systems/hvac diff --git a/arshipdesign/systems/hvac/chiller_sizing.py b/arshipdesign/systems/hvac/chiller_sizing.py new file mode 100644 index 0000000..d8b73f2 --- /dev/null +++ b/arshipdesign/systems/hvac/chiller_sizing.py @@ -0,0 +1,2 @@ +"""Chiller. Stub — Sprint 8.""" +raise NotImplementedError("chiller_sizing — Sprint 8") diff --git a/arshipdesign/systems/hvac/heat_balance.py b/arshipdesign/systems/hvac/heat_balance.py new file mode 100644 index 0000000..a883e0e --- /dev/null +++ b/arshipdesign/systems/hvac/heat_balance.py @@ -0,0 +1,2 @@ +"""Balance térmico. Stub — Sprint 8.""" +raise NotImplementedError("heat_balance — Sprint 8") diff --git a/arshipdesign/systems/steering/__init__.py b/arshipdesign/systems/steering/__init__.py new file mode 100644 index 0000000..fe52d65 --- /dev/null +++ b/arshipdesign/systems/steering/__init__.py @@ -0,0 +1 @@ +# systems/steering diff --git a/arshipdesign/systems/steering/rudder_design.py b/arshipdesign/systems/steering/rudder_design.py new file mode 100644 index 0000000..0c22853 --- /dev/null +++ b/arshipdesign/systems/steering/rudder_design.py @@ -0,0 +1,2 @@ +"""Timón. Stub — Sprint 8.""" +raise NotImplementedError("rudder_design — Sprint 8") diff --git a/arshipdesign/systems/steering/steering_gear.py b/arshipdesign/systems/steering/steering_gear.py new file mode 100644 index 0000000..4e88b8b --- /dev/null +++ b/arshipdesign/systems/steering/steering_gear.py @@ -0,0 +1,2 @@ +"""Servo. Stub — Sprint 8.""" +raise NotImplementedError("steering_gear — Sprint 8") diff --git a/arshipdesign/systems/steering/thrusters.py b/arshipdesign/systems/steering/thrusters.py new file mode 100644 index 0000000..b4aa06e --- /dev/null +++ b/arshipdesign/systems/steering/thrusters.py @@ -0,0 +1,2 @@ +"""Thruster. Stub — Sprint 8.""" +raise NotImplementedError("thrusters — Sprint 8") diff --git a/arshipdesign/tanks/__init__.py b/arshipdesign/tanks/__init__.py new file mode 100644 index 0000000..16fae3a --- /dev/null +++ b/arshipdesign/tanks/__init__.py @@ -0,0 +1 @@ +# arshipdesign/tanks diff --git a/arshipdesign/tanks/capacity_plan.py b/arshipdesign/tanks/capacity_plan.py new file mode 100644 index 0000000..24d7ef9 --- /dev/null +++ b/arshipdesign/tanks/capacity_plan.py @@ -0,0 +1,2 @@ +"""Plan capacidades. Stub — Sprint 4.""" +raise NotImplementedError("capacity_plan — Sprint 4") diff --git a/arshipdesign/tanks/fsm.py b/arshipdesign/tanks/fsm.py new file mode 100644 index 0000000..b70edcc --- /dev/null +++ b/arshipdesign/tanks/fsm.py @@ -0,0 +1,2 @@ +"""Free Surface Moment. Stub — Sprint 4.""" +raise NotImplementedError("fsm — Sprint 4") diff --git a/arshipdesign/tanks/sounding_table.py b/arshipdesign/tanks/sounding_table.py new file mode 100644 index 0000000..711837d --- /dev/null +++ b/arshipdesign/tanks/sounding_table.py @@ -0,0 +1,2 @@ +"""Tablas sondaje. Stub — Sprint 4.""" +raise NotImplementedError("sounding_table — Sprint 4") diff --git a/arshipdesign/tanks/tank_definition.py b/arshipdesign/tanks/tank_definition.py new file mode 100644 index 0000000..7079b68 --- /dev/null +++ b/arshipdesign/tanks/tank_definition.py @@ -0,0 +1,2 @@ +"""Definición tanque. Stub — Sprint 4.""" +raise NotImplementedError("tank_definition — Sprint 4") diff --git a/arshipdesign/ui/__init__.py b/arshipdesign/ui/__init__.py new file mode 100644 index 0000000..422754d --- /dev/null +++ b/arshipdesign/ui/__init__.py @@ -0,0 +1 @@ +# arshipdesign/ui diff --git a/arshipdesign/ui/commands/__init__.py b/arshipdesign/ui/commands/__init__.py new file mode 100644 index 0000000..f5b8473 --- /dev/null +++ b/arshipdesign/ui/commands/__init__.py @@ -0,0 +1 @@ +# ui/commands diff --git a/arshipdesign/ui/commands/add_tank.py b/arshipdesign/ui/commands/add_tank.py new file mode 100644 index 0000000..e3918db --- /dev/null +++ b/arshipdesign/ui/commands/add_tank.py @@ -0,0 +1,2 @@ +"""Comando añadir tanque. Stub — Sprint 4.""" +raise NotImplementedError("add_tank — Sprint 4") diff --git a/arshipdesign/ui/commands/command.py b/arshipdesign/ui/commands/command.py new file mode 100644 index 0000000..71b0fbd --- /dev/null +++ b/arshipdesign/ui/commands/command.py @@ -0,0 +1,2 @@ +"""Command base Undo/Redo. Stub — Sprint 1.""" +raise NotImplementedError("command — Sprint 1") diff --git a/arshipdesign/ui/commands/modify_hull.py b/arshipdesign/ui/commands/modify_hull.py new file mode 100644 index 0000000..34eb737 --- /dev/null +++ b/arshipdesign/ui/commands/modify_hull.py @@ -0,0 +1,2 @@ +"""Comando modificar casco. Stub — Sprint 1.""" +raise NotImplementedError("modify_hull — Sprint 1") diff --git a/arshipdesign/ui/dialogs/__init__.py b/arshipdesign/ui/dialogs/__init__.py new file mode 100644 index 0000000..47afb07 --- /dev/null +++ b/arshipdesign/ui/dialogs/__init__.py @@ -0,0 +1 @@ +# ui/dialogs diff --git a/arshipdesign/ui/dialogs/liquid_picker.py b/arshipdesign/ui/dialogs/liquid_picker.py new file mode 100644 index 0000000..b83675c --- /dev/null +++ b/arshipdesign/ui/dialogs/liquid_picker.py @@ -0,0 +1,2 @@ +"""Selector líquido. Stub — Sprint 4.""" +raise NotImplementedError("liquid_picker — Sprint 4") diff --git a/arshipdesign/ui/dialogs/preferences.py b/arshipdesign/ui/dialogs/preferences.py new file mode 100644 index 0000000..c02ddf8 --- /dev/null +++ b/arshipdesign/ui/dialogs/preferences.py @@ -0,0 +1,2 @@ +"""Preferencias. Stub — Sprint 1.""" +raise NotImplementedError("preferences — Sprint 1") diff --git a/arshipdesign/ui/dialogs/stability_criteria_picker.py b/arshipdesign/ui/dialogs/stability_criteria_picker.py new file mode 100644 index 0000000..fad4aa9 --- /dev/null +++ b/arshipdesign/ui/dialogs/stability_criteria_picker.py @@ -0,0 +1,2 @@ +"""Criterios estabilidad. Stub — Sprint 3.""" +raise NotImplementedError("stability_criteria_picker — Sprint 3") diff --git a/arshipdesign/ui/dialogs/wizards.py b/arshipdesign/ui/dialogs/wizards.py new file mode 100644 index 0000000..99aa997 --- /dev/null +++ b/arshipdesign/ui/dialogs/wizards.py @@ -0,0 +1,2 @@ +"""Wizards embarcaciones. Stub — Sprint 1.""" +raise NotImplementedError("wizards — Sprint 1") diff --git a/arshipdesign/ui/i18n/en.json b/arshipdesign/ui/i18n/en.json new file mode 100644 index 0000000..a0a4532 --- /dev/null +++ b/arshipdesign/ui/i18n/en.json @@ -0,0 +1,80 @@ +{ + "app_title": "AR-ShipDesign", + "menu_file": "File", + "menu_edit": "Edit", + "menu_view": "View", + "menu_model": "Model", + "menu_analysis": "Analysis", + "menu_systems": "Systems", + "menu_fabrication": "Fabrication", + "menu_reports": "Reports", + "menu_help": "Help", + "file_new": "New Project", + "file_open": "Open...", + "file_save": "Save", + "file_save_as": "Save As...", + "file_recent": "Recent Files", + "file_exit": "Exit", + "edit_undo": "Undo", + "edit_redo": "Redo", + "edit_preferences": "Preferences...", + "panel_project": "Project", + "panel_properties": "Properties", + "panel_hydrostatics": "Hydrostatics", + "tab_3d": "3D", + "tab_lines": "Lines", + "tab_offsets": "Offsets", + "tab_curves": "Curves", + "tab_tanks": "Tanks", + "tab_capacity": "Capacity", + "tab_stability": "GZ Stability", + "tab_resistance": "Resistance", + "tab_propulsion": "Propulsion", + "tab_vpp": "Sailing VPP", + "tab_seakeeping": "Seakeeping", + "tab_electrical": "Electrical", + "tab_fuel": "Fuel", + "tab_freshwater": "Fresh Water", + "tab_bilge": "Bilge", + "tab_firefighting": "Fire Fighting", + "tab_hvac": "HVAC", + "tab_scantling": "Scantling", + "tab_fabrication": "Fabrication", + "tab_molds": "FRP Molds", + "tab_report": "Report", + "hydro_draft": "Draft", + "hydro_displacement": "Δ", + "hydro_lcb": "LCB", + "hydro_kb": "KB", + "hydro_kmt": "KMT", + "hydro_gmt": "GMT", + "hydro_tpc": "TPC", + "hydro_mct": "MCT", + "hydro_cb": "Cb", + "hydro_cw": "Cw", + "hydro_cm": "Cm", + "hydro_imo_ok": "IMO OK", + "hydro_imo_fail": "IMO FAIL", + "status_ready": "Ready", + "status_modified": "Modified", + "status_calculating": "Calculating...", + "new_project_title": "New Project", + "new_project_name": "Project name", + "new_project_type": "Vessel type", + "type_motor": "Motor vessel (displacement)", + "type_planing": "Planing craft", + "type_semi_planing": "Semi-planing", + "type_sailing_mono": "Sailing monohull", + "type_sailing_cat": "Sailing catamaran", + "type_workboat": "Workboat", + "about_title": "About AR-ShipDesign", + "about_version": "Version", + "about_copyright": "Copyright © 2025 Álvaro Rodríguez. All rights reserved.", + "tooltip_kmt": "KMT = KB + IT/∇ (transverse metacentric height)", + "tooltip_gmt": "GMT = KMT − KG (corrected metacentric height)", + "tooltip_tpc": "TPC = Aw · ρ / 100 (tonnes per cm immersion)", + "tooltip_mct": "MCT = Δ · GML / (100 · Lpp) (moment to change trim 1 cm)", + "tooltip_cb": "Cb = ∇ / (Lwl · B · T) (block coefficient)", + "tooltip_cw": "Cw = Aw / (Lwl · B) (waterplane coefficient)", + "tooltip_cm": "Cm = Am / (B · T) (midship section coefficient)" +} diff --git a/arshipdesign/ui/i18n/es.json b/arshipdesign/ui/i18n/es.json new file mode 100644 index 0000000..8f3abe5 --- /dev/null +++ b/arshipdesign/ui/i18n/es.json @@ -0,0 +1,80 @@ +{ + "app_title": "AR-ShipDesign", + "menu_file": "Archivo", + "menu_edit": "Editar", + "menu_view": "Ver", + "menu_model": "Modelo", + "menu_analysis": "Análisis", + "menu_systems": "Sistemas", + "menu_fabrication": "Fabricación", + "menu_reports": "Reportes", + "menu_help": "Ayuda", + "file_new": "Nuevo proyecto", + "file_open": "Abrir...", + "file_save": "Guardar", + "file_save_as": "Guardar como...", + "file_recent": "Recientes", + "file_exit": "Salir", + "edit_undo": "Deshacer", + "edit_redo": "Rehacer", + "edit_preferences": "Preferencias...", + "panel_project": "Proyecto", + "panel_properties": "Propiedades", + "panel_hydrostatics": "Hidrostáticos", + "tab_3d": "3D", + "tab_lines": "Líneas", + "tab_offsets": "Offsets", + "tab_curves": "Curvas", + "tab_tanks": "Tanques", + "tab_capacity": "Capacidad", + "tab_stability": "Estabilidad GZ", + "tab_resistance": "Resistencia", + "tab_propulsion": "Propulsión", + "tab_vpp": "VPP Velero", + "tab_seakeeping": "Movimientos", + "tab_electrical": "Eléctrico", + "tab_fuel": "Combustible", + "tab_freshwater": "Agua Dulce", + "tab_bilge": "Achique", + "tab_firefighting": "C. Incendios", + "tab_hvac": "HVAC", + "tab_scantling": "Escantillado", + "tab_fabrication": "Fabricación", + "tab_molds": "Moldes FRP", + "tab_report": "Reporte", + "hydro_draft": "Calado", + "hydro_displacement": "Δ", + "hydro_lcb": "LCB", + "hydro_kb": "KB", + "hydro_kmt": "KMT", + "hydro_gmt": "GMT", + "hydro_tpc": "TPC", + "hydro_mct": "MCT", + "hydro_cb": "Cb", + "hydro_cw": "Cw", + "hydro_cm": "Cm", + "hydro_imo_ok": "IMO OK", + "hydro_imo_fail": "IMO FALLA", + "status_ready": "Listo", + "status_modified": "Modificado", + "status_calculating": "Calculando...", + "new_project_title": "Nuevo Proyecto", + "new_project_name": "Nombre del proyecto", + "new_project_type": "Tipo de embarcación", + "type_motor": "Motor (desplazamiento)", + "type_planing": "Motor (planeo)", + "type_semi_planing": "Motor (semi-planeo)", + "type_sailing_mono": "Velero monocasco", + "type_sailing_cat": "Velero catamarán", + "type_workboat": "Workboat / Embarcación de trabajo", + "about_title": "Acerca de AR-ShipDesign", + "about_version": "Versión", + "about_copyright": "Copyright © 2025 Álvaro Rodríguez", + "tooltip_kmt": "KMT = KB + IT/∇ (altura metacéntrica transversal)", + "tooltip_gmt": "GMT = KMT − KG (altura metacéntrica corregida)", + "tooltip_tpc": "TPC = Aw · ρ / 100 (toneladas por cm de inmersión)", + "tooltip_mct": "MCT = Δ · GML / (100 · Lpp) (momento para cambiar asiento 1 cm)", + "tooltip_cb": "Cb = ∇ / (Lwl · B · T) (coeficiente de bloque)", + "tooltip_cw": "Cw = Aw / (Lwl · B) (coeficiente de plano de flotación)", + "tooltip_cm": "Cm = Am / (B · T) (coeficiente de cuaderna maestra)" +} diff --git a/arshipdesign/ui/main_window.py b/arshipdesign/ui/main_window.py new file mode 100644 index 0000000..b063476 --- /dev/null +++ b/arshipdesign/ui/main_window.py @@ -0,0 +1,802 @@ +""" +Ventana principal de AR-ShipDesign. + +Layout inspirado en DELFTship: + ┌─────────────────────────────────────────────────────────┐ + │ Menú | Toolbar │ + ├──────────┬──────────────────────────────┬───────────────┤ + │ │ │ │ + │ Árbol │ Vista central (tabs) │ Propiedades │ + │ Proyecto│ 3D / Líneas / Análisis │ │ + │ │ │ │ + ├──────────┴──────────────────────────────┴───────────────┤ + │ PANEL HIDROSTÁTICOS EN VIVO (siempre visible) │ + ├─────────────────────────────────────────────────────────┤ + │ Barra de tabs de módulos │ + └─────────────────────────────────────────────────────────┘ +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Optional + +from PySide6.QtCore import Qt, QTimer, Signal +from PySide6.QtGui import QAction, QFont, QIcon, QKeySequence +from PySide6.QtWidgets import ( + QApplication, + QDockWidget, + QFileDialog, + QFrame, + QHBoxLayout, + QLabel, + QMainWindow, + QMessageBox, + QSizePolicy, + QSplitter, + QStatusBar, + QTabWidget, + QToolBar, + QTreeWidget, + QTreeWidgetItem, + QVBoxLayout, + QWidget, +) + +from arshipdesign import __version__ +from arshipdesign.core.project import Project +from arshipdesign.utils.logger import get_logger +from arshipdesign.utils.settings import ( + add_recent_file, + get_language, + get_recent_files, + get_settings, + get_theme, + set_theme, +) + +logger = get_logger("ui.main_window") + +# Carga de strings de i18n +def _load_i18n(lang: str = "es") -> dict: + i18n_path = Path(__file__).parent / "i18n" / f"{lang}.json" + if not i18n_path.exists(): + i18n_path = Path(__file__).parent / "i18n" / "es.json" + try: + return json.loads(i18n_path.read_text(encoding="utf-8")) + except Exception: + return {} + + +class HydrostaticsPanel(QFrame): + """ + Panel de hidrostáticos en vivo — siempre visible en la parte inferior. + + En Sprint 2 se conectará al motor de cálculo. + Por ahora muestra valores placeholder. + """ + + def __init__(self, strings: dict, parent: Optional[QWidget] = None) -> None: + super().__init__(parent) + self.strings = strings + self.setObjectName("hydrostaticsPanel") + self.setFixedHeight(62) + self._build_ui() + + def _build_ui(self) -> None: + layout = QHBoxLayout(self) + layout.setContentsMargins(10, 4, 10, 4) + layout.setSpacing(0) + + # Título + title = QLabel(" HIDROSTÁTICOS ") + title.setFont(QFont("Segoe UI", 9, QFont.Weight.Bold)) + title.setProperty("label", True) + layout.addWidget(title) + + sep = self._make_sep() + layout.addWidget(sep) + + # Campos hidrostáticos + self._fields: dict[str, QLabel] = {} + hydro_items = [ + ("T", "3.20 m", "Calado [m]"), + ("Δ", "2 845 t", "Desplazamiento [t]"), + ("LCB", "12.30 m", "Centro Long. Carena [m desde AP]"), + ("KB", "1.85 m", "Centro Vert. Carena [m]"), + ("KMT", "4.20 m", "Altura Metacéntrica Transv. [m]"), + ("GMT", "1.05 m", "Altura Metacéntrica Corregida [m]"), + ("TPC", "8.2 t/cm", "Toneladas por cm de Inmersión"), + ("MCT", "42.5 t·m/cm", "Momento para Cambiar Asiento 1 cm"), + ("Cb", "0.682", "Coeficiente de Bloque"), + ("Cw", "0.821", "Coeficiente Plano Flotación"), + ("Cm", "0.985", "Coeficiente Cuaderna Maestra"), + ] + + for key, default_val, tooltip in hydro_items: + lbl_key = QLabel(f" {key} ") + lbl_key.setProperty("label", True) + lbl_key.setToolTip(tooltip) + + lbl_val = QLabel(default_val) + lbl_val.setProperty("value", True) + lbl_val.setToolTip(tooltip) + lbl_val.setMinimumWidth(72) + + self._fields[key] = lbl_val + layout.addWidget(lbl_key) + layout.addWidget(lbl_val) + + sep = self._make_sep() + layout.addWidget(sep) + + # Indicador IMO + self._imo_label = QLabel(" ⚠ IMO — ") + self._imo_label.setProperty("label", True) + self._imo_status = QLabel("SIN DATOS") + self._imo_status.setToolTip("Cumplimiento IMO IS Code 2008. Activo cuando haya un caso de carga calculado.") + layout.addWidget(self._imo_label) + layout.addWidget(self._imo_status) + layout.addStretch() + + @staticmethod + def _make_sep() -> QFrame: + sep = QFrame() + sep.setFrameShape(QFrame.Shape.VLine) + sep.setFrameShadow(QFrame.Shadow.Sunken) + sep.setFixedWidth(1) + sep.setStyleSheet("QFrame { color: #3a3f4b; margin: 6px 4px; }") + return sep + + def update_values(self, values: dict[str, str]) -> None: + """Actualiza los valores del panel. Llamar desde el motor de cálculo.""" + for key, val in values.items(): + if key in self._fields: + self._fields[key].setText(val) + + def set_imo_status(self, ok: bool, detail: str = "") -> None: + if ok: + self._imo_status.setText("✅ CUMPLE") + self._imo_status.setProperty("imo_ok", True) + self._imo_status.setProperty("imo_fail", False) + else: + self._imo_status.setText(f"❌ FALLA {detail}") + self._imo_status.setProperty("imo_ok", False) + self._imo_status.setProperty("imo_fail", True) + self._imo_status.style().polish(self._imo_status) + + +class ProjectTreePanel(QWidget): + """Panel árbol de proyecto (izquierda).""" + + item_selected = Signal(str) # nombre del ítem seleccionado + + def __init__(self, strings: dict, parent: Optional[QWidget] = None) -> None: + super().__init__(parent) + self.strings = strings + self.setObjectName("projectTree") + self.setMinimumWidth(180) + self._build_ui() + + def _build_ui(self) -> None: + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + title = QLabel(f" {self.strings.get('panel_project', 'Proyecto')}") + title.setFixedHeight(28) + title.setStyleSheet("background: #252830; color: #90caf9; font-weight: bold; padding-left: 8px;") + layout.addWidget(title) + + self.tree = QTreeWidget() + self.tree.setHeaderHidden(True) + self.tree.setIndentation(16) + self.tree.setAnimated(True) + layout.addWidget(self.tree) + + self._populate_default() + self.tree.itemClicked.connect(self._on_item_clicked) + + def _populate_default(self) -> None: + self.tree.clear() + root_items = [ + ("🚢 Buque", [ + ("📐 Casco", ["Superficie 1"]), + ("⚓ Apéndices", ["Quilla", "Timón"]), + ("🏗 Superestructura", []), + ]), + ("⛽ Tanques", ["FO 1 BR", "FO 1 ER", "FW 1", "Lastre AP"]), + ("📦 Bodegas", []), + ("📊 Casos de Carga", ["Lightship", "Salida Lleno", "Llegada Lleno", "Lastre"]), + ("⛵ Aparejo", ["Mástil Principal", "Mayor", "Génova"]), + ("⚙ Motor", ["Motor Principal", "Hélice"]), + ("🔌 Sistemas", [ + "Eléctrico", "Combustible", "Agua Dulce", + "Achique", "Lastre", "C. Incendios", "HVAC" + ]), + ("🏭 Fabricación CNC", []), + ("🧴 Moldes FRP", []), + ] + + for name, children in root_items: + parent = QTreeWidgetItem(self.tree, [name]) + parent.setExpanded(False) + self._add_children(parent, children) + + self.tree.expandToDepth(0) + + def _add_children(self, parent: QTreeWidgetItem, children: list) -> None: + for child in children: + if isinstance(child, tuple): + child_name, grandchildren = child + child_item = QTreeWidgetItem(parent, [child_name]) + self._add_children(child_item, grandchildren) + else: + QTreeWidgetItem(parent, [child]) + + def _on_item_clicked(self, item: QTreeWidgetItem, _col: int) -> None: + self.item_selected.emit(item.text(0)) + + def set_project(self, project: Project) -> None: + """Actualiza el árbol con los datos del proyecto. Sprint 1.""" + pass # Se implementará en Sprint 1 + + +class PropertiesPanel(QWidget): + """Panel de propiedades del ítem seleccionado (derecha).""" + + def __init__(self, strings: dict, parent: Optional[QWidget] = None) -> None: + super().__init__(parent) + self.strings = strings + self.setObjectName("propertiesPanel") + self.setMinimumWidth(200) + self._build_ui() + + def _build_ui(self) -> None: + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + title = QLabel(f" {self.strings.get('panel_properties', 'Propiedades')}") + title.setFixedHeight(28) + title.setStyleSheet("background: #252830; color: #90caf9; font-weight: bold; padding-left: 8px;") + layout.addWidget(title) + + # Placeholder con dimensiones principales + content = QWidget() + content_layout = QVBoxLayout(content) + content_layout.setContentsMargins(10, 10, 10, 10) + content_layout.setSpacing(6) + + props = [ + ("LOA", "— m"), + ("LPP", "— m"), + ("B", "— m"), + ("T", "— m"), + ("D", "— m"), + ("Δ", "— t"), + ("GMT", "— m"), + ] + + mono_font = QFont("Consolas", 11) + for label, value in props: + row = QHBoxLayout() + lbl = QLabel(label) + lbl.setProperty("muted", True) + lbl.setFixedWidth(50) + val = QLabel(value) + val.setFont(mono_font) + row.addWidget(lbl) + row.addWidget(val) + row.addStretch() + content_layout.addLayout(row) + + content_layout.addStretch() + layout.addWidget(content) + + +class CentralTabsWidget(QWidget): + """ + Widget central con tabs de vistas. + + En Sprint 0 muestra placeholders. + Los viewers reales (3D, líneas, etc.) se implementan en Sprint 1. + """ + + def __init__(self, strings: dict, parent: Optional[QWidget] = None) -> None: + super().__init__(parent) + self.strings = strings + self._build_ui() + + def _build_ui(self) -> None: + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + + self.tabs = QTabWidget() + self.tabs.setTabPosition(QTabWidget.TabPosition.North) + + tab_names = [ + ("tab_3d", "3D"), + ("tab_lines", "Líneas"), + ("tab_offsets", "Offsets"), + ("tab_curves", "Curvas Hidrost."), + ("tab_tanks", "Tanques"), + ("tab_capacity", "Capacidad"), + ("tab_stability", "Estabilidad GZ"), + ("tab_resistance", "Resistencia"), + ("tab_propulsion", "Propulsión"), + ("tab_vpp", "VPP Velero"), + ("tab_seakeeping", "Movimientos"), + ("tab_electrical", "Eléctrico"), + ("tab_fuel", "Combustible"), + ("tab_freshwater", "Agua Dulce"), + ("tab_bilge", "Achique"), + ("tab_firefighting", "C. Incendios"), + ("tab_hvac", "HVAC"), + ("tab_scantling", "Escantillado"), + ("tab_fabrication", "Fabricación CNC"), + ("tab_molds", "Moldes FRP"), + ("tab_report", "Reporte"), + ] + + for key, default_name in tab_names: + name = self.strings.get(key, default_name) + placeholder = self._make_placeholder(name) + self.tabs.addTab(placeholder, name) + + layout.addWidget(self.tabs) + + @staticmethod + def _make_placeholder(tab_name: str) -> QWidget: + w = QWidget() + layout = QVBoxLayout(w) + layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + + icon_label = QLabel("🔜") + icon_label.setFont(QFont("Segoe UI Emoji", 48)) + icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + + msg = QLabel(f"Módulo: {tab_name}\nSe implementará en el sprint correspondiente.") + msg.setAlignment(Qt.AlignmentFlag.AlignCenter) + msg.setProperty("muted", True) + + layout.addWidget(icon_label) + layout.addWidget(msg) + return w + + +class NewProjectDialog(QMessageBox): + """Dialog simple de nuevo proyecto para Sprint 0.""" + pass + + +class MainWindow(QMainWindow): + """ + Ventana principal de AR-ShipDesign. + + Implementa el layout DELFTship-style con: + - Menú bar completo + - Toolbar principal + - Panel árbol de proyecto (izquierda, dock) + - Vista central con tabs de módulos + - Panel de propiedades (derecha, dock) + - Panel de hidrostáticos en vivo (inferior, fijo) + - Barra de estado + """ + + def __init__(self) -> None: + super().__init__() + self._project: Optional[Project] = None + self._lang = get_language() + self._strings = _load_i18n(self._lang) + self._setup_ui() + self._setup_menu() + self._setup_toolbar() + self._setup_status_bar() + self._restore_geometry() + self._update_title() + logger.info("MainWindow inicializada") + + # ────────────────────────────────────────────── + # SETUP UI + # ────────────────────────────────────────────── + + def _setup_ui(self) -> None: + self.setMinimumSize(1100, 700) + + # Panel árbol proyecto (dock izquierda) + self._project_tree = ProjectTreePanel(self._strings) + dock_tree = QDockWidget(self._strings.get("panel_project", "Proyecto"), self) + dock_tree.setObjectName("dockProjectTree") + dock_tree.setWidget(self._project_tree) + dock_tree.setFeatures( + QDockWidget.DockWidgetFeature.DockWidgetMovable | + QDockWidget.DockWidgetFeature.DockWidgetFloatable + ) + dock_tree.setMinimumWidth(200) + self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, dock_tree) + + # Panel propiedades (dock derecha) + self._properties_panel = PropertiesPanel(self._strings) + dock_props = QDockWidget(self._strings.get("panel_properties", "Propiedades"), self) + dock_props.setObjectName("dockProperties") + dock_props.setWidget(self._properties_panel) + dock_props.setFeatures( + QDockWidget.DockWidgetFeature.DockWidgetMovable | + QDockWidget.DockWidgetFeature.DockWidgetFloatable + ) + dock_props.setMinimumWidth(220) + self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, dock_props) + + # Widget central con tabs + self._central_tabs = CentralTabsWidget(self._strings) + self.setCentralWidget(self._central_tabs) + + # Panel hidrostáticos (dock inferior, no ocultable) + self._hydro_panel = HydrostaticsPanel(self._strings) + dock_hydro = QDockWidget(self._strings.get("panel_hydrostatics", "Hidrostáticos"), self) + dock_hydro.setObjectName("dockHydrostatics") + dock_hydro.setWidget(self._hydro_panel) + dock_hydro.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures) + dock_hydro.setTitleBarWidget(QWidget()) # Oculta la barra de título del dock + self.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, dock_hydro) + + # Conectar señales + self._project_tree.item_selected.connect(self._on_tree_item_selected) + + def _setup_menu(self) -> None: + s = self._strings + menubar = self.menuBar() + + # ── ARCHIVO ── + menu_file = menubar.addMenu(s.get("menu_file", "Archivo")) + + act_new = QAction(s.get("file_new", "Nuevo proyecto"), self) + act_new.setShortcut(QKeySequence.StandardKey.New) + act_new.triggered.connect(self._on_new_project) + menu_file.addAction(act_new) + + act_open = QAction(s.get("file_open", "Abrir..."), self) + act_open.setShortcut(QKeySequence.StandardKey.Open) + act_open.triggered.connect(self._on_open_project) + menu_file.addAction(act_open) + + menu_file.addSeparator() + + act_save = QAction(s.get("file_save", "Guardar"), self) + act_save.setShortcut(QKeySequence.StandardKey.Save) + act_save.triggered.connect(self._on_save_project) + menu_file.addAction(act_save) + + act_save_as = QAction(s.get("file_save_as", "Guardar como..."), self) + act_save_as.setShortcut(QKeySequence("Ctrl+Shift+S")) + act_save_as.triggered.connect(self._on_save_as_project) + menu_file.addAction(act_save_as) + + menu_file.addSeparator() + + # Recientes + self._recent_menu = menu_file.addMenu(s.get("file_recent", "Recientes")) + self._update_recent_menu() + + menu_file.addSeparator() + + act_exit = QAction(s.get("file_exit", "Salir"), self) + act_exit.setShortcut(QKeySequence("Alt+F4")) + act_exit.triggered.connect(self.close) + menu_file.addAction(act_exit) + + # ── EDITAR ── + menu_edit = menubar.addMenu(s.get("menu_edit", "Editar")) + + act_undo = QAction(s.get("edit_undo", "Deshacer"), self) + act_undo.setShortcut(QKeySequence.StandardKey.Undo) + act_undo.setEnabled(False) # Sprint 1 + menu_edit.addAction(act_undo) + + act_redo = QAction(s.get("edit_redo", "Rehacer"), self) + act_redo.setShortcut(QKeySequence.StandardKey.Redo) + act_redo.setEnabled(False) # Sprint 1 + menu_edit.addAction(act_redo) + + menu_edit.addSeparator() + + act_prefs = QAction(s.get("edit_preferences", "Preferencias..."), self) + act_prefs.triggered.connect(self._on_preferences) + menu_edit.addAction(act_prefs) + + # ── VER ── + menu_view = menubar.addMenu(s.get("menu_view", "Ver")) + act_theme = QAction("Cambiar tema (claro/oscuro)", self) + act_theme.triggered.connect(self._on_toggle_theme) + menu_view.addAction(act_theme) + + # ── MODELO ── + menu_model = menubar.addMenu(s.get("menu_model", "Modelo")) + menu_model.addAction("Nuevo casco... (Sprint 1)") + menu_model.addAction("Wizard de embarcación... (Sprint 1)") + menu_model.addAction("Importar offsets... (Sprint 1)") + menu_model.addAction("Importar DXF... (Sprint 1)") + + # ── ANÁLISIS ── + menu_analysis = menubar.addMenu(s.get("menu_analysis", "Análisis")) + menu_analysis.addAction("Hidrostáticos (Sprint 2)") + menu_analysis.addAction("Estabilidad GZ (Sprint 3)") + menu_analysis.addAction("Escantillado ISO 12215 (Sprint 2.5)") + menu_analysis.addAction("Resistencia y Propulsión (Sprint 5)") + menu_analysis.addAction("VPP Velero (Sprint 6)") + menu_analysis.addAction("Movimientos / Seakeeping (Sprint 9)") + + # ── SISTEMAS ── + menu_systems = menubar.addMenu(s.get("menu_systems", "Sistemas")) + for sys_name in ["Eléctrico (Sprint 7)", "Combustible (Sprint 7)", + "Agua Dulce (Sprint 7)", "Achique (Sprint 7)", + "Lastre (Sprint 7)", "C. Incendios (Sprint 8)", + "HVAC (Sprint 8)", "Gobierno (Sprint 8)"]: + menu_systems.addAction(sys_name) + + # ── FABRICACIÓN ── + menu_fab = menubar.addMenu(s.get("menu_fabrication", "Fabricación")) + menu_fab.addAction("Estimación de material (Sprint 13)") + menu_fab.addAction("Nesting / Optimización de cortes (Sprint 13)") + menu_fab.addAction("Generar G-code CNC (Sprint 13)") + menu_fab.addSeparator() + menu_fab.addAction("Moldes FRP — Lofting (Sprint 13B)") + menu_fab.addAction("Moldes FRP — Schedule laminado (Sprint 13B)") + menu_fab.addAction("Moldes FRP — BOM materiales (Sprint 13B)") + + # ── REPORTES ── + menu_reports = menubar.addMenu(s.get("menu_reports", "Reportes")) + menu_reports.addAction("Reporte Hidrostático (Sprint 10)") + menu_reports.addAction("Cuaderno de Estabilidad (Sprint 10)") + menu_reports.addAction("Plano de Líneas (Sprint 10)") + menu_reports.addAction("Reporte Escantillado (Sprint 10)") + menu_reports.addAction("Balance Eléctrico (Sprint 10)") + + # ── AYUDA ── + menu_help = menubar.addMenu(s.get("menu_help", "Ayuda")) + act_about = QAction(s.get("about_title", "Acerca de..."), self) + act_about.triggered.connect(self._on_about) + menu_help.addAction(act_about) + + def _setup_toolbar(self) -> None: + tb = QToolBar("Principal", self) + tb.setObjectName("mainToolbar") + tb.setMovable(False) + self.addToolBar(tb) + + buttons = [ + ("🗎", "Nuevo proyecto (Ctrl+N)", self._on_new_project), + ("📂", "Abrir proyecto (Ctrl+O)", self._on_open_project), + ("💾", "Guardar (Ctrl+S)", self._on_save_project), + ] + for icon_text, tip, slot in buttons: + btn = tb.addAction(icon_text) + btn.setToolTip(tip) + btn.triggered.connect(slot) + + tb.addSeparator() + btn_undo = tb.addAction("↩") + btn_undo.setToolTip("Deshacer (Ctrl+Z)") + btn_undo.setEnabled(False) + + btn_redo = tb.addAction("↪") + btn_redo.setToolTip("Rehacer (Ctrl+Y)") + btn_redo.setEnabled(False) + + tb.addSeparator() + btn_wizard = tb.addAction("🚢 Wizard") + btn_wizard.setToolTip("Wizard de nueva embarcación (Sprint 1)") + btn_wizard.setEnabled(False) + + tb.addSeparator() + btn_theme = tb.addAction("☀/🌙") + btn_theme.setToolTip("Cambiar tema claro/oscuro") + btn_theme.triggered.connect(self._on_toggle_theme) + + # Separador flexible + spacer = QWidget() + spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + tb.addWidget(spacer) + + # Selector de unidades + tb.addWidget(QLabel(" Unidades: ")) + self._units_label = QLabel("SI") + self._units_label.setStyleSheet("color: #90caf9; font-weight: bold; margin-right: 8px;") + tb.addWidget(self._units_label) + + # Idioma + tb.addWidget(QLabel(" 🌍 ")) + self._lang_label = QLabel(self._lang.upper()) + self._lang_label.setStyleSheet("color: #90caf9; font-weight: bold; margin-right: 8px;") + tb.addWidget(self._lang_label) + + def _setup_status_bar(self) -> None: + sb = self.statusBar() + sb.showMessage(self._strings.get("status_ready", "Listo")) + + self._status_version = QLabel(f" AR-ShipDesign v{__version__} ") + sb.addPermanentWidget(self._status_version) + + # ────────────────────────────────────────────── + # ACCIONES DE PROYECTO + # ────────────────────────────────────────────── + + def _on_new_project(self) -> None: + if self._project and self._project.is_modified: + if not self._ask_save_changes(): + return + self._project = Project.new("Proyecto sin título") + self._on_project_loaded() + self.statusBar().showMessage("Nuevo proyecto creado") + + def _on_open_project(self) -> None: + if self._project and self._project.is_modified: + if not self._ask_save_changes(): + return + + path, _ = QFileDialog.getOpenFileName( + self, + "Abrir proyecto AR-ShipDesign", + str(Path.home()), + "Proyectos AR-ShipDesign (*.arsd);;Todos los archivos (*)", + ) + if not path: + return + + try: + self._project = Project.load(Path(path)) + add_recent_file(path) + self._update_recent_menu() + self._on_project_loaded() + self.statusBar().showMessage(f"Proyecto abierto: {path}") + except Exception as e: + QMessageBox.critical(self, "Error al abrir", f"No se pudo abrir el proyecto:\n{e}") + logger.error("Error abriendo proyecto: %s", e) + + def _on_save_project(self) -> None: + if self._project is None: + return + if self._project.path is None: + self._on_save_as_project() + return + try: + self._project.save() + self._update_title() + self.statusBar().showMessage(f"Guardado: {self._project.path}") + except Exception as e: + QMessageBox.critical(self, "Error al guardar", str(e)) + + def _on_save_as_project(self) -> None: + if self._project is None: + return + path, _ = QFileDialog.getSaveFileName( + self, + "Guardar proyecto como...", + str(Path.home() / f"{self._project.name}.arsd"), + "Proyectos AR-ShipDesign (*.arsd)", + ) + if not path: + return + try: + self._project.save(Path(path)) + add_recent_file(path) + self._update_recent_menu() + self._update_title() + self.statusBar().showMessage(f"Guardado como: {path}") + except Exception as e: + QMessageBox.critical(self, "Error al guardar", str(e)) + + def _on_project_loaded(self) -> None: + """Callback cuando se carga o crea un proyecto.""" + self._update_title() + self._project_tree.set_project(self._project) + + def _ask_save_changes(self) -> bool: + """Pregunta si guardar antes de cerrar/nuevo. Retorna True si se puede continuar.""" + reply = QMessageBox.question( + self, + "Cambios sin guardar", + f"El proyecto '{self._project.name}' tiene cambios sin guardar.\n¿Desea guardar antes de continuar?", + QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Cancel, + ) + if reply == QMessageBox.StandardButton.Save: + self._on_save_project() + return True + elif reply == QMessageBox.StandardButton.Discard: + return True + return False # Cancel + + # ────────────────────────────────────────────── + # ACCIONES DE UI + # ────────────────────────────────────────────── + + def _on_tree_item_selected(self, name: str) -> None: + self.statusBar().showMessage(f"Seleccionado: {name}") + + def _on_preferences(self) -> None: + QMessageBox.information(self, "Preferencias", "Diálogo de preferencias — Sprint 1") + + def _on_toggle_theme(self) -> None: + current = get_theme() + new_theme = "light" if current == "dark" else "dark" + set_theme(new_theme) + self._apply_theme(new_theme) + + def _apply_theme(self, theme: str) -> None: + qss_path = Path(__file__).parent / "themes" / f"{theme}.qss" + try: + qss = qss_path.read_text(encoding="utf-8") + QApplication.instance().setStyleSheet(qss) + except Exception as e: + logger.warning("No se pudo aplicar el tema %s: %s", theme, e) + + def _on_about(self) -> None: + QMessageBox.about( + self, + self._strings.get("about_title", "Acerca de AR-ShipDesign"), + f"""AR-ShipDesign v{__version__}
+Software profesional de diseño naval.

+Motor geométrico: NURBS (geomdl)
+Visualización 3D: PyVista + VTK
+Estándares: ISO 12215, IMO IS Code 2008

+{self._strings.get("about_copyright", "Copyright © 2025 Álvaro Rodríguez")}""", + ) + + def _update_title(self) -> None: + if self._project: + self.setWindowTitle(f"AR-ShipDesign — {self._project.display_name}") + else: + self.setWindowTitle("AR-ShipDesign") + + def _update_recent_menu(self) -> None: + self._recent_menu.clear() + recent = get_recent_files() + if not recent: + act = QAction("(sin archivos recientes)", self) + act.setEnabled(False) + self._recent_menu.addAction(act) + return + for path in recent: + act = QAction(Path(path).name, self) + act.setToolTip(path) + act.triggered.connect(lambda checked=False, p=path: self._open_recent(p)) + self._recent_menu.addAction(act) + + def _open_recent(self, path: str) -> None: + if self._project and self._project.is_modified: + if not self._ask_save_changes(): + return + try: + self._project = Project.load(Path(path)) + add_recent_file(path) + self._on_project_loaded() + except Exception as e: + QMessageBox.critical(self, "Error", f"No se pudo abrir:\n{e}") + + # ────────────────────────────────────────────── + # GEOMETRÍA Y ESTADO + # ────────────────────────────────────────────── + + def _restore_geometry(self) -> None: + s = get_settings() + geom = s.value("ui/windowGeometry") + state = s.value("ui/windowState") + if geom: + self.restoreGeometry(geom) + else: + self.resize(1280, 800) + self.showMaximized() + if state: + self.restoreState(state) + + def closeEvent(self, event) -> None: + if self._project and self._project.is_modified: + if not self._ask_save_changes(): + event.ignore() + return + s = get_settings() + s.setValue("ui/windowGeometry", self.saveGeometry()) + s.setValue("ui/windowState", self.saveState()) + event.accept() diff --git a/arshipdesign/ui/shortcuts.py b/arshipdesign/ui/shortcuts.py new file mode 100644 index 0000000..4fb548d --- /dev/null +++ b/arshipdesign/ui/shortcuts.py @@ -0,0 +1 @@ +# Atajos de teclado — Stub Sprint 1 diff --git a/arshipdesign/ui/themes/dark.qss b/arshipdesign/ui/themes/dark.qss new file mode 100644 index 0000000..43f7948 --- /dev/null +++ b/arshipdesign/ui/themes/dark.qss @@ -0,0 +1,162 @@ +/* AR-ShipDesign — Tema Oscuro */ + +QMainWindow, QDialog, QWidget { + background-color: #1e2128; + color: #e0e0e0; + font-family: "Segoe UI", Arial, sans-serif; + font-size: 13px; +} + +QMenuBar { + background-color: #252830; + color: #e0e0e0; + border-bottom: 1px solid #3a3f4b; +} +QMenuBar::item:selected { background-color: #3a4050; } + +QMenu { + background-color: #2c313a; + color: #e0e0e0; + border: 1px solid #3a3f4b; +} +QMenu::item:selected { background-color: #1565c0; } +QMenu::separator { height: 1px; background: #3a3f4b; } + +QToolBar { + background-color: #252830; + border-bottom: 1px solid #3a3f4b; + spacing: 4px; + padding: 2px; +} +QToolButton { + background: transparent; + border: 1px solid transparent; + border-radius: 3px; + padding: 3px; + color: #e0e0e0; +} +QToolButton:hover { background-color: #3a4050; border-color: #5a6070; } +QToolButton:pressed { background-color: #1565c0; } + +QSplitter::handle { background-color: #3a3f4b; } +QSplitter::handle:horizontal { width: 3px; } +QSplitter::handle:vertical { height: 3px; } + +QTreeWidget, QTreeView, QListView, QTableView, QTableWidget { + background-color: #1e2128; + alternate-background-color: #242830; + color: #e0e0e0; + border: 1px solid #3a3f4b; + gridline-color: #3a3f4b; + selection-background-color: #1565c0; + selection-color: #ffffff; +} +QTreeWidget::item:hover, QTreeView::item:hover { background-color: #2a2f3a; } +QHeaderView::section { + background-color: #2c313a; + color: #b0b8c8; + border: 1px solid #3a3f4b; + padding: 4px 6px; + font-weight: bold; +} + +QTabWidget::pane { + border: 1px solid #3a3f4b; + background-color: #1e2128; +} +QTabBar::tab { + background-color: #252830; + color: #a0a8b8; + border: 1px solid #3a3f4b; + border-bottom: none; + padding: 5px 10px; + margin-right: 1px; +} +QTabBar::tab:selected { + background-color: #1e2128; + color: #ffffff; + border-bottom: 2px solid #1976d2; +} +QTabBar::tab:hover { background-color: #2c313a; color: #e0e0e0; } + +QScrollBar:vertical { + background-color: #1e2128; + width: 10px; +} +QScrollBar::handle:vertical { + background-color: #4a4f5b; + border-radius: 4px; + min-height: 20px; +} +QScrollBar::handle:vertical:hover { background-color: #5a6070; } +QScrollBar:horizontal { + background-color: #1e2128; + height: 10px; +} +QScrollBar::handle:horizontal { + background-color: #4a4f5b; + border-radius: 4px; + min-width: 20px; +} + +QPushButton { + background-color: #1976d2; + color: #ffffff; + border: none; + border-radius: 4px; + padding: 6px 14px; + font-weight: 500; +} +QPushButton:hover { background-color: #1e88e5; } +QPushButton:pressed { background-color: #1565c0; } +QPushButton:disabled { background-color: #3a3f4b; color: #6a7080; } + +QPushButton[secondary="true"] { + background-color: transparent; + color: #90caf9; + border: 1px solid #1976d2; +} +QPushButton[secondary="true"]:hover { background-color: #1a2a3a; } + +QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox { + background-color: #2c313a; + color: #e0e0e0; + border: 1px solid #4a4f5b; + border-radius: 3px; + padding: 4px 6px; +} +QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QComboBox:focus { + border-color: #1976d2; +} + +QComboBox::drop-down { border: none; } +QComboBox::down-arrow { image: none; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid #a0a8b8; margin-right: 6px; } +QComboBox QAbstractItemView { background-color: #2c313a; color: #e0e0e0; selection-background-color: #1565c0; } + +QLabel { color: #e0e0e0; } +QLabel[heading="true"] { font-size: 14px; font-weight: bold; color: #90caf9; } +QLabel[muted="true"] { color: #7a8090; } + +QStatusBar { + background-color: #1565c0; + color: #ffffff; + font-size: 12px; +} +QStatusBar::item { border: none; } + +/* Panel Hidrostáticos — siempre visible abajo */ +#hydrostaticsPanel { + background-color: #151a22; + border-top: 2px solid #1976d2; +} +#hydrostaticsPanel QLabel { font-family: "Consolas", monospace; font-size: 12px; } +#hydrostaticsPanel QLabel[value="true"] { color: #80deea; font-weight: bold; } +#hydrostaticsPanel QLabel[label="true"] { color: #7a8090; } +#hydrostaticsPanel QLabel[imo_ok="true"] { color: #66bb6a; font-weight: bold; } +#hydrostaticsPanel QLabel[imo_fail="true"] { color: #ef5350; font-weight: bold; } + +/* Panel Árbol de Proyecto */ +#projectTree { border-right: 1px solid #3a3f4b; } + +/* Panel Propiedades */ +#propertiesPanel { border-left: 1px solid #3a3f4b; } diff --git a/arshipdesign/ui/themes/light.qss b/arshipdesign/ui/themes/light.qss new file mode 100644 index 0000000..0592dfa --- /dev/null +++ b/arshipdesign/ui/themes/light.qss @@ -0,0 +1,49 @@ +/* AR-ShipDesign — Tema Claro */ + +QMainWindow, QDialog, QWidget { + background-color: #f5f5f5; + color: #212121; + font-family: "Segoe UI", Arial, sans-serif; + font-size: 13px; +} + +QMenuBar { background-color: #ffffff; color: #212121; border-bottom: 1px solid #e0e0e0; } +QMenuBar::item:selected { background-color: #e3f2fd; } +QMenu { background-color: #ffffff; color: #212121; border: 1px solid #e0e0e0; } +QMenu::item:selected { background-color: #1976d2; color: #ffffff; } + +QToolBar { background-color: #ffffff; border-bottom: 1px solid #e0e0e0; } +QToolButton { background: transparent; border: 1px solid transparent; border-radius: 3px; padding: 3px; } +QToolButton:hover { background-color: #e3f2fd; } +QToolButton:pressed { background-color: #1976d2; color: #ffffff; } + +QTreeWidget, QTreeView, QListView, QTableView { + background-color: #ffffff; + alternate-background-color: #f8f9fa; + color: #212121; + border: 1px solid #e0e0e0; + selection-background-color: #1976d2; + selection-color: #ffffff; +} +QHeaderView::section { background-color: #f5f5f5; color: #555; border: 1px solid #e0e0e0; padding: 4px 6px; } + +QTabWidget::pane { border: 1px solid #e0e0e0; background-color: #ffffff; } +QTabBar::tab { background-color: #f5f5f5; color: #666; border: 1px solid #e0e0e0; border-bottom: none; padding: 5px 10px; } +QTabBar::tab:selected { background-color: #ffffff; color: #212121; border-bottom: 2px solid #1976d2; } + +QPushButton { background-color: #1976d2; color: #ffffff; border: none; border-radius: 4px; padding: 6px 14px; } +QPushButton:hover { background-color: #1e88e5; } +QPushButton:pressed { background-color: #1565c0; } + +QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox { background-color: #ffffff; color: #212121; border: 1px solid #bdbdbd; border-radius: 3px; padding: 4px 6px; } +QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus { border-color: #1976d2; } + +QStatusBar { background-color: #1976d2; color: #ffffff; } + +#hydrostaticsPanel { + background-color: #e8f4fd; + border-top: 2px solid #1976d2; +} +#hydrostaticsPanel QLabel[value="true"] { color: #0277bd; font-weight: bold; } +#hydrostaticsPanel QLabel[imo_ok="true"] { color: #2e7d32; font-weight: bold; } +#hydrostaticsPanel QLabel[imo_fail="true"] { color: #c62828; font-weight: bold; } diff --git a/arshipdesign/ui/widgets/__init__.py b/arshipdesign/ui/widgets/__init__.py new file mode 100644 index 0000000..0c3b4f6 --- /dev/null +++ b/arshipdesign/ui/widgets/__init__.py @@ -0,0 +1 @@ +# ui/widgets diff --git a/arshipdesign/ui/widgets/curve_plotter.py b/arshipdesign/ui/widgets/curve_plotter.py new file mode 100644 index 0000000..ae6ee5d --- /dev/null +++ b/arshipdesign/ui/widgets/curve_plotter.py @@ -0,0 +1,2 @@ +"""Graficador curvas pyqtgraph. Stub — Sprint 3.""" +raise NotImplementedError("curve_plotter — Sprint 3") diff --git a/arshipdesign/ui/widgets/electrical_load_table.py b/arshipdesign/ui/widgets/electrical_load_table.py new file mode 100644 index 0000000..06e5789 --- /dev/null +++ b/arshipdesign/ui/widgets/electrical_load_table.py @@ -0,0 +1,2 @@ +"""Tabla EPLA. Stub — Sprint 7.""" +raise NotImplementedError("electrical_load_table — Sprint 7") diff --git a/arshipdesign/ui/widgets/engine_picker.py b/arshipdesign/ui/widgets/engine_picker.py new file mode 100644 index 0000000..3bc673f --- /dev/null +++ b/arshipdesign/ui/widgets/engine_picker.py @@ -0,0 +1,2 @@ +"""Selector motor. Stub — Sprint 5.""" +raise NotImplementedError("engine_picker — Sprint 5") diff --git a/arshipdesign/ui/widgets/loadcase_editor.py b/arshipdesign/ui/widgets/loadcase_editor.py new file mode 100644 index 0000000..780849f --- /dev/null +++ b/arshipdesign/ui/widgets/loadcase_editor.py @@ -0,0 +1,2 @@ +"""Editor casos carga. Stub — Sprint 4.""" +raise NotImplementedError("loadcase_editor — Sprint 4") diff --git a/arshipdesign/ui/widgets/offsets_editor.py b/arshipdesign/ui/widgets/offsets_editor.py new file mode 100644 index 0000000..dd56c16 --- /dev/null +++ b/arshipdesign/ui/widgets/offsets_editor.py @@ -0,0 +1,2 @@ +"""Editor offsets. Stub — Sprint 1.""" +raise NotImplementedError("offsets_editor — Sprint 1") diff --git a/arshipdesign/ui/widgets/propeller_designer.py b/arshipdesign/ui/widgets/propeller_designer.py new file mode 100644 index 0000000..cd4f88e --- /dev/null +++ b/arshipdesign/ui/widgets/propeller_designer.py @@ -0,0 +1,2 @@ +"""Diseñador hélice. Stub — Sprint 5.""" +raise NotImplementedError("propeller_designer — Sprint 5") diff --git a/arshipdesign/ui/widgets/rig_editor.py b/arshipdesign/ui/widgets/rig_editor.py new file mode 100644 index 0000000..43e112c --- /dev/null +++ b/arshipdesign/ui/widgets/rig_editor.py @@ -0,0 +1,2 @@ +"""Editor aparejo. Stub — Sprint 6.""" +raise NotImplementedError("rig_editor — Sprint 6") diff --git a/arshipdesign/ui/widgets/system_diagram_canvas.py b/arshipdesign/ui/widgets/system_diagram_canvas.py new file mode 100644 index 0000000..62cd1db --- /dev/null +++ b/arshipdesign/ui/widgets/system_diagram_canvas.py @@ -0,0 +1,2 @@ +"""Canvas P&ID. Stub — Sprint 7.""" +raise NotImplementedError("system_diagram_canvas — Sprint 7") diff --git a/arshipdesign/ui/widgets/tank_editor.py b/arshipdesign/ui/widgets/tank_editor.py new file mode 100644 index 0000000..bfb12c5 --- /dev/null +++ b/arshipdesign/ui/widgets/tank_editor.py @@ -0,0 +1,2 @@ +"""Editor tanques. Stub — Sprint 4.""" +raise NotImplementedError("tank_editor — Sprint 4") diff --git a/arshipdesign/ui/widgets/viewer_3d.py b/arshipdesign/ui/widgets/viewer_3d.py new file mode 100644 index 0000000..b055e57 --- /dev/null +++ b/arshipdesign/ui/widgets/viewer_3d.py @@ -0,0 +1,2 @@ +"""Vista 3D PyVista. Stub — Sprint 1.""" +raise NotImplementedError("viewer_3d — Sprint 1") diff --git a/arshipdesign/ui/widgets/viewer_lines.py b/arshipdesign/ui/widgets/viewer_lines.py new file mode 100644 index 0000000..3799903 --- /dev/null +++ b/arshipdesign/ui/widgets/viewer_lines.py @@ -0,0 +1,2 @@ +"""Plano líneas 2D. Stub — Sprint 1.""" +raise NotImplementedError("viewer_lines — Sprint 1") diff --git a/arshipdesign/ui/widgets/viewer_polar.py b/arshipdesign/ui/widgets/viewer_polar.py new file mode 100644 index 0000000..e1c8548 --- /dev/null +++ b/arshipdesign/ui/widgets/viewer_polar.py @@ -0,0 +1,2 @@ +"""Diagrama polar. Stub — Sprint 6.""" +raise NotImplementedError("viewer_polar — Sprint 6") diff --git a/arshipdesign/utils/__init__.py b/arshipdesign/utils/__init__.py new file mode 100644 index 0000000..4a1c88c --- /dev/null +++ b/arshipdesign/utils/__init__.py @@ -0,0 +1 @@ +# arshipdesign/utils diff --git a/arshipdesign/utils/async_runner.py b/arshipdesign/utils/async_runner.py new file mode 100644 index 0000000..d644135 --- /dev/null +++ b/arshipdesign/utils/async_runner.py @@ -0,0 +1,20 @@ +""" +Runner asíncrono para cálculos pesados usando QThreadPool. + +Evita congelar la UI durante cálculos largos (>200ms). +Stub — Implementación en Sprint 2. +""" + +from __future__ import annotations +from typing import Callable, Any + + +class AsyncRunner: + """ + Stub. Implementación en Sprint 2. + + Ejecutará tareas en QThreadPool y emitirá señales Qt con el resultado. + """ + + def run(self, func: Callable, *args: Any, callback: Callable | None = None) -> None: + raise NotImplementedError("AsyncRunner — Implementación en Sprint 2") diff --git a/arshipdesign/utils/logger.py b/arshipdesign/utils/logger.py new file mode 100644 index 0000000..6df32a4 --- /dev/null +++ b/arshipdesign/utils/logger.py @@ -0,0 +1,67 @@ +""" +Configuración de logging para AR-ShipDesign. + +Logs rotativos en %APPDATA%/ARShipDesign/logs/ +""" + +import logging +import os +from logging.handlers import RotatingFileHandler +from pathlib import Path + + +def get_log_dir() -> Path: + """Retorna el directorio de logs en %APPDATA%.""" + appdata = os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming") + log_dir = Path(appdata) / "ARShipDesign" / "logs" + log_dir.mkdir(parents=True, exist_ok=True) + return log_dir + + +def setup_logging(level: str = "INFO") -> logging.Logger: + """ + Configura el sistema de logging de la aplicación. + + Parameters + ---------- + level : str + Nivel de logging: DEBUG, INFO, WARNING, ERROR + + Returns + ------- + logging.Logger + Logger raíz de la aplicación. + """ + numeric_level = getattr(logging, level.upper(), logging.INFO) + + log_file = get_log_dir() / "arshipdesign.log" + + formatter = logging.Formatter( + fmt="%(asctime)s [%(levelname)-8s] %(name)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + # Handler de archivo rotativo (5 MB × 3 archivos) + file_handler = RotatingFileHandler( + log_file, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8" + ) + file_handler.setFormatter(formatter) + file_handler.setLevel(numeric_level) + + # Handler de consola + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + console_handler.setLevel(numeric_level) + + root_logger = logging.getLogger("arshipdesign") + root_logger.setLevel(numeric_level) + root_logger.addHandler(file_handler) + root_logger.addHandler(console_handler) + + root_logger.info("AR-ShipDesign logging iniciado — nivel: %s", level) + return root_logger + + +def get_logger(name: str) -> logging.Logger: + """Retorna un logger hijo del logger raíz de la app.""" + return logging.getLogger(f"arshipdesign.{name}") diff --git a/arshipdesign/utils/settings.py b/arshipdesign/utils/settings.py new file mode 100644 index 0000000..66dc946 --- /dev/null +++ b/arshipdesign/utils/settings.py @@ -0,0 +1,89 @@ +""" +Gestión de configuración de usuario con QSettings. + +Persiste preferencias: idioma, tema, unidades, archivos recientes, etc. +""" + +from __future__ import annotations + +from PySide6.QtCore import QSettings + +APP_NAME = "ARShipDesign" +ORG_NAME = "AlvaroRodriguez" + +# Claves de configuración +KEY_LANGUAGE = "ui/language" +KEY_THEME = "ui/theme" +KEY_UNITS = "ui/units" +KEY_RECENT_FILES = "project/recentFiles" +KEY_MAX_RECENT = "project/maxRecentFiles" +KEY_WINDOW_GEOMETRY = "ui/windowGeometry" +KEY_WINDOW_STATE = "ui/windowState" +KEY_HYDRO_DENSITY = "calc/waterDensity" +KEY_LOG_LEVEL = "system/logLevel" + + +def get_settings() -> QSettings: + """Retorna la instancia de QSettings de la aplicación.""" + return QSettings(ORG_NAME, APP_NAME) + + +def get_language() -> str: + s = get_settings() + return s.value(KEY_LANGUAGE, "es") + + +def set_language(lang: str) -> None: + s = get_settings() + s.setValue(KEY_LANGUAGE, lang) + + +def get_theme() -> str: + s = get_settings() + return s.value(KEY_THEME, "dark") + + +def set_theme(theme: str) -> None: + s = get_settings() + s.setValue(KEY_THEME, theme) + + +def get_units() -> str: + """'si' o 'imperial'""" + s = get_settings() + return s.value(KEY_UNITS, "si") + + +def set_units(units: str) -> None: + s = get_settings() + s.setValue(KEY_UNITS, units) + + +def get_recent_files() -> list[str]: + s = get_settings() + val = s.value(KEY_RECENT_FILES, []) + if isinstance(val, str): + return [val] + return list(val) if val else [] + + +def add_recent_file(path: str) -> None: + s = get_settings() + recent = get_recent_files() + if path in recent: + recent.remove(path) + recent.insert(0, path) + max_recent = int(s.value(KEY_MAX_RECENT, 10)) + recent = recent[:max_recent] + s.setValue(KEY_RECENT_FILES, recent) + + +def get_water_density() -> float: + """Densidad del agua de mar en kg/m³ (por defecto 1025).""" + s = get_settings() + return float(s.value(KEY_HYDRO_DENSITY, 1025.0)) + + +def get_log_level() -> str: + s = get_settings() + return s.value(KEY_LOG_LEVEL, "INFO") diff --git a/arshipdesign/utils/validation.py b/arshipdesign/utils/validation.py new file mode 100644 index 0000000..ab6b9d1 --- /dev/null +++ b/arshipdesign/utils/validation.py @@ -0,0 +1,19 @@ +""" +Utilidades de validación de datos de entrada. +""" + +from __future__ import annotations + + +def is_positive(value: float, name: str = "valor") -> float: + """Valida que un valor sea positivo. Lanza ValueError si no.""" + if value <= 0: + raise ValueError(f"{name} debe ser positivo, se recibió {value}") + return value + + +def is_in_range(value: float, low: float, high: float, name: str = "valor") -> float: + """Valida que un valor esté en el rango [low, high].""" + if not (low <= value <= high): + raise ValueError(f"{name} debe estar entre {low} y {high}, se recibió {value}") + return value diff --git a/create_stubs.py b/create_stubs.py new file mode 100644 index 0000000..59c8066 --- /dev/null +++ b/create_stubs.py @@ -0,0 +1,253 @@ +"""Script de creación de stubs — ejecutar una sola vez desde D:/AR-ShipDesign/""" +import os + +stubs = { + "arshipdesign/core/__init__.py": "# arshipdesign/core\n", + "arshipdesign/core/ship.py": '"""Clase Ship. Stub — Sprint 1."""\nclass Ship:\n def __init__(self): raise NotImplementedError("Ship — Sprint 1")\n', + "arshipdesign/core/hull.py": '"""Geometría del casco NURBS. Stub — Sprint 1."""\nraise NotImplementedError("hull — Sprint 1")\n', + "arshipdesign/core/appendage.py": '"""Apéndices. Stub — Sprint 1."""\nraise NotImplementedError("appendage — Sprint 1")\n', + "arshipdesign/core/superstructure.py": '"""Superestructura. Stub — Sprint 1."""\nraise NotImplementedError("superstructure — Sprint 1")\n', + "arshipdesign/core/offsets.py": '"""Tabla de offsets. Stub — Sprint 1."""\nraise NotImplementedError("offsets — Sprint 1")\n', + "arshipdesign/core/waterplane.py": '"""Plano de flotación. Stub — Sprint 2."""\nraise NotImplementedError("waterplane — Sprint 2")\n', + "arshipdesign/core/section.py": '"""Sección transversal. Stub — Sprint 1."""\nraise NotImplementedError("section — Sprint 1")\n', + "arshipdesign/core/compartment.py": '"""Compartimento. Stub — Sprint 4."""\nraise NotImplementedError("compartment — Sprint 4")\n', + "arshipdesign/core/tank.py": '"""Tanque. Stub — Sprint 4."""\nraise NotImplementedError("tank — Sprint 4")\n', + "arshipdesign/core/liquid.py": '"""Líquidos. Stub — Sprint 4."""\nraise NotImplementedError("liquid — Sprint 4")\n', + "arshipdesign/core/weight_item.py": '"""Peso puntual. Stub — Sprint 4."""\nraise NotImplementedError("weight_item — Sprint 4")\n', + "arshipdesign/core/lightship.py": '"""Peso en rosca. Stub — Sprint 4."""\nraise NotImplementedError("lightship — Sprint 4")\n', + "arshipdesign/core/loadcase.py": '"""Condiciones de carga. Stub — Sprint 4."""\nraise NotImplementedError("loadcase — Sprint 4")\n', + "arshipdesign/core/rig.py": '"""Aparejo velero. Stub — Sprint 6."""\nraise NotImplementedError("rig — Sprint 6")\n', + "arshipdesign/core/sail.py": '"""Velas. Stub — Sprint 6."""\nraise NotImplementedError("sail — Sprint 6")\n', + "arshipdesign/core/engine.py": '"""Motor. Stub — Sprint 5."""\nraise NotImplementedError("engine — Sprint 5")\n', + "arshipdesign/core/propeller.py": '"""Hélice. Stub — Sprint 5."""\nraise NotImplementedError("propeller — Sprint 5")\n', + "arshipdesign/core/gear.py": '"""Reductora. Stub — Sprint 5."""\nraise NotImplementedError("gear — Sprint 5")\n', + "arshipdesign/core/generator.py": '"""Genset. Stub — Sprint 7."""\nraise NotImplementedError("generator — Sprint 7")\n', + "arshipdesign/core/battery.py": '"""Baterías. Stub — Sprint 7."""\nraise NotImplementedError("battery — Sprint 7")\n', + "arshipdesign/core/electrical_load.py": '"""Carga eléctrica. Stub — Sprint 7."""\nraise NotImplementedError("electrical_load — Sprint 7")\n', + "arshipdesign/core/pipe.py": '"""Tubería. Stub — Sprint 7."""\nraise NotImplementedError("pipe — Sprint 7")\n', + "arshipdesign/core/valve.py": '"""Válvula. Stub — Sprint 7."""\nraise NotImplementedError("valve — Sprint 7")\n', + "arshipdesign/core/pump.py": '"""Bomba. Stub — Sprint 7."""\nraise NotImplementedError("pump — Sprint 7")\n', + "arshipdesign/core/system.py": '"""Sistema del buque. Stub — Sprint 7."""\nraise NotImplementedError("system — Sprint 7")\n', + "arshipdesign/geometry/__init__.py": "# arshipdesign/geometry\n", + "arshipdesign/geometry/nurbs_surface.py": '"""Superficie NURBS geomdl. Stub — Sprint 1."""\nraise NotImplementedError("nurbs_surface — Sprint 1")\n', + "arshipdesign/geometry/nurbs_curve.py": '"""Curva NURBS. Stub — Sprint 1."""\nraise NotImplementedError("nurbs_curve — Sprint 1")\n', + "arshipdesign/geometry/mesh.py": '"""Malla triangular. Stub — Sprint 1."""\nraise NotImplementedError("mesh — Sprint 1")\n', + "arshipdesign/geometry/slicer.py": '"""Corte por planos. Stub — Sprint 1."""\nraise NotImplementedError("slicer — Sprint 1")\n', + "arshipdesign/geometry/intersection.py": '"""Intersecciones. Stub — Sprint 1."""\nraise NotImplementedError("intersection — Sprint 1")\n', + "arshipdesign/geometry/fairing.py": '"""Análisis curvatura. Stub — Sprint 1."""\nraise NotImplementedError("fairing — Sprint 1")\n', + "arshipdesign/geometry/boolean.py": '"""Booleanas trimesh. Stub — Sprint 1."""\nraise NotImplementedError("boolean — Sprint 1")\n', + "arshipdesign/geometry/fitting.py": '"""Ajuste NURBS. Stub — Sprint 1."""\nraise NotImplementedError("fitting — Sprint 1")\n', + "arshipdesign/hydrostatics/__init__.py": "# arshipdesign/hydrostatics\n", + "arshipdesign/hydrostatics/integrator.py": '"""Integradores Simpson. Stub — Sprint 2."""\nraise NotImplementedError("integrator — Sprint 2")\n', + "arshipdesign/hydrostatics/upright.py": '"""Hidrostáticos vertical. Stub — Sprint 2."""\nraise NotImplementedError("upright — Sprint 2")\n', + "arshipdesign/hydrostatics/heeled.py": '"""Equilibrio escorado. Stub — Sprint 3."""\nraise NotImplementedError("heeled — Sprint 3")\n', + "arshipdesign/hydrostatics/trim.py": '"""Equilibrio asiento. Stub — Sprint 3."""\nraise NotImplementedError("trim — Sprint 3")\n', + "arshipdesign/hydrostatics/free_floating.py": '"""Equilibrio libre 3DOF. Stub — Sprint 3."""\nraise NotImplementedError("free_floating — Sprint 3")\n', + "arshipdesign/hydrostatics/coefficients.py": '"""Coeficientes de forma. Stub — Sprint 2."""\nraise NotImplementedError("coefficients — Sprint 2")\n', + "arshipdesign/hydrostatics/curves_of_form.py": '"""Curvas hidrostáticas. Stub — Sprint 2."""\nraise NotImplementedError("curves_of_form — Sprint 2")\n', + "arshipdesign/hydrostatics/bonjean.py": '"""Curvas Bonjean. Stub — Sprint 2."""\nraise NotImplementedError("bonjean — Sprint 2")\n', + "arshipdesign/stability/__init__.py": "# arshipdesign/stability\n", + "arshipdesign/stability/intact.py": '"""Estabilidad intacta GZ. Stub — Sprint 3."""\nraise NotImplementedError("intact — Sprint 3")\n', + "arshipdesign/stability/damage.py": '"""Estabilidad avería. Stub — Sprint 3."""\nraise NotImplementedError("damage — Sprint 3")\n', + "arshipdesign/stability/criteria.py": '"""Criterios IMO. Stub — Sprint 3."""\nraise NotImplementedError("criteria — Sprint 3")\n', + "arshipdesign/stability/floodable_length.py": '"""Longitud inundable. Stub — Sprint 3."""\nraise NotImplementedError("floodable_length — Sprint 3")\n', + "arshipdesign/stability/weather.py": '"""Criterio meteorológico. Stub — Sprint 3."""\nraise NotImplementedError("weather — Sprint 3")\n', + "arshipdesign/scantling/__init__.py": "# arshipdesign/scantling\n", + "arshipdesign/scantling/design_pressures.py": '"""Presiones diseño ISO 12215-5. Stub — Sprint 2.5."""\nraise NotImplementedError("design_pressures — Sprint 2.5")\n', + "arshipdesign/scantling/plating.py": '"""Espesor mínimo forros. Stub — Sprint 2.5."""\nraise NotImplementedError("plating — Sprint 2.5")\n', + "arshipdesign/scantling/frames.py": '"""Cuadernas módulo sección. Stub — Sprint 2.5."""\nraise NotImplementedError("frames — Sprint 2.5")\n', + "arshipdesign/scantling/longitudinals.py": '"""Refuerzos longitudinales. Stub — Sprint 2.5."""\nraise NotImplementedError("longitudinals — Sprint 2.5")\n', + "arshipdesign/scantling/deck_beams.py": '"""Baos cubierta. Stub — Sprint 2.5."""\nraise NotImplementedError("deck_beams — Sprint 2.5")\n', + "arshipdesign/scantling/bulkheads.py": '"""Mamparos. Stub — Sprint 2.5."""\nraise NotImplementedError("bulkheads — Sprint 2.5")\n', + "arshipdesign/scantling/keel_structure.py": '"""Quilla y varengas. Stub — Sprint 2.5."""\nraise NotImplementedError("keel_structure — Sprint 2.5")\n', + "arshipdesign/scantling/mast_step.py": '"""Paso de mástil. Stub — Sprint 2.5."""\nraise NotImplementedError("mast_step — Sprint 2.5")\n', + "arshipdesign/scantling/engine_beds.py": '"""Bancadas motor. Stub — Sprint 2.5."""\nraise NotImplementedError("engine_beds — Sprint 2.5")\n', + "arshipdesign/scantling/keel_bolts.py": '"""Pernos quilla. Stub — Sprint 2.5."""\nraise NotImplementedError("keel_bolts — Sprint 2.5")\n', + "arshipdesign/scantling/section_library.py": '"""Perfiles estructurales. Stub — Sprint 2.5."""\nraise NotImplementedError("section_library — Sprint 2.5")\n', + "arshipdesign/scantling/framing_system.py": '"""Sistema de cuadernas. Stub — Sprint 2.5."""\nraise NotImplementedError("framing_system — Sprint 2.5")\n', + "arshipdesign/scantling/optimizer.py": '"""Optimización peso/resistencia. Stub — Sprint 2.5."""\nraise NotImplementedError("optimizer — Sprint 2.5")\n', + "arshipdesign/scantling/scantling_report.py": '"""Reporte escantillado. Stub — Sprint 2.5."""\nraise NotImplementedError("scantling_report — Sprint 2.5")\n', + "arshipdesign/scantling/standards/__init__.py": "# scantling/standards\n", + "arshipdesign/scantling/standards/iso_12215_5.py": '"""ISO 12215-5. Stub — Sprint 2.5."""\nraise NotImplementedError("iso_12215_5 — Sprint 2.5")\n', + "arshipdesign/scantling/standards/iso_12215_6.py": '"""ISO 12215-6. Stub — Sprint 2.5."""\nraise NotImplementedError("iso_12215_6 — Sprint 2.5")\n', + "arshipdesign/scantling/standards/iso_12215_9.py": '"""ISO 12215-9 veleros. Stub — Sprint 2.5."""\nraise NotImplementedError("iso_12215_9 — Sprint 2.5")\n', + "arshipdesign/scantling/standards/lloyds_small_craft.py": '"""Lloyds Small Craft. Stub — Sprint 2.5."""\nraise NotImplementedError("lloyds_small_craft — Sprint 2.5")\n', + "arshipdesign/scantling/materials/__init__.py": "# scantling/materials\n", + "arshipdesign/scantling/materials/steel_structural.py": '"""Acero naval. Stub — Sprint 2.5."""\nraise NotImplementedError("steel_structural — Sprint 2.5")\n', + "arshipdesign/scantling/materials/aluminum_marine.py": '"""Aluminio marino. Stub — Sprint 2.5."""\nraise NotImplementedError("aluminum_marine — Sprint 2.5")\n', + "arshipdesign/scantling/materials/plywood_marine.py": '"""Plywood marino BS1088. Stub — Sprint 2.5."""\nraise NotImplementedError("plywood_marine — Sprint 2.5")\n', + "arshipdesign/scantling/materials/frp_laminates.py": '"""Laminados FRP. Stub — Sprint 2.5."""\nraise NotImplementedError("frp_laminates — Sprint 2.5")\n', + "arshipdesign/resistance/__init__.py": "# arshipdesign/resistance\n", + "arshipdesign/resistance/holtrop_mennen.py": '"""Holtrop Mennen 1984. Stub — Sprint 5."""\nraise NotImplementedError("holtrop_mennen — Sprint 5")\n', + "arshipdesign/resistance/savitsky.py": '"""Savitsky 1964. Stub — Sprint 5."""\nraise NotImplementedError("savitsky — Sprint 5")\n', + "arshipdesign/resistance/van_oortmerssen.py": '"""Van Oortmerssen. Stub — Sprint 5."""\nraise NotImplementedError("van_oortmerssen — Sprint 5")\n', + "arshipdesign/resistance/compton.py": '"""Compton 1986. Stub — Sprint 5."""\nraise NotImplementedError("compton — Sprint 5")\n', + "arshipdesign/resistance/dsyhs.py": '"""DSYHS veleros. Stub — Sprint 6."""\nraise NotImplementedError("dsyhs — Sprint 6")\n', + "arshipdesign/resistance/effective_power.py": '"""PE = RT x V. Stub — Sprint 5."""\nraise NotImplementedError("effective_power — Sprint 5")\n', + "arshipdesign/resistance/added_resistance.py": '"""Resistencia en olas. Stub — Sprint 9."""\nraise NotImplementedError("added_resistance — Sprint 9")\n', + "arshipdesign/propulsion/__init__.py": "# arshipdesign/propulsion\n", + "arshipdesign/propulsion/wageningen_b.py": '"""Serie B Wageningen. Stub — Sprint 5."""\nraise NotImplementedError("wageningen_b — Sprint 5")\n', + "arshipdesign/propulsion/propeller_design.py": '"""Diseño hélice. Stub — Sprint 5."""\nraise NotImplementedError("propeller_design — Sprint 5")\n', + "arshipdesign/propulsion/propeller_matching.py": '"""Matching motor-hélice. Stub — Sprint 5."""\nraise NotImplementedError("propeller_matching — Sprint 5")\n', + "arshipdesign/propulsion/cavitation.py": '"""Cavitación. Stub — Sprint 5."""\nraise NotImplementedError("cavitation — Sprint 5")\n', + "arshipdesign/propulsion/waterjet.py": '"""Waterjet. Stub — Sprint 5."""\nraise NotImplementedError("waterjet — Sprint 5")\n', + "arshipdesign/propulsion/pod.py": '"""Pod azimutal. Stub — Sprint 5."""\nraise NotImplementedError("pod — Sprint 5")\n', + "arshipdesign/propulsion/electric_drive.py": '"""Propulsión eléctrica. Stub — Sprint 5."""\nraise NotImplementedError("electric_drive — Sprint 5")\n', + "arshipdesign/fabrication/__init__.py": "# arshipdesign/fabrication\n", + "arshipdesign/fabrication/bom.py": '"""BOM lista de materiales. Stub — Sprint 13."""\nraise NotImplementedError("bom — Sprint 13")\n', + "arshipdesign/fabrication/material_estimator.py": '"""Estimación de material. Stub — Sprint 13."""\nraise NotImplementedError("material_estimator — Sprint 13")\n', + "arshipdesign/fabrication/part_splitter.py": '"""División de piezas. Stub — Sprint 13."""\nraise NotImplementedError("part_splitter — Sprint 13")\n', + "arshipdesign/fabrication/nesting.py": '"""Nesting 2D. Stub — Sprint 13."""\nraise NotImplementedError("nesting — Sprint 13")\n', + "arshipdesign/fabrication/gcode_generator.py": '"""G-code CNC plasma/router/laser. Stub — Sprint 13."""\nraise NotImplementedError("gcode_generator — Sprint 13")\n', + "arshipdesign/fabrication/sheet.py": '"""Lámina de material. Stub — Sprint 13."""\nraise NotImplementedError("sheet — Sprint 13")\n', + "arshipdesign/fabrication/construction_sequence.py": '"""Secuencia de montaje. Stub — Sprint 13."""\nraise NotImplementedError("construction_sequence — Sprint 13")\n', + "arshipdesign/fabrication/fabrication_report.py": '"""Reporte fabricación. Stub — Sprint 13."""\nraise NotImplementedError("fabrication_report — Sprint 13")\n', + "arshipdesign/fabrication/joints/__init__.py": "# fabrication/joints\n", + "arshipdesign/fabrication/joints/scarf_joint.py": '"""Scarf joint. Stub — Sprint 13."""\nraise NotImplementedError("scarf_joint — Sprint 13")\n', + "arshipdesign/fabrication/joints/butt_joint.py": '"""Butt joint. Stub — Sprint 13."""\nraise NotImplementedError("butt_joint — Sprint 13")\n', + "arshipdesign/fabrication/joints/weld_prep.py": '"""Preparación soldadura. Stub — Sprint 13."""\nraise NotImplementedError("weld_prep — Sprint 13")\n', + "arshipdesign/fabrication/joints/epoxy_bond.py": '"""Unión epóxica. Stub — Sprint 13."""\nraise NotImplementedError("epoxy_bond — Sprint 13")\n', + "arshipdesign/fabrication/details/__init__.py": "# fabrication/details\n", + "arshipdesign/fabrication/details/slot_tab.py": '"""Slots y tabs. Stub — Sprint 13."""\nraise NotImplementedError("slot_tab — Sprint 13")\n', + "arshipdesign/fabrication/details/dogbone.py": '"""Dog-bones CNC. Stub — Sprint 13."""\nraise NotImplementedError("dogbone — Sprint 13")\n', + "arshipdesign/fabrication/details/limber_holes.py": '"""Agujeros achique. Stub — Sprint 13."""\nraise NotImplementedError("limber_holes — Sprint 13")\n', + "arshipdesign/fabrication/details/lightening_holes.py": '"""Vaciados aligeramiento. Stub — Sprint 13."""\nraise NotImplementedError("lightening_holes — Sprint 13")\n', + "arshipdesign/fabrication/details/alignment_marks.py": '"""Marcas alineación. Stub — Sprint 13."""\nraise NotImplementedError("alignment_marks — Sprint 13")\n', + "arshipdesign/fabrication/details/grain_direction.py": '"""Dirección fibra plywood. Stub — Sprint 13."""\nraise NotImplementedError("grain_direction — Sprint 13")\n', + "arshipdesign/fabrication/standards/__init__.py": "# fabrication/standards\n", + "arshipdesign/fabrication/standards/iso_12215.py": '"""ISO 12215 FRP construcción. Stub — Sprint 13B."""\nraise NotImplementedError("iso_12215 fabrication — Sprint 13B")\n', + "arshipdesign/fabrication/standards/abyc.py": '"""ABYC. Stub — Sprint 13B."""\nraise NotImplementedError("abyc — Sprint 13B")\n', + "arshipdesign/fabrication/materials/__init__.py": "# fabrication/materials\n", + "arshipdesign/fabrication/molds/__init__.py": "# fabrication/molds — Moldes FRP\n", + "arshipdesign/fabrication/molds/plug.py": '"""Plug macho FRP. Stub — Sprint 13B."""\nraise NotImplementedError("plug — Sprint 13B")\n', + "arshipdesign/fabrication/molds/mold_female.py": '"""Molde hembra. Stub — Sprint 13B."""\nraise NotImplementedError("mold_female — Sprint 13B")\n', + "arshipdesign/fabrication/molds/parting_line.py": '"""Línea partición. Stub — Sprint 13B."""\nraise NotImplementedError("parting_line — Sprint 13B")\n', + "arshipdesign/fabrication/molds/station_molds.py": '"""Cuadernas del molde con offsets. Stub — Sprint 13B."""\nraise NotImplementedError("station_molds — Sprint 13B")\n', + "arshipdesign/fabrication/molds/lofting.py": '"""Lofting tabla offsets. Stub — Sprint 13B."""\nraise NotImplementedError("lofting — Sprint 13B")\n', + "arshipdesign/fabrication/molds/laminate_schedule.py": '"""Schedule laminado molde. Stub — Sprint 13B."""\nraise NotImplementedError("laminate_schedule — Sprint 13B")\n', + "arshipdesign/fabrication/molds/hull_laminate.py": '"""Schedule laminado casco FRP. Stub — Sprint 13B."""\nraise NotImplementedError("hull_laminate — Sprint 13B")\n', + "arshipdesign/fabrication/molds/resin_calculator.py": '"""Cálculo materiales FRP. Stub — Sprint 13B."""\nraise NotImplementedError("resin_calculator — Sprint 13B")\n', + "arshipdesign/fabrication/molds/mold_structure.py": '"""Estructura del molde. Stub — Sprint 13B."""\nraise NotImplementedError("mold_structure — Sprint 13B")\n', + "arshipdesign/fabrication/molds/mold_report.py": '"""Reporte molde FRP. Stub — Sprint 13B."""\nraise NotImplementedError("mold_report — Sprint 13B")\n', + "arshipdesign/io/__init__.py": "# arshipdesign/io\n", + "arshipdesign/io/offsets_csv.py": '"""Offsets CSV. Stub — Sprint 1."""\nraise NotImplementedError("offsets_csv — Sprint 1")\n', + "arshipdesign/io/offsets_excel.py": '"""Offsets Excel. Stub — Sprint 1."""\nraise NotImplementedError("offsets_excel — Sprint 1")\n', + "arshipdesign/io/dxf_reader.py": '"""Lectura DXF. Stub — Sprint 1."""\nraise NotImplementedError("dxf_reader — Sprint 1")\n', + "arshipdesign/io/dxf_writer.py": '"""Escritura DXF. Stub — Sprint 10."""\nraise NotImplementedError("dxf_writer — Sprint 10")\n', + "arshipdesign/io/stl_export.py": '"""Exportación STL. Stub — Sprint 1."""\nraise NotImplementedError("stl_export — Sprint 1")\n', + "arshipdesign/io/obj_export.py": '"""Exportación OBJ. Stub — Sprint 1."""\nraise NotImplementedError("obj_export — Sprint 1")\n', + "arshipdesign/io/pdf_export.py": '"""Exportación PDF. Stub — Sprint 10."""\nraise NotImplementedError("pdf_export — Sprint 10")\n', + "arshipdesign/ui/__init__.py": "# arshipdesign/ui\n", + "arshipdesign/ui/commands/__init__.py": "# ui/commands\n", + "arshipdesign/ui/commands/command.py": '"""Command base Undo/Redo. Stub — Sprint 1."""\nraise NotImplementedError("command — Sprint 1")\n', + "arshipdesign/ui/commands/modify_hull.py": '"""Comando modificar casco. Stub — Sprint 1."""\nraise NotImplementedError("modify_hull — Sprint 1")\n', + "arshipdesign/ui/commands/add_tank.py": '"""Comando añadir tanque. Stub — Sprint 4."""\nraise NotImplementedError("add_tank — Sprint 4")\n', + "arshipdesign/ui/shortcuts.py": "# Atajos de teclado — Stub Sprint 1\n", + "arshipdesign/ui/widgets/__init__.py": "# ui/widgets\n", + "arshipdesign/ui/widgets/viewer_3d.py": '"""Vista 3D PyVista. Stub — Sprint 1."""\nraise NotImplementedError("viewer_3d — Sprint 1")\n', + "arshipdesign/ui/widgets/viewer_lines.py": '"""Plano líneas 2D. Stub — Sprint 1."""\nraise NotImplementedError("viewer_lines — Sprint 1")\n', + "arshipdesign/ui/widgets/viewer_polar.py": '"""Diagrama polar. Stub — Sprint 6."""\nraise NotImplementedError("viewer_polar — Sprint 6")\n', + "arshipdesign/ui/widgets/offsets_editor.py": '"""Editor offsets. Stub — Sprint 1."""\nraise NotImplementedError("offsets_editor — Sprint 1")\n', + "arshipdesign/ui/widgets/tank_editor.py": '"""Editor tanques. Stub — Sprint 4."""\nraise NotImplementedError("tank_editor — Sprint 4")\n', + "arshipdesign/ui/widgets/loadcase_editor.py": '"""Editor casos carga. Stub — Sprint 4."""\nraise NotImplementedError("loadcase_editor — Sprint 4")\n', + "arshipdesign/ui/widgets/rig_editor.py": '"""Editor aparejo. Stub — Sprint 6."""\nraise NotImplementedError("rig_editor — Sprint 6")\n', + "arshipdesign/ui/widgets/engine_picker.py": '"""Selector motor. Stub — Sprint 5."""\nraise NotImplementedError("engine_picker — Sprint 5")\n', + "arshipdesign/ui/widgets/propeller_designer.py": '"""Diseñador hélice. Stub — Sprint 5."""\nraise NotImplementedError("propeller_designer — Sprint 5")\n', + "arshipdesign/ui/widgets/electrical_load_table.py": '"""Tabla EPLA. Stub — Sprint 7."""\nraise NotImplementedError("electrical_load_table — Sprint 7")\n', + "arshipdesign/ui/widgets/system_diagram_canvas.py": '"""Canvas P&ID. Stub — Sprint 7."""\nraise NotImplementedError("system_diagram_canvas — Sprint 7")\n', + "arshipdesign/ui/widgets/curve_plotter.py": '"""Graficador curvas pyqtgraph. Stub — Sprint 3."""\nraise NotImplementedError("curve_plotter — Sprint 3")\n', + "arshipdesign/ui/dialogs/__init__.py": "# ui/dialogs\n", + "arshipdesign/ui/dialogs/wizards.py": '"""Wizards embarcaciones. Stub — Sprint 1."""\nraise NotImplementedError("wizards — Sprint 1")\n', + "arshipdesign/ui/dialogs/stability_criteria_picker.py": '"""Criterios estabilidad. Stub — Sprint 3."""\nraise NotImplementedError("stability_criteria_picker — Sprint 3")\n', + "arshipdesign/ui/dialogs/liquid_picker.py": '"""Selector líquido. Stub — Sprint 4."""\nraise NotImplementedError("liquid_picker — Sprint 4")\n', + "arshipdesign/ui/dialogs/preferences.py": '"""Preferencias. Stub — Sprint 1."""\nraise NotImplementedError("preferences — Sprint 1")\n', + "arshipdesign/systems/__init__.py": "# arshipdesign/systems\n", + "arshipdesign/systems/electrical/__init__.py": "# systems/electrical\n", + "arshipdesign/systems/electrical/load_analysis.py": '"""EPLA. Stub — Sprint 7."""\nraise NotImplementedError("load_analysis — Sprint 7")\n', + "arshipdesign/systems/electrical/generator_sizing.py": '"""Generadores. Stub — Sprint 7."""\nraise NotImplementedError("generator_sizing — Sprint 7")\n', + "arshipdesign/systems/electrical/battery_sizing.py": '"""Baterías. Stub — Sprint 7."""\nraise NotImplementedError("battery_sizing — Sprint 7")\n', + "arshipdesign/systems/electrical/one_line_diagram.py": '"""Diagrama unifilar. Stub — Sprint 7."""\nraise NotImplementedError("one_line_diagram — Sprint 7")\n', + "arshipdesign/systems/electrical/cable_sizing.py": '"""Cables. Stub — Sprint 7."""\nraise NotImplementedError("cable_sizing — Sprint 7")\n', + "arshipdesign/systems/fuel/__init__.py": "# systems/fuel\n", + "arshipdesign/systems/fuel/fuel_system.py": '"""Combustible. Stub — Sprint 7."""\nraise NotImplementedError("fuel_system — Sprint 7")\n', + "arshipdesign/systems/fuel/autonomy.py": '"""Autonomía. Stub — Sprint 7."""\nraise NotImplementedError("autonomy — Sprint 7")\n', + "arshipdesign/systems/fuel/day_tank.py": '"""Tanque diario. Stub — Sprint 7."""\nraise NotImplementedError("day_tank — Sprint 7")\n', + "arshipdesign/systems/freshwater/__init__.py": "# systems/freshwater\n", + "arshipdesign/systems/freshwater/fw_system.py": '"""Agua dulce. Stub — Sprint 7."""\nraise NotImplementedError("fw_system — Sprint 7")\n', + "arshipdesign/systems/freshwater/watermaker.py": '"""Osmosis inversa. Stub — Sprint 7."""\nraise NotImplementedError("watermaker — Sprint 7")\n', + "arshipdesign/systems/ballast/__init__.py": "# systems/ballast\n", + "arshipdesign/systems/ballast/ballast_system.py": '"""Lastre. Stub — Sprint 7."""\nraise NotImplementedError("ballast_system — Sprint 7")\n', + "arshipdesign/systems/bilge/__init__.py": "# systems/bilge\n", + "arshipdesign/systems/bilge/bilge_system.py": '"""Achique. Stub — Sprint 7."""\nraise NotImplementedError("bilge_system — Sprint 7")\n', + "arshipdesign/systems/bilge/oily_water_separator.py": '"""Separador sentinas OWS. Stub — Sprint 7."""\nraise NotImplementedError("oily_water_separator — Sprint 7")\n', + "arshipdesign/systems/firefighting/__init__.py": "# systems/firefighting\n", + "arshipdesign/systems/firefighting/fire_main.py": '"""CI agua salada. Stub — Sprint 8."""\nraise NotImplementedError("fire_main — Sprint 8")\n', + "arshipdesign/systems/firefighting/co2_system.py": '"""CO2. Stub — Sprint 8."""\nraise NotImplementedError("co2_system — Sprint 8")\n', + "arshipdesign/systems/firefighting/detection.py": '"""Detección incendios. Stub — Sprint 8."""\nraise NotImplementedError("detection — Sprint 8")\n', + "arshipdesign/systems/hvac/__init__.py": "# systems/hvac\n", + "arshipdesign/systems/hvac/heat_balance.py": '"""Balance térmico. Stub — Sprint 8."""\nraise NotImplementedError("heat_balance — Sprint 8")\n', + "arshipdesign/systems/hvac/chiller_sizing.py": '"""Chiller. Stub — Sprint 8."""\nraise NotImplementedError("chiller_sizing — Sprint 8")\n', + "arshipdesign/systems/steering/__init__.py": "# systems/steering\n", + "arshipdesign/systems/steering/rudder_design.py": '"""Timón. Stub — Sprint 8."""\nraise NotImplementedError("rudder_design — Sprint 8")\n', + "arshipdesign/systems/steering/steering_gear.py": '"""Servo. Stub — Sprint 8."""\nraise NotImplementedError("steering_gear — Sprint 8")\n', + "arshipdesign/systems/steering/thrusters.py": '"""Thruster. Stub — Sprint 8."""\nraise NotImplementedError("thrusters — Sprint 8")\n', + "arshipdesign/systems/anchoring/__init__.py": "# systems/anchoring\n", + "arshipdesign/systems/anchoring/anchor_selection.py": '"""Ancla. Stub — Sprint 8."""\nraise NotImplementedError("anchor_selection — Sprint 8")\n', + "arshipdesign/systems/anchoring/windlass.py": '"""Molinete. Stub — Sprint 8."""\nraise NotImplementedError("windlass — Sprint 8")\n', + "arshipdesign/seakeeping/__init__.py": "# arshipdesign/seakeeping\n", + "arshipdesign/seakeeping/strip_theory.py": '"""Strip theory STF. Stub — Sprint 9."""\nraise NotImplementedError("strip_theory — Sprint 9")\n', + "arshipdesign/seakeeping/rao.py": '"""RAOs. Stub — Sprint 9."""\nraise NotImplementedError("rao — Sprint 9")\n', + "arshipdesign/seakeeping/spectra.py": '"""Espectros olas. Stub — Sprint 9."""\nraise NotImplementedError("spectra — Sprint 9")\n', + "arshipdesign/seakeeping/short_term.py": '"""Mar corto. Stub — Sprint 9."""\nraise NotImplementedError("short_term — Sprint 9")\n', + "arshipdesign/seakeeping/slamming.py": '"""Pantocazo. Stub — Sprint 9."""\nraise NotImplementedError("slamming — Sprint 9")\n', + "arshipdesign/seakeeping/motion_sickness.py": '"""MSI ISO 2631. Stub — Sprint 9."""\nraise NotImplementedError("motion_sickness — Sprint 9")\n', + "arshipdesign/sailing/__init__.py": "# arshipdesign/sailing\n", + "arshipdesign/sailing/vpp.py": '"""VPP. Stub — Sprint 6."""\nraise NotImplementedError("vpp — Sprint 6")\n', + "arshipdesign/sailing/aero_model.py": '"""Modelo aerodinámico. Stub — Sprint 6."""\nraise NotImplementedError("aero_model — Sprint 6")\n', + "arshipdesign/sailing/hydro_model.py": '"""Hidrodinámica velero. Stub — Sprint 6."""\nraise NotImplementedError("hydro_model — Sprint 6")\n', + "arshipdesign/sailing/sail_coefficients.py": '"""CL CD velas. Stub — Sprint 6."""\nraise NotImplementedError("sail_coefficients — Sprint 6")\n', + "arshipdesign/sailing/polar_diagram.py": '"""Diagrama polar. Stub — Sprint 6."""\nraise NotImplementedError("polar_diagram — Sprint 6")\n', + "arshipdesign/sailing/multihull.py": '"""Multicasco. Stub — Sprint 6."""\nraise NotImplementedError("multihull — Sprint 6")\n', + "arshipdesign/reports/__init__.py": "# arshipdesign/reports\n", + "arshipdesign/reports/hydrostatic_report.py": '"""Reporte hidrostático. Stub — Sprint 10."""\nraise NotImplementedError("hydrostatic_report — Sprint 10")\n', + "arshipdesign/reports/stability_booklet.py": '"""Cuaderno estabilidad. Stub — Sprint 10."""\nraise NotImplementedError("stability_booklet — Sprint 10")\n', + "arshipdesign/reports/lines_plan.py": '"""Plano de líneas. Stub — Sprint 10."""\nraise NotImplementedError("lines_plan — Sprint 10")\n', + "arshipdesign/reports/resistance_powering.py": '"""Reporte resistencia. Stub — Sprint 10."""\nraise NotImplementedError("resistance_powering — Sprint 10")\n', + "arshipdesign/reports/pdf_builder.py": '"""Constructor PDFs. Stub — Sprint 10."""\nraise NotImplementedError("pdf_builder — Sprint 10")\n', + "arshipdesign/parametric/__init__.py": "# arshipdesign/parametric\n", + "arshipdesign/parametric/wigley.py": '"""Wigley validación. Stub — Sprint 1."""\nraise NotImplementedError("wigley — Sprint 1")\n', + "arshipdesign/parametric/series60.py": '"""Serie 60. Stub — Sprint 11."""\nraise NotImplementedError("series60 — Sprint 11")\n', + "arshipdesign/parametric/wizard_workboat.py": '"""Wizard workboat. Stub — Sprint 11."""\nraise NotImplementedError("wizard_workboat — Sprint 11")\n', + "arshipdesign/parametric/wizard_cruiser.py": '"""Wizard crucero. Stub — Sprint 11."""\nraise NotImplementedError("wizard_cruiser — Sprint 11")\n', + "arshipdesign/parametric/wizard_sailing_mono.py": '"""Wizard velero mono. Stub — Sprint 11."""\nraise NotImplementedError("wizard_sailing_mono — Sprint 11")\n', + "arshipdesign/parametric/wizard_planing.py": '"""Wizard planeador. Stub — Sprint 11."""\nraise NotImplementedError("wizard_planing — Sprint 11")\n', + "arshipdesign/parametric/lackenby_transform.py": '"""Lackenby. Stub — Sprint 11."""\nraise NotImplementedError("lackenby_transform — Sprint 11")\n', + "arshipdesign/tanks/__init__.py": "# arshipdesign/tanks\n", + "arshipdesign/tanks/tank_definition.py": '"""Definición tanque. Stub — Sprint 4."""\nraise NotImplementedError("tank_definition — Sprint 4")\n', + "arshipdesign/tanks/sounding_table.py": '"""Tablas sondaje. Stub — Sprint 4."""\nraise NotImplementedError("sounding_table — Sprint 4")\n', + "arshipdesign/tanks/fsm.py": '"""Free Surface Moment. Stub — Sprint 4."""\nraise NotImplementedError("fsm — Sprint 4")\n', + "arshipdesign/tanks/capacity_plan.py": '"""Plan capacidades. Stub — Sprint 4."""\nraise NotImplementedError("capacity_plan — Sprint 4")\n', + "arshipdesign/engines/__init__.py": "# arshipdesign/engines\n", + "arshipdesign/engines/engine_db.py": '"""Catálogo motores. Stub — Sprint 5."""\nraise NotImplementedError("engine_db — Sprint 5")\n', + "arshipdesign/engines/engine_selection.py": '"""Selección motor. Stub — Sprint 5."""\nraise NotImplementedError("engine_selection — Sprint 5")\n', + "arshipdesign/engines/fuel_consumption.py": '"""Consumo combustible. Stub — Sprint 5."""\nraise NotImplementedError("fuel_consumption — Sprint 5")\n', + "arshipdesign/engines/engine_curve.py": '"""Curva BHP RPM. Stub — Sprint 5."""\nraise NotImplementedError("engine_curve — Sprint 5")\n', + "arshipdesign/engines/nox_emissions.py": '"""Emisiones NOx IMO. Stub — Sprint 5."""\nraise NotImplementedError("nox_emissions — Sprint 5")\n', + "data/benchmarks/README.md": "# Cascos de validacion\n\nArchivos de referencia para validar los modulos de calculo.\n", + "docs/user_manual.md": "# Manual de Usuario AR-ShipDesign\n\n*En construccion*\n", + "docs/theory_manual.md": "# Manual Teorico AR-ShipDesign\n\nFormulas y referencias bibliograficas.\n\n*En construccion*\n", + "docs/developer_guide.md": "# Guia del Desarrollador\n\n*En construccion*\n", + "docs/validation_report.md": "# Reporte de Validacion\n\n*En construccion*\n", +} + +created = 0 +for path, content in stubs.items(): + if not os.path.exists(path): + d = os.path.dirname(path) + if d: + os.makedirs(d, exist_ok=True) + with open(path, "w", encoding="utf-8") as f: + f.write(content) + created += 1 + +print(f"OK {created} archivos creados") diff --git a/data/benchmarks/README.md b/data/benchmarks/README.md new file mode 100644 index 0000000..b11bac8 --- /dev/null +++ b/data/benchmarks/README.md @@ -0,0 +1,3 @@ +# Cascos de validacion + +Archivos de referencia para validar los modulos de calculo. diff --git a/data/liquids.json b/data/liquids.json new file mode 100644 index 0000000..5baef0a --- /dev/null +++ b/data/liquids.json @@ -0,0 +1,17 @@ +{ + "HFO": {"name": "Heavy Fuel Oil", "density_kg_m3": 991, "viscosity_cSt": 380, "color": "#3d2b1f"}, + "MGO": {"name": "Marine Gas Oil", "density_kg_m3": 890, "viscosity_cSt": 6, "color": "#c8a96e"}, + "MDO": {"name": "Marine Diesel Oil", "density_kg_m3": 870, "viscosity_cSt": 11, "color": "#b8953a"}, + "LSFO": {"name": "Low Sulphur Fuel Oil", "density_kg_m3": 985, "viscosity_cSt": 180, "color": "#4a3728"}, + "LO": {"name": "Lubricating Oil", "density_kg_m3": 920, "viscosity_cSt": 100, "color": "#8b6914"}, + "HO": {"name": "Hydraulic Oil", "density_kg_m3": 875, "viscosity_cSt": 46, "color": "#d4a017"}, + "FW": {"name": "Fresh Water", "density_kg_m3": 1000, "viscosity_cSt": 1.0, "color": "#4fc3f7"}, + "SW": {"name": "Sea Water (ballast)", "density_kg_m3": 1025, "viscosity_cSt": 1.05, "color": "#0277bd"}, + "DW": {"name": "Distilled Water", "density_kg_m3": 999, "viscosity_cSt": 1.0, "color": "#b3e5fc"}, + "SLP": {"name": "Slop / Bilge", "density_kg_m3": 1010, "viscosity_cSt": 5, "color": "#5d4037"}, + "SEW": {"name": "Sewage", "density_kg_m3": 1010, "viscosity_cSt": 3, "color": "#6d4c41"}, + "URE": {"name": "Urea 40% (SCR/AdBlue)", "density_kg_m3": 1090, "viscosity_cSt": 1.4, "color": "#e1f5fe"}, + "LNG": {"name": "LNG (gas natural lic.)", "density_kg_m3": 450, "viscosity_cSt": 0.2, "color": "#e8f5e9"}, + "MeOH": {"name": "Methanol", "density_kg_m3": 791, "viscosity_cSt": 0.74, "color": "#f3e5f5"}, + "NH3": {"name": "Ammonia (liquid)", "density_kg_m3": 682, "viscosity_cSt": 0.27, "color": "#fff9c4"} +} diff --git a/data/stability_criteria.json b/data/stability_criteria.json new file mode 100644 index 0000000..8fa2c3b --- /dev/null +++ b/data/stability_criteria.json @@ -0,0 +1,29 @@ +{ + "IMO_IS_Code_2008": { + "name": "IMO IS Code 2008 (MSC.267(85))", + "description": "Código Internacional de Estabilidad en Condición Intacta 2008", + "criteria": { + "area_0_30": {"min": 0.055, "unit": "m·rad", "description": "Área bajo curva GZ de 0° a 30°"}, + "area_0_40": {"min": 0.090, "unit": "m·rad", "description": "Área bajo curva GZ de 0° a 40°"}, + "area_30_40": {"min": 0.030, "unit": "m·rad", "description": "Área bajo curva GZ de 30° a 40°"}, + "gz_at_30": {"min": 0.200, "unit": "m", "description": "GZ mínimo a 30° de escora"}, + "angle_gz_max": {"min": 25.0, "unit": "grados", "description": "Ángulo de GZ máximo ≥ 25°"}, + "gmt_initial": {"min": 0.150, "unit": "m", "description": "Altura metacéntrica inicial GM_T ≥ 0.15 m"} + } + }, + "IMO_Weather_A749": { + "name": "Criterio Meteorológico IMO A.749(18)", + "description": "Resolución A.749(18) — Criterio de resistencia al viento y al balance", + "criteria": { + "weather_ratio": {"min": 1.0, "unit": "adimensional", "description": "b/a ≥ 1.0 donde a=área escorante, b=área adrizante"} + } + }, + "USCG": { + "name": "USCG Stability Criteria", + "description": "United States Coast Guard — embarcaciones de recreo", + "criteria": { + "gmt_initial": {"min": 0.100, "unit": "m", "description": "GM_T ≥ 0.10 m"}, + "range_stability": {"min": 60.0, "unit": "grados", "description": "Rango de estabilidad positiva ≥ 60°"} + } + } +} diff --git a/docs/developer_guide.md b/docs/developer_guide.md new file mode 100644 index 0000000..b98df49 --- /dev/null +++ b/docs/developer_guide.md @@ -0,0 +1,3 @@ +# Guia del Desarrollador + +*En construccion* diff --git a/docs/theory_manual.md b/docs/theory_manual.md new file mode 100644 index 0000000..5352827 --- /dev/null +++ b/docs/theory_manual.md @@ -0,0 +1,5 @@ +# Manual Teorico AR-ShipDesign + +Formulas y referencias bibliograficas. + +*En construccion* diff --git a/docs/user_manual.md b/docs/user_manual.md new file mode 100644 index 0000000..ca33c2b --- /dev/null +++ b/docs/user_manual.md @@ -0,0 +1,3 @@ +# Manual de Usuario AR-ShipDesign + +*En construccion* diff --git a/docs/validation_report.md b/docs/validation_report.md new file mode 100644 index 0000000..f6cafda --- /dev/null +++ b/docs/validation_report.md @@ -0,0 +1,3 @@ +# Reporte de Validacion + +*En construccion* diff --git a/main.py b/main.py new file mode 100644 index 0000000..7902f28 --- /dev/null +++ b/main.py @@ -0,0 +1,67 @@ +""" +AR-ShipDesign — Punto de entrada principal. + +Uso: + python main.py +""" + +import sys +import os +from pathlib import Path + + +def main() -> int: + # Asegurar que el directorio raíz del proyecto está en el path + project_root = Path(__file__).parent + if str(project_root) not in sys.path: + sys.path.insert(0, str(project_root)) + + # Configurar logging antes de importar Qt + from arshipdesign.utils.logger import setup_logging + from arshipdesign.utils.settings import get_log_level + setup_logging(get_log_level()) + + from arshipdesign.utils.logger import get_logger + logger = get_logger("main") + logger.info("Iniciando AR-ShipDesign v0.1.0") + + # Importar Qt + try: + from PySide6.QtWidgets import QApplication + from PySide6.QtCore import Qt + from PySide6.QtGui import QFont + except ImportError as e: + print(f"ERROR: No se puede importar PySide6: {e}") + print("Instala las dependencias con: pip install -r requirements.txt") + return 1 + + # High DPI + os.environ.setdefault("QT_AUTO_SCREEN_SCALE_FACTOR", "1") + + app = QApplication(sys.argv) + app.setApplicationName("AR-ShipDesign") + app.setOrganizationName("AlvaroRodriguez") + app.setApplicationVersion("0.1.0") + + # Fuente por defecto + font = QFont("Segoe UI", 10) + app.setFont(font) + + # Aplicar tema + from arshipdesign.utils.settings import get_theme + theme = get_theme() + theme_path = Path(__file__).parent / "arshipdesign" / "ui" / "themes" / f"{theme}.qss" + if theme_path.exists(): + app.setStyleSheet(theme_path.read_text(encoding="utf-8")) + + # Ventana principal + from arshipdesign.ui.main_window import MainWindow + window = MainWindow() + window.show() + + logger.info("Ventana principal lista") + return app.exec() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b3c0c77 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.backends.legacy:build" + +[project] +name = "arshipdesign" +version = "0.1.0" +description = "Software profesional de diseño naval — AR-ShipDesign" +authors = [{ name = "Álvaro Rodríguez" }] +license = { file = "LICENSE.txt" } +readme = "README.md" +requires-python = ">=3.11" +keywords = ["naval", "ship design", "hydrostatics", "stability", "CAD"] +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", +] + +dependencies = [ + "PySide6>=6.6.0", + "pyvista>=0.43.0", + "pyvistaqt>=0.11.0", + "pyqtgraph>=0.13.0", + "geomdl>=5.3.1", + "numpy>=1.26.0", + "scipy>=1.11.0", + "pandas>=2.1.0", + "openpyxl>=3.1.0", + "ezdxf>=1.1.0", + "trimesh>=4.0.0", + "reportlab>=4.0.0", + "shapely>=2.0.0", +] + +[project.scripts] +arshipdesign = "main:main" + +[tool.setuptools.packages.find] +where = ["."] +include = ["arshipdesign*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = "-v --tb=short" + +[tool.mypy] +python_version = "3.11" +strict = false +ignore_missing_imports = true diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..f3ba612 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,7 @@ +-r requirements.txt +pytest>=7.4.0 +pytest-qt>=4.2.0 +pytest-cov>=4.1.0 +mypy>=1.6.0 +ruff>=0.1.0 +black>=23.0.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a1aee10 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +PySide6>=6.6.0 +pyvista>=0.43.0 +pyvistaqt>=0.11.0 +pyqtgraph>=0.13.0 +geomdl>=5.3.1 +numpy>=1.26.0 +scipy>=1.11.0 +pandas>=2.1.0 +openpyxl>=3.1.0 +ezdxf>=1.1.0 +trimesh>=4.0.0 +reportlab>=4.0.0 +shapely>=2.0.0 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5ae3b01 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,9 @@ +""" +Configuración global de pytest para AR-ShipDesign. +""" + +import sys +from pathlib import Path + +# Asegurar que el proyecto raíz está en sys.path +sys.path.insert(0, str(Path(__file__).parent.parent)) diff --git a/tests/test_startup.py b/tests/test_startup.py new file mode 100644 index 0000000..3931cdd --- /dev/null +++ b/tests/test_startup.py @@ -0,0 +1,166 @@ +""" +Tests de Sprint 0 — Validación del esqueleto del proyecto. +""" + +import json +from pathlib import Path +import pytest + + +def test_version_importable(): + """La versión del paquete debe ser importable.""" + from arshipdesign import __version__ + assert __version__ == "0.1.0" + + +def test_units_conversions(): + """Conversiones de unidades deben ser correctas.""" + from arshipdesign.core.units import ( + m_to_ft, ft_to_m, + kn_to_ms, ms_to_kn, + kg_to_ton_metric, + w_to_kw, + c_to_f, + ) + # Longitud + assert abs(m_to_ft(1.0) - 3.280839895) < 1e-6 + assert abs(ft_to_m(3.280839895) - 1.0) < 1e-6 + + # Velocidad + assert abs(kn_to_ms(1.0) - 0.514444) < 1e-4 + assert abs(ms_to_kn(0.514444) - 1.0) < 1e-3 + + # Masa + assert abs(kg_to_ton_metric(1000.0) - 1.0) < 1e-9 + + # Potencia + assert abs(w_to_kw(1000.0) - 1.0) < 1e-9 + + # Temperatura + assert abs(c_to_f(0.0) - 32.0) < 1e-6 + assert abs(c_to_f(100.0) - 212.0) < 1e-6 + + +def test_units_roundtrip(): + """Las conversiones deben ser reversibles (roundtrip).""" + from arshipdesign.core.units import m_to_ft, ft_to_m, kn_to_ms, ms_to_kn + + for val in [1.0, 5.5, 100.0, 0.01]: + assert abs(ft_to_m(m_to_ft(val)) - val) < 1e-9 + assert abs(ms_to_kn(kn_to_ms(val)) - val) < 1e-9 + + +def test_project_new(): + """Crear un proyecto nuevo debe generar metadatos correctos.""" + from arshipdesign.core.project import Project + + p = Project.new("Test Boat", author="Test Author") + assert p.name == "Test Boat" + assert p.metadata.author == "Test Author" + assert p.is_modified is True + assert p.path is None + + +def test_project_save_load(tmp_path): + """Guardar y cargar un proyecto debe preservar los datos.""" + from arshipdesign.core.project import Project + + project = Project.new("Mi Velero de Prueba", author="Álvaro") + save_path = tmp_path / "test_project.arsd" + + project.save(save_path) + assert save_path.exists() + assert not project.is_modified + + # Recargar + loaded = Project.load(save_path) + assert loaded.name == "Mi Velero de Prueba" + assert loaded.metadata.author == "Álvaro" + assert loaded.metadata.format_version == "1.0" + assert not loaded.is_modified + + +def test_project_save_creates_valid_zip(tmp_path): + """El archivo .arsd debe ser un ZIP válido con manifest.json.""" + import zipfile + from arshipdesign.core.project import Project + + p = Project.new("Zip Test") + path = tmp_path / "ziptest.arsd" + p.save(path) + + assert zipfile.is_zipfile(path) + with zipfile.ZipFile(path) as zf: + assert "manifest.json" in zf.namelist() + assert "ship.json" in zf.namelist() + + +def test_project_load_nonexistent(): + """Cargar un archivo inexistente debe lanzar FileNotFoundError.""" + from arshipdesign.core.project import Project + + with pytest.raises(FileNotFoundError): + Project.load(Path("/ruta/que/no/existe.arsd")) + + +def test_project_load_wrong_extension(tmp_path): + """Cargar un archivo con extensión incorrecta debe lanzar ValueError.""" + from arshipdesign.core.project import Project + + fake = tmp_path / "archivo.txt" + fake.write_text("hola") + + with pytest.raises(ValueError): + Project.load(fake) + + +def test_i18n_files_valid(): + """Los archivos de i18n deben ser JSON válido con claves básicas.""" + base = Path(__file__).parent.parent / "arshipdesign" / "ui" / "i18n" + for lang in ("es", "en"): + f = base / f"{lang}.json" + assert f.exists(), f"Falta el archivo i18n/{lang}.json" + data = json.loads(f.read_text(encoding="utf-8")) + assert "app_title" in data + assert "menu_file" in data + assert "hydro_draft" in data + + +def test_liquids_json_valid(): + """El archivo de líquidos debe tener las propiedades requeridas.""" + liq_path = Path(__file__).parent.parent / "data" / "liquids.json" + assert liq_path.exists() + liquids = json.loads(liq_path.read_text(encoding="utf-8")) + + required_codes = {"FW", "SW", "MGO", "HFO"} + for code in required_codes: + assert code in liquids, f"Líquido {code} faltante" + assert "density_kg_m3" in liquids[code] + assert liquids[code]["density_kg_m3"] > 0 + + +def test_stability_criteria_json_valid(): + """El archivo de criterios de estabilidad debe tener IMO IS Code 2008.""" + crit_path = Path(__file__).parent.parent / "data" / "stability_criteria.json" + assert crit_path.exists() + criteria = json.loads(crit_path.read_text(encoding="utf-8")) + assert "IMO_IS_Code_2008" in criteria + imo = criteria["IMO_IS_Code_2008"]["criteria"] + assert "area_0_30" in imo + assert imo["area_0_30"]["min"] == 0.055 + + +def test_directory_structure(): + """Los módulos principales deben existir como paquetes Python.""" + root = Path(__file__).parent.parent / "arshipdesign" + required_packages = [ + "core", "geometry", "hydrostatics", "stability", + "scantling", "resistance", "propulsion", + "fabrication", "fabrication/molds", + "sailing", "seakeeping", "systems", + "ui", "ui/widgets", "ui/dialogs", "utils", + "reports", "parametric", "tanks", "engines", "io", + ] + for pkg in required_packages: + init_file = root / pkg / "__init__.py" + assert init_file.exists(), f"Falta {pkg}/__init__.py"