Modulo 1: serializacion Hull / Project en formato .arsd (Task 11)
- hull.py: Hull.to_dict() serializa a dict JSON con formato hull_v1 (arrays numpy -> listas Python); Hull.from_dict() deserializa con validacion de claves y forma de array. - project.py: Project.hull (property lazy) deserializa el Hull desde ship_data; Project.set_hull() persiste el Hull y marca is_modified. - main_window.py: _on_new_project guarda el Hull en el proyecto; _on_project_loaded restaura el Hull en todos los visores al abrir un archivo .arsd; _on_hull_changed_from_editor mantiene el proyecto sincronizado con ediciones en el editor de offsets. - test_serialization.py: 26 tests (round-trip dict, round-trip ZIP, 5 familias parametricas, escritura atomica, proyecto sin Hull). Suite total: 112 tests -- 112 passed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -389,6 +389,76 @@ class Hull:
|
|||||||
mesh = pv.PolyData(all_pts, faces_arr)
|
mesh = pv.PolyData(all_pts, faces_arr)
|
||||||
return mesh.triangulate()
|
return mesh.triangulate()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Serialización JSON (.arsd)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Serializa el Hull a un diccionario JSON-serializable.
|
||||||
|
|
||||||
|
Formato interno: ``hull_v1``.
|
||||||
|
Los arrays numpy se convierten a listas de Python para compatibilidad
|
||||||
|
con json.dumps sin dependencias adicionales.
|
||||||
|
|
||||||
|
IACS Rec.34 §6 — trazabilidad de datos de entrada (offsets guardados
|
||||||
|
fielmente con la precisión de la tabla original).
|
||||||
|
"""
|
||||||
|
ot = self.offsets
|
||||||
|
return {
|
||||||
|
"format": "hull_v1",
|
||||||
|
"name": self.name,
|
||||||
|
"lpp": self.lpp,
|
||||||
|
"beam": self.beam,
|
||||||
|
"depth": self.depth,
|
||||||
|
"draft": self.draft,
|
||||||
|
"offsets": {
|
||||||
|
"lpp": ot.lpp,
|
||||||
|
"beam": ot.beam,
|
||||||
|
"draft": ot.draft,
|
||||||
|
"x_stations": ot.x_stations.tolist(),
|
||||||
|
"z_waterlines": ot.z_waterlines.tolist(),
|
||||||
|
"station_labels": list(ot.station_labels),
|
||||||
|
"data": ot.data.tolist(), # (n_sta, n_wl)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> "Hull":
|
||||||
|
"""Deserializa un Hull desde un diccionario (leído de un archivo .arsd).
|
||||||
|
|
||||||
|
Compatible con los formatos ``hull_v1`` y datos heredados sin versión.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
data : dict
|
||||||
|
Diccionario generado por ``Hull.to_dict()``.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
KeyError
|
||||||
|
Si faltan campos obligatorios.
|
||||||
|
ValueError
|
||||||
|
Si las dimensiones de la tabla son inconsistentes.
|
||||||
|
"""
|
||||||
|
od = data["offsets"]
|
||||||
|
offsets = OffsetsTable(
|
||||||
|
x_stations = np.array(od["x_stations"], dtype=float),
|
||||||
|
z_waterlines = np.array(od["z_waterlines"], dtype=float),
|
||||||
|
data = np.array(od["data"], dtype=float),
|
||||||
|
station_labels = od.get("station_labels", []),
|
||||||
|
lpp = float(od["lpp"]),
|
||||||
|
beam = float(od["beam"]),
|
||||||
|
draft = float(od["draft"]),
|
||||||
|
)
|
||||||
|
return cls(
|
||||||
|
name = str(data["name"]),
|
||||||
|
lpp = float(data["lpp"]),
|
||||||
|
beam = float(data["beam"]),
|
||||||
|
depth = float(data["depth"]),
|
||||||
|
draft = float(data["draft"]),
|
||||||
|
offsets = offsets,
|
||||||
|
)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Dunder
|
# Dunder
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
@@ -248,6 +248,45 @@ class Project:
|
|||||||
"""Marca el proyecto como modificado."""
|
"""Marca el proyecto como modificado."""
|
||||||
self._is_modified = True
|
self._is_modified = True
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
# HULL
|
||||||
|
# ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hull(self):
|
||||||
|
"""Hull activo del proyecto, o None si no hay geometría guardada.
|
||||||
|
|
||||||
|
El Hull se deserializa bajo demanda desde ship_data["hull"].
|
||||||
|
La primera llamada realiza la conversión; las siguientes también
|
||||||
|
(el objeto no se cachea para mantener la coherencia con ediciones
|
||||||
|
posteriores de ship_data).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Hull | None
|
||||||
|
"""
|
||||||
|
hull_data = self.ship_data.get("hull")
|
||||||
|
if not hull_data or hull_data.get("format") not in ("hull_v1",):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
from arshipdesign.core.hull import Hull
|
||||||
|
return Hull.from_dict(hull_data)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("No se pudo deserializar el Hull: %s", exc)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_hull(self, hull) -> None:
|
||||||
|
"""Serializa el Hull en ship_data y marca el proyecto como modificado.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
hull : Hull
|
||||||
|
El casco a guardar. Se serializa llamando a ``hull.to_dict()``.
|
||||||
|
"""
|
||||||
|
self.ship_data["hull"] = hull.to_dict()
|
||||||
|
self._is_modified = True
|
||||||
|
logger.debug("Hull '%s' guardado en proyecto '%s'", hull.name, self.name)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Project(name={self.name!r}, path={self.path})"
|
return f"Project(name={self.name!r}, path={self.path})"
|
||||||
|
|
||||||
|
|||||||
@@ -1224,6 +1224,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._on_project_loaded()
|
self._on_project_loaded()
|
||||||
if hull is not None:
|
if hull is not None:
|
||||||
self._current_hull = hull
|
self._current_hull = hull
|
||||||
|
self._project.set_hull(hull) # persistir en ship_data
|
||||||
self._load_hull_viewers(hull)
|
self._load_hull_viewers(hull)
|
||||||
self.statusBar().showMessage(
|
self.statusBar().showMessage(
|
||||||
f"Nuevo proyecto: {self._project.name}"
|
f"Nuevo proyecto: {self._project.name}"
|
||||||
@@ -1282,6 +1283,12 @@ class MainWindow(QMainWindow):
|
|||||||
def _on_project_loaded(self) -> None:
|
def _on_project_loaded(self) -> None:
|
||||||
self._update_title()
|
self._update_title()
|
||||||
self._layers_panel.set_project(self._project)
|
self._layers_panel.set_project(self._project)
|
||||||
|
# Restaurar Hull si el proyecto contiene geometría guardada
|
||||||
|
hull = self._project.hull
|
||||||
|
if hull is not None:
|
||||||
|
self._current_hull = hull
|
||||||
|
self._load_hull_viewers(hull)
|
||||||
|
logger.info("Hull '%s' restaurado desde proyecto", hull.name)
|
||||||
|
|
||||||
def _load_hull_viewers(self, hull, *, _skip_offsets_editor: bool = False) -> None:
|
def _load_hull_viewers(self, hull, *, _skip_offsets_editor: bool = False) -> None:
|
||||||
"""Carga el casco en todos los visores (2D, 3D, offsets) y actualiza hidrostáticos.
|
"""Carga el casco en todos los visores (2D, 3D, offsets) y actualiza hidrostáticos.
|
||||||
@@ -1306,8 +1313,10 @@ class MainWindow(QMainWindow):
|
|||||||
self._update_hydrostatics(hull)
|
self._update_hydrostatics(hull)
|
||||||
|
|
||||||
def _on_hull_changed_from_editor(self, hull) -> None:
|
def _on_hull_changed_from_editor(self, hull) -> None:
|
||||||
"""Slot: el editor de offsets reconstruyo el Hull — propagar a los demas visores."""
|
"""Slot: el editor de offsets reconstruyo el Hull — propagar a visores y proyecto."""
|
||||||
self._current_hull = hull
|
self._current_hull = hull
|
||||||
|
if self._project is not None:
|
||||||
|
self._project.set_hull(hull) # mantener proyecto sincronizado
|
||||||
# _skip_offsets_editor=True para no re-poblar la tabla (ya esta actualizada)
|
# _skip_offsets_editor=True para no re-poblar la tabla (ya esta actualizada)
|
||||||
self._load_hull_viewers(hull, _skip_offsets_editor=True)
|
self._load_hull_viewers(hull, _skip_offsets_editor=True)
|
||||||
self.statusBar().showMessage(f"Offsets actualizados — {hull.name}")
|
self.statusBar().showMessage(f"Offsets actualizados — {hull.name}")
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
"""
|
||||||
|
Tests Modulo 1 -- Serializacion Hull / Project (.arsd).
|
||||||
|
|
||||||
|
Verifica que:
|
||||||
|
- Hull.to_dict() produce un dict JSON-serializable
|
||||||
|
- Hull.from_dict(Hull.to_dict(h)) recupera el mismo Hull bit a bit
|
||||||
|
- Project.set_hull() / Project.hull guarda y restaura el Hull
|
||||||
|
- Project.save() / Project.load() persiste el Hull en el ZIP .arsd
|
||||||
|
- Proyectos sin Hull (legados o nuevos vacios) cargan sin error
|
||||||
|
|
||||||
|
Autor: Alvaro Romero | Modulo 1 -- AR-ShipDesign
|
||||||
|
IACS Rec.34 par.6 -- trazabilidad de datos de entrada.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from arshipdesign.core.hull import Hull
|
||||||
|
from arshipdesign.core.offsets import OffsetsTable
|
||||||
|
from arshipdesign.core.project import Project
|
||||||
|
from arshipdesign.parametric import generate_hull, HullFamily
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Fixtures
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def wigley_hull() -> Hull:
|
||||||
|
return Hull.from_wigley(lpp=15.0, beam=4.0, draft=1.60,
|
||||||
|
n_stations=21, n_waterlines=11)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def displacement_hull() -> Hull:
|
||||||
|
return generate_hull(HullFamily.DISPLACEMENT, lpp=20.0, beam=6.0,
|
||||||
|
draft=2.40, depth=3.20, cb=0.55)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Hull.to_dict
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestHullToDict:
|
||||||
|
def test_format_key(self, wigley_hull: Hull) -> None:
|
||||||
|
d = wigley_hull.to_dict()
|
||||||
|
assert d["format"] == "hull_v1"
|
||||||
|
|
||||||
|
def test_required_scalar_keys(self, wigley_hull: Hull) -> None:
|
||||||
|
d = wigley_hull.to_dict()
|
||||||
|
for key in ("name", "lpp", "beam", "depth", "draft"):
|
||||||
|
assert key in d, f"Falta clave '{key}' en Hull.to_dict()"
|
||||||
|
|
||||||
|
def test_offsets_sub_dict(self, wigley_hull: Hull) -> None:
|
||||||
|
od = wigley_hull.to_dict()["offsets"]
|
||||||
|
for key in ("lpp", "beam", "draft", "x_stations", "z_waterlines",
|
||||||
|
"station_labels", "data"):
|
||||||
|
assert key in od, f"Falta clave '{key}' en offsets dict"
|
||||||
|
|
||||||
|
def test_json_serializable(self, wigley_hull: Hull) -> None:
|
||||||
|
"""El dict debe poder pasarse por json.dumps sin error."""
|
||||||
|
d = wigley_hull.to_dict()
|
||||||
|
txt = json.dumps(d)
|
||||||
|
assert len(txt) > 100
|
||||||
|
|
||||||
|
def test_data_shape(self, wigley_hull: Hull) -> None:
|
||||||
|
od = wigley_hull.to_dict()["offsets"]
|
||||||
|
n_sta = wigley_hull.offsets.n_stations
|
||||||
|
n_wl = wigley_hull.offsets.n_waterlines
|
||||||
|
assert len(od["data"]) == n_sta
|
||||||
|
assert len(od["data"][0]) == n_wl
|
||||||
|
|
||||||
|
def test_values_preserved(self, wigley_hull: Hull) -> None:
|
||||||
|
d = wigley_hull.to_dict()
|
||||||
|
assert abs(d["lpp"] - wigley_hull.lpp) < 1e-12
|
||||||
|
assert abs(d["draft"] - wigley_hull.draft) < 1e-12
|
||||||
|
assert d["name"] == wigley_hull.name
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Hull.from_dict round-trip
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestHullFromDict:
|
||||||
|
def test_roundtrip_dimensions(self, wigley_hull: Hull) -> None:
|
||||||
|
h2 = Hull.from_dict(wigley_hull.to_dict())
|
||||||
|
assert abs(h2.lpp - wigley_hull.lpp) < 1e-12
|
||||||
|
assert abs(h2.beam - wigley_hull.beam) < 1e-12
|
||||||
|
assert abs(h2.depth - wigley_hull.depth) < 1e-12
|
||||||
|
assert abs(h2.draft - wigley_hull.draft) < 1e-12
|
||||||
|
assert h2.name == wigley_hull.name
|
||||||
|
|
||||||
|
def test_roundtrip_offsets_shape(self, wigley_hull: Hull) -> None:
|
||||||
|
h2 = Hull.from_dict(wigley_hull.to_dict())
|
||||||
|
assert h2.offsets.n_stations == wigley_hull.offsets.n_stations
|
||||||
|
assert h2.offsets.n_waterlines == wigley_hull.offsets.n_waterlines
|
||||||
|
|
||||||
|
def test_roundtrip_offsets_values(self, wigley_hull: Hull) -> None:
|
||||||
|
h2 = Hull.from_dict(wigley_hull.to_dict())
|
||||||
|
np.testing.assert_array_almost_equal(
|
||||||
|
h2.offsets.data, wigley_hull.offsets.data, decimal=12
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_roundtrip_hydrostatics(self, wigley_hull: Hull) -> None:
|
||||||
|
h2 = Hull.from_dict(wigley_hull.to_dict())
|
||||||
|
assert abs(h2.volume_of_displacement() - wigley_hull.volume_of_displacement()) < 1e-9
|
||||||
|
assert abs(h2.block_coefficient() - wigley_hull.block_coefficient()) < 1e-12
|
||||||
|
assert abs(h2.km_transverse() - wigley_hull.km_transverse()) < 1e-9
|
||||||
|
|
||||||
|
def test_roundtrip_station_labels(self, wigley_hull: Hull) -> None:
|
||||||
|
h2 = Hull.from_dict(wigley_hull.to_dict())
|
||||||
|
assert h2.offsets.station_labels == wigley_hull.offsets.station_labels
|
||||||
|
|
||||||
|
def test_from_dict_displacement_hull(self, displacement_hull: Hull) -> None:
|
||||||
|
h2 = Hull.from_dict(displacement_hull.to_dict())
|
||||||
|
assert abs(h2.lpp - displacement_hull.lpp) < 1e-9
|
||||||
|
assert h2.offsets.n_stations == displacement_hull.offsets.n_stations
|
||||||
|
|
||||||
|
def test_missing_key_raises(self, wigley_hull: Hull) -> None:
|
||||||
|
d = wigley_hull.to_dict()
|
||||||
|
del d["offsets"]["x_stations"]
|
||||||
|
with pytest.raises((KeyError, Exception)):
|
||||||
|
Hull.from_dict(d)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Project.set_hull / Project.hull property
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestProjectHull:
|
||||||
|
def test_new_project_has_no_hull(self) -> None:
|
||||||
|
proj = Project.new("Sin casco")
|
||||||
|
assert proj.hull is None
|
||||||
|
|
||||||
|
def test_set_hull_stores_format(self, wigley_hull: Hull) -> None:
|
||||||
|
proj = Project.new("Test")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
assert proj.ship_data["hull"]["format"] == "hull_v1"
|
||||||
|
|
||||||
|
def test_set_hull_marks_modified(self, wigley_hull: Hull) -> None:
|
||||||
|
proj = Project.new("Test")
|
||||||
|
proj._is_modified = False # reset manually
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
assert proj.is_modified
|
||||||
|
|
||||||
|
def test_hull_property_returns_hull(self, wigley_hull: Hull) -> None:
|
||||||
|
proj = Project.new("Test")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
h = proj.hull
|
||||||
|
assert h is not None
|
||||||
|
assert abs(h.lpp - wigley_hull.lpp) < 1e-9
|
||||||
|
|
||||||
|
def test_hull_property_preserves_hydrostatics(self, wigley_hull: Hull) -> None:
|
||||||
|
proj = Project.new("Test")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
h = proj.hull
|
||||||
|
assert abs(h.block_coefficient() - wigley_hull.block_coefficient()) < 1e-9
|
||||||
|
|
||||||
|
def test_corrupt_hull_data_returns_none(self) -> None:
|
||||||
|
proj = Project.new("Test")
|
||||||
|
proj.ship_data["hull"] = {"format": "unknown_v99", "bad": True}
|
||||||
|
assert proj.hull is None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Project.save / Project.load (.arsd round-trip)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestProjectSaveLoad:
|
||||||
|
def test_save_and_load_preserves_hull(self, wigley_hull: Hull) -> None:
|
||||||
|
proj = Project.new("Velero de prueba")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
p = Path(tmp) / "velero.arsd"
|
||||||
|
proj.save(p)
|
||||||
|
assert p.exists()
|
||||||
|
assert p.stat().st_size > 500 # no vacio
|
||||||
|
|
||||||
|
proj2 = Project.load(p)
|
||||||
|
h2 = proj2.hull
|
||||||
|
assert h2 is not None
|
||||||
|
assert h2.name == wigley_hull.name
|
||||||
|
assert abs(h2.lpp - wigley_hull.lpp) < 1e-9
|
||||||
|
np.testing.assert_array_almost_equal(
|
||||||
|
h2.offsets.data, wigley_hull.offsets.data, decimal=12
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_save_and_load_no_hull(self) -> None:
|
||||||
|
"""Un proyecto sin Hull debe cargarse sin error y retornar hull=None."""
|
||||||
|
proj = Project.new("Proyecto vacio")
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
p = Path(tmp) / "vacio.arsd"
|
||||||
|
proj.save(p)
|
||||||
|
proj2 = Project.load(p)
|
||||||
|
assert proj2.hull is None
|
||||||
|
|
||||||
|
def test_file_is_valid_zip(self, wigley_hull: Hull) -> None:
|
||||||
|
import zipfile
|
||||||
|
proj = Project.new("Test ZIP")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
p = Path(tmp) / "test.arsd"
|
||||||
|
proj.save(p)
|
||||||
|
assert zipfile.is_zipfile(p)
|
||||||
|
|
||||||
|
def test_ship_json_inside_zip(self, wigley_hull: Hull) -> None:
|
||||||
|
import zipfile
|
||||||
|
proj = Project.new("Test ZIP")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
p = Path(tmp) / "test.arsd"
|
||||||
|
proj.save(p)
|
||||||
|
with zipfile.ZipFile(p, "r") as zf:
|
||||||
|
assert "ship.json" in zf.namelist()
|
||||||
|
ship = json.loads(zf.read("ship.json").decode("utf-8"))
|
||||||
|
assert ship["hull"]["format"] == "hull_v1"
|
||||||
|
|
||||||
|
def test_roundtrip_five_hull_families(self) -> None:
|
||||||
|
"""Todos los generadores parametricos deben sobrevivir el round-trip."""
|
||||||
|
for family in HullFamily:
|
||||||
|
hull = generate_hull(family, lpp=12.0, beam=3.5,
|
||||||
|
draft=1.20, depth=2.00)
|
||||||
|
d = hull.to_dict()
|
||||||
|
h2 = Hull.from_dict(d)
|
||||||
|
assert abs(h2.lpp - hull.lpp) < 1e-9, f"{family.value}: Lpp no coincide"
|
||||||
|
# Hidrostáticos reproducibles
|
||||||
|
assert abs(h2.volume_of_displacement() -
|
||||||
|
hull.volume_of_displacement()) < 1e-6, \
|
||||||
|
f"{family.value}: V no coincide"
|
||||||
|
|
||||||
|
def test_save_is_atomic(self, wigley_hull: Hull) -> None:
|
||||||
|
"""El archivo temporal .arsd.tmp no debe quedar si save tiene exito."""
|
||||||
|
proj = Project.new("Atomic test")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
p = Path(tmp) / "atomic.arsd"
|
||||||
|
proj.save(p)
|
||||||
|
tmp_p = p.with_suffix(".arsd.tmp")
|
||||||
|
assert not tmp_p.exists(), ".arsd.tmp debe eliminarse tras save exitoso"
|
||||||
|
|
||||||
|
def test_save_as_updates_path(self, wigley_hull: Hull) -> None:
|
||||||
|
proj = Project.new("SaveAs test")
|
||||||
|
proj.set_hull(wigley_hull)
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
p1 = Path(tmp) / "first.arsd"
|
||||||
|
p2 = Path(tmp) / "second.arsd"
|
||||||
|
proj.save(p1)
|
||||||
|
assert proj.path == p1
|
||||||
|
proj.save(p2)
|
||||||
|
assert proj.path == p2
|
||||||
|
assert p2.exists()
|
||||||
Reference in New Issue
Block a user