mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-23 21:18:49 +00:00
Fix #4204. Add support for XLSX schedules on sheets.
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
from blenderbim.bim.module.drawing.svgwriter import SvgWriter
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
import openpyxl
|
||||||
|
|
||||||
from odf.opendocument import load as load_ods
|
from odf.opendocument import load as load_ods
|
||||||
from odf.table import Table, TableRow, TableColumn, TableCell
|
from odf.table import Table, TableRow, TableColumn, TableCell
|
||||||
@@ -62,6 +63,147 @@ class Scheduler:
|
|||||||
)
|
)
|
||||||
self.padding = 1
|
self.padding = 1
|
||||||
self.margin = 1
|
self.margin = 1
|
||||||
|
if infile.endswith("ods"):
|
||||||
|
self.schedule_ods(infile, outfile)
|
||||||
|
elif infile.endswith("xlsx"):
|
||||||
|
self.schedule_xlsx(infile, outfile)
|
||||||
|
|
||||||
|
def schedule_xlsx(self, infile, outfile):
|
||||||
|
workbook = openpyxl.open(infile, data_only=True)
|
||||||
|
sheet = workbook.active
|
||||||
|
|
||||||
|
column_dimensions = {k: v.width for k, v in sheet.column_dimensions.items()}
|
||||||
|
row_dimensions = {k: v.height for k, v in sheet.row_dimensions.items()}
|
||||||
|
print(row_dimensions)
|
||||||
|
default_width = 8.43 # 8.43 characters wide
|
||||||
|
default_height = 15 # 15pt seems to be the default height
|
||||||
|
|
||||||
|
merged_cells = []
|
||||||
|
for cell_range in sheet.merged_cells.ranges:
|
||||||
|
pass
|
||||||
|
|
||||||
|
y = self.margin
|
||||||
|
for row in sheet.iter_rows():
|
||||||
|
x = self.margin
|
||||||
|
for cell in row:
|
||||||
|
if isinstance(cell, openpyxl.cell.cell.MergedCell):
|
||||||
|
column_letter = openpyxl.utils.get_column_letter(cell.column)
|
||||||
|
width = column_dimensions.get(column_letter, default_width)
|
||||||
|
else:
|
||||||
|
width = column_dimensions.get(cell.column_letter, default_width)
|
||||||
|
width = self.convert_character_width_to_mm(width)
|
||||||
|
height = row_dimensions.get(cell.row, default_height)
|
||||||
|
height = self.convert_to_mm(f"{height}pt")
|
||||||
|
|
||||||
|
if isinstance(cell, openpyxl.cell.cell.MergedCell):
|
||||||
|
x += width
|
||||||
|
continue
|
||||||
|
|
||||||
|
unmerged_width = width
|
||||||
|
unmerged_height = height
|
||||||
|
|
||||||
|
for cell_range in sheet.merged_cells.ranges:
|
||||||
|
if cell.coordinate not in cell_range:
|
||||||
|
continue
|
||||||
|
merged_width = 0
|
||||||
|
merged_height = 0
|
||||||
|
for merged_column in cell_range.cols:
|
||||||
|
merged_cell = sheet.cell(*merged_column[0])
|
||||||
|
column_letter = openpyxl.utils.get_column_letter(merged_cell.column)
|
||||||
|
width = column_dimensions.get(cell.column_letter, default_width)
|
||||||
|
width = self.convert_character_width_to_mm(width)
|
||||||
|
merged_width += width
|
||||||
|
for merged_row in cell_range.rows:
|
||||||
|
merged_cell = sheet.cell(*merged_row[0])
|
||||||
|
height = row_dimensions.get(merged_cell.row, default_height)
|
||||||
|
height = self.convert_to_mm(f"{height}pt")
|
||||||
|
merged_height += height
|
||||||
|
width = merged_width
|
||||||
|
height = merged_height
|
||||||
|
break
|
||||||
|
|
||||||
|
if cell.fill.patternType:
|
||||||
|
background_color = "#" + str(cell.fill.bgColor.rgb).lower()[2:]
|
||||||
|
else:
|
||||||
|
background_color = "#ffffff"
|
||||||
|
|
||||||
|
self.svg.add(
|
||||||
|
self.svg.rect(
|
||||||
|
insert=(x, y),
|
||||||
|
size=(width, height),
|
||||||
|
style=f"fill: {background_color}; stroke-width:.125; stroke: #000000;",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
font_size = cell.font.size or 11 # 11pt default
|
||||||
|
font_size = font_size / FONT_SIZE_PT * FONT_SIZE # Magic?
|
||||||
|
|
||||||
|
text_position = [0.0, 0.0]
|
||||||
|
if cell.alignment.horizontal == "left":
|
||||||
|
text_position[0] = x + self.padding
|
||||||
|
horizontal_align = "left"
|
||||||
|
elif cell.alignment.horizontal == "general" and isinstance(cell.value, str):
|
||||||
|
text_position[0] = x + self.padding
|
||||||
|
horizontal_align = "left"
|
||||||
|
elif cell.alignment.horizontal in ("center", "justify", "centerContinuous", "distributed"):
|
||||||
|
text_position[0] = x + width / 2
|
||||||
|
horizontal_align = "middle"
|
||||||
|
elif cell.alignment.horizontal == "right":
|
||||||
|
text_position[0] = x + width - self.padding
|
||||||
|
horizontal_align = "right"
|
||||||
|
elif cell.alignment.horizontal == "general" and not isinstance(cell.value, str):
|
||||||
|
text_position[0] = x + width - self.padding
|
||||||
|
horizontal_align = "right"
|
||||||
|
else:
|
||||||
|
text_position[0] = x + self.padding
|
||||||
|
horizontal_align = "left"
|
||||||
|
|
||||||
|
if cell.alignment.vertical == "top":
|
||||||
|
text_position[1] = y + self.padding
|
||||||
|
vertical_align = "top"
|
||||||
|
elif cell.alignment.vertical in ("center", "justify", "distributed"):
|
||||||
|
text_position[1] = y + height / 2
|
||||||
|
vertical_align = "middle"
|
||||||
|
elif cell.alignment.vertical == "bottom":
|
||||||
|
text_position[1] = y + height - self.padding
|
||||||
|
vertical_align = "bottom"
|
||||||
|
else:
|
||||||
|
text_position[1] = y + height - self.padding
|
||||||
|
vertical_align = "bottom"
|
||||||
|
|
||||||
|
if vertical_align == "middle" and horizontal_align == "middle":
|
||||||
|
box_alignment = "center"
|
||||||
|
else:
|
||||||
|
box_alignment = f"{vertical_align}-{horizontal_align}"
|
||||||
|
|
||||||
|
if cell.font.color:
|
||||||
|
text_color = "#" + str(cell.font.color.rgb).lower()[2:]
|
||||||
|
else:
|
||||||
|
text_color = "#000000"
|
||||||
|
|
||||||
|
self.add_text(
|
||||||
|
[cell.value],
|
||||||
|
*text_position,
|
||||||
|
font_size=font_size,
|
||||||
|
box_alignment=box_alignment,
|
||||||
|
wrap_text=cell.alignment.wrapText or False,
|
||||||
|
cell_width=width,
|
||||||
|
bold=cell.font.b,
|
||||||
|
italic=cell.font.i,
|
||||||
|
text_color=text_color,
|
||||||
|
)
|
||||||
|
|
||||||
|
x += unmerged_width
|
||||||
|
y += unmerged_height
|
||||||
|
|
||||||
|
total_width = x + self.margin
|
||||||
|
total_height = y + self.margin
|
||||||
|
self.svg["width"] = "{}mm".format(total_width)
|
||||||
|
self.svg["height"] = "{}mm".format(total_height)
|
||||||
|
self.svg["viewBox"] = "0 0 {} {}".format(total_width, total_height)
|
||||||
|
self.svg.save(pretty=True)
|
||||||
|
|
||||||
|
def schedule_ods(self, infile, outfile):
|
||||||
doc = load_ods(infile)
|
doc = load_ods(infile)
|
||||||
|
|
||||||
# useful for debugging ods
|
# useful for debugging ods
|
||||||
@@ -425,6 +567,14 @@ class Scheduler:
|
|||||||
|
|
||||||
self.svg.add(text_tag)
|
self.svg.add(text_tag)
|
||||||
|
|
||||||
|
def convert_character_width_to_mm(self, value):
|
||||||
|
# Excel measures widths in terms of "number of characters of the
|
||||||
|
# default font", which is most commonly 11pt Calibri. The width of the
|
||||||
|
# "0" character in Calibri is approximately 7px at 100% zoom.
|
||||||
|
px_per_character = 7
|
||||||
|
px_to_pt = 0.75
|
||||||
|
return self.convert_to_mm(f"{value * px_per_character * px_to_pt}pt")
|
||||||
|
|
||||||
def convert_to_mm(self, value):
|
def convert_to_mm(self, value):
|
||||||
# XSL is what defines the units of measurements in ODF
|
# XSL is what defines the units of measurements in ODF
|
||||||
# https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-positiveLength
|
# https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-positiveLength
|
||||||
|
|||||||
Reference in New Issue
Block a user