From 67f73a942ff959824502716bb962c3d1cd506fb3 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Thu, 29 Feb 2024 12:59:02 +0100 Subject: [PATCH 01/16] First ideas --- finalcif/appwindow.py | 6 +++--- finalcif/report/templated_report.py | 25 +++++++++++++++++++++---- finalcif/tools/space_groups.py | 6 +++--- tests/test_templated_report.py | 29 +++++++++++------------------ 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index fec4b821..9a6f146f 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -1126,9 +1126,9 @@ def make_report_tables(self) -> None: else: print('Report with templates') t = TemplatedReport() - ok = t.make_templated_report(options=self.options, cif=self.cif, - output_filename=str(report_filename), picfile=picfile, - template_path=Path(self.get_checked_templates_list_text())) + ok = t.make_templated_docx_report(options=self.options, cif=self.cif, + output_filename=str(report_filename), picfile=picfile, + template_path=Path(self.get_checked_templates_list_text())) if not ok: return None except FileNotFoundError as e: diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 35fa8f61..5be6ccb2 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -288,7 +288,7 @@ def symmsearch(cif: CifContainer, newsymms: Dict[int, str], num: int, return num -class TemplatedReport(): +class RichTextFormatter(): def __init__(self): self.literature = {'finalcif' : FinalCifReference(), @@ -536,8 +536,8 @@ def make_picture(self, options: Options, picfile: Path, tpl_doc: DocxTemplate): return InlineImage(tpl_doc, str(picfile.resolve()), width=Cm(options.picture_width)) return None - def make_templated_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, - template_path: Path) -> bool: + def make_templated_docx_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, + template_path: Path) -> bool: tpl_doc = DocxTemplate(template_path) context, tpl_doc = self.prepare_report_data(cif, options, picfile, tpl_doc) # Filter definition for {{foobar|filter}} things: @@ -552,6 +552,13 @@ def make_templated_report(self, options: Options, cif: CifContainer, output_file info_text=str(e)) return False + def prepare_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, + template_path: Path) -> bool: + context = self._get_context(cif, options, picfile, tpl_doc) + + def do_html_report(self, context: dict): + pass + def prepare_report_data(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate): maincontext = {} if not cif.is_multi_cif: @@ -573,7 +580,16 @@ def prepare_report_data(self, cif: CifContainer, options: Options, picfile: Path cif.load_block_by_name(current_block) return maincontext, tpl_doc - def _get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate): + +text_factory = { + richtext : RichTextFormatter(), + html : HtmlFormatter(), + plaintext: StringFormatter(), +} + + +class TemplatedReport(): + def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate = None): ba = BondsAndAngles(cif, without_h=options.without_h) t = TorsionAngles(cif, without_h=options.without_h) h = HydrogenBonds(cif) @@ -653,6 +669,7 @@ def _get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_d if __name__ == '__main__': from unittest import mock from pprint import pprint + data = Path('tests') testcif = (data / 'examples/1979688.cif').absolute() cif = CifContainer(testcif) diff --git a/finalcif/tools/space_groups.py b/finalcif/tools/space_groups.py index 0f785738..17ebb979 100644 --- a/finalcif/tools/space_groups.py +++ b/finalcif/tools/space_groups.py @@ -65,8 +65,8 @@ def to_mathml(self, space_group: str) -> str: def to_html(self, space_group: str) -> str: if not space_group: return '' - txt = self._to_html_without_body(space_group) - return '{}  ({})'.format(txt, self.spgrps[space_group][1].get('itnumber')) + txt = self.to_html_without_body(space_group) + return f'{txt}  ({self.spgrps[space_group][1].get("itnumber")})' def to_plain_text(self, space_group: str) -> str: """ @@ -76,7 +76,7 @@ def to_plain_text(self, space_group: str) -> str: return '' return ''.join([x[0] for x in self.spgrps[space_group][0]]) - def _to_html_without_body(self, space_group: str) -> str: + def to_html_without_body(self, space_group: str) -> str: """ Uses the general format dictionary in order to output a space group as html. """ diff --git a/tests/test_templated_report.py b/tests/test_templated_report.py index 934df89e..930eb521 100644 --- a/tests/test_templated_report.py +++ b/tests/test_templated_report.py @@ -93,11 +93,9 @@ def tearDown(self) -> None: def test_ccdc_num_in_table(self): t = TemplatedReport() - ok = t.make_templated_report(options=self.options, - cif=CifContainer(self.testcif), - output_filename=str(self.reportdoc), - picfile=self.report_pic, - template_path=self.text_template) + ok = t.make_templated_docx_report(options=self.options, cif=CifContainer(self.testcif), + output_filename=str(self.reportdoc), picfile=self.report_pic, + template_path=self.text_template) self.assertTrue(ok) doc = Document(str(self.reportdoc.absolute())) table: Table = doc.tables[0] @@ -110,11 +108,9 @@ def test_picture_has_correct_size(self): has to be set correctly. """ t = TemplatedReport() - ok = t.make_templated_report(options=self.options, - cif=CifContainer(self.testcif), - output_filename=self.reportdoc, - picfile=self.report_pic, - template_path=self.text_template) + ok = t.make_templated_docx_report(options=self.options, cif=CifContainer(self.testcif), + output_filename=self.reportdoc, picfile=self.report_pic, + template_path=self.text_template) self.assertTrue(ok) doc = Document(self.reportdoc.absolute()) shapes: InlineShapes = doc.inline_shapes @@ -123,11 +119,9 @@ def test_picture_has_correct_size(self): def test_citations(self): t = TemplatedReport() - t.make_templated_report(options=self.options, - cif=CifContainer(self.testcif), - output_filename=self.reportdoc, - picfile=self.report_pic, - template_path=self.text_template) + t.make_templated_docx_report(options=self.options, cif=CifContainer(self.testcif), + output_filename=self.reportdoc, picfile=self.report_pic, + template_path=self.text_template) doc = Document(self.reportdoc.absolute()) # for num, p in enumerate(doc.paragraphs): # print(num, p.text) @@ -150,9 +144,8 @@ def test_get_distance_from_atoms(self): mock = Mock() mock.without_h = False mock.picture_width = 7.43 - ok = t.make_templated_report(options=mock, cif=self.cif, - output_filename='test.docx', picfile=Path(), - template_path=self.docx_templ) + ok = t.make_templated_docx_report(options=mock, cif=self.cif, output_filename='test.docx', picfile=Path(), + template_path=self.docx_templ) self.assertTrue(ok) doc = Document(self.reportdoc.resolve().__str__()) self.assertEqual('C1-C2 in p-1 distance: 1.5123(17)', doc.paragraphs[0].text) From 05f5ca2406bec039dde58aef8652d1541fef1d34 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Thu, 29 Feb 2024 13:57:29 +0100 Subject: [PATCH 02/16] Refactored most of templated report to use a factory --- finalcif/report/templated_report.py | 369 +++++++++++++++++----------- 1 file changed, 227 insertions(+), 142 deletions(-) diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 5be6ccb2..bbb1a402 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -5,14 +5,13 @@ from contextlib import suppress from math import sin, radians from pathlib import Path -from typing import List, Dict, Union +from typing import List, Dict, Union, Iterator import jinja2 from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.shared import Cm from docx.text.paragraph import Paragraph from docxtpl import DocxTemplate, RichText, InlineImage, Subdoc -from shelxfile.misc.dsrmath import my_isnumeric from finalcif.cif.cif_file_io import CifContainer from finalcif.gui.dialogs import show_general_warning @@ -26,6 +25,8 @@ from finalcif.tools.options import Options from finalcif.tools.space_groups import SpaceGroups +AdpWithMinus = namedtuple('AdpWithMinus', ('label', 'U11', 'U22', 'U33', 'U23', 'U13', 'U12')) + @dataclasses.dataclass(frozen=True) class Bond(): @@ -75,13 +76,13 @@ def __init__(self, cif: CifContainer, without_h: bool): self.bonds_as_string: List[Bond] = [] self.angles_as_string: List[Angle] = [] # These can be used as Richtext for python-docx-tpl: - self.bonds: List[dict] = self._get_bonds_list(without_h) - self.angles: List[dict] = self._get_angles_list(without_h) + self.bonds_richtext: List[Dict[str, RichText]] = self._get_bonds_list(without_h) + self.angles_richtext: List[Dict[str, RichText]] = self._get_angles_list(without_h) # The list of symmetry elements at the table end used for generated atoms: self.symminfo: str = get_symminfo(self._symmlist) def __len__(self): - return len(self.bonds) + len(self.angles) + return len(self.bonds_richtext) + len(self.angles_richtext) @property def symmetry_generated_atoms_used(self): @@ -288,9 +289,8 @@ def symmsearch(cif: CifContainer, newsymms: Dict[int, str], num: int, return num -class RichTextFormatter(): - - def __init__(self): +class Formatter: + def __init__(self, options: Options) -> None: self.literature = {'finalcif' : FinalCifReference(), 'ccdc' : CCDCReference(), 'absorption' : '[no reference found]', @@ -298,62 +298,27 @@ def __init__(self): 'refinement' : '[no reference found]', 'integration': '[no reference found]', } + self._bonds_angles = BondsAndAngles(cif, without_h=options.without_h) + self._torsions = TorsionAngles(cif, without_h=options.without_h) + self._hydrogens = HydrogenBonds(cif) - def format_sum_formula(self, sum_formula: str) -> RichText: - sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] - richtext = RichText('') - if sum_formula_group: - for _, word in enumerate(sum_formula_group): - if isnumeric(word): - richtext.add(word, subscript=True) - elif ')' in word: - richtext.add(word.split(')')[0], subscript=True) - richtext.add(')') - elif ']' in word: - richtext.add(word.split(']')[0], subscript=True) - richtext.add(']') - else: - richtext.add(word) - if word == ',': - richtext.add(' ') - return richtext - else: - return RichText('no formula') + def get_bonds(self): + raise NotImplemented - @staticmethod - def space_group_subdoc(tpl_doc: DocxTemplate, cif: CifContainer) -> Subdoc: - """ - Generates a Subdoc subdocument with the xml code for a math element in MSWord. - """ - s = SpaceGroups() - try: - spgrxml = s.to_mathml(cif.space_group) - except KeyError: - spgrxml = '?' - spgr_word = math_to_word(spgrxml) - # I have to create a subdocument in order to add the xml: - sd = tpl_doc.new_subdoc() - p: Paragraph = sd.add_paragraph() - p.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT - p._element.append(spgr_word) - try: - p.add_run(f' ({cif.spgr_number})') - except AttributeError: - pass - return sd + def get_angles(self): + raise NotImplemented - @staticmethod - def get_from_to_theta_range(cif: CifContainer) -> str: - theta_min = cif['_diffrn_reflns_theta_min'] - theta_max = cif['_diffrn_reflns_theta_max'] - radiation_wavelength = cif['_diffrn_radiation_wavelength'] - try: - d_max = f' ({float(radiation_wavelength) / (2 * sin(radians(float(theta_max)))):.2f}' \ - f'{protected_space}{angstrom})' - # 2theta range: - return f"{2 * float(theta_min):.2f} to {2 * float(theta_max):.2f}{d_max}" - except ValueError: - return '? to ?' + def get_bonds_angles_symminfo(self): + return self._bonds_angles.symminfo + + def get_torsion_symminfo(self): + return self._torsions.symminfo + + def get_hydrogen_symminfo(self): + return self._hydrogens.symminfo + + def get_crystallization_method(self, cif): + return gstr(cif['_exptl_crystal_recrystallization_method']) or '[No crystallization method given!]' @staticmethod def hkl_index_limits(cif: CifContainer) -> str: @@ -371,20 +336,17 @@ def hkl_index_limits(cif: CifContainer) -> str: f'{less_or_equal} l {less_or_equal} {limit_l_max}' @staticmethod - def get_radiation(cif: CifContainer) -> RichText: - rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) - radiation = RichText(rad_element) - radiation.add(radtype, italic=True) - radiation.add(radline, italic=True, subscript=True) - return radiation - - @staticmethod - def get_completeness(cif: CifContainer) -> str: + def get_from_to_theta_range(cif: CifContainer) -> str: + theta_min = cif['_diffrn_reflns_theta_min'] + theta_max = cif['_diffrn_reflns_theta_max'] + radiation_wavelength = cif['_diffrn_radiation_wavelength'] try: - completeness = f"{float(cif['_diffrn_measured_fraction_theta_full']) * 100:.1f}{protected_space}%" + d_max = f' ({float(radiation_wavelength) / (2 * sin(radians(float(theta_max)))):.2f}' \ + f'{protected_space}{angstrom})' + # 2theta range: + return f"{2 * float(theta_min):.2f} to {2 * float(theta_max):.2f}{d_max}" except ValueError: - completeness = '?' - return completeness + return '? to ?' @staticmethod def get_diff_density_min(cif: CifContainer) -> str: @@ -442,6 +404,21 @@ def get_integration_program(self, cif: CifContainer) -> str: self.literature['absorption'] = CrysalisProReference(version=version, year=year) return integration_prog + def _get_scaling_program(self, absdetails: str) -> tuple[str, str]: + scale_prog = '' + version = '' + if 'SADABS' in absdetails.upper() or 'TWINABS' in absdetails.upper(): + if len(absdetails.split()) > 1: + version = absdetails.split()[1] + else: + version = 'unknown version' + if 'SADABS' in absdetails: + scale_prog = 'SADABS' + else: + scale_prog = 'TWINABS' + self.literature['absorption'] = SadabsTwinabsReference() + return scale_prog, version + def get_absortion_correction_program(self, cif: CifContainer) -> str: absdetails = cif['_exptl_absorpt_process_details'].replace('-', ' ').replace(':', '') bruker_scaling = cif['_computing_bruker_data_scaling'].replace('-', ' ').replace(':', '') @@ -458,35 +435,10 @@ def get_absortion_correction_program(self, cif: CifContainer) -> str: return scale_prog - def _get_scaling_program(self, absdetails: str) -> tuple[str, str]: - scale_prog = '' - version = '' - if 'SADABS' in absdetails.upper() or 'TWINABS' in absdetails.upper(): - if len(absdetails.split()) > 1: - version = absdetails.split()[1] - else: - version = 'unknown version' - if 'SADABS' in absdetails: - scale_prog = 'SADABS' - else: - scale_prog = 'TWINABS' - self.literature['absorption'] = SadabsTwinabsReference() - return scale_prog, version - def solution_method(self, cif: CifContainer) -> str: solution_method = gstr(cif['_atom_sites_solution_primary']) or '??' return solution_method.strip('\n\r') - def solution_program(self, cif: CifContainer) -> str: - solution_prog = gstr(cif['_computing_structure_solution']) or '??' - if solution_prog.upper().startswith(('SHELXT', 'XT')): - self.literature['solution'] = SHELXTReference() - if 'SHELXS' in solution_prog.upper(): - self.literature['solution'] = SHELXSReference() - if 'SHELXD' in solution_prog.upper(): - self.literature['solution'] = SHELXDReference() - return solution_prog.split()[0] - def refinement_prog(self, cif: CifContainer) -> str: refined = gstr(cif['_computing_structure_refinement']) or '??' if 'SHELXL' in refined.upper() or 'XL' in refined.upper(): @@ -498,7 +450,22 @@ def refinement_prog(self, cif: CifContainer) -> str: self.literature['refinement'] = Nosphera2Reference() return refined.split()[0] - def get_atomic_coordinates(self, cif: CifContainer): + def solution_program(self, cif: CifContainer) -> str: + solution_prog = gstr(cif['_computing_structure_solution']) or '??' + if solution_prog.upper().startswith(('SHELXT', 'XT')): + self.literature['solution'] = SHELXTReference() + if 'SHELXS' in solution_prog.upper(): + self.literature['solution'] = SHELXSReference() + if 'SHELXD' in solution_prog.upper(): + self.literature['solution'] = SHELXDReference() + return solution_prog.split()[0] + + def _tvalue(self, tval: str) -> str: + with suppress(ValueError): + return f'{float(tval):.3f}' + return tval + + def get_atomic_coordinates(self, cif: CifContainer) -> Iterator[Dict[str:str]]: for at in cif.atoms(without_h=False): yield {'label': at.label, 'type' : at.type, @@ -509,27 +476,142 @@ def get_atomic_coordinates(self, cif: CifContainer): 'occ' : at.occ.replace('-', minus_sign), 'u_eq' : at.u_eq.replace('-', minus_sign)} - def get_displacement_parameters(self, cif: CifContainer): + def get_displacement_parameters(self, cif: CifContainer) -> Iterator[AdpWithMinus]: """ Yields the anisotropic displacement parameters. With hypehens replaced to minus signs. """ - adp = namedtuple('adp', ('label', 'U11', 'U22', 'U33', 'U23', 'U13', 'U12')) for label, u11, u22, u33, u23, u13, u12 in cif.displacement_parameters(): - yield adp(label=label, - U11=u11.replace('-', minus_sign), - U22=u22.replace('-', minus_sign), - U33=u33.replace('-', minus_sign), - U12=u12.replace('-', minus_sign), - U13=u13.replace('-', minus_sign), - U23=u23.replace('-', minus_sign)) + yield AdpWithMinus(label=label, + U11=u11.replace('-', minus_sign), + U22=u22.replace('-', minus_sign), + U33=u33.replace('-', minus_sign), + U12=u12.replace('-', minus_sign), + U13=u13.replace('-', minus_sign), + U23=u23.replace('-', minus_sign)) - def _tvalue(self, tval: str) -> str: - with suppress(ValueError): - return f'{float(tval):.3f}' - return tval - def get_crystallization_method(self, cif): - return gstr(cif['_exptl_crystal_recrystallization_method']) or '[No crystallization method given!]' +class HtmlFormatter(Formatter): + def __init__(self, options: Options): + super().__init__(options) + + def get_bonds_and_angles(self): + return self._bonds_angles.bonds_as_string + + def get_bonds(self): + raise self._bonds_angles.bonds_as_string + + def get_angles(self): + raise self._bonds_angles.angles_as_string + + def space_group_subdoc(self, cif: CifContainer, _: None) -> str: + s = SpaceGroups() + try: + spgrxml = s.to_mathml(cif.space_group) + except KeyError: + spgrxml = '?' + return f'{spgrxml} ({cif.spgr_number})' + + def make_picture(self, options: Options, picfile: Path, _: None): + picture_path = '' + if options.report_text and picfile and picfile.exists(): + picture_path = str(picfile.resolve()) + return (f'') + + def format_sum_formula(self, sum_formula: str) -> str: + sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] + html_text = '' + if sum_formula_group: + for _, word in enumerate(sum_formula_group): + if isnumeric(word): + html_text += f'{word}' + elif ')' in word: + html_text += f"{word.split(')')[0]})" + elif ']' in word: + html_text += f"{word.split(']')[0]})" + else: + html_text += word + if word == ',': + html_text += '; ' + return html_text + else: + return 'no formula' + + def get_radiation(cif: CifContainer) -> str: + rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) + radiation = f'{rad_element}{radtype}{radline}' + return radiation + + +class RichTextFormatter(Formatter): + + def __init__(self): + super().__init__() + + def get_bonds(self) -> List[Dict[str, RichText]]: + raise self._bonds_angles.bonds_richtext + + def get_angles(self) -> List[Dict[str, RichText]]: + return self._bonds_angles.angles_richtext + + def format_sum_formula(self, sum_formula: str) -> RichText: + sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] + richtext = RichText('') + if sum_formula_group: + for _, word in enumerate(sum_formula_group): + if isnumeric(word): + richtext.add(word, subscript=True) + elif ')' in word: + richtext.add(word.split(')')[0], subscript=True) + richtext.add(')') + elif ']' in word: + richtext.add(word.split(']')[0], subscript=True) + richtext.add(']') + else: + richtext.add(word) + if word == ',': + richtext.add(' ') + return richtext + else: + return RichText('no formula') + + @staticmethod + def space_group_subdoc(cif: CifContainer, tpl_doc: DocxTemplate) -> Subdoc: + """ + Generates a Subdoc subdocument with the xml code for a math element in MSWord. + """ + s = SpaceGroups() + try: + spgrxml = s.to_mathml(cif.space_group) + except KeyError: + spgrxml = '?' + spgr_word = math_to_word(spgrxml) + # I have to create a subdocument in order to add the xml: + sd = tpl_doc.new_subdoc() + p: Paragraph = sd.add_paragraph() + p.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT + p._element.append(spgr_word) + try: + p.add_run(f' ({cif.spgr_number})') + except AttributeError: + pass + return sd + + @staticmethod + def get_radiation(cif: CifContainer) -> RichText: + rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) + radiation = RichText(rad_element) + radiation.add(radtype, italic=True) + radiation.add(radline, italic=True, subscript=True) + return radiation + + @staticmethod + def get_completeness(cif: CifContainer) -> str: + try: + completeness = f"{float(cif['_diffrn_measured_fraction_theta_full']) * 100:.1f}{protected_space}%" + except ValueError: + completeness = '?' + return completeness def make_picture(self, options: Options, picfile: Path, tpl_doc: DocxTemplate): if options.report_text and picfile and picfile.exists(): @@ -589,20 +671,21 @@ def prepare_report_data(self, cif: CifContainer, options: Options, picfile: Path class TemplatedReport(): + def __init__(self, format: str): + self.format = format + self.text_formatter = text_factory[self.format] + def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate = None): - ba = BondsAndAngles(cif, without_h=options.without_h) - t = TorsionAngles(cif, without_h=options.without_h) - h = HydrogenBonds(cif) context = {'options' : options, # {'without_h': True, 'atoms_table': True, 'text': True, 'bonds_table': True}, 'cif' : cif, 'name' : cif.block.name, - 'space_group' : self.space_group_subdoc(tpl_doc, cif), - 'structure_figure' : self.make_picture(options, picfile, tpl_doc), - 'crystallization_method' : self.get_crystallization_method(cif), - 'sum_formula' : self.format_sum_formula( + 'space_group' : self.text_formatter.space_group_subdoc(cif, tpl_doc), + 'structure_figure' : self.text_formatter.make_picture(options, picfile, tpl_doc), + 'crystallization_method' : self.text_formatter.get_crystallization_method(cif), + 'sum_formula' : self.text_formatter.format_sum_formula( cif['_chemical_formula_sum'].replace(" ", "")), - 'moiety_formula' : self.format_sum_formula( + 'moiety_formula' : self.text_formatter.format_sum_formula( cif['_chemical_formula_moiety'].replace(" ", "")), 'itnum' : cif['_space_group_IT_number'], 'crystal_size' : this_or_quest(cif['_exptl_crystal_size_min']) + timessym + @@ -610,9 +693,9 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do this_or_quest(cif['_exptl_crystal_size_max']), 'crystal_colour' : this_or_quest(cif['_exptl_crystal_colour']), 'crystal_shape' : this_or_quest(cif['_exptl_crystal_description']), - 'radiation' : self.get_radiation(cif), + 'radiation' : self.text_formatter.get_radiation(cif), 'wavelength' : cif['_diffrn_radiation_wavelength'], - 'theta_range' : self.get_from_to_theta_range(cif), + 'theta_range' : self.text_formatter.get_from_to_theta_range(cif), 'diffr_type' : gstr(cif['_diffrn_measurement_device_type']) or '[No measurement device type given]', 'diffr_device' : gstr(cif['_diffrn_measurement_device']) @@ -624,39 +707,41 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do 'detector' : gstr(cif['_diffrn_detector_type']) \ or '[No detector type given]', 'lowtemp_dev' : MachineType._get_cooling_device(cif), - 'index_ranges' : self.hkl_index_limits(cif), + 'index_ranges' : self.text_formatter.hkl_index_limits(cif), 'indepentent_refl' : this_or_quest(cif['_reflns_number_total']), 'r_int' : this_or_quest(cif['_diffrn_reflns_av_R_equivalents']), 'r_sigma' : this_or_quest(cif['_diffrn_reflns_av_unetI/netI']), - 'completeness' : self.get_completeness(cif), + 'completeness' : self.text_formatter.get_completeness(cif), 'theta_full' : cif['_diffrn_reflns_theta_full'], 'data' : this_or_quest(cif['_refine_ls_number_reflns']), 'restraints' : this_or_quest(cif['_refine_ls_number_restraints']), 'parameters' : this_or_quest(cif['_refine_ls_number_parameters']), 'goof' : this_or_quest(cif['_refine_ls_goodness_of_fit_ref']), - 't_min' : self._tvalue(this_or_quest(cif['_exptl_absorpt_correction_T_min'])), - 't_max' : self._tvalue(this_or_quest(cif['_exptl_absorpt_correction_T_max'])), + 't_min' : self.text_formatter._tvalue( + this_or_quest(cif['_exptl_absorpt_correction_T_min'])), + 't_max' : self.text_formatter._tvalue( + this_or_quest(cif['_exptl_absorpt_correction_T_max'])), 'ls_R_factor_gt' : this_or_quest(cif['_refine_ls_R_factor_gt']), 'ls_wR_factor_gt' : this_or_quest(cif['_refine_ls_wR_factor_gt']), 'ls_R_factor_all' : this_or_quest(cif['_refine_ls_R_factor_all']), 'ls_wR_factor_ref' : this_or_quest(cif['_refine_ls_wR_factor_ref']), - 'diff_dens_min' : self.get_diff_density_min(cif).replace('-', minus_sign), - 'diff_dens_max' : self.get_diff_density_max(cif).replace('-', minus_sign), - 'exti' : self.get_exti(cif), - 'flack_x' : self.get_flackx(cif), - 'integration_progr' : self.get_integration_program(cif), + 'diff_dens_min' : self.text_formatter.get_diff_density_min(cif).replace('-', minus_sign), + 'diff_dens_max' : self.text_formatter.get_diff_density_max(cif).replace('-', minus_sign), + 'exti' : self.text_formatter.get_exti(cif), + 'flack_x' : self.text_formatter.get_flackx(cif), + 'integration_progr' : self.text_formatter.get_integration_program(cif), 'abstype' : gstr(cif['_exptl_absorpt_correction_type']) or '??', - 'abs_details' : self.get_absortion_correction_program(cif), - 'solution_method' : self.solution_method(cif), - 'solution_program' : self.solution_program(cif), + 'abs_details' : self.text_formatter.get_absortion_correction_program(cif), + 'solution_method' : self.text_formatter.solution_method(cif), + 'solution_program' : self.text_formatter.solution_program(cif), 'refinement_details' : ' '.join( cif['_refine_special_details'].splitlines(keepends=False)).strip(), - 'refinement_prog' : self.refinement_prog(cif), - 'atomic_coordinates' : self.get_atomic_coordinates(cif), - 'displacement_parameters': self.get_displacement_parameters(cif), - 'bonds' : ba.bonds, - 'angles' : ba.angles, - 'ba_symminfo' : ba.symminfo, + 'refinement_prog' : self.text_formatter.refinement_prog(cif), + 'atomic_coordinates' : self.text_formatter.get_atomic_coordinates(cif), + 'displacement_parameters': self.text_formatter.get_displacement_parameters(cif), + 'bonds' : self.text_formatter.get_bonds(), + 'angles' : self.text_formatter.get_angles(), + 'ba_symminfo' : self.text_formatter.get_bonds_angles_symminfo(), 'torsions' : t.torsion_angles, 'torsion_symminfo' : t.symminfo, 'hydrogen_bonds' : h.hydrogen_bonds, From 3fa045d7641e08e9679723df46034b99602c0650 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Thu, 29 Feb 2024 16:08:30 +0100 Subject: [PATCH 03/16] more report improvements --- .gitignore | 1 + finalcif/appwindow.py | 8 +- finalcif/report/templated_report.py | 157 +++++++++++++++++----------- finalcif/template/report.tmpl | 41 ++++---- 4 files changed, 123 insertions(+), 84 deletions(-) diff --git a/.gitignore b/.gitignore index 74000842..badaa78b 100644 --- a/.gitignore +++ b/.gitignore @@ -217,3 +217,4 @@ olex2 x64 test-data/1000007-multi-multitable.docx test-data/report_1000007-multi-finalcif.docx +test.html diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 9a6f146f..1f286852 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -47,6 +47,7 @@ from finalcif.gui.plaintextedit import MyQPlainTextEdit from finalcif.gui.text_value_editor import MyTextTemplateEdit, TextEditItem from finalcif.gui.vrf_classes import MyVRFContainer, VREF +from finalcif.report.templated_report import TextFormat from finalcif.template.templates import ReportTemplates from finalcif.tools.download import MyDownloader from finalcif.tools.dsrmath import my_isnumeric @@ -1125,9 +1126,10 @@ def make_report_tables(self) -> None: make_multi_tables(cif=self.cif, output_filename=str(multi_table_document)) else: print('Report with templates') - t = TemplatedReport() - ok = t.make_templated_docx_report(options=self.options, cif=self.cif, - output_filename=str(report_filename), picfile=picfile, + t = TemplatedReport(format=TextFormat.RICHTEXT, options=self.options, cif=self.cif) + ok = t.make_templated_docx_report(options=self.options, + output_filename=str(report_filename), + picfile=picfile, template_path=Path(self.get_checked_templates_list_text())) if not ok: return None diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index bbb1a402..80400693 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -1,5 +1,7 @@ import dataclasses +import enum import itertools +import os import re from collections import namedtuple from contextlib import suppress @@ -145,12 +147,12 @@ def __init__(self, cif: CifContainer, without_h: bool): self.without_h = without_h self._symmlist = {} self.torsion_angles_as_string: List[Torsion] = [] - self.torsion_angles = self._get_torsion_angles_list(without_h) + self.torsion_angles_as_richtext = self._get_torsion_angles_list(without_h) # The list of symmetry elements at the table end used for generated atoms: self.symminfo: str = get_symminfo(self._symmlist) def __len__(self): - return len(self.torsion_angles) + return len(self.torsion_angles_as_richtext) @property def symmetry_generated_atoms_used(self): @@ -290,7 +292,7 @@ def symmsearch(cif: CifContainer, newsymms: Dict[int, str], num: int, class Formatter: - def __init__(self, options: Options) -> None: + def __init__(self, options: Options, cif: CifContainer) -> None: self.literature = {'finalcif' : FinalCifReference(), 'ccdc' : CCDCReference(), 'absorption' : '[no reference found]', @@ -303,12 +305,18 @@ def __init__(self, options: Options) -> None: self._hydrogens = HydrogenBonds(cif) def get_bonds(self): - raise NotImplemented + raise NotImplementedError def get_angles(self): - raise NotImplemented + raise NotImplementedError - def get_bonds_angles_symminfo(self): + def get_torsion_angles(self) -> list[dict[str, RichText]] | list[Torsion]: + raise NotImplementedError + + def get_hydrogen_bonds(self) -> list[dict[str, RichText]] | list[HydrogenBond]: + raise NotImplementedError + + def get_bonds_angles_symminfo(self) -> str: return self._bonds_angles.symminfo def get_torsion_symminfo(self): @@ -320,6 +328,9 @@ def get_hydrogen_symminfo(self): def get_crystallization_method(self, cif): return gstr(cif['_exptl_crystal_recrystallization_method']) or '[No crystallization method given!]' + def get_radiation(self, cif: CifContainer) -> str: + raise NotImplementedError + @staticmethod def hkl_index_limits(cif: CifContainer) -> str: limit_h_min = cif['_diffrn_reflns_limit_h_min'] @@ -465,7 +476,7 @@ def _tvalue(self, tval: str) -> str: return f'{float(tval):.3f}' return tval - def get_atomic_coordinates(self, cif: CifContainer) -> Iterator[Dict[str:str]]: + def get_atomic_coordinates(self, cif: CifContainer) -> Iterator[dict[str, str]]: for at in cif.atoms(without_h=False): yield {'label': at.label, 'type' : at.type, @@ -489,19 +500,29 @@ def get_displacement_parameters(self, cif: CifContainer) -> Iterator[AdpWithMinu U13=u13.replace('-', minus_sign), U23=u23.replace('-', minus_sign)) + def get_completeness(self, cif: CifContainer) -> str: + try: + completeness = f"{float(cif['_diffrn_measured_fraction_theta_full']) * 100:.1f}{protected_space}%" + except ValueError: + completeness = '?' + return completeness + class HtmlFormatter(Formatter): - def __init__(self, options: Options): - super().__init__(options) + def __init__(self, options: Options, cif: CifContainer): + super().__init__(options, cif) - def get_bonds_and_angles(self): + def get_bonds(self) -> list[Bond]: return self._bonds_angles.bonds_as_string - def get_bonds(self): - raise self._bonds_angles.bonds_as_string + def get_angles(self) -> list[Angle]: + return self._bonds_angles.angles_as_string - def get_angles(self): - raise self._bonds_angles.angles_as_string + def get_torsion_angles(self) -> list[Torsion]: + return self._torsions.torsion_angles_as_string + + def get_hydrogen_bonds(self) -> list[HydrogenBond]: + return self._hydrogens.hydrogen_bonds_as_str def space_group_subdoc(self, cif: CifContainer, _: None) -> str: s = SpaceGroups() @@ -537,7 +558,7 @@ def format_sum_formula(self, sum_formula: str) -> str: else: return 'no formula' - def get_radiation(cif: CifContainer) -> str: + def get_radiation(self, cif: CifContainer) -> str: rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) radiation = f'{rad_element}{radtype}{radline}' return radiation @@ -545,15 +566,21 @@ def get_radiation(cif: CifContainer) -> str: class RichTextFormatter(Formatter): - def __init__(self): - super().__init__() + def __init__(self, options: Options, cif: CifContainer) -> None: + super().__init__(options, cif) def get_bonds(self) -> List[Dict[str, RichText]]: - raise self._bonds_angles.bonds_richtext + return self._bonds_angles.bonds_richtext def get_angles(self) -> List[Dict[str, RichText]]: return self._bonds_angles.angles_richtext + def get_torsion_angles(self) -> List[Dict[str, RichText]]: + return self._torsions.torsion_angles_as_richtext + + def get_hydrogen_bonds(self) -> list[dict[str, RichText]]: + return self._hydrogens.hydrogen_bonds + def format_sum_formula(self, sum_formula: str) -> RichText: sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] richtext = RichText('') @@ -597,31 +624,47 @@ def space_group_subdoc(cif: CifContainer, tpl_doc: DocxTemplate) -> Subdoc: pass return sd - @staticmethod - def get_radiation(cif: CifContainer) -> RichText: + def get_radiation(self, cif: CifContainer) -> RichText: rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) radiation = RichText(rad_element) radiation.add(radtype, italic=True) radiation.add(radline, italic=True, subscript=True) return radiation - @staticmethod - def get_completeness(cif: CifContainer) -> str: - try: - completeness = f"{float(cif['_diffrn_measured_fraction_theta_full']) * 100:.1f}{protected_space}%" - except ValueError: - completeness = '?' - return completeness - def make_picture(self, options: Options, picfile: Path, tpl_doc: DocxTemplate): if options.report_text and picfile and picfile.exists(): return InlineImage(tpl_doc, str(picfile.resolve()), width=Cm(options.picture_width)) return None - def make_templated_docx_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, + def prepare_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, + template_path: Path) -> bool: + context = self._get_context(cif, options, picfile, tpl_doc) + + +class TextFormat(enum.StrEnum): + RICHTEXT = 'richtext' + HTML = 'html' + + +def text_factory(options: Options, cif: CifContainer) -> dict[str, Formatter]: + factory = { + TextFormat.RICHTEXT: RichTextFormatter(options, cif), + TextFormat.HTML : HtmlFormatter(options, cif), + # 'plaintext': StringFormatter(), + } + return factory + + +class TemplatedReport(): + def __init__(self, format: str, options: Options, cif: CifContainer) -> None: + self.format = format + self.cif = cif + self.text_formatter = text_factory(options, cif)[self.format] + + def make_templated_docx_report(self, options: Options, output_filename: str, picfile: Path, template_path: Path) -> bool: tpl_doc = DocxTemplate(template_path) - context, tpl_doc = self.prepare_report_data(cif, options, picfile, tpl_doc) + context, tpl_doc = self.prepare_report_data(self.cif, options, picfile, tpl_doc) # Filter definition for {{foobar|filter}} things: jinja_env = jinja2.Environment() jinja_env.filters['inv_article'] = get_inf_article @@ -634,27 +677,20 @@ def make_templated_docx_report(self, options: Options, cif: CifContainer, output info_text=str(e)) return False - def prepare_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, - template_path: Path) -> bool: - context = self._get_context(cif, options, picfile, tpl_doc) - - def do_html_report(self, context: dict): - pass - def prepare_report_data(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate): maincontext = {} if not cif.is_multi_cif: - maincontext = self._get_context(cif, options, picfile, tpl_doc) + maincontext = self.get_context(cif, options, picfile, tpl_doc) else: current_block = cif.block.name current_file = cif.fileobj - maincontext.update(self._get_context(cif, options, picfile, tpl_doc)) + maincontext.update(self.get_context(cif, options, picfile, tpl_doc)) block_list = [] blocks = {} for block in cif.doc: cif2 = CifContainer(file=current_file) cif2.load_block_by_name(block.name) - context = self._get_context(cif2, options, picfile, tpl_doc) + context = self.get_context(cif2, options, picfile, tpl_doc) block_list.append(context) blocks[block.name] = context maincontext['blocklist'] = block_list @@ -662,19 +698,6 @@ def prepare_report_data(self, cif: CifContainer, options: Options, picfile: Path cif.load_block_by_name(current_block) return maincontext, tpl_doc - -text_factory = { - richtext : RichTextFormatter(), - html : HtmlFormatter(), - plaintext: StringFormatter(), -} - - -class TemplatedReport(): - def __init__(self, format: str): - self.format = format - self.text_formatter = text_factory[self.format] - def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate = None): context = {'options' : options, # {'without_h': True, 'atoms_table': True, 'text': True, 'bonds_table': True}, @@ -742,11 +765,11 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do 'bonds' : self.text_formatter.get_bonds(), 'angles' : self.text_formatter.get_angles(), 'ba_symminfo' : self.text_formatter.get_bonds_angles_symminfo(), - 'torsions' : t.torsion_angles, - 'torsion_symminfo' : t.symminfo, - 'hydrogen_bonds' : h.hydrogen_bonds, - 'hydrogen_symminfo' : h.symminfo, - 'literature' : self.literature + 'torsions' : self.text_formatter.get_torsion_angles(), + 'torsion_symminfo' : self.text_formatter.get_torsion_symminfo(), + 'hydrogen_bonds' : self.text_formatter.get_hydrogen_bonds(), + 'hydrogen_symminfo' : self.text_formatter.get_hydrogen_symminfo(), + 'literature' : self.text_formatter.literature } return context @@ -754,10 +777,22 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do if __name__ == '__main__': from unittest import mock from pprint import pprint + import subprocess data = Path('tests') testcif = (data / 'examples/1979688.cif').absolute() cif = CifContainer(testcif) - t = TemplatedReport() - maincontext = t._get_context(cif, options=mock.Mock(), picfile=None, tpl_doc=mock.Mock()) - pprint(maincontext) + t = TemplatedReport(format=TextFormat.HTML, options=mock.Mock(), cif=cif) + maincontext = t.get_context(cif, options=mock.Mock(), picfile=None, tpl_doc=mock.Mock()) + #pprint(maincontext) + templateLoader = jinja2.FileSystemLoader(searchpath=r"D:\_DEV\GitHub\FinalCif\finalcif\template") + jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False) + jinja_env.filters['inv_article'] = get_inf_article + TEMPLATE_FILE = "report.tmpl" + template = jinja_env.get_template(TEMPLATE_FILE) + outputText = template.render(maincontext) + + print(outputText) + p = Path('test.html') + p.write_text(outputText, encoding="utf-8") + subprocess.Popen(['explorer', str(p.resolve())], shell=True) \ No newline at end of file diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index 76a93fea..f5f4b875 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -5,10 +5,11 @@ Structure Tables -{% if structure_figure%} - +{% if structure_figure %} + {%endif%} +

Structure Tables

A {{crystal_colour}}, {{crystal_shape}} shaped crystal was mounted on a {{ cif._diffrn_measurement_specimen_support }} with perfluoroether oil. The sample was crystallized {{crystallization_method}}. Data for {{cif.block.name}} were collected from a shock-cooled single crystal at {{cif._diffrn_ambient_temperature}} K @@ -30,7 +31,7 @@ - + @@ -39,7 +40,7 @@ Formula weight {{cif._chemical_formula_weight }} Temperature [K] {{cif._diffrn_ambient_temperature}} Crystal system {{ cif._space_group_crystal_system }} - Space group (number) {{p space_group}} {{ itnum }} + Space group (number) {{space_group}} {{ itnum }} a [Å] {{cif._cell_length_a }} b [Å] {{cif._cell_length_b }} c [Å] {{cif._cell_length_c }} @@ -72,47 +73,47 @@ [all data] R1 = {{ls_R_factor_all}} wR2 = {{ls_wR_factor_ref}} Largest peak/hole [eÅ−3] {{diff_dens_max}}/{{diff_dens_min}} - {%tr if exti %} + {% if exti %} Extinction coefficient {{exti}} - {%tr endif %} - {%tr if flack_x %} + {% endif %} + {% if flack_x %} Flack X parameter {{flack_x}} - {%tr endif %} + {% endif %}
CCDC number{{ cif._database_code_depnum_ccdc_archive{{ cif._database_code_depnum_ccdc_archive }}
Empirical formula
{% if refinement_details %}Refinement details for {{ cif.block.name }} {{ refinement_details }} {% endif %}{% if atomic_coordinates %} Table 2. Atomic coordinates and Ueq [Å2] for {{ cif.block.name }} Atom x y z Ueq -{%tr for atom in atomic_coordinates %} +{% for atom in atomic_coordinates %} {{ atom.label }} {{ atom.x }} {{ atom.y }} {{ atom.z }} {{ atom.u_eq }} -{%tr endfor %} +{% endfor %} Ueq is defined as 1/3 of the trace of the orthogonalized Uij tensor. {% endif %}{% if displacement_parameters %} Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the form: −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] Atom U11 U22 U33 U23 U13 U12 -{%tr for atom in displacement_parameters %} +{% for atom in displacement_parameters %} {{ atom.label }} {{ atom.U11 }} {{ atom.U22 }} {{ atom.U33 }} {{ atom.U23 }} {{ atom.U13 }} {{ atom.U12 }} -{%tr endfor %} +{% endfor %} {% endif %}{% if options.bonds_table %}{%if bonds%} Table 4. Bond lengths and angles for {{ cif.block.name }} Atom–Atom Length [Å] -{%tr for b in bonds %} +{% for b in bonds %} {{b.atoms}} {{b.dist}} -{%tr endfor %} +{% endfor %} Atom–Atom–Atom Angle [°] -{%tr for a in angles %} +{% for a in angles %} {{a.atoms}} {{a.angle}} -{%tr endfor %} +{% endfor %} {% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if ba_symminfo%} {{ ba_symminfo}}{%endif%}{%endif%} {% endif %}{%if torsions%} Table 5. Torsion angles for {{ cif.block.name }} Atom–Atom–Atom–Atom Torsion Angle [°] -{%tr for t in torsions %} +{% for t in torsions %} {{t.atoms}} {{t.angle}} -{%tr endfor %} +{% endfor %} {% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if torsion_symminfo%} {{ torsion_symminfo}}{%endif%}{%endif%} @@ -120,9 +121,9 @@ Atom–Atom–Atom–Atom Torsion Angle [°] {% if options.hydrogen_bonds and hydrogen_bonds %} Table 6. Hydrogen bonds for {{ cif.block.name }} D–H⋯A [Å] d(D–H) [Å] d(H⋯A) [Å] d(D⋯A) [Å] <(DHA) [°] -{%tr for h in hydrogen_bonds %} +{% for h in hydrogen_bonds %} {{h.atoms}} {{h.dist_dh}} {{h.dist_ha}} {{h.dist_da}} {{h.angle_dha}} -{%tr endfor %} +{% endfor %} {%if hydrogen_symminfo%}{{hydrogen_symminfo}}{%endif%}{%endif%} Bibliography [1] {{literature.integration.richtext}} From 53cebff632ecc212ab8cc78e716543d01209f9d9 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Thu, 29 Feb 2024 19:22:16 +0100 Subject: [PATCH 04/16] atoms table --- finalcif/report/templated_report.py | 63 +++-- finalcif/template/report.tmpl | 391 +++++++++++++++++++--------- 2 files changed, 313 insertions(+), 141 deletions(-) diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 80400693..27decd51 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -3,6 +3,7 @@ import itertools import os import re +import sys from collections import namedtuple from contextlib import suppress from math import sin, radians @@ -331,20 +332,8 @@ def get_crystallization_method(self, cif): def get_radiation(self, cif: CifContainer) -> str: raise NotImplementedError - @staticmethod - def hkl_index_limits(cif: CifContainer) -> str: - limit_h_min = cif['_diffrn_reflns_limit_h_min'] - limit_h_max = cif['_diffrn_reflns_limit_h_max'] - limit_k_min = cif['_diffrn_reflns_limit_k_min'] - limit_k_max = cif['_diffrn_reflns_limit_k_max'] - limit_l_min = cif['_diffrn_reflns_limit_l_min'] - limit_l_max = cif['_diffrn_reflns_limit_l_max'] - return f'{minus_sign if limit_h_min != "0" else ""}{limit_h_min.replace("-", "")} ' \ - f'{less_or_equal} h {less_or_equal} {limit_h_max}\n' \ - + f'{minus_sign if limit_k_min != "0" else ""}{limit_k_min.replace("-", "")} ' \ - f'{less_or_equal} k {less_or_equal} {limit_k_max}\n' \ - + f'{minus_sign if limit_l_min != "0" else ""}{limit_l_min.replace("-", "")} ' \ - f'{less_or_equal} l {less_or_equal} {limit_l_max}' + def hkl_index_limits(self, cif: CifContainer) -> str: + raise NotImplementedError @staticmethod def get_from_to_theta_range(cif: CifContainer) -> str: @@ -457,7 +446,7 @@ def refinement_prog(self, cif: CifContainer) -> str: if 'OLEX' in refined.upper(): self.literature['refinement'] = Olex2Reference() if ('NOSPHERA2' in refined.upper() or 'NOSPHERA2' in cif['_refine_special_details'].upper() or - 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): + 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): self.literature['refinement'] = Nosphera2Reference() return refined.split()[0] @@ -536,8 +525,8 @@ def make_picture(self, options: Options, picfile: Path, _: None): picture_path = '' if options.report_text and picfile and picfile.exists(): picture_path = str(picfile.resolve()) - return (f'') + return picture_path # (f'') def format_sum_formula(self, sum_formula: str) -> str: sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] @@ -560,9 +549,23 @@ def format_sum_formula(self, sum_formula: str) -> str: def get_radiation(self, cif: CifContainer) -> str: rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) - radiation = f'{rad_element}{radtype}{radline}' + radiation = f'{rad_element}{radtype}{radline}' return radiation + def hkl_index_limits(self, cif: CifContainer) -> str: + limit_h_min = cif['_diffrn_reflns_limit_h_min'] + limit_h_max = cif['_diffrn_reflns_limit_h_max'] + limit_k_min = cif['_diffrn_reflns_limit_k_min'] + limit_k_max = cif['_diffrn_reflns_limit_k_max'] + limit_l_min = cif['_diffrn_reflns_limit_l_min'] + limit_l_max = cif['_diffrn_reflns_limit_l_max'] + return (f'{minus_sign if limit_h_min != "0" else ""}{limit_h_min.replace("-", "")} ' + f'{less_or_equal} h {less_or_equal} {limit_h_max}
' + f'{minus_sign if limit_k_min != "0" else ""}{limit_k_min.replace("-", "")} ' + f'{less_or_equal} k {less_or_equal} {limit_k_max}
' + f'{minus_sign if limit_l_min != "0" else ""}{limit_l_min.replace("-", "")} ' + f'{less_or_equal} l {less_or_equal} {limit_l_max}') + class RichTextFormatter(Formatter): @@ -640,6 +643,20 @@ def prepare_report(self, options: Options, cif: CifContainer, output_filename: s template_path: Path) -> bool: context = self._get_context(cif, options, picfile, tpl_doc) + def hkl_index_limits(self, cif: CifContainer) -> str: + limit_h_min = cif['_diffrn_reflns_limit_h_min'] + limit_h_max = cif['_diffrn_reflns_limit_h_max'] + limit_k_min = cif['_diffrn_reflns_limit_k_min'] + limit_k_max = cif['_diffrn_reflns_limit_k_max'] + limit_l_min = cif['_diffrn_reflns_limit_l_min'] + limit_l_max = cif['_diffrn_reflns_limit_l_max'] + return (f'{minus_sign if limit_h_min != "0" else ""}{limit_h_min.replace("-", "")} ' + f'{less_or_equal} h {less_or_equal} {limit_h_max}\n' + f'{minus_sign if limit_k_min != "0" else ""}{limit_k_min.replace("-", "")} ' + f'{less_or_equal} k {less_or_equal} {limit_k_max}\n' + f'{minus_sign if limit_l_min != "0" else ""}{limit_l_min.replace("-", "")} ' + f'{less_or_equal} l {less_or_equal} {limit_l_max}') + class TextFormat(enum.StrEnum): RICHTEXT = 'richtext' @@ -784,8 +801,8 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do cif = CifContainer(testcif) t = TemplatedReport(format=TextFormat.HTML, options=mock.Mock(), cif=cif) maincontext = t.get_context(cif, options=mock.Mock(), picfile=None, tpl_doc=mock.Mock()) - #pprint(maincontext) - templateLoader = jinja2.FileSystemLoader(searchpath=r"D:\_DEV\GitHub\FinalCif\finalcif\template") + # pprint(maincontext) + templateLoader = jinja2.FileSystemLoader(searchpath=r"finalcif/template") jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False) jinja_env.filters['inv_article'] = get_inf_article TEMPLATE_FILE = "report.tmpl" @@ -795,4 +812,8 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do print(outputText) p = Path('test.html') p.write_text(outputText, encoding="utf-8") - subprocess.Popen(['explorer', str(p.resolve())], shell=True) \ No newline at end of file + if sys.platform == 'darwin': + subprocess.call(['open', str(p.resolve())]) + else: + subprocess.Popen(['explorer', str(p.resolve())], shell=True) + print(maincontext.get('')) diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index f5f4b875..7ba5b7e1 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -2,137 +2,288 @@ + Structure Tables + - -{% if structure_figure %} + + +

+ {% if structure_figure %} -{%endif%} + {%endif%} -

Structure Tables

-

- A {{crystal_colour}}, {{crystal_shape}} shaped crystal was mounted on a {{ cif._diffrn_measurement_specimen_support }} with perfluoroether - oil. The sample was crystallized {{crystallization_method}}. Data for {{cif.block.name}} were collected from a shock-cooled single crystal at {{cif._diffrn_ambient_temperature}} K - on {{diffr_type|inv_article}} {{diffr_type}} {{diffr_device}} with {{diffr_source|inv_article}} {{diffr_source}} using a {{monochromator}} as monochromator and - {{detector|inv_article}} {{detector}} detector. The diffractometer {% if lowtemp_dev %}was equipped with {{lowtemp_dev|inv_article}} {{lowtemp_dev}} low temperature device and - {%endif%}used {{radiation}} radiation {%if wavelength%}(λ = {{wavelength}} Å){%endif%}. All data were integrated with {{integration_progr}} and a {{abstype}} absorption correction - using {{abs_details}} was applied. [1,2] The structure was solved by {{solution_method}} methods with {{solution_program}} and refined by full-matrix least-squares methods against - F2 using {{refinement_prog}}.[3,4] All non-hydrogen atoms were refined with anisotropic displacement parameters. {%if cif.hydrogen_atoms_present%}The hydrogen atoms were refined - isotropically on calculated positions using a riding model with their Uiso values constrained to 1.5 times the Ueq of their pivot atoms for terminal sp3 carbon atoms and 1.2 times - for all other carbon atoms. {%endif%}Crystallographic data for the structures reported in this paper have been deposited with the Cambridge Crystallographic Data Centre.[5] CCDC - {%if cif._database_code_depnum_ccdc_archive%}{{cif._database_code_depnum_ccdc_archive}}{%else%}??????{%endif%} contain the supplementary crystallographic data for this paper. These - data can be obtained free of charge from The Cambridge Crystallographic Data Centre via www.ccdc.cam.ac.uk/structures. This report and the CIF file were generated using - FinalCif.[6] -

+

Structure Tables

+

+ A {{crystal_colour}}, {{crystal_shape}} shaped crystal was mounted on a {{ cif._diffrn_measurement_specimen_support }} + with perfluoroether oil. The sample was crystallized {{crystallization_method}}. Data for {{cif.block.name}} were collected + from a shock-cooled single crystal at {{cif._diffrn_ambient_temperature}} K on {{diffr_type|inv_article}} {{diffr_type}} + {{diffr_device}} with {{diffr_source|inv_article}} {{diffr_source}} using a {{monochromator}} as monochromator and + {{detector|inv_article}} {{detector}} detector. The diffractometer {% if lowtemp_dev %}was equipped with + {{lowtemp_dev|inv_article}} {{lowtemp_dev}} low temperature device and {%endif%}used {{radiation}} radiation + {%if wavelength%}(λ = {{wavelength}} Å){%endif%}. All data were integrated with {{integration_progr}} and a + {{abstype}} absorption correction using {{abs_details}} was applied.[1,2] The structure was solved by + {{solution_method}} methods with {{solution_program}} and refined by full-matrix least-squares methods against + F2 using {{refinement_prog}}.[3,4] All non-hydrogen atoms were refined with anisotropic + displacement parameters. {%if cif.hydrogen_atoms_present%}The hydrogen atoms were refined isotropically on calculated + positions using a riding model with their Uiso values constrained to 1.5 times the Ueq of their pivot + atoms for terminal sp3carbon atoms and 1.2 times for all other carbon atoms. {%endif%}Crystallographic data for the + structures reported in this paper have been deposited with the Cambridge Crystallographic Data Centre.[5] CCDC + {%if cif._database_code_depnum_ccdc_archive%}{{cif._database_code_depnum_ccdc_archive}}{%else%}??????{%endif%} + contain the supplementary crystallographic data for this paper. + These data can be obtained free of charge from The Cambridge Crystallographic Data Centre via www.ccdc.cam.ac.uk/structures. + This report and the CIF file were generated using FinalCif.[6] +

-

- Table 1. Crystal data and structure refinement for {{ cif.block.name }} -

- - - - - - - - - - Formula weight {{cif._chemical_formula_weight }} - Temperature [K] {{cif._diffrn_ambient_temperature}} - Crystal system {{ cif._space_group_crystal_system }} - Space group (number) {{space_group}} {{ itnum }} - a [Å] {{cif._cell_length_a }} - b [Å] {{cif._cell_length_b }} - c [Å] {{cif._cell_length_c }} - α [°] {{cif._cell_angle_alpha }} - β [°] {{cif._cell_angle_beta }} - γ [°] {{cif._cell_angle_gamma }} - Volume [Å3] {{cif._cell_volume}} - Z {{cif._cell_formula_units_Z}} - ρcalc [gcm−3] {{cif._exptl_crystal_density_diffrn}} - μ [mm−1] {{cif._exptl_absorpt_coefficient_mu}} - F(000) {{cif._exptl_crystal_F_000}} - Crystal size [mm3] {{crystal_size}} - Crystal colour {{crystal_colour}} - Crystal shape {{crystal_shape }} - Radiation {{radiation}}{%if wavelength%} (λ={{wavelength}} Å){%endif%} - 2θ range [°] {{theta_range}} - Index ranges {{index_ranges}} - Reflections collected {{cif._diffrn_reflns_number}} - Independent reflections {{indepentent_refl}} - Rint = {{r_int}} - Rsigma = {{r_sigma}} - Completeness{%if theta_full%} to - θ = {{theta_full}}°{%endif%} {{completeness}} - Data / Restraints / Parameters {{data}} / {{restraints}} / {{parameters}} - Goodness-of-fit on F2 {{ goof }} - Final R indexes - [I≥2σ(I)] R1 = {{ls_R_factor_gt}} - wR2 = {{ls_wR_factor_gt}} - Final R indexes - [all data] R1 = {{ls_R_factor_all}} - wR2 = {{ls_wR_factor_ref}} - Largest peak/hole [eÅ−3] {{diff_dens_max}}/{{diff_dens_min}} - {% if exti %} - Extinction coefficient {{exti}} - {% endif %} - {% if flack_x %} - Flack X parameter {{flack_x}} +

+ Table 1. Crystal data and structure refinement for {{ cif.block.name }} +

+
+
+
CCDC number{{ cif._database_code_depnum_ccdc_archive }}
Empirical formula{{ sum_formula }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if exti %} + + + + + {% endif %} + {% if flack_x %} + + + + + {% endif %} + +
CCDC number{{ cif._database_code_depnum_ccdc_archive }}
Empirical formula{{ sum_formula }}
Formula weight{{cif._chemical_formula_weight }}
Temperature [K]{{cif._diffrn_ambient_temperature}}
Crystal system{{ cif._space_group_crystal_system }}
Space group (number){{space_group}}
a [Å]{{cif._cell_length_a }}
b [Å]{{cif._cell_length_b }}
c [Å]{{cif._cell_length_c }}
α [°]{{cif._cell_angle_alpha }}
β [°]{{cif._cell_angle_beta }}
γ [°]{{cif._cell_angle_gamma }}
Volume [Å3]{{cif._cell_volume}}
Z{{cif._cell_formula_units_Z}}
ρcalc [gcm−3]{{cif._exptl_crystal_density_diffrn}}
μ [mm−1]{{cif._exptl_absorpt_coefficient_mu}}
F(000){{cif._exptl_crystal_F_000}}
Crystal size [mm3]{{crystal_size}}
Crystal colour{{crystal_colour}}
Crystal shape{{crystal_shape }}
Radiation{{radiation}}{%if wavelength%} (λ={{wavelength}} Å){%endif%}
2θ range [°]{{theta_range}}
Index ranges{{index_ranges}}
Reflections collected{{cif._diffrn_reflns_number}}
Independent reflections{{indepentent_refl}}
+ Rint = {{r_int}}
+ Rsigma = {{r_sigma}} +
Completeness{%if theta_full%} to + θ = {{theta_full}}°{%endif%} + {{completeness}}
Data / Restraints / Parameters{{data}} / {{restraints}} / {{parameters}}
Goodness-of-fit on F2{{ goof }}
Final R indexes
+ [I≥2σ(I)] +
R1 = {{ls_R_factor_gt}}
+ wR2 = {{ls_wR_factor_gt}} +
Final R indexes
+ [all data] + +
R1 = {{ls_R_factor_all}}
+ wR2 = {{ls_wR_factor_ref}} +
Largest peak/hole [eÅ−3]{{diff_dens_max}}/{{diff_dens_min}}
Extinction coefficient{{exti}}
Flack X parameter{{flack_x}}
+
+ + + {% if refinement_details %} +

+ Refinement details for {{ cif.block.name }} +

+ {{ refinement_details }} {% endif %} - -{% if refinement_details %}Refinement details for {{ cif.block.name }} -{{ refinement_details }} -{% endif %}{% if atomic_coordinates %} Table 2. Atomic coordinates and Ueq [Å2] for {{ cif.block.name }} -Atom x y z Ueq -{% for atom in atomic_coordinates %} -{{ atom.label }} {{ atom.x }} {{ atom.y }} {{ atom.z }} {{ atom.u_eq }} -{% endfor %} -Ueq is defined as 1/3 of the trace of the orthogonalized Uij tensor. -{% endif %}{% if displacement_parameters %} Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the form: -−2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] -Atom U11 U22 U33 U23 U13 U12 -{% for atom in displacement_parameters %} -{{ atom.label }} {{ atom.U11 }} {{ atom.U22 }} {{ atom.U33 }} {{ atom.U23 }} {{ atom.U13 }} {{ atom.U12 }} -{% endfor %} -{% endif %}{% if options.bonds_table %}{%if bonds%} -Table 4. Bond lengths and angles for {{ cif.block.name }} -Atom–Atom Length [Å] -{% for b in bonds %} -{{b.atoms}} {{b.dist}} -{% endfor %} -Atom–Atom–Atom Angle [°] -{% for a in angles %} -{{a.atoms}} {{a.angle}} -{% endfor %} -{% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if ba_symminfo%} -{{ ba_symminfo}}{%endif%}{%endif%} -{% endif %}{%if torsions%} -Table 5. Torsion angles for {{ cif.block.name }} + {% if atomic_coordinates %} +

+ Table 2. Atomic coordinates and Ueq2] for {{ cif.block.name }} +

+ +
+
+ + + + + + + + + + + {% for atom in atomic_coordinates %} + + + + + + + + {% endfor %} + +
AtomxyzUeq
{{ atom.label }}{{ atom.x }}{{ atom.y }}{{ atom.z }}{{ atom.u_eq }}
+
+
+ + + Ueq is defined as 1/3 of the trace of the orthogonalized Uij tensor. + {% endif %}{% if displacement_parameters %} Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the + form: + −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] + Atom U11 U22 U33 U23 U13 U12 + {% for atom in displacement_parameters %} + {{ atom.label }} {{ atom.U11 }} {{ atom.U22 }} {{ atom.U33 }} {{ atom.U23 }} {{ atom.U13 }} {{ atom.U12 }} + {% endfor %} + {% endif %}{% if options.bonds_table %}{%if bonds%} + Table 4. Bond lengths and angles for {{ cif.block.name }} + Atom–Atom Length [Å] + {% for b in bonds %} + {{b.atoms}} {{b.dist}} + {% endfor %} -Atom–Atom–Atom–Atom Torsion Angle [°] -{% for t in torsions %} -{{t.atoms}} {{t.angle}} -{% endfor %} -{% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if torsion_symminfo%} -{{ torsion_symminfo}}{%endif%}{%endif%} + Atom–Atom–Atom Angle [°] + {% for a in angles %} + {{a.atoms}} {{a.angle}} + {% endfor %} + {% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if ba_symminfo%} + {{ ba_symminfo}}{%endif%}{%endif%} + {% endif %}{%if torsions%} + Table 5. Torsion angles for {{ cif.block.name }} + Atom–Atom–Atom–Atom Torsion Angle [°] + {% for t in torsions %} + {{t.atoms}} {{t.angle}} + {% endfor %} + {% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if torsion_symminfo%} + {{ torsion_symminfo}}{%endif%}{%endif%} -{% if options.hydrogen_bonds and hydrogen_bonds %} -Table 6. Hydrogen bonds for {{ cif.block.name }} -D–H⋯A [Å] d(D–H) [Å] d(H⋯A) [Å] d(D⋯A) [Å] <(DHA) [°] -{% for h in hydrogen_bonds %} -{{h.atoms}} {{h.dist_dh}} {{h.dist_ha}} {{h.dist_da}} {{h.angle_dha}} -{% endfor %} -{%if hydrogen_symminfo%}{{hydrogen_symminfo}}{%endif%}{%endif%} -Bibliography -[1] {{literature.integration.richtext}} -[2] {{literature.absorption.richtext}} -[3] {{literature.solution.richtext}} -[4] {{literature.refinement.richtext}} -[5] {{literature.ccdc.richtext}} -[6] {{literature.finalcif.richtext}} + {% if options.hydrogen_bonds and hydrogen_bonds %} + Table 6. Hydrogen bonds for {{ cif.block.name }} + D–H⋯A [Å] d(D–H) [Å] d(H⋯A) [Å] d(D⋯A) [Å] <(DHA) [°] + {% for h in hydrogen_bonds %} + {{h.atoms}} {{h.dist_dh}} {{h.dist_ha}} {{h.dist_da}} {{h.angle_dha}} + {% endfor %} + {%if hydrogen_symminfo%}{{hydrogen_symminfo}}{%endif%}{%endif%} + Bibliography + [1] {{literature.integration.richtext}} + [2] {{literature.absorption.richtext}} + [3] {{literature.solution.richtext}} + [4] {{literature.refinement.richtext}} + [5] {{literature.ccdc.richtext}} + [6] {{literature.finalcif.richtext}} + \ No newline at end of file From 053501198be6ff7ee28fdd972cebec3670253431 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Thu, 29 Feb 2024 21:17:36 +0100 Subject: [PATCH 05/16] Update report.tmpl --- finalcif/template/report.tmpl | 197 ++++++++++++++++++++++++++-------- 1 file changed, 153 insertions(+), 44 deletions(-) diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index 7ba5b7e1..ba7cb215 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -14,7 +14,7 @@ {%endif%} -

Structure Tables

+

Structure Tables

A {{crystal_colour}}, {{crystal_shape}} shaped crystal was mounted on a {{ cif._diffrn_measurement_specimen_support }} with perfluoroether oil. The sample was crystallized {{crystallization_method}}. Data for {{cif.block.name}} were collected @@ -36,9 +36,9 @@ This report and the CIF file were generated using FinalCif.[6]

-

+

Table 1. Crystal data and structure refinement for {{ cif.block.name }} -

+
@@ -199,27 +199,33 @@ {% if refinement_details %} -

+

Refinement details for {{ cif.block.name }} -

- {{ refinement_details }} + +

+ {{ refinement_details }} +

{% endif %} + {% if atomic_coordinates %} -

+

Table 2. Atomic coordinates and Ueq2] for {{ cif.block.name }} -

+
- - - - - - - + + + + + + + + {% for atom in atomic_coordinates %} @@ -235,43 +241,146 @@
AtomxyzUeq
Ueq is defined as 1/3 of the trace of the orthogonalized + Uij tensor. +
AtomxyzUeq
+ {% endif %} - Ueq is defined as 1/3 of the trace of the orthogonalized Uij tensor. - {% endif %}{% if displacement_parameters %} Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the - form: - −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] - Atom U11 U22 U33 U23 U13 U12 - {% for atom in displacement_parameters %} - {{ atom.label }} {{ atom.U11 }} {{ atom.U22 }} {{ atom.U33 }} {{ atom.U23 }} {{ atom.U13 }} {{ atom.U12 }} - {% endfor %} - {% endif %}{% if options.bonds_table %}{%if bonds%} - Table 4. Bond lengths and angles for {{ cif.block.name }} - Atom–Atom Length [Å] - {% for b in bonds %} - {{b.atoms}} {{b.dist}} - {% endfor %} + {% if displacement_parameters %} +

+ Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the + form: + −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] +

- Atom–Atom–Atom Angle [°] - {% for a in angles %} - {{a.atoms}} {{a.angle}} - {% endfor %} - {% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if ba_symminfo%} - {{ ba_symminfo}}{%endif%}{%endif%} - {% endif %}{%if torsions%} - Table 5. Torsion angles for {{ cif.block.name }} +
+
+ + + + + + + + + + + + + {% for atom in displacement_parameters %} + + + + + + + + + + {% endfor %} + +
Ueq is defined as 1/3 of the trace of the orthogonalized + Uij tensor. +
AtomU11U22U33U23U13U12
{{ atom.label }}{{ atom.U11 }}{{ atom.U22 }}{{ atom.U33 }}{{ atom.U23 }}{{ atom.U13 }}{{ atom.U12 }}
+
+
+ {% endif %} - Atom–Atom–Atom–Atom Torsion Angle [°] - {% for t in torsions %} - {{t.atoms}} {{t.angle}} - {% endfor %} - {% if options.without_h %}Bonds and angles to hydrogen atoms were omitted.{% endif %}{%if torsion_symminfo%} - {{ torsion_symminfo}}{%endif%}{%endif%} + + {% if options.bonds_table %} + {%if bonds%} +

+ Table 4. Bond lengths and angles for {{ cif.block.name }} +

+
+
+ + + + + + + + + {% for b in bonds %} + + + + + {% endfor %} + + + + + + + + + {% for a in angles %} + + + + + {% endfor %} + +
+ {%if ba_symminfo%} + {{ ba_symminfo}} + {% endif %} + {% if options.without_h %} + Bonds and angles to hydrogen atoms were omitted. + {% endif %} +
Atom–AtomLength [Å]
{{b.atoms}}{{b.dist}}
+ Atom–Atom–Atom + + Angle [°] +
{{a.atom1}}{{a.symmstr1}}{{a.atom2}}{{a.symmstr2}}{{a.angle}}
+
+
+ {% endif %} + {% endif %} + + {%if torsions%} +

+ Table 5. Torsion angles for {{ cif.block.name }} +

+
+
+ + + + + + + + + {% for t in torsions %} + + + + + {% endfor %} + +
+ {%if torsion_symminfo%} + {{ torsion_symminfo}} + {%endif%} + {% if options.without_h %} + Bonds and angles to hydrogen atoms were omitted. + {% endif %} +
Atom–Atom–Atom–AtomTorsion Angle [°]
+ {{t.atom1}}{{t.symminfo1}}–{{t.atom2}}{{t.symminfo2}}–{{t.atom3}}{{t.symminfo3}}–{{t.atom4}}{{t.symminfo4}} + + {{t.angle}} +
+
+
+ {%endif%} {% if options.hydrogen_bonds and hydrogen_bonds %} +

Table 6. Hydrogen bonds for {{ cif.block.name }} - D–H⋯A [Å] d(D–H) [Å] d(H⋯A) [Å] d(D⋯A) [Å] <(DHA) [°] +

+ D–H⋯A [Å] d(D–H) [Å] d(H⋯A) [Å] d(D⋯A) [Å] <(DHA) [°] {% for h in hydrogen_bonds %} {{h.atoms}} {{h.dist_dh}} {{h.dist_ha}} {{h.dist_da}} {{h.angle_dha}} {% endfor %} From 018da8d45879d699a99a3ea70fa7a2da4690e846 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 09:07:44 +0100 Subject: [PATCH 06/16] Fixed last tables --- finalcif/report/references.py | 26 ++ finalcif/report/templated_report.py | 2 +- finalcif/template/report.tmpl | 557 ++++++++++++++++------------ 3 files changed, 346 insertions(+), 239 deletions(-) diff --git a/finalcif/report/references.py b/finalcif/report/references.py index 4e114d14..722c11d4 100644 --- a/finalcif/report/references.py +++ b/finalcif/report/references.py @@ -191,6 +191,32 @@ def richtext(self) -> RichText: return r @property + def html(self) -> str: + txt = '' + if self.authors: + txt += f'{self.authors}, ' + if self.journal: + txt += f'{self.journal}' + if not self.journal.endswith('.'): + txt += ', ' + else: + txt += ' ' + if self.year: + txt += f'{self.year}' + txt += ', ' + if self.volume: + txt += f'{self.volume}' + txt += ', ' + if self.pages: + txt += f'{self.pages}' + if self.doi: + txt += ', ' + if self.doi: + txt += f'{self.doi}' + if any([self.journal, self.pages, self.year, self.volume, self.doi]): + txt += '.' + return txt + @property def short_ref(self) -> RichText: """ Adds a reference with (name year) instead of a number. diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 27decd51..e03d97d3 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -549,7 +549,7 @@ def format_sum_formula(self, sum_formula: str) -> str: def get_radiation(self, cif: CifContainer) -> str: rad_element, radtype, radline = format_radiation(cif['_diffrn_radiation_type']) - radiation = f'{rad_element}{radtype}{radline}' + radiation = f'{rad_element}{radtype}{radline}' return radiation def hkl_index_limits(self, cif: CifContainer) -> str: diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index ba7cb215..d395fe75 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -2,37 +2,67 @@ - + Structure Tables + - +
{% if structure_figure %} - - {%endif%} + + {% endif %}

Structure Tables

- A {{crystal_colour}}, {{crystal_shape}} shaped crystal was mounted on a {{ cif._diffrn_measurement_specimen_support }} - with perfluoroether oil. The sample was crystallized {{crystallization_method}}. Data for {{cif.block.name}} were collected - from a shock-cooled single crystal at {{cif._diffrn_ambient_temperature}} K on {{diffr_type|inv_article}} {{diffr_type}} - {{diffr_device}} with {{diffr_source|inv_article}} {{diffr_source}} using a {{monochromator}} as monochromator and - {{detector|inv_article}} {{detector}} detector. The diffractometer {% if lowtemp_dev %}was equipped with - {{lowtemp_dev|inv_article}} {{lowtemp_dev}} low temperature device and {%endif%}used {{radiation}} radiation - {%if wavelength%}(λ = {{wavelength}} Å){%endif%}. All data were integrated with {{integration_progr}} and a - {{abstype}} absorption correction using {{abs_details}} was applied.[1,2] The structure was solved by - {{solution_method}} methods with {{solution_program}} and refined by full-matrix least-squares methods against - F2 using {{refinement_prog}}.[3,4] All non-hydrogen atoms were refined with anisotropic - displacement parameters. {%if cif.hydrogen_atoms_present%}The hydrogen atoms were refined isotropically on calculated - positions using a riding model with their Uiso values constrained to 1.5 times the Ueq of their pivot - atoms for terminal sp3carbon atoms and 1.2 times for all other carbon atoms. {%endif%}Crystallographic data for the - structures reported in this paper have been deposited with the Cambridge Crystallographic Data Centre.[5] CCDC - {%if cif._database_code_depnum_ccdc_archive%}{{cif._database_code_depnum_ccdc_archive}}{%else%}??????{%endif%} + A {{ crystal_colour }}, {{ crystal_shape }} shaped crystal was mounted on + a {{ cif._diffrn_measurement_specimen_support }} + with perfluoroether oil. {{ crystallization_method }}. Data for {{ cif.block.name }} were collected + from a shock-cooled single crystal at {{ cif._diffrn_ambient_temperature }} K + on {{ diffr_type|inv_article }} + {{ diffr_type }} + {{ diffr_device }} with {{ diffr_source|inv_article }} {{ diffr_source }} using a {{ monochromator }} as + monochromator + and + {{ detector|inv_article }} {{ detector }} detector. The diffractometer {% if lowtemp_dev %}was equipped with + {{ lowtemp_dev|inv_article }} {{ lowtemp_dev }} low temperature device and {% endif %}used {{ radiation }} + radiation + {% if wavelength %}(λ = {{ wavelength }} Å){% endif %}. All data were integrated with {{ integration_progr }} + and a + {{ abstype }} absorption correction using {{ abs_details }} was applied.[1,2] The structure was + solved by + {{ solution_method }} methods with {{ solution_program }} and refined by full-matrix least-squares methods + against + F2 using {{ refinement_prog }}.[3,4] All non-hydrogen atoms were refined with + anisotropic + displacement parameters. {% if cif.hydrogen_atoms_present %}The hydrogen atoms were refined isotropically on + calculated + positions using a riding model with their Uiso values constrained to 1.5 times the Ueq of + their pivot + atoms for terminal sp3carbon atoms and 1.2 times for all other carbon atoms. + {% endif %}Crystallographic data for the + structures reported in this paper have been deposited with the Cambridge Crystallographic Data + Centre.[5] CCDC + {% if cif._database_code_depnum_ccdc_archive %}{{ cif._database_code_depnum_ccdc_archive }}{% else %} + ??????{% endif %} contain the supplementary crystallographic data for this paper. - These data can be obtained free of charge from The Cambridge Crystallographic Data Centre via www.ccdc.cam.ac.uk/structures. + These data can be obtained free of charge from The Cambridge Crystallographic Data Centre via + www.ccdc.cam.ac.uk/structures. This report and the CIF file were generated using FinalCif.[6]

@@ -53,11 +83,11 @@ Formula weight - {{cif._chemical_formula_weight }} + {{ cif._chemical_formula_weight }} Temperature [K] - {{cif._diffrn_ambient_temperature}} + {{ cif._diffrn_ambient_temperature }} Crystal system @@ -65,107 +95,112 @@ Space group (number) - {{space_group}} + {{ space_group }} - a [Å] - {{cif._cell_length_a }} + a [Å] + {{ cif._cell_length_a }} - b [Å] - {{cif._cell_length_b }} + b [Å] + {{ cif._cell_length_b }} - c [Å] - {{cif._cell_length_c }} + c [Å] + {{ cif._cell_length_c }} - α [°] - {{cif._cell_angle_alpha }} + α [°] + {{ cif._cell_angle_alpha }} - β [°] - {{cif._cell_angle_beta }} + β [°] + {{ cif._cell_angle_beta }} - γ [°] - {{cif._cell_angle_gamma }} + γ [°] + {{ cif._cell_angle_gamma }} Volume [Å3] - {{cif._cell_volume}} + {{ cif._cell_volume }} - Z - {{cif._cell_formula_units_Z}} + Z + {{ cif._cell_formula_units_Z }} - ρcalc [gcm−3] - {{cif._exptl_crystal_density_diffrn}} + ρcalc [gcm−3] + {{ cif._exptl_crystal_density_diffrn }} - μ [mm−1] - {{cif._exptl_absorpt_coefficient_mu}} + μ [mm−1] + {{ cif._exptl_absorpt_coefficient_mu }} - F(000) - {{cif._exptl_crystal_F_000}} + F(000) + {{ cif._exptl_crystal_F_000 }} Crystal size [mm3] - {{crystal_size}} + {{ crystal_size }} Crystal colour - {{crystal_colour}} + {{ crystal_colour }} Crystal shape - {{crystal_shape }} + {{ crystal_shape }} Radiation - {{radiation}}{%if wavelength%} (λ={{wavelength}} Å){%endif%} + {{ radiation }}{% if wavelength %} (λ={{ wavelength }} Å){% endif %} 2θ range [°] - {{theta_range}} + {{ theta_range }} Index ranges - {{index_ranges}} + {{ index_ranges }} Reflections collected - {{cif._diffrn_reflns_number}} + {{ cif._diffrn_reflns_number }} Independent reflections - {{indepentent_refl}}
- Rint = {{r_int}}
- Rsigma = {{r_sigma}} + {{ indepentent_refl }}
+ Rint = {{ r_int }}
+ Rsigma = {{ r_sigma }} - Completeness{%if theta_full%} to - θ = {{theta_full}}°{%endif%} + Completeness{% if theta_full %} to + θ = {{ theta_full }}°{% endif %} - {{completeness}} + {{ completeness }} Data / Restraints / Parameters - {{data}} / {{restraints}} / {{parameters}} + {{ data }} / {{ restraints }} / {{ parameters }} - Goodness-of-fit on F2 + Absorption correction Tmin/Tmax (method) + + {{ t_min }} / {{ t_max }} {% if abstype %}({{ abstype }}){% endif %} + + + Goodness-of-fit on F2 {{ goof }} Final R indexes
[I≥2σ(I)] - R1 = {{ls_R_factor_gt}}
- wR2 = {{ls_wR_factor_gt}} + R1 = {{ ls_R_factor_gt }}
+ wR2 = {{ ls_wR_factor_gt }} @@ -173,25 +208,25 @@ [all data] - R1 = {{ls_R_factor_all}}
- wR2 = {{ls_wR_factor_ref}} + R1 = {{ ls_R_factor_all }}
+ wR2 = {{ ls_wR_factor_ref }} Largest peak/hole [eÅ−3] - {{diff_dens_max}}/{{diff_dens_min}} + {{ diff_dens_max }}/{{ diff_dens_min }} {% if exti %} - - Extinction coefficient - {{exti}} - + + Extinction coefficient + {{ exti }} + {% endif %} {% if flack_x %} - - Flack X parameter - {{flack_x}} - + + Flack X parameter + {{ flack_x }} + {% endif %} @@ -199,199 +234,245 @@
{% if refinement_details %} -

- Refinement details for {{ cif.block.name }} -

-

- {{ refinement_details }} -

+

+ Refinement details for {{ cif.block.name }} +

+

+ {{ refinement_details }} +

{% endif %} {% if atomic_coordinates %} -

- Table 2. Atomic coordinates and Ueq2] for {{ cif.block.name }} -

+

+ Table 2. Atomic coordinates and Ueq2] for {{ cif.block.name }} +

-
-
- - - - - - - - - - +
+
+
Ueq is defined as 1/3 of the trace of the orthogonalized - Uij tensor. -
AtomxyzUeq
+ + + + + + + + + - {% for atom in atomic_coordinates %} - - - - - - - - {% endfor %} - -
Ueq is defined as 1/3 of the trace of the orthogonalized + Uij tensor. +
AtomxyzUeq
{{ atom.label }}{{ atom.x }}{{ atom.y }}{{ atom.z }}{{ atom.u_eq }}
+ {% for atom in atomic_coordinates %} + + {{ atom.label }} + {{ atom.x }} + {{ atom.y }} + {{ atom.z }} + {{ atom.u_eq }} + + {% endfor %} + + +
- {% endif %} {% if displacement_parameters %} -

- Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the - form: - −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] -

+

+ Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement + factor + exponent takes the + form: + −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] +

-
-
- - - - - - - - - - - - - {% for atom in displacement_parameters %} - - - - - - - - - - {% endfor %} - -
Ueq is defined as 1/3 of the trace of the orthogonalized - Uij tensor. -
AtomU11U22U33U23U13U12
{{ atom.label }}{{ atom.U11 }}{{ atom.U22 }}{{ atom.U33 }}{{ atom.U23 }}{{ atom.U13 }}{{ atom.U12 }}
+
+
+ + + + + + + + + + + + + {% for atom in displacement_parameters %} + + + + + + + + + + {% endfor %} + +
Ueq is defined as 1/3 of the trace of the orthogonalized + Uij tensor. +
AtomU11U22U33U23U13U12
{{ atom.label }}{{ atom.U11 }}{{ atom.U22 }}{{ atom.U33 }}{{ atom.U23 }}{{ atom.U13 }}{{ atom.U12 }}
+
-
{% endif %} {% if options.bonds_table %} - {%if bonds%} -

- Table 4. Bond lengths and angles for {{ cif.block.name }} -

-
-
- - + {% if bonds %} +

+ Table 4. Bond lengths and angles for {{ cif.block.name }} +

+
+
+
- {%if ba_symminfo%} - {{ ba_symminfo}} - {% endif %} - {% if options.without_h %} - Bonds and angles to hydrogen atoms were omitted. - {% endif %} -
+ - - - - - - {% for b in bonds %} - - - - - {% endfor %} - - - - - - - - - {% for a in angles %} - - - - - {% endfor %} - -
+ {% if ba_symminfo %} + {{ ba_symminfo }} + {% endif %} + {% if options.without_h %} + Bonds and angles to hydrogen atoms were omitted. + {% endif %} +
Atom–AtomLength [Å]
{{b.atoms}}{{b.dist}}
- Atom–Atom–Atom - - Angle [°] -
{{a.atom1}}{{a.symmstr1}}{{a.atom2}}{{a.symmstr2}}{{a.angle}}
-
-
- {% endif %} + + Atom–Atom + Length [Å] + + + {% for b in bonds %} + + {{ b.atoms }} + {{ b.dist }} + + {% endfor %} + + + + + + + Atom–Atom–Atom + + + Angle [°] + + + {% for a in angles %} + + {{ a.atom1 }}{{ a.symmstr1 }}{{ a.atom2 }}{{ a.symmstr2 }} + {{ a.angle }} + + {% endfor %} + + +
+ + {% endif %} {% endif %} - {%if torsions%} -

- Table 5. Torsion angles for {{ cif.block.name }} -

-
-
- - + {% if torsions %} +

+ Table 5. Torsion angles for {{ cif.block.name }} +

+
+
+
- {%if torsion_symminfo%} - {{ torsion_symminfo}} - {%endif%} - {% if options.without_h %} - Bonds and angles to hydrogen atoms were omitted. - {% endif %} -
+ - - - - - - {% for t in torsions %} - - - - - {% endfor %} - -
+ {% if torsion_symminfo %} + {{ torsion_symminfo }} + {% endif %} + {% if options.without_h %} + Bonds and angles to hydrogen atoms were omitted. + {% endif %} +
Atom–Atom–Atom–AtomTorsion Angle [°]
- {{t.atom1}}{{t.symminfo1}}–{{t.atom2}}{{t.symminfo2}}–{{t.atom3}}{{t.symminfo3}}–{{t.atom4}}{{t.symminfo4}} - - {{t.angle}} -
+ + Atom–Atom–Atom–Atom + Torsion Angle [°] + + + {% for t in torsions %} + + + {{ t.atom1 }}{{ t.symminfo1 }}–{{ t.atom2 }}{{ t.symminfo2 }}–{{ t.atom3 }}{{ t.symminfo3 }}–{{ t.atom4 }}{{ t.symminfo4 }} + + + {{ t.angle }} + + + {% endfor %} + + +
- - {%endif%} + {% endif %} {% if options.hydrogen_bonds and hydrogen_bonds %} +

+ Table 6. Hydrogen bonds for {{ cif.block.name }} +

+ +
+
+ + + + + + + + + + + + {% for h in hydrogen_bonds %} + + + + + + + + {% endfor %} + +
+ {% if hydrogen_symminfo %} + {{ hydrogen_symminfo }} + {% endif %} +
D–H⋯A [Å]d(D–H) [Å] d(H⋯A) [Å]d(D⋯A) [Å]<(DHA) [°]
+ {{ h.atoms }} + + {{ h.dist_dh }} + + {{ h.dist_ha }} + + {{ h.dist_da }} + + {{ h.angle_dha }} +
+
+
+ {% endif %} +

- Table 6. Hydrogen bonds for {{ cif.block.name }} + Bibliography

- D–H⋯A [Å] d(D–H) [Å] d(H⋯A) [Å] d(D⋯A) [Å] <(DHA) [°] - {% for h in hydrogen_bonds %} - {{h.atoms}} {{h.dist_dh}} {{h.dist_ha}} {{h.dist_da}} {{h.angle_dha}} - {% endfor %} - {%if hydrogen_symminfo%}{{hydrogen_symminfo}}{%endif%}{%endif%} - Bibliography - [1] {{literature.integration.richtext}} - [2] {{literature.absorption.richtext}} - [3] {{literature.solution.richtext}} - [4] {{literature.refinement.richtext}} - [5] {{literature.ccdc.richtext}} - [6] {{literature.finalcif.richtext}} + +
    +
  1. [1] {{ literature.integration.html }}
  2. +
  3. [2] {{ literature.absorption.html }}
  4. +
  5. [3] {{ literature.solution.html }}
  6. +
  7. [4] {{ literature.refinement.html }}
  8. +
  9. [5] {{ literature.ccdc.html }}
  10. +
  11. [6] {{ literature.finalcif.html }}
  12. +
+ From 4845909b8112659bc74921b29ad0b5f709145194 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 10:20:16 +0100 Subject: [PATCH 07/16] More format improvements --- finalcif/report/report_text.py | 13 +- finalcif/report/templated_report.py | 8 +- finalcif/template/report.tmpl | 188 +++++++++++++++------------- 3 files changed, 116 insertions(+), 93 deletions(-) diff --git a/finalcif/report/report_text.py b/finalcif/report/report_text.py index 22df9233..916f77ac 100644 --- a/finalcif/report/report_text.py +++ b/finalcif/report/report_text.py @@ -18,7 +18,7 @@ SHELXLReference, SHELXTReference, SHELXSReference, FinalCifReference, ShelXleReference, Olex2Reference, \ SHELXDReference, SadabsTwinabsReference, CrysalisProReference, Nosphera2Reference, XDSReference, DSRReference2015, \ DSRReference2018, XRedReference -from finalcif.tools.misc import protected_space, angstrom, zero_width_space, remove_line_endings, flatten +from finalcif.tools.misc import protected_space, angstrom, zero_width_space, remove_line_endings, flatten, minus_sign def math_to_word(eq: str) -> BaseOxmlElement: @@ -524,6 +524,17 @@ def get_inf_article(next_word: str) -> str: return 'an' if next_word[0].lower() in voc else 'a' +def align_by_dot(number: str) -> str: + """ + The intention of this filter is to align the number + on the dot, but this doesn't work as expected. + """ + if '.' in number: + num, dec = number.split('.') + return (f'{num}' + f'.{dec}') + + def format_radiation(radiation_type: str) -> list: radtype = list(radiation_type.partition("K")) if len(radtype) > 2: diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index e03d97d3..5f62bf44 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -21,7 +21,7 @@ from finalcif.report.references import SAINTReference, SHELXLReference, SadabsTwinabsReference, SHELXTReference, \ SHELXSReference, SHELXDReference, SORTAVReference, FinalCifReference, CCDCReference, \ CrysalisProReference, Nosphera2Reference, Olex2Reference -from finalcif.report.report_text import math_to_word, gstr, format_radiation, get_inf_article, MachineType +from finalcif.report.report_text import math_to_word, gstr, format_radiation, get_inf_article, MachineType, align_by_dot from finalcif.report.symm import SymmetryElement from finalcif.tools.misc import isnumeric, this_or_quest, timessym, angstrom, protected_space, less_or_equal, \ halbgeviert, minus_sign, ellipsis_mid @@ -796,8 +796,9 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do from pprint import pprint import subprocess - data = Path('tests') - testcif = (data / 'examples/1979688.cif').absolute() + #data = Path('tests') + #testcif = Path(data / 'examples/1979688.cif').absolute() + testcif = Path(r'test-data/p31c.cif').absolute() cif = CifContainer(testcif) t = TemplatedReport(format=TextFormat.HTML, options=mock.Mock(), cif=cif) maincontext = t.get_context(cif, options=mock.Mock(), picfile=None, tpl_doc=mock.Mock()) @@ -805,6 +806,7 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do templateLoader = jinja2.FileSystemLoader(searchpath=r"finalcif/template") jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False) jinja_env.filters['inv_article'] = get_inf_article + jinja_env.filters['align_dot'] = align_by_dot TEMPLATE_FILE = "report.tmpl" template = jinja_env.get_template(TEMPLATE_FILE) outputText = template.render(maincontext) diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index d395fe75..19f060c0 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -23,55 +23,66 @@ -
- {% if structure_figure %} - - {% endif %} - -

Structure Tables

-

- A {{ crystal_colour }}, {{ crystal_shape }} shaped crystal was mounted on - a {{ cif._diffrn_measurement_specimen_support }} - with perfluoroether oil. {{ crystallization_method }}. Data for {{ cif.block.name }} were collected - from a shock-cooled single crystal at {{ cif._diffrn_ambient_temperature }} K - on {{ diffr_type|inv_article }} - {{ diffr_type }} - {{ diffr_device }} with {{ diffr_source|inv_article }} {{ diffr_source }} using a {{ monochromator }} as - monochromator - and - {{ detector|inv_article }} {{ detector }} detector. The diffractometer {% if lowtemp_dev %}was equipped with - {{ lowtemp_dev|inv_article }} {{ lowtemp_dev }} low temperature device and {% endif %}used {{ radiation }} - radiation - {% if wavelength %}(λ = {{ wavelength }} Å){% endif %}. All data were integrated with {{ integration_progr }} - and a - {{ abstype }} absorption correction using {{ abs_details }} was applied.[1,2] The structure was - solved by - {{ solution_method }} methods with {{ solution_program }} and refined by full-matrix least-squares methods - against - F2 using {{ refinement_prog }}.[3,4] All non-hydrogen atoms were refined with - anisotropic - displacement parameters. {% if cif.hydrogen_atoms_present %}The hydrogen atoms were refined isotropically on - calculated - positions using a riding model with their Uiso values constrained to 1.5 times the Ueq of - their pivot - atoms for terminal sp3carbon atoms and 1.2 times for all other carbon atoms. - {% endif %}Crystallographic data for the - structures reported in this paper have been deposited with the Cambridge Crystallographic Data - Centre.[5] CCDC - {% if cif._database_code_depnum_ccdc_archive %}{{ cif._database_code_depnum_ccdc_archive }}{% else %} - ??????{% endif %} - contain the supplementary crystallographic data for this paper. - These data can be obtained free of charge from The Cambridge Crystallographic Data Centre via - www.ccdc.cam.ac.uk/structures. - This report and the CIF file were generated using FinalCif.[6] -

+
-

- Table 1. Crystal data and structure refinement for {{ cif.block.name }} -

-
- +
+ + {% if structure_figure %} + + {% endif %} + +

Structure Tables

+

+ A {{ crystal_colour }}, {{ crystal_shape }} shaped crystal was mounted on + a {{ cif._diffrn_measurement_specimen_support }} + with perfluoroether oil. {{ crystallization_method }}. Data for {{ cif.block.name }} were collected + from a shock-cooled single crystal at {{ cif._diffrn_ambient_temperature }} K + on {{ diffr_type|inv_article }} + {{ diffr_type }} + {{ diffr_device }} with {{ diffr_source|inv_article }} {{ diffr_source }} using a {{ monochromator }} as + monochromator + and + {{ detector|inv_article }} {{ detector }} detector. The diffractometer {% if lowtemp_dev %}was equipped + with + {{ lowtemp_dev|inv_article }} {{ lowtemp_dev }} low temperature device and {% endif %} + used {{ radiation }} + radiation + {% if wavelength %}(λ = {{ wavelength }} Å){% endif %}. All data were integrated + with {{ integration_progr }} + and a + {{ abstype }} absorption correction using {{ abs_details }} was applied.[1,2] The structure + was + solved by + {{ solution_method }} methods with {{ solution_program }} and refined by full-matrix least-squares + methods + against + F2 using {{ refinement_prog }}.[3,4] All non-hydrogen atoms were refined + with + anisotropic + displacement parameters. {% if cif.hydrogen_atoms_present %}The hydrogen atoms were refined + isotropically on + calculated + positions using a riding model with their Uiso values constrained to 1.5 times the Ueq + of + their pivot + atoms for terminal sp3carbon atoms and 1.2 times for all other carbon atoms. + {% endif %}Crystallographic data for the + structures reported in this paper have been deposited with the Cambridge Crystallographic Data + Centre.[5] CCDC + {% if cif._database_code_depnum_ccdc_archive %}{{ cif._database_code_depnum_ccdc_archive }}{% else %} + ??????{% endif %} + contain the supplementary crystallographic data for this paper. + These data can be obtained free of charge from The Cambridge Crystallographic Data Centre via + www.ccdc.cam.ac.uk/structures. + This report and the CIF file were generated using FinalCif.[6] +

+
+
+

+ Table 1. Crystal data and structure refinement for {{ cif.block.name }} +

+
@@ -249,11 +260,12 @@
-
-
CCDC number
+
+
+ @@ -261,14 +273,14 @@ + - {% for atom in atomic_coordinates %} - - - + + + {% endfor %} @@ -281,19 +293,18 @@ {% if displacement_parameters %}

- Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. The anisotropic displacement - factor - exponent takes the - form: - −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] + Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. + The anisotropic displacement factor exponent takes the form: + −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ]

-
-
Ueq is defined as 1/3 of the trace of the orthogonalized Uij tensor.
Atom xz Ueq
{{ atom.label }}{{ atom.x }}{{ atom.y }}{{ atom.z }}{{ atom.x|align_dot }}{{ atom.y|align_dot }}{{ atom.z|align_dot }} {{ atom.u_eq }}
+
+
+ @@ -303,6 +314,7 @@ + {% for atom in displacement_parameters %} @@ -321,15 +333,14 @@ {% endif %} - - {% if options.bonds_table %} - {% if bonds %} -

- Table 4. Bond lengths and angles for {{ cif.block.name }} -

-
-
-
Ueq is defined as 1/3 of the trace of the orthogonalized Uij tensor.
Atom U11U13 U12
+
+
+ {% if options.bonds_table %} + {% if bonds %} +

+ Table 4. Bond lengths and angles for {{ cif.block.name }} +

+
- + + {% for b in bonds %} @@ -370,18 +382,15 @@ {% endfor %}
{% if ba_symminfo %} {{ ba_symminfo }} @@ -338,11 +349,12 @@ Bonds and angles to hydrogen atoms were omitted. {% endif %}
Atom–Atom Length [Å]
-
-
- {% endif %} - {% endif %} - - {% if torsions %} -

- Table 5. Torsion angles for {{ cif.block.name }} -

-
-
- + {% endif %} + {% endif %} + +
+ {% if torsions %} +

+ Table 5. Torsion angles for {{ cif.block.name }} +

+
- + + {% for t in torsions %} @@ -408,10 +418,9 @@ {% endfor %}
{% if torsion_symminfo %} {{ torsion_symminfo }} @@ -390,11 +399,12 @@ Bonds and angles to hydrogen atoms were omitted. {% endif %}
Atom–Atom–Atom–Atom Torsion Angle [°]
-
+ {% endif %}
- {% endif %} - +
{% if options.hydrogen_bonds and hydrogen_bonds %}

@@ -419,14 +428,14 @@

-
- +
+
- + @@ -434,6 +443,7 @@ + {% for h in hydrogen_bonds %} From 86d91034b4c47266164c1a7f7a001fd84a027131 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 11:48:11 +0100 Subject: [PATCH 08/16] Many small format fixes --- finalcif/report/report_text.py | 1 + finalcif/report/templated_report.py | 33 ++++++++++++++++++++++------- finalcif/template/report.tmpl | 11 ++++++---- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/finalcif/report/report_text.py b/finalcif/report/report_text.py index 916f77ac..8b231d81 100644 --- a/finalcif/report/report_text.py +++ b/finalcif/report/report_text.py @@ -528,6 +528,7 @@ def align_by_dot(number: str) -> str: """ The intention of this filter is to align the number on the dot, but this doesn't work as expected. + https://codepen.io/dominiccomtois/pen/YZdred """ if '.' in number: num, dec = number.split('.') diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 5f62bf44..d11d25e2 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -320,13 +320,13 @@ def get_hydrogen_bonds(self) -> list[dict[str, RichText]] | list[HydrogenBond]: def get_bonds_angles_symminfo(self) -> str: return self._bonds_angles.symminfo - def get_torsion_symminfo(self): + def get_torsion_symminfo(self) -> str: return self._torsions.symminfo - def get_hydrogen_symminfo(self): + def get_hydrogen_symminfo(self) -> str: return self._hydrogens.symminfo - def get_crystallization_method(self, cif): + def get_crystallization_method(self, cif) -> str: return gstr(cif['_exptl_crystal_recrystallization_method']) or '[No crystallization method given!]' def get_radiation(self, cif: CifContainer) -> str: @@ -446,7 +446,7 @@ def refinement_prog(self, cif: CifContainer) -> str: if 'OLEX' in refined.upper(): self.literature['refinement'] = Olex2Reference() if ('NOSPHERA2' in refined.upper() or 'NOSPHERA2' in cif['_refine_special_details'].upper() or - 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): + 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): self.literature['refinement'] = Nosphera2Reference() return refined.split()[0] @@ -513,6 +513,21 @@ def get_torsion_angles(self) -> list[Torsion]: def get_hydrogen_bonds(self) -> list[HydrogenBond]: return self._hydrogens.hydrogen_bonds_as_str + def _format_symminfo(self, txt): + return (txt.replace('#1:', '
#1:') + .replace(', ', ', ') + .replace(': ', ': ') + .replace('-', minus_sign)) + + def get_bonds_angles_symminfo(self) -> str: + return self._format_symminfo(self._bonds_angles.symminfo) + + def get_torsion_symminfo(self): + return self._format_symminfo(self._torsions.symminfo) + + def get_hydrogen_symminfo(self): + return self._format_symminfo(self._hydrogens.symminfo) + def space_group_subdoc(self, cif: CifContainer, _: None) -> str: s = SpaceGroups() try: @@ -796,12 +811,14 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do from pprint import pprint import subprocess - #data = Path('tests') - #testcif = Path(data / 'examples/1979688.cif').absolute() + # data = Path('tests') + # testcif = Path(data / 'examples/1979688.cif').absolute() testcif = Path(r'test-data/p31c.cif').absolute() cif = CifContainer(testcif) - t = TemplatedReport(format=TextFormat.HTML, options=mock.Mock(), cif=cif) - maincontext = t.get_context(cif, options=mock.Mock(), picfile=None, tpl_doc=mock.Mock()) + mock_mock = mock.Mock() + mock.without_h = '' + t = TemplatedReport(format=TextFormat.HTML, options=mock_mock, cif=cif) + maincontext = t.get_context(cif, options=mock_mock, picfile=None, tpl_doc=mock_mock) # pprint(maincontext) templateLoader = jinja2.FileSystemLoader(searchpath=r"finalcif/template") jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False) diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index 19f060c0..b3d10c71 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -293,7 +293,7 @@ {% if displacement_parameters %}

- Table 3. Anisotropic displacement parameters (Å2) for {{ cif.block.name }}. + Table 3. Anisotropic displacement parameters [Å2] for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the form: −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ]

@@ -358,7 +358,7 @@
{% for b in bonds %} - + {% endfor %} @@ -376,7 +376,7 @@ {% for a in angles %} - + {% endfor %} @@ -384,6 +384,9 @@
{% if hydrogen_symminfo %} {{ hydrogen_symminfo }} {% endif %}
D–H⋯A [Å] d(D–H) [Å]d(D⋯A) [Å] <(DHA) [°]
{{ b.atoms }}{{ b.atoms }}{{ b.symm }} {{ b.dist }}
{{ a.atom1 }}{{ a.symmstr1 }}{{ a.atom2 }}{{ a.symmstr2 }}{{ a.atom1 }}{{ a.symm1 }}{{ a.atom2 }}{{ a.symm2 }} {{ a.angle }}
{% endif %} {% endif %} +
+
+
{% if torsions %} @@ -409,7 +412,7 @@ {% for t in torsions %} - {{ t.atom1 }}{{ t.symminfo1 }}–{{ t.atom2 }}{{ t.symminfo2 }}–{{ t.atom3 }}{{ t.symminfo3 }}–{{ t.atom4 }}{{ t.symminfo4 }} + {{ t.atom1 }}{{ t.symm1 }}–{{ t.atom2 }}{{ t.symm2 }}–{{ t.atom3 }}{{ t.symm3 }}–{{ t.atom4 }}{{ t.symm4 }} {{ t.angle }} From d84517c400651762ea706da19e888d8b231db6dc Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 13:34:03 +0100 Subject: [PATCH 09/16] Fix tests --- finalcif/report/templated_report.py | 33 +++++++------- tests/test_templated_report.py | 68 ++++++++++++++++------------- 2 files changed, 55 insertions(+), 46 deletions(-) diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index d11d25e2..52622ba7 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -1,14 +1,13 @@ import dataclasses import enum import itertools -import os import re import sys from collections import namedtuple from contextlib import suppress from math import sin, radians from pathlib import Path -from typing import List, Dict, Union, Iterator +from typing import List, Dict, Union, Iterator, Protocol import jinja2 from docx.enum.text import WD_PARAGRAPH_ALIGNMENT @@ -31,6 +30,11 @@ AdpWithMinus = namedtuple('AdpWithMinus', ('label', 'U11', 'U22', 'U33', 'U23', 'U13', 'U12')) +class TextFormat(enum.StrEnum): + RICHTEXT = 'richtext' + HTML = 'html' + + @dataclasses.dataclass(frozen=True) class Bond(): atoms: str @@ -292,7 +296,7 @@ def symmsearch(cif: CifContainer, newsymms: Dict[int, str], num: int, return num -class Formatter: +class Formatter(Protocol): def __init__(self, options: Options, cif: CifContainer) -> None: self.literature = {'finalcif' : FinalCifReference(), 'ccdc' : CCDCReference(), @@ -329,12 +333,15 @@ def get_hydrogen_symminfo(self) -> str: def get_crystallization_method(self, cif) -> str: return gstr(cif['_exptl_crystal_recrystallization_method']) or '[No crystallization method given!]' - def get_radiation(self, cif: CifContainer) -> str: + def get_radiation(self, cif: CifContainer) -> str | RichText: raise NotImplementedError def hkl_index_limits(self, cif: CifContainer) -> str: raise NotImplementedError + def make_3d(self, cif: CifContainer, options: Options) -> str: + raise NotImplementedError + @staticmethod def get_from_to_theta_range(cif: CifContainer) -> str: theta_min = cif['_diffrn_reflns_theta_min'] @@ -498,8 +505,6 @@ def get_completeness(self, cif: CifContainer) -> str: class HtmlFormatter(Formatter): - def __init__(self, options: Options, cif: CifContainer): - super().__init__(options, cif) def get_bonds(self) -> list[Bond]: return self._bonds_angles.bonds_as_string @@ -528,6 +533,9 @@ def get_torsion_symminfo(self): def get_hydrogen_symminfo(self): return self._format_symminfo(self._hydrogens.symminfo) + def make_3d(self, cif: CifContainer, options: Options) -> str: + return '[3D representation of the structure in html/javascript not implemented]' + def space_group_subdoc(self, cif: CifContainer, _: None) -> str: s = SpaceGroups() try: @@ -584,9 +592,6 @@ def hkl_index_limits(self, cif: CifContainer) -> str: class RichTextFormatter(Formatter): - def __init__(self, options: Options, cif: CifContainer) -> None: - super().__init__(options, cif) - def get_bonds(self) -> List[Dict[str, RichText]]: return self._bonds_angles.bonds_richtext @@ -599,6 +604,9 @@ def get_torsion_angles(self) -> List[Dict[str, RichText]]: def get_hydrogen_bonds(self) -> list[dict[str, RichText]]: return self._hydrogens.hydrogen_bonds + def make_3d(self, cif: CifContainer, options: Options) -> str: + return '[3D representation not implemented for .docx files]' + def format_sum_formula(self, sum_formula: str) -> RichText: sum_formula_group = [''.join(x[1]) for x in itertools.groupby(sum_formula, lambda x: x.isalpha())] richtext = RichText('') @@ -673,11 +681,6 @@ def hkl_index_limits(self, cif: CifContainer) -> str: f'{less_or_equal} l {less_or_equal} {limit_l_max}') -class TextFormat(enum.StrEnum): - RICHTEXT = 'richtext' - HTML = 'html' - - def text_factory(options: Options, cif: CifContainer) -> dict[str, Formatter]: factory = { TextFormat.RICHTEXT: RichTextFormatter(options, cif), @@ -737,6 +740,7 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do 'name' : cif.block.name, 'space_group' : self.text_formatter.space_group_subdoc(cif, tpl_doc), 'structure_figure' : self.text_formatter.make_picture(options, picfile, tpl_doc), + '3d_structure' : self.text_formatter.make_3d(cif, options), 'crystallization_method' : self.text_formatter.get_crystallization_method(cif), 'sum_formula' : self.text_formatter.format_sum_formula( cif['_chemical_formula_sum'].replace(" ", "")), @@ -808,7 +812,6 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do if __name__ == '__main__': from unittest import mock - from pprint import pprint import subprocess # data = Path('tests') diff --git a/tests/test_templated_report.py b/tests/test_templated_report.py index 930eb521..4b245cb1 100644 --- a/tests/test_templated_report.py +++ b/tests/test_templated_report.py @@ -14,7 +14,7 @@ from finalcif import VERSION from finalcif.appwindow import AppWindow from finalcif.cif.cif_file_io import CifContainer -from finalcif.report.templated_report import TemplatedReport +from finalcif.report.templated_report import TemplatedReport, TextFormat data = Path('tests') test_data = Path('test-data') @@ -92,8 +92,8 @@ def tearDown(self) -> None: self.report_zip.unlink(missing_ok=True) def test_ccdc_num_in_table(self): - t = TemplatedReport() - ok = t.make_templated_docx_report(options=self.options, cif=CifContainer(self.testcif), + t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) + ok = t.make_templated_docx_report(options=self.options, output_filename=str(self.reportdoc), picfile=self.report_pic, template_path=self.text_template) self.assertTrue(ok) @@ -107,22 +107,23 @@ def test_picture_has_correct_size(self): For this test, self.myapp.set_report_picture(Path('finalcif/icon/finalcif.png')) has to be set correctly. """ - t = TemplatedReport() - ok = t.make_templated_docx_report(options=self.options, cif=CifContainer(self.testcif), - output_filename=self.reportdoc, picfile=self.report_pic, + t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) + ok = t.make_templated_docx_report(options=self.options, + output_filename=str(self.reportdoc), picfile=self.report_pic, template_path=self.text_template) self.assertTrue(ok) - doc = Document(self.reportdoc.absolute()) + doc = Document(self.reportdoc.absolute().__str__()) shapes: InlineShapes = doc.inline_shapes self.assertEqual(WD_INLINE_SHAPE.PICTURE, shapes[0].type) self.assertEqual(Cm(7.43).emu, shapes[0].width) def test_citations(self): - t = TemplatedReport() - t.make_templated_docx_report(options=self.options, cif=CifContainer(self.testcif), - output_filename=self.reportdoc, picfile=self.report_pic, + t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) + t.make_templated_docx_report(options=self.options, + output_filename=self.reportdoc.__str__(), + picfile=self.report_pic, template_path=self.text_template) - doc = Document(self.reportdoc.absolute()) + doc = Document(self.reportdoc.absolute().__str__()) # for num, p in enumerate(doc.paragraphs): # print(num, p.text) self.assertEqual('Bibliography', doc.paragraphs[20].text) @@ -135,16 +136,17 @@ def setUp(self): self.docx_templ = test_data / 'templates/test_template_for_multitable_dist.docx' self.multi_cif = test_data / '1000007-multi.cif' self.cif = CifContainer(file=self.multi_cif) + self.options = Mock() + self.options.without_h = False + self.options.picture_width = 7.43 def tearDown(self): self.reportdoc.unlink(missing_ok=True) def test_get_distance_from_atoms(self): - t = TemplatedReport() - mock = Mock() - mock.without_h = False - mock.picture_width = 7.43 - ok = t.make_templated_docx_report(options=mock, cif=self.cif, output_filename='test.docx', picfile=Path(), + t = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + ok = t.make_templated_docx_report(options=self.options, + output_filename='test.docx', picfile=Path(), template_path=self.docx_templ) self.assertTrue(ok) doc = Document(self.reportdoc.resolve().__str__()) @@ -158,51 +160,55 @@ class TestData(unittest.TestCase): def setUp(self) -> None: # creating a new CIF with a new block: self.cif = CifContainer('foo.cif', 'testblock') + self.options = Mock() + self.options.picture_width = 7.43 + self.options.without_h = False def test_get_integration_program_with_spaces(self): # Here we have the special case, that there is no space character between the version number and the bracket. self.cif['_computing_data_reduction'] = 'CrysAlisPro 1.171.39.20a (Rigaku OD, 2015)' self.assertEqual('CrysAlisPro 1.171.39.20a (Rigaku OD, 2015)', self.cif['_computing_data_reduction']) - r = TemplatedReport() - result = r.get_integration_program(self.cif) + r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) - self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.literature['integration'])) + self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.text_formatter.literature['integration'])) def test_get_integration_program_with_line_break(self): # Here we have the special case, that there is no space character between the version number and the bracket. self.cif['_computing_data_reduction'] = "CrysAlisPro 1.171.39.20a\n" \ "(Rigaku OD, 2015)\n" self.assertEqual('CrysAlisPro 1.171.39.20a\n(Rigaku OD, 2015)\n', self.cif['_computing_data_reduction']) - r = TemplatedReport() - result = r.get_integration_program(self.cif) + r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) - self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.literature['integration'])) + self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.text_formatter.literature['integration'])) def test_get_integration_program_with_missing_information(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'CrysAlisPro 1.171.39.20a (Rigaku OD)' self.assertEqual('CrysAlisPro 1.171.39.20a (Rigaku OD)', self.cif['_computing_data_reduction']) - r = TemplatedReport() - result = r.get_integration_program(self.cif) + r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) - self.assertEqual('Crysalispro, unknown version, Rigaku OD.', str(r.literature['integration'])) + self.assertEqual('Crysalispro, unknown version, Rigaku OD.', str(r.text_formatter.literature['integration'])) def test_get_integration_program_saint(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'SAINT V8.40A' - r = TemplatedReport() - result = r.get_integration_program(self.cif) + r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('SAINT V8.40A', result) self.assertEqual('Bruker, SAINT, V8.40A, Bruker AXS Inc., Madison, Wisconsin, USA.', - str(r.literature['integration'])) + str(r.text_formatter.literature['integration'])) def test_get_integration_program_saint_without_version(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'SAINT' - r = TemplatedReport() - result = r.get_integration_program(self.cif) + r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('SAINT', result) - self.assertEqual('Bruker, SAINT, Bruker AXS Inc., Madison, Wisconsin, USA.', str(r.literature['integration'])) + self.assertEqual('Bruker, SAINT, Bruker AXS Inc., Madison, Wisconsin, USA.', + str(r.text_formatter.literature['integration'])) if __name__ == '__main__': From 3b6118e583ce12f77e1af0d30b0153981f3b348a Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 15:26:43 +0100 Subject: [PATCH 10/16] More improvements in html report --- finalcif/appwindow.py | 4 + finalcif/report/templated_report.py | 56 +++++++++-- finalcif/template/report.tmpl | 150 +++++++++++++++------------- 3 files changed, 133 insertions(+), 77 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 1f286852..55744ddb 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -1131,6 +1131,10 @@ def make_report_tables(self) -> None: output_filename=str(report_filename), picfile=picfile, template_path=Path(self.get_checked_templates_list_text())) + t = TemplatedReport(format=TextFormat.HTML, options=self.options, cif=self.cif) + t.make_templated_html_report( + options=self.options, + picfile=picfile) if not ok: return None except FileNotFoundError as e: diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 52622ba7..a757e6e1 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -1,13 +1,14 @@ import dataclasses import enum import itertools +import pathlib import re import sys from collections import namedtuple from contextlib import suppress from math import sin, radians from pathlib import Path -from typing import List, Dict, Union, Iterator, Protocol +from typing import List, Dict, Union, Iterator, Protocol, Any import jinja2 from docx.enum.text import WD_PARAGRAPH_ALIGNMENT @@ -15,6 +16,7 @@ from docx.text.paragraph import Paragraph from docxtpl import DocxTemplate, RichText, InlineImage, Subdoc +from finalcif import app_path from finalcif.cif.cif_file_io import CifContainer from finalcif.gui.dialogs import show_general_warning from finalcif.report.references import SAINTReference, SHELXLReference, SadabsTwinabsReference, SHELXTReference, \ @@ -696,7 +698,9 @@ def __init__(self, format: str, options: Options, cif: CifContainer) -> None: self.cif = cif self.text_formatter = text_factory(options, cif)[self.format] - def make_templated_docx_report(self, options: Options, output_filename: str, picfile: Path, + def make_templated_docx_report(self, options: Options, + output_filename: str, + picfile: Path, template_path: Path) -> bool: tpl_doc = DocxTemplate(template_path) context, tpl_doc = self.prepare_report_data(self.cif, options, picfile, tpl_doc) @@ -712,7 +716,39 @@ def make_templated_docx_report(self, options: Options, output_filename: str, pic info_text=str(e)) return False - def prepare_report_data(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate): + def make_templated_html_report(self, options: Options, + output_filename: str = 'test.html', + picfile: Path = None, + template_file: str = "report.tmpl") -> bool: + maincontext = self.get_context(self.cif, options, picfile, None) + template_path = app_path.application_path / 'template' + print(template_path.resolve()) + jinja_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(searchpath=template_path.resolve()), + autoescape=False) + jinja_env.filters['inv_article'] = get_inf_article + jinja_env.filters['align_dot'] = align_by_dot + template = jinja_env.get_template(template_file) + try: + outputText = template.render(maincontext) + print(outputText) + p = Path(output_filename) + p.write_text(outputText, encoding="utf-8") + import subprocess + if sys.platform == 'darwin': + subprocess.call(['open', str(p.resolve())]) + else: + subprocess.Popen(['explorer', str(p.resolve())], shell=True) + return True + except Exception as e: + show_general_warning(parent=None, window_title='Warning', warn_text='Document generation failed', + info_text=str(e)) + return False + + def prepare_report_data(self, cif: CifContainer, + options: Options, + picfile: Path, + tpl_doc: DocxTemplate | None) -> tuple[dict[str, Any], DocxTemplate]: maincontext = {} if not cif.is_multi_cif: maincontext = self.get_context(cif, options, picfile, tpl_doc) @@ -813,18 +849,20 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do if __name__ == '__main__': from unittest import mock import subprocess - - # data = Path('tests') - # testcif = Path(data / 'examples/1979688.cif').absolute() - testcif = Path(r'test-data/p31c.cif').absolute() + # TODO: Add _refine.ls_weighting_details + # _reflns_number_gt / _reflns_number_total were greater than %(_BAXS_sigma_cutoff + data = Path('tests') + testcif = Path(data / 'examples/1979688.cif').absolute() + #testcif = Path(r'test-data/p31c.cif').absolute() cif = CifContainer(testcif) mock_mock = mock.Mock() mock.without_h = '' t = TemplatedReport(format=TextFormat.HTML, options=mock_mock, cif=cif) - maincontext = t.get_context(cif, options=mock_mock, picfile=None, tpl_doc=mock_mock) + pic = pathlib.Path("tests/examples/work/cu_BruecknerJK_153F40_0m-finalcif.png") + maincontext = t.get_context(cif, options=mock_mock, picfile=pic, tpl_doc=mock_mock) # pprint(maincontext) templateLoader = jinja2.FileSystemLoader(searchpath=r"finalcif/template") - jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False) + jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False, line_comment_prefix='##') jinja_env.filters['inv_article'] = get_inf_article jinja_env.filters['align_dot'] = align_by_dot TEMPLATE_FILE = "report.tmpl" diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index b3d10c71..972da019 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -12,6 +12,10 @@ padding-left: 0; } + #report_text { + text-align: justify; + } + h2 { margin-top: 4rem; } @@ -19,69 +23,87 @@ h3 { margin-top: 3rem; } + + h4 { + margin-top: 3rem; + } + + img { + border-radius: 5px; + } - +
+

Structure Tables

+ {% if structure_figure %} - +
+ Structure Figure +
{% endif %} -

Structure Tables

-

+

A {{ crystal_colour }}, {{ crystal_shape }} shaped crystal was mounted on a {{ cif._diffrn_measurement_specimen_support }} - with perfluoroether oil. {{ crystallization_method }}. Data for {{ cif.block.name }} were collected - from a shock-cooled single crystal at {{ cif._diffrn_ambient_temperature }} K + with perfluoroether oil. {{ crystallization_method }}. Data for {{ cif.block.name }} + were collected from a shock-cooled single crystal at {{ cif._diffrn_ambient_temperature }} K on {{ diffr_type|inv_article }} - {{ diffr_type }} - {{ diffr_device }} with {{ diffr_source|inv_article }} {{ diffr_source }} using a {{ monochromator }} as - monochromator - and - {{ detector|inv_article }} {{ detector }} detector. The diffractometer {% if lowtemp_dev %}was equipped - with - {{ lowtemp_dev|inv_article }} {{ lowtemp_dev }} low temperature device and {% endif %} - used {{ radiation }} - radiation - {% if wavelength %}(λ = {{ wavelength }} Å){% endif %}. All data were integrated - with {{ integration_progr }} - and a - {{ abstype }} absorption correction using {{ abs_details }} was applied.[1,2] The structure - was - solved by - {{ solution_method }} methods with {{ solution_program }} and refined by full-matrix least-squares - methods - against - F2 using {{ refinement_prog }}.[3,4] All non-hydrogen atoms were refined - with - anisotropic - displacement parameters. {% if cif.hydrogen_atoms_present %}The hydrogen atoms were refined - isotropically on - calculated - positions using a riding model with their Uiso values constrained to 1.5 times the Ueq - of - their pivot - atoms for terminal sp3carbon atoms and 1.2 times for all other carbon atoms. - {% endif %}Crystallographic data for the - structures reported in this paper have been deposited with the Cambridge Crystallographic Data - Centre.[5] CCDC - {% if cif._database_code_depnum_ccdc_archive %}{{ cif._database_code_depnum_ccdc_archive }}{% else %} - ??????{% endif %} + {{ diffr_type }} {{ diffr_device }} with {{ diffr_source|inv_article }} {{ diffr_source }} + using a {{ monochromator }} as monochromator and {{ detector|inv_article }} {{ detector }} + detector. The diffractometer + {% if lowtemp_dev %}was equipped with {{ lowtemp_dev|inv_article }} {{ lowtemp_dev }} + low temperature device and + {% endif %} + used {{ radiation }} radiation {% if wavelength %}(λ = {{ wavelength }} Å){% endif %}. + All data were integrated with {{ integration_progr }} yielding {{ cif._diffrn_reflns_number }} + reflections of which {{ cif._reflns_number_total }} where independent and + {{ '%0.1f'| format((cif._reflns_number_gt|float() / cif._reflns_number_total|float()) * 100) }} % + were greater than 2σ(F2). A + {{ abstype }} absorption correction using {{ abs_details }} was applied.[1,2] + The structure was solved by + {{ solution_method }} methods with {{ solution_program }} and refined by full-matrix + least-squares methods against + F2 using {{ refinement_prog }}.[3,4] All non-hydrogen atoms + were refined with anisotropic displacement parameters. + {% if cif.hydrogen_atoms_present %}The hydrogen atoms were refined isotropically on + calculated positions using a riding model with their Uiso values + constrained to 1.5 times + the Ueq of their pivot atoms for terminal sp3 carbon atoms + and 1.2 times for all other carbon atoms. + {% endif %} + Crystallographic data for the structures reported in this paper have been deposited + with the Cambridge Crystallographic Data Centre.[5] CCDC + {% if cif._database_code_depnum_ccdc_archive %} + {{ cif._database_code_depnum_ccdc_archive|replace('CCDC ', '') }}{% else %} + ?????? + {% endif %} contain the supplementary crystallographic data for this paper. - These data can be obtained free of charge from The Cambridge Crystallographic Data Centre via - www.ccdc.cam.ac.uk/structures. + These data can be obtained free of charge from The Cambridge Crystallographic Data + Centre via www.ccdc.cam.ac.uk/structures. This report and the CIF file were generated using FinalCif.[6]

+ {% if refinement_details %} +

+ Refinement details for {{ cif.block.name }} +

+

+ {{ refinement_details }} +

+ {% endif %} +
+
+ {# Just an empty column to separate the other #}
-

+

Table 1. Crystal data and structure refinement for {{ cif.block.name }} -

+ @@ -244,20 +266,11 @@ - {% if refinement_details %} -

- Refinement details for {{ cif.block.name }} -

-

- {{ refinement_details }} -

- {% endif %} - - {% if atomic_coordinates %} -

- Table 2. Atomic coordinates and Ueq2] for {{ cif.block.name }} -

+

+ Table 2. Atomic coordinates and Ueq2] + for {{ cif.block.name }} +

@@ -292,11 +305,11 @@ {% if displacement_parameters %} -

+

Table 3. Anisotropic displacement parameters [Å2] for {{ cif.block.name }}. The anisotropic displacement factor exponent takes the form: −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] -

+
@@ -337,9 +350,9 @@
{% if options.bonds_table %} {% if bonds %} -

+

Table 4. Bond lengths and angles for {{ cif.block.name }} -

+
- + {% endfor %} @@ -386,13 +400,13 @@ {% endif %}
- + {# Just an empty column to separate the other #}
{% if torsions %} -

+

Table 5. Torsion angles for {{ cif.block.name }} -

+
{% if ba_symminfo %} @@ -376,7 +389,8 @@ {% for a in angles %}
{{ a.atom1 }}{{ a.symm1 }}{{ a.atom2 }}{{ a.symm2 }}{{ a.atom1 }}{{ a.symm1 }}{{ a.atom2 }}{{ a.symm2 }} + {{ a.angle }}
+ {% for atom in atomic_coordinates %} + + + + + + + + {% endfor %} + +
{% if torsion_symminfo %} @@ -426,9 +440,9 @@ {% if options.hydrogen_bonds and hydrogen_bonds %} -

+

Table 6. Hydrogen bonds for {{ cif.block.name }} -

+
@@ -473,9 +487,9 @@
{% endif %} -

+

Bibliography -

+
  1. [1] {{ literature.integration.html }}
  2. From 99058977eb06212631f9d3b7b752abb525754671 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 15:34:22 +0100 Subject: [PATCH 11/16] Update templated_report.py --- finalcif/report/templated_report.py | 31 ++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index a757e6e1..2254dc49 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -849,17 +849,31 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do if __name__ == '__main__': from unittest import mock import subprocess - # TODO: Add _refine.ls_weighting_details - # _reflns_number_gt / _reflns_number_total were greater than %(_BAXS_sigma_cutoff + data = Path('tests') testcif = Path(data / 'examples/1979688.cif').absolute() - #testcif = Path(r'test-data/p31c.cif').absolute() + # testcif = Path(r'test-data/p31c.cif').absolute() cif = CifContainer(testcif) - mock_mock = mock.Mock() - mock.without_h = '' - t = TemplatedReport(format=TextFormat.HTML, options=mock_mock, cif=cif) + + + class MOptions(): + report_text = True + report_adp = True + without_h = False + picture_width = 7.6 + checkcif_url = '' + atoms_table = True + bonds_table = True + hydrogen_bonds = True + current_report_template = '' + cod_url = '' + track_changes = False + + + options = MOptions() + t = TemplatedReport(format=TextFormat.HTML, options=options, cif=cif) pic = pathlib.Path("tests/examples/work/cu_BruecknerJK_153F40_0m-finalcif.png") - maincontext = t.get_context(cif, options=mock_mock, picfile=pic, tpl_doc=mock_mock) + maincontext = t.get_context(cif, options=options, picfile=pic, tpl_doc=None) # pprint(maincontext) templateLoader = jinja2.FileSystemLoader(searchpath=r"finalcif/template") jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False, line_comment_prefix='##') @@ -869,11 +883,10 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do template = jinja_env.get_template(TEMPLATE_FILE) outputText = template.render(maincontext) - print(outputText) + # print(outputText) p = Path('test.html') p.write_text(outputText, encoding="utf-8") if sys.platform == 'darwin': subprocess.call(['open', str(p.resolve())]) else: subprocess.Popen(['explorer', str(p.resolve())], shell=True) - print(maincontext.get('')) From e03c2182f4c990e69c2104ce6f7373d9b9d9fa15 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 18:37:26 +0100 Subject: [PATCH 12/16] remove unused method --- finalcif/report/templated_report.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 2254dc49..1db2eb48 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -664,10 +664,6 @@ def make_picture(self, options: Options, picfile: Path, tpl_doc: DocxTemplate): return InlineImage(tpl_doc, str(picfile.resolve()), width=Cm(options.picture_width)) return None - def prepare_report(self, options: Options, cif: CifContainer, output_filename: str, picfile: Path, - template_path: Path) -> bool: - context = self._get_context(cif, options, picfile, tpl_doc) - def hkl_index_limits(self, cif: CifContainer) -> str: limit_h_min = cif['_diffrn_reflns_limit_h_min'] limit_h_max = cif['_diffrn_reflns_limit_h_max'] From 976c1632379b03d42d911023d48184e8608c8951 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 19:05:57 +0100 Subject: [PATCH 13/16] Fix for Python3.10 --- finalcif/appwindow.py | 6 +++--- finalcif/report/templated_report.py | 14 +++++++------- tests/test_templated_report.py | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 55744ddb..72d492b9 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -47,7 +47,7 @@ from finalcif.gui.plaintextedit import MyQPlainTextEdit from finalcif.gui.text_value_editor import MyTextTemplateEdit, TextEditItem from finalcif.gui.vrf_classes import MyVRFContainer, VREF -from finalcif.report.templated_report import TextFormat +from finalcif.report.templated_report import ReportFormat from finalcif.template.templates import ReportTemplates from finalcif.tools.download import MyDownloader from finalcif.tools.dsrmath import my_isnumeric @@ -1126,12 +1126,12 @@ def make_report_tables(self) -> None: make_multi_tables(cif=self.cif, output_filename=str(multi_table_document)) else: print('Report with templates') - t = TemplatedReport(format=TextFormat.RICHTEXT, options=self.options, cif=self.cif) + t = TemplatedReport(format=ReportFormat.RICHTEXT, options=self.options, cif=self.cif) ok = t.make_templated_docx_report(options=self.options, output_filename=str(report_filename), picfile=picfile, template_path=Path(self.get_checked_templates_list_text())) - t = TemplatedReport(format=TextFormat.HTML, options=self.options, cif=self.cif) + t = TemplatedReport(format=ReportFormat.HTML, options=self.options, cif=self.cif) t.make_templated_html_report( options=self.options, picfile=picfile) diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 1db2eb48..e9f1a340 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -32,7 +32,7 @@ AdpWithMinus = namedtuple('AdpWithMinus', ('label', 'U11', 'U22', 'U33', 'U23', 'U13', 'U12')) -class TextFormat(enum.StrEnum): +class ReportFormat(enum.Enum): RICHTEXT = 'richtext' HTML = 'html' @@ -455,7 +455,7 @@ def refinement_prog(self, cif: CifContainer) -> str: if 'OLEX' in refined.upper(): self.literature['refinement'] = Olex2Reference() if ('NOSPHERA2' in refined.upper() or 'NOSPHERA2' in cif['_refine_special_details'].upper() or - 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): + 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): self.literature['refinement'] = Nosphera2Reference() return refined.split()[0] @@ -679,17 +679,17 @@ def hkl_index_limits(self, cif: CifContainer) -> str: f'{less_or_equal} l {less_or_equal} {limit_l_max}') -def text_factory(options: Options, cif: CifContainer) -> dict[str, Formatter]: +def text_factory(options: Options, cif: CifContainer) -> dict[ReportFormat, Formatter]: factory = { - TextFormat.RICHTEXT: RichTextFormatter(options, cif), - TextFormat.HTML : HtmlFormatter(options, cif), + ReportFormat.RICHTEXT: RichTextFormatter(options, cif), + ReportFormat.HTML : HtmlFormatter(options, cif), # 'plaintext': StringFormatter(), } return factory class TemplatedReport(): - def __init__(self, format: str, options: Options, cif: CifContainer) -> None: + def __init__(self, format: ReportFormat, options: Options, cif: CifContainer) -> None: self.format = format self.cif = cif self.text_formatter = text_factory(options, cif)[self.format] @@ -867,7 +867,7 @@ class MOptions(): options = MOptions() - t = TemplatedReport(format=TextFormat.HTML, options=options, cif=cif) + t = TemplatedReport(format=ReportFormat.HTML, options=options, cif=cif) pic = pathlib.Path("tests/examples/work/cu_BruecknerJK_153F40_0m-finalcif.png") maincontext = t.get_context(cif, options=options, picfile=pic, tpl_doc=None) # pprint(maincontext) diff --git a/tests/test_templated_report.py b/tests/test_templated_report.py index 4b245cb1..4c7211ef 100644 --- a/tests/test_templated_report.py +++ b/tests/test_templated_report.py @@ -14,7 +14,7 @@ from finalcif import VERSION from finalcif.appwindow import AppWindow from finalcif.cif.cif_file_io import CifContainer -from finalcif.report.templated_report import TemplatedReport, TextFormat +from finalcif.report.templated_report import TemplatedReport, ReportFormat data = Path('tests') test_data = Path('test-data') @@ -92,7 +92,7 @@ def tearDown(self) -> None: self.report_zip.unlink(missing_ok=True) def test_ccdc_num_in_table(self): - t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) + t = TemplatedReport(cif=CifContainer(self.testcif), format=ReportFormat.RICHTEXT, options=self.options) ok = t.make_templated_docx_report(options=self.options, output_filename=str(self.reportdoc), picfile=self.report_pic, template_path=self.text_template) @@ -107,7 +107,7 @@ def test_picture_has_correct_size(self): For this test, self.myapp.set_report_picture(Path('finalcif/icon/finalcif.png')) has to be set correctly. """ - t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) + t = TemplatedReport(cif=CifContainer(self.testcif), format=ReportFormat.RICHTEXT, options=self.options) ok = t.make_templated_docx_report(options=self.options, output_filename=str(self.reportdoc), picfile=self.report_pic, template_path=self.text_template) @@ -118,7 +118,7 @@ def test_picture_has_correct_size(self): self.assertEqual(Cm(7.43).emu, shapes[0].width) def test_citations(self): - t = TemplatedReport(cif=CifContainer(self.testcif), format=TextFormat.RICHTEXT, options=self.options) + t = TemplatedReport(cif=CifContainer(self.testcif), format=ReportFormat.RICHTEXT, options=self.options) t.make_templated_docx_report(options=self.options, output_filename=self.reportdoc.__str__(), picfile=self.report_pic, @@ -144,7 +144,7 @@ def tearDown(self): self.reportdoc.unlink(missing_ok=True) def test_get_distance_from_atoms(self): - t = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + t = TemplatedReport(cif=self.cif, format=ReportFormat.RICHTEXT, options=self.options) ok = t.make_templated_docx_report(options=self.options, output_filename='test.docx', picfile=Path(), template_path=self.docx_templ) @@ -168,7 +168,7 @@ def test_get_integration_program_with_spaces(self): # Here we have the special case, that there is no space character between the version number and the bracket. self.cif['_computing_data_reduction'] = 'CrysAlisPro 1.171.39.20a (Rigaku OD, 2015)' self.assertEqual('CrysAlisPro 1.171.39.20a (Rigaku OD, 2015)', self.cif['_computing_data_reduction']) - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + r = TemplatedReport(cif=self.cif, format=ReportFormat.RICHTEXT, options=self.options) result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.text_formatter.literature['integration'])) @@ -178,7 +178,7 @@ def test_get_integration_program_with_line_break(self): self.cif['_computing_data_reduction'] = "CrysAlisPro 1.171.39.20a\n" \ "(Rigaku OD, 2015)\n" self.assertEqual('CrysAlisPro 1.171.39.20a\n(Rigaku OD, 2015)\n', self.cif['_computing_data_reduction']) - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + r = TemplatedReport(cif=self.cif, format=ReportFormat.RICHTEXT, options=self.options) result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) self.assertEqual('Crysalispro, 1.171.39.20a, 2015, Rigaku OD.', str(r.text_formatter.literature['integration'])) @@ -187,7 +187,7 @@ def test_get_integration_program_with_missing_information(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'CrysAlisPro 1.171.39.20a (Rigaku OD)' self.assertEqual('CrysAlisPro 1.171.39.20a (Rigaku OD)', self.cif['_computing_data_reduction']) - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + r = TemplatedReport(cif=self.cif, format=ReportFormat.RICHTEXT, options=self.options) result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('CrysAlisPro', result) self.assertEqual('Crysalispro, unknown version, Rigaku OD.', str(r.text_formatter.literature['integration'])) @@ -195,7 +195,7 @@ def test_get_integration_program_with_missing_information(self): def test_get_integration_program_saint(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'SAINT V8.40A' - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + r = TemplatedReport(cif=self.cif, format=ReportFormat.RICHTEXT, options=self.options) result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('SAINT V8.40A', result) self.assertEqual('Bruker, SAINT, V8.40A, Bruker AXS Inc., Madison, Wisconsin, USA.', @@ -204,7 +204,7 @@ def test_get_integration_program_saint(self): def test_get_integration_program_saint_without_version(self): # Here we have all in one line with spaces inbetween: self.cif['_computing_data_reduction'] = 'SAINT' - r = TemplatedReport(cif=self.cif, format=TextFormat.RICHTEXT, options=self.options) + r = TemplatedReport(cif=self.cif, format=ReportFormat.RICHTEXT, options=self.options) result = r.text_formatter.get_integration_program(self.cif) self.assertEqual('SAINT', result) self.assertEqual('Bruker, SAINT, Bruker AXS Inc., Madison, Wisconsin, USA.', From 47dc2e2c39166032a611d386d4e091659f8ce794 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Fri, 1 Mar 2024 19:13:12 +0100 Subject: [PATCH 14/16] Update templated_report.py --- finalcif/report/templated_report.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index e9f1a340..3802ac24 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -4,11 +4,12 @@ import pathlib import re import sys +from abc import ABC from collections import namedtuple from contextlib import suppress from math import sin, radians from pathlib import Path -from typing import List, Dict, Union, Iterator, Protocol, Any +from typing import List, Dict, Union, Iterator, Any import jinja2 from docx.enum.text import WD_PARAGRAPH_ALIGNMENT @@ -298,7 +299,7 @@ def symmsearch(cif: CifContainer, newsymms: Dict[int, str], num: int, return num -class Formatter(Protocol): +class Formatter(ABC): def __init__(self, options: Options, cif: CifContainer) -> None: self.literature = {'finalcif' : FinalCifReference(), 'ccdc' : CCDCReference(), @@ -508,6 +509,9 @@ def get_completeness(self, cif: CifContainer) -> str: class HtmlFormatter(Formatter): + def __init__(self, options: Options, cif: CifContainer) -> None: + super().__init__(options, cif) + def get_bonds(self) -> list[Bond]: return self._bonds_angles.bonds_as_string @@ -594,6 +598,9 @@ def hkl_index_limits(self, cif: CifContainer) -> str: class RichTextFormatter(Formatter): + def __init__(self, options: Options, cif: CifContainer) -> None: + super().__init__(options, cif) + def get_bonds(self) -> List[Dict[str, RichText]]: return self._bonds_angles.bonds_richtext @@ -843,7 +850,6 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do if __name__ == '__main__': - from unittest import mock import subprocess data = Path('tests') From 2650ddd7c4999f177e81c724bad54ba8382a42c4 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Mon, 4 Mar 2024 14:14:58 +0100 Subject: [PATCH 15/16] Make options setable for debug prurposes --- finalcif/tools/options.py | 57 +++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/finalcif/tools/options.py b/finalcif/tools/options.py index e6c2518e..61fd6a2b 100644 --- a/finalcif/tools/options.py +++ b/finalcif/tools/options.py @@ -5,9 +5,15 @@ class Options: - def __init__(self, ui: Ui_FinalCifWindow, settings: FinalCifSettings): + def __init__(self, ui: Ui_FinalCifWindow = None, settings: FinalCifSettings = None, debug: bool = False): + """ + :param ui: UI of FinalCif + :param settings: Settings of FinalCif + :param debug: If turned on, this object can set and get attributes without using the settings. + """ self.ui = ui self.settings = settings + self.debug = debug # initial default, otherwise we have width=0.0 and no picture visible: if ui: self.ui.PictureWidthDoubleSpinBox.setValue(7.5) @@ -64,30 +70,57 @@ def __getitem__(self, item): return self.settings.load_options()[item] def _get_setting(self, setting: str, default: object): - with suppress(KeyError): + try: return self.settings.load_options()[setting] - return default + except KeyError: + return default + except AttributeError: + # Be able to set op.without_h = True without having settings attribute + if self.debug: + return getattr(self, f'_{setting}', default) + else: + raise @property def report_text(self) -> bool: return self._get_setting('report_text', True) + @report_text.setter + def report_text(self, value: bool): + self._report_text = value + @property def report_adp(self) -> bool: return self._get_setting('report_adp', True) + @report_adp.setter + def report_adp(self, value: bool): + self._report_adp = value + @property def without_h(self) -> bool: return self._get_setting('without_h', False) + @without_h.setter + def without_h(self, value: bool): + self._without_h = value + @property def track_changes(self) -> bool: return self._get_setting('track_changes', False) + @track_changes.setter + def track_changes(self, value: bool): + self._track_changes = value + @property def current_template(self) -> int: return self._get_setting('current_report_template', 0) + @current_template.setter + def current_template(self, value: bool): + self._current_template = value + @property def picture_width(self) -> float: width = self._get_setting('picture_width', 7.5) @@ -97,6 +130,10 @@ def picture_width(self) -> float: else: return width + @picture_width.setter + def picture_width(self, value: bool): + self._picture_width = value + @property def checkcif_url(self) -> str: try: @@ -108,9 +145,14 @@ def checkcif_url(self) -> str: except KeyError: return self.set_default_checkcif_url() + @checkcif_url.setter + def checkcif_url(self, value: bool): + self._checkcif_url = value + def set_default_checkcif_url(self): url = 'https://checkcif.iucr.org/cgi-bin/checkcif_hkl.pl' - self.ui.CheckCIFServerURLTextedit.setText(url) + if self.ui: + self.ui.CheckCIFServerURLTextedit.setText(url) return url @property @@ -124,7 +166,12 @@ def cod_url(self) -> str: except KeyError: return self.set_default_cod_url() + @cod_url.setter + def cod_url(self, value: bool): + self._cod_url = value + def set_default_cod_url(self): url = 'https://www.crystallography.net/cod/cgi-bin/cif-deposit.pl' - self.ui.CODURLTextedit.setText(url) + if self.ui: + self.ui.CODURLTextedit.setText(url) return url From da965532bdb29b0c36e52a7615b3e38697ce4705 Mon Sep 17 00:00:00 2001 From: Daniel Kratzert Date: Mon, 4 Mar 2024 15:23:09 +0100 Subject: [PATCH 16/16] Refactoring of template report to get it into final shape --- finalcif/appwindow.py | 6 +- finalcif/report/report_text.py | 12 - finalcif/report/templated_report.py | 78 ++-- finalcif/template/report.tmpl | 677 ++++++++++++++-------------- finalcif/tools/options.py | 43 +- 5 files changed, 379 insertions(+), 437 deletions(-) diff --git a/finalcif/appwindow.py b/finalcif/appwindow.py index 72d492b9..e5197f31 100644 --- a/finalcif/appwindow.py +++ b/finalcif/appwindow.py @@ -1131,10 +1131,8 @@ def make_report_tables(self) -> None: output_filename=str(report_filename), picfile=picfile, template_path=Path(self.get_checked_templates_list_text())) - t = TemplatedReport(format=ReportFormat.HTML, options=self.options, cif=self.cif) - t.make_templated_html_report( - options=self.options, - picfile=picfile) + #t = TemplatedReport(format=ReportFormat.HTML, options=self.options, cif=self.cif) + #t.make_templated_html_report(options=self.options, picfile=picfile) if not ok: return None except FileNotFoundError as e: diff --git a/finalcif/report/report_text.py b/finalcif/report/report_text.py index 8b231d81..6a2d6021 100644 --- a/finalcif/report/report_text.py +++ b/finalcif/report/report_text.py @@ -524,18 +524,6 @@ def get_inf_article(next_word: str) -> str: return 'an' if next_word[0].lower() in voc else 'a' -def align_by_dot(number: str) -> str: - """ - The intention of this filter is to align the number - on the dot, but this doesn't work as expected. - https://codepen.io/dominiccomtois/pen/YZdred - """ - if '.' in number: - num, dec = number.split('.') - return (f'{num}' - f'.{dec}') - - def format_radiation(radiation_type: str) -> list: radtype = list(radiation_type.partition("K")) if len(radtype) > 2: diff --git a/finalcif/report/templated_report.py b/finalcif/report/templated_report.py index 3802ac24..8d534a89 100644 --- a/finalcif/report/templated_report.py +++ b/finalcif/report/templated_report.py @@ -23,7 +23,7 @@ from finalcif.report.references import SAINTReference, SHELXLReference, SadabsTwinabsReference, SHELXTReference, \ SHELXSReference, SHELXDReference, SORTAVReference, FinalCifReference, CCDCReference, \ CrysalisProReference, Nosphera2Reference, Olex2Reference -from finalcif.report.report_text import math_to_word, gstr, format_radiation, get_inf_article, MachineType, align_by_dot +from finalcif.report.report_text import math_to_word, gstr, format_radiation, get_inf_article, MachineType from finalcif.report.symm import SymmetryElement from finalcif.tools.misc import isnumeric, this_or_quest, timessym, angstrom, protected_space, less_or_equal, \ halbgeviert, minus_sign, ellipsis_mid @@ -456,7 +456,7 @@ def refinement_prog(self, cif: CifContainer) -> str: if 'OLEX' in refined.upper(): self.literature['refinement'] = Olex2Reference() if ('NOSPHERA2' in refined.upper() or 'NOSPHERA2' in cif['_refine_special_details'].upper() or - 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): + 'NOSPHERAT2' in cif['_olex2_refine_details'].upper()): self.literature['refinement'] = Nosphera2Reference() return refined.split()[0] @@ -699,6 +699,7 @@ class TemplatedReport(): def __init__(self, format: ReportFormat, options: Options, cif: CifContainer) -> None: self.format = format self.cif = cif + self.options = options self.text_formatter = text_factory(options, cif)[self.format] def make_templated_docx_report(self, options: Options, @@ -719,29 +720,20 @@ def make_templated_docx_report(self, options: Options, info_text=str(e)) return False - def make_templated_html_report(self, options: Options, + def make_templated_html_report(self, output_filename: str = 'test.html', picfile: Path = None, + template_path: Path = Path('.'), template_file: str = "report.tmpl") -> bool: - maincontext = self.get_context(self.cif, options, picfile, None) - template_path = app_path.application_path / 'template' - print(template_path.resolve()) - jinja_env = jinja2.Environment( - loader=jinja2.FileSystemLoader(searchpath=template_path.resolve()), - autoescape=False) + context = self.get_context(self.cif, self.options, picfile, None) + jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=template_path.resolve()), + autoescape=False) jinja_env.filters['inv_article'] = get_inf_article - jinja_env.filters['align_dot'] = align_by_dot template = jinja_env.get_template(template_file) try: - outputText = template.render(maincontext) - print(outputText) - p = Path(output_filename) - p.write_text(outputText, encoding="utf-8") - import subprocess - if sys.platform == 'darwin': - subprocess.call(['open', str(p.resolve())]) - else: - subprocess.Popen(['explorer', str(p.resolve())], shell=True) + with open(output_filename, encoding='utf-8', mode='w+t') as f: + outputText = template.render(context) + f.write(outputText) return True except Exception as e: show_general_warning(parent=None, window_title='Warning', warn_text='Document generation failed', @@ -774,7 +766,6 @@ def prepare_report_data(self, cif: CifContainer, def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_doc: DocxTemplate = None): context = {'options' : options, - # {'without_h': True, 'atoms_table': True, 'text': True, 'bonds_table': True}, 'cif' : cif, 'name' : cif.block.name, 'space_group' : self.text_formatter.space_group_subdoc(cif, tpl_doc), @@ -857,38 +848,25 @@ def get_context(self, cif: CifContainer, options: Options, picfile: Path, tpl_do # testcif = Path(r'test-data/p31c.cif').absolute() cif = CifContainer(testcif) + pic = pathlib.Path("screenshots/finalcif_checkcif.png") - class MOptions(): - report_text = True - report_adp = True - without_h = False - picture_width = 7.6 - checkcif_url = '' - atoms_table = True - bonds_table = True - hydrogen_bonds = True - current_report_template = '' - cod_url = '' - track_changes = False - + options = Options() + # Set options with leading underscore without settings parameter in Options: + # options._bonds_table = True + # options._report_adp = True + # options._without_h = True + # options._report_text = False + # options._hydrogen_bonds = True - options = MOptions() t = TemplatedReport(format=ReportFormat.HTML, options=options, cif=cif) - pic = pathlib.Path("tests/examples/work/cu_BruecknerJK_153F40_0m-finalcif.png") - maincontext = t.get_context(cif, options=options, picfile=pic, tpl_doc=None) - # pprint(maincontext) - templateLoader = jinja2.FileSystemLoader(searchpath=r"finalcif/template") - jinja_env = jinja2.Environment(loader=templateLoader, autoescape=False, line_comment_prefix='##') - jinja_env.filters['inv_article'] = get_inf_article - jinja_env.filters['align_dot'] = align_by_dot - TEMPLATE_FILE = "report.tmpl" - template = jinja_env.get_template(TEMPLATE_FILE) - outputText = template.render(maincontext) - - # print(outputText) - p = Path('test.html') - p.write_text(outputText, encoding="utf-8") + output = 'test.html' + template_path = app_path.application_path / 'template' + ok = t.make_templated_html_report(output_filename=output, picfile=pic, template_path=template_path) + if ok: + print('HTML report successfully generated') + else: + print('HTML report failed') if sys.platform == 'darwin': - subprocess.call(['open', str(p.resolve())]) + subprocess.call(['open', output]) else: - subprocess.Popen(['explorer', str(p.resolve())], shell=True) + subprocess.Popen(['explorer', output], shell=True) diff --git a/finalcif/template/report.tmpl b/finalcif/template/report.tmpl index 972da019..6b6bd11c 100644 --- a/finalcif/template/report.tmpl +++ b/finalcif/template/report.tmpl @@ -12,6 +12,10 @@ padding-left: 0; } + #residuals_table_header { + margin-top: 4rem; + } + #report_text { text-align: justify; } @@ -29,7 +33,8 @@ } img { - border-radius: 5px; + border-radius: 6px; + //padding: 1px; } @@ -38,9 +43,14 @@
    -
    + {% if options.report_text %} +
    + {% else %} +
    + {% endif %} -

    Structure Tables

    +

    Structure Tables

    + {% if options.report_text %} {% if structure_figure %}
    @@ -96,259 +106,262 @@ {{ refinement_details }}

    {% endif %} -
    -
    - {# Just an empty column to separate the other #} -
    -
    -

    - Table 1. Crystal data and structure refinement for {{ cif.block.name }} -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
    CCDC number{{ cif._database_code_depnum_ccdc_archive }}
    Empirical formula{{ sum_formula }}
    Formula weight{{ cif._chemical_formula_weight }}
    Temperature [K]{{ cif._diffrn_ambient_temperature }}
    Crystal system{{ cif._space_group_crystal_system }}
    Space group (number){{ space_group }}
    a [Å]{{ cif._cell_length_a }}
    b [Å]{{ cif._cell_length_b }}
    c [Å]{{ cif._cell_length_c }}
    α [°]{{ cif._cell_angle_alpha }}
    β [°]{{ cif._cell_angle_beta }}
    γ [°]{{ cif._cell_angle_gamma }}
    Volume [Å3]{{ cif._cell_volume }}
    Z{{ cif._cell_formula_units_Z }}
    ρcalc [gcm−3]{{ cif._exptl_crystal_density_diffrn }}
    μ [mm−1]{{ cif._exptl_absorpt_coefficient_mu }}
    F(000){{ cif._exptl_crystal_F_000 }}
    Crystal size [mm3]{{ crystal_size }}
    Crystal colour{{ crystal_colour }}
    Crystal shape{{ crystal_shape }}
    Radiation{{ radiation }}{% if wavelength %} (λ={{ wavelength }} Å){% endif %}
    2θ range [°]{{ theta_range }}
    Index ranges{{ index_ranges }}
    Reflections collected{{ cif._diffrn_reflns_number }}
    Independent reflections{{ indepentent_refl }}
    - Rint = {{ r_int }}
    - Rsigma = {{ r_sigma }} -
    Completeness{% if theta_full %} to - θ = {{ theta_full }}°{% endif %} - {{ completeness }}
    Data / Restraints / Parameters{{ data }} / {{ restraints }} / {{ parameters }}
    Absorption correction Tmin/Tmax (method) - {{ t_min }} / {{ t_max }} {% if abstype %}({{ abstype }}){% endif %}
    Goodness-of-fit on F2{{ goof }}
    Final R indexes
    - [I≥2σ(I)] -
    R1 = {{ ls_R_factor_gt }}
    - wR2 = {{ ls_wR_factor_gt }} -
    Final R indexes
    - [all data] + {% endif %} + {% if options.report_text %} + +
    + {# Just an empty column to separate the other #} +
    +
    + {% endif %} +

    + Table 1. Crystal data and structure refinement for {{ cif.block.name }} +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + {% if exti %} + + + + {% endif %} + {% if flack_x %} - - + + - {% if exti %} - - - - - {% endif %} - {% if flack_x %} - - - - - {% endif %} - -
    CCDC number{{ cif._database_code_depnum_ccdc_archive }}
    Empirical formula{{ sum_formula }}
    Formula weight{{ cif._chemical_formula_weight }}
    Temperature [K]{{ cif._diffrn_ambient_temperature }}
    Crystal system{{ cif._space_group_crystal_system }}
    Space group (number){{ space_group }}
    a [Å]{{ cif._cell_length_a }}
    b [Å]{{ cif._cell_length_b }}
    c [Å]{{ cif._cell_length_c }}
    α [°]{{ cif._cell_angle_alpha }}
    β [°]{{ cif._cell_angle_beta }}
    γ [°]{{ cif._cell_angle_gamma }}
    Volume [Å3]{{ cif._cell_volume }}
    Z{{ cif._cell_formula_units_Z }}
    ρcalc [gcm−3]{{ cif._exptl_crystal_density_diffrn }}
    μ [mm−1]{{ cif._exptl_absorpt_coefficient_mu }}
    F(000){{ cif._exptl_crystal_F_000 }}
    Crystal size [mm3]{{ crystal_size }}
    Crystal colour{{ crystal_colour }}
    Crystal shape{{ crystal_shape }}
    Radiation{{ radiation }}{% if wavelength %} (λ={{ wavelength }} Å){% endif %}
    2θ range [°]{{ theta_range }}
    Index ranges{{ index_ranges }}
    Reflections collected{{ cif._diffrn_reflns_number }}
    Independent reflections{{ indepentent_refl }}
    + Rint = {{ r_int }}
    + Rsigma = {{ r_sigma }} +
    Completeness{% if theta_full %} to + θ = {{ theta_full }}°{% endif %} + {{ completeness }}
    Data / Restraints / Parameters{{ data }} / {{ restraints }} / {{ parameters }}
    Absorption correction Tmin/Tmax (method) + {{ t_min }} / {{ t_max }} {% if abstype %}({{ abstype }}){% endif %}
    Goodness-of-fit on F2{{ goof }}
    Final R indexes
    + [I≥2σ(I)] +
    R1 = {{ ls_R_factor_gt }}
    + wR2 = {{ ls_wR_factor_gt }} +
    Final R indexes
    + [all data] -
    R1 = {{ ls_R_factor_all }}
    - wR2 = {{ ls_wR_factor_ref }} -
    R1 = {{ ls_R_factor_all }}
    + wR2 = {{ ls_wR_factor_ref }} +
    Largest peak/hole [eÅ−3]{{ diff_dens_max }}/{{ diff_dens_min }}
    Extinction coefficient{{ exti }}
    Largest peak/hole [eÅ−3]{{ diff_dens_max }}/{{ diff_dens_min }}Flack X parameter{{ flack_x }}
    Extinction coefficient{{ exti }}
    Flack X parameter{{ flack_x }}
    + {% endif %} +
    +
    -
    - {% if atomic_coordinates %} -

    - Table 2. Atomic coordinates and Ueq2] - for {{ cif.block.name }} -

    + {% if atomic_coordinates %} +

    + Table 2. Atomic coordinates and Ueq2] + for {{ cif.block.name }} +

    -
    -
    - - - - - - - - - - - - - {% for atom in atomic_coordinates %} +
    +
    +
    Ueq is defined as 1/3 of the trace of the orthogonalized - Uij tensor. -
    AtomxyzUeq
    + + - - - - - + + + + + - {% endfor %} - -
    Ueq is defined as 1/3 of the trace of the orthogonalized + Uij tensor. +
    {{ atom.label }}{{ atom.x|align_dot }}{{ atom.y|align_dot }}{{ atom.z|align_dot }}{{ atom.u_eq }}AtomxyzUeq
    + +
{{ atom.label }}{{ atom.x }}{{ atom.y }}{{ atom.z }}{{ atom.u_eq }}
+
-
- {% endif %} + {% endif %} - {% if displacement_parameters %} -

- Table 3. Anisotropic displacement parameters [Å2] for {{ cif.block.name }}. - The anisotropic displacement factor exponent takes the form: - −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] -

+ {% if displacement_parameters and options.report_adp %} +

+ Table 3. Anisotropic displacement parameters [Å2] for {{ cif.block.name }}. + The anisotropic displacement factor exponent takes the form: + −2π2[ h2(a*)2U11 + k2(b*)2U22 + … + 2hka*b*U12 ] +

-
-
- - - - - - - - - - - - - - - {% for atom in displacement_parameters %} +
+
+
Ueq is defined as 1/3 of the trace of the orthogonalized - Uij tensor. -
AtomU11U22U33U23U13U12
+ + - - - - - - - + + + + + + + - {% endfor %} - -
Ueq is defined as 1/3 of the trace of the orthogonalized + Uij tensor. +
{{ atom.label }}{{ atom.U11 }}{{ atom.U22 }}{{ atom.U33 }}{{ atom.U23 }}{{ atom.U13 }}{{ atom.U12 }}AtomU11U22U33U23U13U12
+ + + {% for atom in displacement_parameters %} + + {{ atom.label }} + {{ atom.U11 }} + {{ atom.U22 }} + {{ atom.U33 }} + {{ atom.U23 }} + {{ atom.U13 }} + {{ atom.U12 }} + + {% endfor %} + + +
-
- {% endif %} + {% endif %} -
-
- {% if options.bonds_table %} +
+
+ {# {% if options.bonds_table %} #} {% if bonds %}

Table 4. Bond lengths and angles for {{ cif.block.name }} @@ -397,110 +410,110 @@ {% endif %} - {% endif %} -

-
- {# Just an empty column to separate the other #} -
-
- {% if torsions %} -

- Table 5. Torsion angles for {{ cif.block.name }} -

- - - - - - - - - - {% for t in torsions %} + {# {% endif %} #} + +
+ {# Just an empty column to separate the other #} +
+
+ {% if torsions %} +

+ Table 5. Torsion angles for {{ cif.block.name }} +

+
- {% if torsion_symminfo %} - {{ torsion_symminfo }} - {% endif %} - {% if options.without_h %} - Bonds and angles to hydrogen atoms were omitted. - {% endif %} -
Atom–Atom–Atom–AtomTorsion Angle [°]
+ + - - + + - {% endfor %} - -
+ {% if torsion_symminfo %} + {{ torsion_symminfo }} + {% endif %} + {% if options.without_h %} + Bonds and angles to hydrogen atoms were omitted. + {% endif %} +
- {{ t.atom1 }}{{ t.symm1 }}–{{ t.atom2 }}{{ t.symm2 }}–{{ t.atom3 }}{{ t.symm3 }}–{{ t.atom4 }}{{ t.symm4 }} - - {{ t.angle }} - Atom–Atom–Atom–AtomTorsion Angle [°]
- {% endif %} + + + {% for t in torsions %} + + + {{ t.atom1 }}{{ t.symm1 }}–{{ t.atom2 }}{{ t.symm2 }}–{{ t.atom3 }}{{ t.symm3 }}–{{ t.atom4 }}{{ t.symm4 }} + + + {{ t.angle }} + + + {% endfor %} + + + {% endif %} +
-
- {% if options.hydrogen_bonds and hydrogen_bonds %} -

- Table 6. Hydrogen bonds for {{ cif.block.name }} -

+ {% if options.hydrogen_bonds and hydrogen_bonds %} +

+ Table 6. Hydrogen bonds for {{ cif.block.name }} +

-
-
- - - - - - - - - - - - - {% for h in hydrogen_bonds %} +
+
+
- {% if hydrogen_symminfo %} - {{ hydrogen_symminfo }} - {% endif %} -
D–H⋯A [Å]d(D–H) [Å] d(H⋯A) [Å]d(D⋯A) [Å]<(DHA) [°]
+ + - - - - - + + + + + - {% endfor %} - -
+ {% if hydrogen_symminfo %} + {{ hydrogen_symminfo }} + {% endif %} +
- {{ h.atoms }} - - {{ h.dist_dh }} - - {{ h.dist_ha }} - - {{ h.dist_da }} - - {{ h.angle_dha }} - D–H⋯A [Å]d(D–H) [Å] d(H⋯A) [Å]d(D⋯A) [Å]<(DHA) [°]
+ + + {% for h in hydrogen_bonds %} + + + {{ h.atoms }} + + + {{ h.dist_dh }} + + + {{ h.dist_ha }} + + + {{ h.dist_da }} + + + {{ h.angle_dha }} + + + {% endfor %} + + +
-
- {% endif %} + {% endif %} -

- Bibliography -

+

+ Bibliography +

-
    -
  1. [1] {{ literature.integration.html }}
  2. -
  3. [2] {{ literature.absorption.html }}
  4. -
  5. [3] {{ literature.solution.html }}
  6. -
  7. [4] {{ literature.refinement.html }}
  8. -
  9. [5] {{ literature.ccdc.html }}
  10. -
  11. [6] {{ literature.finalcif.html }}
  12. -
+
    +
  1. [1] {{ literature.integration.html }}
  2. +
  3. [2] {{ literature.absorption.html }}
  4. +
  5. [3] {{ literature.solution.html }}
  6. +
  7. [4] {{ literature.refinement.html }}
  8. +
  9. [5] {{ literature.ccdc.html }}
  10. +
  11. [6] {{ literature.finalcif.html }}
  12. +
-
+
\ No newline at end of file diff --git a/finalcif/tools/options.py b/finalcif/tools/options.py index 61fd6a2b..7d271873 100644 --- a/finalcif/tools/options.py +++ b/finalcif/tools/options.py @@ -5,7 +5,7 @@ class Options: - def __init__(self, ui: Ui_FinalCifWindow = None, settings: FinalCifSettings = None, debug: bool = False): + def __init__(self, ui: Ui_FinalCifWindow = None, settings: FinalCifSettings = None): """ :param ui: UI of FinalCif :param settings: Settings of FinalCif @@ -13,7 +13,6 @@ def __init__(self, ui: Ui_FinalCifWindow = None, settings: FinalCifSettings = No """ self.ui = ui self.settings = settings - self.debug = debug # initial default, otherwise we have width=0.0 and no picture visible: if ui: self.ui.PictureWidthDoubleSpinBox.setValue(7.5) @@ -75,52 +74,30 @@ def _get_setting(self, setting: str, default: object): except KeyError: return default except AttributeError: - # Be able to set op.without_h = True without having settings attribute - if self.debug: - return getattr(self, f'_{setting}', default) - else: - raise + # Be able to set op._without_h = True and get its value without having settings attribute. + # Only for debugging! + return getattr(self, f'_{setting}', default) @property def report_text(self) -> bool: return self._get_setting('report_text', True) - @report_text.setter - def report_text(self, value: bool): - self._report_text = value - @property def report_adp(self) -> bool: return self._get_setting('report_adp', True) - @report_adp.setter - def report_adp(self, value: bool): - self._report_adp = value - @property def without_h(self) -> bool: return self._get_setting('without_h', False) - @without_h.setter - def without_h(self, value: bool): - self._without_h = value - @property def track_changes(self) -> bool: return self._get_setting('track_changes', False) - @track_changes.setter - def track_changes(self, value: bool): - self._track_changes = value - @property def current_template(self) -> int: return self._get_setting('current_report_template', 0) - @current_template.setter - def current_template(self, value: bool): - self._current_template = value - @property def picture_width(self) -> float: width = self._get_setting('picture_width', 7.5) @@ -130,10 +107,6 @@ def picture_width(self) -> float: else: return width - @picture_width.setter - def picture_width(self, value: bool): - self._picture_width = value - @property def checkcif_url(self) -> str: try: @@ -145,10 +118,6 @@ def checkcif_url(self) -> str: except KeyError: return self.set_default_checkcif_url() - @checkcif_url.setter - def checkcif_url(self, value: bool): - self._checkcif_url = value - def set_default_checkcif_url(self): url = 'https://checkcif.iucr.org/cgi-bin/checkcif_hkl.pl' if self.ui: @@ -166,10 +135,6 @@ def cod_url(self) -> str: except KeyError: return self.set_default_cod_url() - @cod_url.setter - def cod_url(self, value: bool): - self._cod_url = value - def set_default_cod_url(self): url = 'https://www.crystallography.net/cod/cgi-bin/cif-deposit.pl' if self.ui: