from __future__ import annotations
"""Export management module for SecInterp main dialog.
This module handles exporting preview data to various file formats
(PNG, PDF, SVG) and orchestrating data exports (SHP, CSV) via ExportService.
"""
from pathlib import Path
from typing import TYPE_CHECKING
from qgis.core import Qgis, QgsSettings
from qgis.PyQt.QtCore import QSize
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtWidgets import QFileDialog
from sec_interp.core.exceptions import ExportError, SecInterpError
from sec_interp.core.performance_metrics import MetricsCollector, PerformanceTimer
from sec_interp.core.services.export_service import ExportService
from sec_interp.exporters import get_exporter
from sec_interp.logger_config import get_logger
from .main_dialog_config import DialogConfig
if TYPE_CHECKING:
pass
logger = get_logger(__name__)
[docs]
class ExportManager:
"""Manages all export operations for the dialog.
This class handles exporting preview data to various file formats
(PNG, PDF, SVG) and orchestrating data exports (SHP, CSV) via ExportService.
"""
[docs]
def __init__(self, dialog: sec_interp.gui.main_dialog.SecInterpDialog):
"""Initialize export manager with reference to parent dialog.
Args:
dialog: The :class:`sec_interp.gui.main_dialog.SecInterpDialog` instance
"""
self.dialog = dialog
self.metrics = MetricsCollector()
self.export_service = ExportService(self.dialog.plugin_instance.controller)
[docs]
def export_preview(self) -> bool:
"""Export the current preview to a file.
Returns:
True if export successful, False otherwise
"""
self.metrics.clear()
try:
with PerformanceTimer("Total Preview Export Time", self.metrics):
if not self.dialog.current_canvas:
self.dialog.message_manager.push_message(
self.dialog.tr("Export Error"),
self.dialog.tr("No preview available to export. Generate a preview first."),
level=Qgis.Warning,
)
return False
canvas = self.dialog.current_canvas
layers = canvas.layers()
if not layers:
self.dialog.message_manager.push_message(
self.dialog.tr("Export Error"),
self.dialog.tr("No layers to export."),
level=Qgis.Warning,
)
return False
# Format selection
settings = QgsSettings()
last_dir = settings.value("SecInterp/lastExportDir", "", type=str)
default_path = str(Path(last_dir) / "preview.png") if last_dir else "preview.png"
file_filter = (
"PNG Image (*.png);;"
"JPEG Image (*.jpg *.jpeg);;"
"PDF Document (*.pdf);;"
"SVG Vector (*.svg)"
)
output_path, _selected_filter = QFileDialog.getSaveFileName(
self.dialog, "Export Preview", default_path, file_filter
)
if not output_path:
return False
output_path = Path(output_path)
settings.setValue("SecInterp/lastExportDir", str(output_path.parent))
# Determine dimensions and DPI
ext = output_path.suffix.lower()
width = self.dialog.preview_widget.canvas.width()
height = self.dialog.preview_widget.canvas.height()
dpi = 96
if ext in [".png", ".jpg", ".jpeg"]:
width *= 3
height *= 3
dpi = 300
# Prepare settings
opts = self.dialog.get_preview_options()
export_params = {
"width": width,
"height": height,
"dpi": dpi,
"background_color": QColor(255, 255, 255),
"show_legend": opts.get("show_legend", True),
"legend_renderer": getattr(
self.dialog.plugin_instance, "preview_renderer", None
),
"title": "Section Interpretation Preview",
"description": "Generated by SecInterp QGIS Plugin",
"extent": canvas.extent(),
}
map_settings = self.export_service.get_map_settings(
layers,
export_params["extent"],
self.dialog.preview_widget.canvas.size(),
export_params["background_color"],
)
# Special size override for rasters
if ext in [".png", ".jpg", ".jpeg"]:
map_settings.setOutputSize(QSize(width, height))
# Execute export
exporter = get_exporter(ext, export_params)
success = exporter.export(output_path, map_settings)
if success:
self.dialog.message_manager.push_message(
self.dialog.tr("Success"),
self.dialog.tr("Preview exported to {}").format(output_path.name),
level=Qgis.Success,
)
else:
raise ExportError(f"Failed to export preview to {output_path.name}")
if DialogConfig.LOG_DETAILED_METRICS:
logger.info(f"Preview Performance: {self.metrics.get_summary()}")
return success
except SecInterpError as e:
self.dialog.handle_error(e, "Export Error")
return False
except Exception as e:
self.dialog.handle_error(e, "Unexpected Export Error")
return False
[docs]
def export_data(self) -> bool:
"""Orchestrate full data export (SHP/CSV) to the selected folder.
Returns:
True if successful, False otherwise
"""
try:
# 1. Validate inputs via dialog
params = self.dialog.plugin_instance._get_and_validate_inputs()
if not params:
return False
# Auto-save current valid settings
self.dialog.settings_manager.save_settings()
# Get values for output path (still needed from dialog/values)
values = self.dialog.get_selected_values()
output_folder = Path(values["output_path"])
# 2. Generate data via controller
self.dialog.preview_widget.results_text.setPlainText("✓ Generating data for export...")
profile_data, geol_data, struct_data, drillhole_data, _ = (
self.dialog.plugin_instance.controller.generate_profile_data(params)
)
if not profile_data:
self.dialog.message_manager.push_message(
self.dialog.tr("Error"),
self.dialog.tr("No profile data generated."),
level=Qgis.Critical,
)
return False
num_interps = len(self.dialog.interpretations)
logger.info(f"Exporting data: found {num_interps} interpretation(s) in dialog.")
# Extract export settings
export_options = {
"exp_topo": values.get("exp_topo", True),
"exp_geol": values.get("exp_geol", True),
"exp_struct": values.get("exp_struct", True),
"exp_drill": values.get("exp_drill", True),
"exp_interp": values.get("exp_interp", True),
"drill_3d_traces": values.get("drill_3d_traces", True),
"drill_3d_intervals": values.get("drill_3d_intervals", True),
"drill_3d_original": values.get("drill_3d_original", True),
"drill_3d_projected": values.get("drill_3d_projected", False),
}
result_msg = self.export_service.export_data(
output_folder,
params,
profile_data,
geol_data,
struct_data,
drillhole_data,
interp_data=self.dialog.interpretations,
export_options=export_options,
)
self.dialog.preview_widget.results_text.setPlainText("\n".join(result_msg))
except SecInterpError as e:
self.dialog.handle_error(e, "Data Export Error")
return False
except Exception as e:
self.dialog.handle_error(e, "Unexpected Data Export Error")
return False
else:
return True