### ===================================================================================================================
### VIIA create appendices
### ===================================================================================================================
# Copyright ©VIIA 2024
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
from __future__ import annotations
from typing import TYPE_CHECKING, Dict, List, Any, Optional
from datetime import datetime
from pathlib import Path
import yaml
# References for functions and classes in the rhdhv_fem package
from rhdhv_fem.fem_tools import fem_create_folder
from rhdhv_fem.fem_math import fem_compare_values
# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
from viiapackage.viiaStatus import ViiaProject
from viiapackage.geometry import viia_get_fstrip_width
from viiapackage.reporting.viia_report_input_check import viia_get_tender_specification_deliverable
from viiapackage.reporting.helper_functions.viia_get_general_info import viia_get_general_info
### ===================================================================================================================
### 2. Functions to create appendices
### ===================================================================================================================
[docs]def viia_create_model_plots_appendix(
project: ViiaProject, template_file: Optional[Path] = None, output_folder: Optional[Path] = None,
pictures_folder: Optional[Path] = None) -> Path:
"""
This function creates appendix of building setup for the engineering report.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- template_location (Path): Optional input for the location of the template file. Default value None. If not
provided the file will be selected from the template library within this function.
- output_folder (Path): Optional input for location where to create the report appendix. Default value is None,
indicating the default location is used. In normal production objects do not change this!
- pictures_folder (Path): Optional input for location where all the pictures should be collected from.
Default value is None. In which case the appendix pictures folder set in project is used.
Output:
- The requested report is generated with the information of the object in py-memory, databases and local
(image-) files. It is saved in the 'ER' folder of the working folder or the location of the folder mentioned
in the input.
"""
# Find template if not provided
if template_file is None:
# Collect the tender specification from the deliverable on MYVIIA
tender_specification = viia_get_tender_specification_deliverable(project=project, report_type='engineering')
# Read yaml with template references
with open(project.viia_settings.project_specific_package_location / 'reporting' / 'reports.yaml') as f:
report_templates = yaml.load(f, Loader=yaml.FullLoader)
# Set template (collect all templates for defined template version)
template_file = \
project.viia_settings.project_specific_package_location / \
report_templates['engineering']['C3'][tender_specification][project.analysis_type]
# Check if the template file exists
if not template_file.exists():
raise NotImplementedError(
"ERROR: Could not find the correct template to be used for the model plots appendix. Please report to the "
"VIIA automation team.")
# Set the location of relevant files and folders, as well as the output document name
if output_folder:
output_document_name = f'VIIA-{project.name}-C3.docx'
else:
time_reference = datetime.now().strftime('%Y%m%d%H%M%S')
output_folder = project.workfolder_location / 'ER'
output_document_name = f'VIIA-{project.name}-C3-{time_reference}.docx'
# Create the report folder
fem_create_folder(output_folder)
# Get the general info for the template
context = viia_get_general_info(project)
# Add necessary data to the context
context['all_pictures'] = get_all_pictures(project=project, pictures_folder=pictures_folder)
context['fstrips'] = get_fstrips_info(project=project)
context['fwalls'] = get_fwalls_info(project=project)
context['floors'] = get_floors_info(project=project)
context['roofs'] = get_roofs_info(project=project)
context['count'] = {
'fstrips': len(project.collections.fstrips),
'fwalls': len([wall for wall in project.collections.walls if 'F-WANDEN' in wall.name]),
'floors': len(project.collections.floors),
'walls': len([wall for wall in project.collections.walls if 'F-WANDEN' not in wall.name]),
'columns': len(project.collections.columns),
'beams': len(project.collections.beams) + len(project.collections.lintels),
'roofs': len(project.collections.roofs),
'linemasses': len(project.collections.line_masses)}
# Getting rid of 'Grids' from context['all_pictures'] dictionary, since a few are not required in
# template version v7_1
if 'Grids' in context['all_pictures']:
del (context['all_pictures']['Grids'])
# Create report
generated_report = project.create_report(
template_file=template_file, data=context, output_file=output_folder / output_document_name, images=True)
project.write_log(
f"A draft of your Appendix building setup {output_document_name} is generated and saved in "
f"'{output_folder.as_posix()}'.")
return generated_report.output_file
### ===================================================================================================================
### 3. Functions to get info for the appendices
### ===================================================================================================================
[docs]def get_all_pictures(project: ViiaProject, pictures_folder: Optional[Path] = None) -> Dict[str, List[str]]:
"""
Function collects all pictures for the building structural setup appendix.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- pictures_folder (Path): Optional input for location where all the pictures should be collected from. Default
value is None.
Output:
- Returns a dictionary with sections (keys) and list of pictures in the picture folder (values).
"""
sections = [
'Grids', 'Side Views', 'Foundations', 'Floors', 'Walls and Columns', 'Beams and Lintels', 'Roof Beams',
'Roof Structure', 'Interface Connections', 'Hinged Connections', 'Loads']
layer_order = [
'Basement', 'Ground', 'First', 'Second', 'Third', 'Fourth', 'Fifth', 'Sixth', 'Seventh', 'Eighth',
'Ninth', 'Tenth', 'Eleventh', 'Twelfth', 'Attic', 'Roof']
all_pictures = {}
# If pictures folder is not provided, the appendix_pictures_folder stored in project is selected
if pictures_folder is None and not project.appendix_pictures_folder:
return all_pictures
if pictures_folder is None:
pictures_folder = project.appendix_pictures_folder
if not pictures_folder.exists():
raise FileNotFoundError(f"The folder '{pictures_folder}' does not exist.")
else:
pictures = [x.name for x in pictures_folder.iterdir() if x.is_file()]
for section in sections:
# Get all pictures for the section and store them in a list
section_pictures = []
for picture in pictures:
pic_name = picture.split('-')
if section in pic_name[0] or pic_name[0] in section:
section_pictures.append(picture)
ordered_section_pictures = []
if section not in ['Side Views', 'Grids', 'Foundations']:
for layer in layer_order:
for picture in section_pictures:
if layer in picture:
if picture not in ordered_section_pictures:
ordered_section_pictures.append(picture)
elif section == 'Foundations':
max_id_details = []
for i, picture in enumerate(section_pictures):
if 'Cross section' in picture:
max_id_details.append(i)
if max_id_details:
ordered_section_pictures = section_pictures[max(max_id_details) + 1:] + \
section_pictures[:max(max_id_details) + 1]
else:
ordered_section_pictures = section_pictures
else:
ordered_section_pictures = section_pictures
ordered_section_pictures = \
[pictures_folder.as_posix() + '/' + pic_name for pic_name in ordered_section_pictures]
all_pictures[section] = ordered_section_pictures
return all_pictures
[docs]def get_fstrips_info(project: ViiaProject) -> List[Dict[str, Any]]:
"""
Function collects the information in building structural setup appendix for the foundation strips.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
Output:
- Returns a list with dictionaries with information per foundation strip.
"""
fstrips = []
for fstrip in project.collections.fstrips:
fstrips.append({
'name': f'Foundation strip {fstrip.id}',
'material': fstrip.material.name.replace('<', ' pre-').replace('>', ' post-'),
'depth': round(fstrip.contour.get_min_z(), 3),
'thickness': round(fstrip.geometry.geometry_model.thickness, 3),
'width': round(viia_get_fstrip_width(fstrip=fstrip), 3),
'density': int(round(fstrip.material.mass_density, 0))})
return fstrips
[docs]def get_fwalls_info(project: ViiaProject) -> List[Dict[str, Any]]:
"""
Function collects the information in building structural setup appendix for the foundation walls.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
Output:
- Returns a list with dictionaries with information per foundation wall.
"""
fwalls = []
layer_f = project.find('F', 'layers')
if layer_f and layer_f.walls:
for fwall in project.find('F', 'layers').walls:
mat_split = fwall.material.name.split('-')
add_ind = 1 if 'LIN' in mat_split else 0
material = '-'.join(fwall.material.name.split('-')[:2 + add_ind])
material = material.replace('<', ' pre-').replace('>', ' post-')
fwalls.append({
'name': f'Foundation wall {fwall.id}',
'linearity': 'Linear' if fwall.material.is_linear else 'Nonlinear',
'material': material,
'thickness': round(fwall.geometry.geometry_model.thickness, 3),
'height': round(abs(fwall.contour.get_max_z() - fwall.contour.get_min_z()), 3),
'density': int(round(fwall.material.mass_density, 0))})
return fwalls
[docs]def get_floors_info(project: ViiaProject) -> Dict[str, Any]:
"""
Function collects the information in building structural setup appendix for the floors in the model.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
Output:
- Returns a dictionary with information of the floors in the model.
"""
timber_floors = []
concrete_combi_floors = []
nehobo_floors = []
ribbed_floors = []
beam_block_floors = []
other_floors = []
for floor in project.collections.floors:
mat_split = floor.material.name.split('-')
add_ind = 1 if 'LIN' in mat_split else 0
# Equivalent timber floors
if any(sub_str in floor.material.name for sub_str in ['PLANKEN', 'PLATEN']):
# If Timber floors are made using NPR version before 2020, then there is no info about the span and width,
# and the length of the list is too short
if len(mat_split) < 7 + add_ind + 1:
span, width = '-', '-'
else:
span = mat_split[6 + add_ind]
width = mat_split[7 + add_ind]
timber_floors.append({
'name': f'Floor {floor.id}',
'material': '-'.join(mat_split[:2 + add_ind]),
't_eq': floor.geometry.geometry_model.thickness * 1E3,
't_pl': mat_split[2 + add_ind],
'joist_width': mat_split[3 + add_ind],
'joist_height': mat_split[4 + add_ind],
'joist_ctc': mat_split[5 + add_ind],
'span': span,
'width': width})
# Concrete and/or combination floors
elif any(sub_str in floor.material.name for sub_str in ['BETON', 'LEWIS', 'MPV', 'KPV']):
concrete_combi_floors.append({
'name': f'Floor {floor.id}',
'material': '-'.join(mat_split[:1 + add_ind]),
'thickness': floor.geometry.geometry_model.thickness,
'top_layer': mat_split[-1] if 'KPV' in floor.material.name else '-'})
# NEHOBO floors
elif 'NEHOBO' in floor.material.name:
nehobo_floors.append({
'name': f'Floor {floor.id}',
'material': '-'.join(mat_split[:2 + add_ind]),
'thickness': floor.geometry.geometry_model.thickness})
# Ribbed floors
elif 'RIB' in floor.material.name:
ribbed_floors.append({
'name': f'Floor {floor.id}',
'material': '-'.join(mat_split[:2 + add_ind]),
'thickness': floor.geometry.geometry_model.thickness})
# Beam-and-block floors
elif any(sub_str in floor.material.name for sub_str in ['COMBI', 'DATO', ]):
beam_block_floors.append({
'name': f'Floor {floor.id}',
'material': floor.material.name,
'thickness': floor.geometry.geometry_model.thickness,
'top_layer': mat_split[-1] if 'COMBI' in floor.material.name else '-'})
# Other floors
else:
other_floors.append({
'name': f'Floor {floor.id}',
'material': floor.material.name,
'geometry': floor.geometry.name})
return {
'timber_floors': timber_floors,
'concrete_combi_floors': concrete_combi_floors,
'nehobo_floors': nehobo_floors,
'ribbed_floors': ribbed_floors,
'beam_block_floors': beam_block_floors,
'other_floors': other_floors}
[docs]def get_roofs_info(project: ViiaProject) -> Dict[str, Any]:
"""
Function collects the information in building structural setup appendix for the roofs.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
Output:
- Returns a dictionary with information of the roofs in the model.
"""
rafter_roofs = []
purlin_roofs = []
other_roofs = []
for roof in project.collections.roofs:
mat_split = roof.material.name.split('-')
add_ind = 1 if 'LIN' in mat_split else 0
# Equivalent timber roofs with purlins
if any(sub_str in roof.material.name for sub_str in ['PLANKEN', 'PLATEN']) and \
fem_compare_values(roof.element_x_axis.vector[2], 0):
purlin_roofs.append({
'name': f'Roof {roof.id}',
'material': '-'.join(mat_split[:2 + add_ind]),
't_eq': roof.geometry.geometry_model.thickness * 1E3,
't_pl': mat_split[2 + add_ind],
'beam_width': mat_split[3 + add_ind],
'beam_height': mat_split[4 + add_ind],
'beam_ctc': mat_split[5 + add_ind]})
# Equivalent timber roofs with rafters
elif any(sub_str in roof.material.name for sub_str in ['PLANKEN', 'PLATEN']) and \
not fem_compare_values(roof.element_x_axis.vector[2], 0):
rafter_roofs.append({
'name': f'Roof {roof.id}',
'material': '-'.join(mat_split[:2 + add_ind]),
't_eq': roof.geometry.geometry_model.thickness * 1E3,
't_pl': mat_split[2 + add_ind],
'beam_width': mat_split[3 + add_ind],
'beam_height': mat_split[4 + add_ind],
'beam_ctc': mat_split[5 + add_ind]})
# Other roofs
else:
other_roofs.append({
'name': f'Roof {roof.id}',
'material': roof.material.name,
'geometry': roof.geometry.name})
return {
'rafter_roofs': rafter_roofs,
'purlin_roofs': purlin_roofs,
'other_roofs': other_roofs}
### ===================================================================================================================
### 4. End of script
### ===================================================================================================================