Athos branch and correction of dipole tilts (rad vs degree issues)
This commit is contained in:
@@ -910,3 +910,159 @@ def populate_table_comparison(
|
||||
|
||||
header.reset_filters()
|
||||
table.setUpdatesEnabled(True)
|
||||
|
||||
|
||||
# ── Excel export ──────────────────────────────────────────────────────────────
|
||||
|
||||
def export_table_to_excel(
|
||||
table: QTableWidget,
|
||||
file_path: str,
|
||||
sheet_name: str,
|
||||
include_hidden_rows: bool = False,
|
||||
) -> bool:
|
||||
"""
|
||||
Write the visible contents of *table* to an Excel file, preserving the
|
||||
row background colours and bold fonts used in the widget.
|
||||
|
||||
Nothing is written when the table has no rows (or no visible rows) —
|
||||
the function returns False in that case and leaves any existing file
|
||||
untouched.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
table : QTableWidget
|
||||
The table to export. Header labels become the first sheet row.
|
||||
file_path : str
|
||||
Destination .xlsx path. Created if missing, overwritten if present.
|
||||
sheet_name : str
|
||||
Name of the worksheet tab inside the workbook.
|
||||
include_hidden_rows : bool
|
||||
When False (default) rows hidden by column filters are skipped, so
|
||||
the export matches exactly what the user sees. When True every row
|
||||
is written regardless of filter state.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True — file written (table had at least one exported row).
|
||||
False — nothing written (table was empty / fully filtered out).
|
||||
|
||||
Raises
|
||||
------
|
||||
ImportError
|
||||
If openpyxl is not installed.
|
||||
|
||||
Notes
|
||||
-----
|
||||
- Cell background colours are taken from each QTableWidgetItem's
|
||||
background brush, so the same _GROUP_COLORS palette used on screen
|
||||
is reproduced in the workbook.
|
||||
- Cells rendered in bold on screen (e.g. differing values or the
|
||||
"New Element" / "Difference" / "Unresolved Element" comments) are
|
||||
written in bold too.
|
||||
- Numeric-looking strings are written as numbers so Excel can sort
|
||||
and chart them; everything else is written as text.
|
||||
"""
|
||||
try:
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"openpyxl is required for Excel export: pip install openpyxl"
|
||||
) from None
|
||||
|
||||
n_rows = table.rowCount()
|
||||
n_cols = table.columnCount()
|
||||
if n_rows == 0 or n_cols == 0:
|
||||
return False
|
||||
|
||||
# Determine which rows to export
|
||||
export_rows = [
|
||||
r for r in range(n_rows)
|
||||
if include_hidden_rows or not table.isRowHidden(r)
|
||||
]
|
||||
if not export_rows:
|
||||
return False
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = sheet_name
|
||||
|
||||
# ── header row ────────────────────────────────────────────────────────────
|
||||
header_font = Font(name="Arial", size=10, bold=True, color="FFFFFFFF")
|
||||
header_fill = PatternFill("solid", fgColor="FF404040")
|
||||
thin = Side(style="thin", color="FFBFBFBF")
|
||||
border = Border(left=thin, right=thin, top=thin, bottom=thin)
|
||||
|
||||
for col in range(n_cols):
|
||||
hdr_item = table.horizontalHeaderItem(col)
|
||||
label = hdr_item.text() if hdr_item is not None else f"Column {col+1}"
|
||||
cell = ws.cell(row=1, column=col + 1, value=label)
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
cell.alignment = Alignment(horizontal="center", vertical="center")
|
||||
cell.border = border
|
||||
|
||||
# ── data rows ─────────────────────────────────────────────────────────────
|
||||
def _argb(qcolor) -> str | None:
|
||||
"""Convert a QColor to an openpyxl ARGB hex string, or None if white."""
|
||||
if qcolor is None or not qcolor.isValid():
|
||||
return None
|
||||
# Skip pure white / fully transparent — leave the cell unfilled
|
||||
if qcolor.alpha() == 0:
|
||||
return None
|
||||
if (qcolor.red(), qcolor.green(), qcolor.blue()) == (255, 255, 255):
|
||||
return None
|
||||
return f"FF{qcolor.red():02X}{qcolor.green():02X}{qcolor.blue():02X}"
|
||||
|
||||
for out_row, src_row in enumerate(export_rows, start=2):
|
||||
for col in range(n_cols):
|
||||
item = table.item(src_row, col)
|
||||
text = item.text() if item is not None else ""
|
||||
|
||||
# Write numbers as numbers so Excel can sort/plot them
|
||||
value: Any = text
|
||||
if text not in ("", "-"):
|
||||
try:
|
||||
if "/" not in text: # skip "a / b" diff strings
|
||||
value = float(text)
|
||||
if value.is_integer() and "." not in text:
|
||||
value = int(value)
|
||||
except (ValueError, TypeError):
|
||||
value = text
|
||||
|
||||
cell = ws.cell(row=out_row, column=col + 1, value=value)
|
||||
cell.border = border
|
||||
|
||||
if item is not None:
|
||||
# Background colour
|
||||
argb = _argb(item.background().color())
|
||||
if argb:
|
||||
cell.fill = PatternFill("solid", fgColor=argb)
|
||||
# Bold font
|
||||
is_bold = item.font().bold()
|
||||
cell.font = Font(name="Arial", size=10, bold=is_bold,
|
||||
color="FF000000")
|
||||
else:
|
||||
cell.font = Font(name="Arial", size=10, color="FF000000")
|
||||
|
||||
# ── column widths ─────────────────────────────────────────────────────────
|
||||
for col in range(n_cols):
|
||||
max_len = 0
|
||||
hdr_item = table.horizontalHeaderItem(col)
|
||||
if hdr_item is not None:
|
||||
max_len = len(hdr_item.text())
|
||||
for src_row in export_rows:
|
||||
item = table.item(src_row, col)
|
||||
if item is not None:
|
||||
max_len = max(max_len, len(item.text()))
|
||||
ws.column_dimensions[get_column_letter(col + 1)].width = min(
|
||||
max(10, max_len + 3), 40
|
||||
)
|
||||
|
||||
# Freeze the header row so it stays visible while scrolling
|
||||
ws.freeze_panes = "A2"
|
||||
|
||||
wb.save(file_path)
|
||||
return True
|
||||
|
||||
@@ -8,7 +8,7 @@ from PyQt5 import QtWidgets
|
||||
|
||||
from ui.BeamlineGUI import Ui_BeamlineGUI
|
||||
from beamline_editor import BeamlineEditorWidget
|
||||
from beamline_editor.table_utils import populate_table, populate_table_comparison
|
||||
from beamline_editor.table_utils import populate_table, populate_table_comparison, export_table_to_excel
|
||||
from beamline_editor.excel_utils import read_excel_sheet
|
||||
from beamline_editor.checkbox_table import (
|
||||
populate_checkbox_table,
|
||||
@@ -54,6 +54,7 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
self.actionLoad.triggered.connect(self.load)
|
||||
|
||||
self.actionCompareLayout.triggered.connect(self.compareLayout)
|
||||
self.actionExport_as_Proto_List.triggered.connect(self.exportLayoutasPL)
|
||||
self.actionExportOnlineModel.triggered.connect(self.exportOnlineModel)
|
||||
|
||||
# elements, lines and branch points
|
||||
@@ -193,6 +194,10 @@ class BeamlineEditor(QtWidgets.QMainWindow, Ui_BeamlineGUI):
|
||||
self.exporter.writeDomain(references[sec],sec,domain.LengthRes,subsec)
|
||||
self.exporter.buildCaseFooter()
|
||||
|
||||
def exportLayoutasPL(self):
|
||||
ok = export_table_to_excel(self.UIProtoList,"Protolist/Export/protolist.xlsx","Proto-HL")
|
||||
if not ok:
|
||||
print("Table is empty — nothing exported")
|
||||
|
||||
def compareLayout(self):
|
||||
if len(self.plrecs) == 0:
|
||||
|
||||
Reference in New Issue
Block a user