### ===================================================================================================================
### FUNCTION: Collect the acceptance criteria for the elements in the NLTH model
### ===================================================================================================================
# Copyright ©2026 Haskoning Nederland B.V.
# For use by VIIA
### ===================================================================================================================
### 1. Import modules
### ===================================================================================================================
# General imports
from __future__ import annotations
from typing import TYPE_CHECKING, List, Dict
import re
# References for functions and classes in the haskoning-structural package
from haskoning_structural.materials import Concrete, Timber, Masonry, Steel
from haskoning_structural.geometries.geometry_models import IsotropicThickness
from haskoning_structural.fem_tools import fem_compare_objects
# References for functions and classes in the viiaPackage
if TYPE_CHECKING:
from viiapackage.viiaStatus import ViiaProject
from viiapackage.connections.get_dimensions import viia_get_timberfloor_dimensions
from viiapackage.results.result_functions.viia_limits import viia_find_limits_NLTH
### ===================================================================================================================
### 2. Function to collect the applicable acceptance criteria in the NLTH model
### ===================================================================================================================
[docs]def viia_get_acceptance_criteria(project: ViiaProject, shape_type: str = 'floors') -> List[Dict[str, str]]:
"""
Function to collect the acceptance criteria per shape type in the model of the object.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- shape_type (string): The type of shape that should be considered. Possible are 'floors', 'roofs', 'beams' and
'columns'. Default is 'floors'.
Output:
- Returns a list with dictionaries with the acceptance criteria per shape.
"""
if shape_type in ['floors', 'roofs']:
return _get_acceptance_criteria_floors_roofs(project=project, shape_type=shape_type)
if shape_type in ['beams', 'columns']:
return _get_acceptance_criteria_beams_columns(project=project, shape_type=shape_type)
raise ValueError(
"ERROR: Provided input for shape_type should be one of ['floors', 'roofs', 'beams', 'columns']. "
f"Provided was: {shape_type}"
)
def _get_acceptance_criteria_floors_roofs(project: ViiaProject, shape_type: str = 'floors') -> List[Dict[str, str]]:
"""
Function to collect the acceptance criteria per shape in the model of the object, for which a NLTH analysis is
performed.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- shape_type (string): The type of shape that should be considered. Possible are 'floors', 'roofs', 'beams' and
'columns'. Default is 'floors'.
Output:
- Returns a list with dictionaries with the acceptance criteria per shape.
"""
shape_types = ['floors', 'roofs']
if shape_type not in shape_types:
raise ValueError(
f"ERROR: Provided input for shape_type should be one of {shape_types}. Provided was: {shape_type}")
_table_dict = {'floors': 'floor', 'roofs': 'roof'}
#Collect the relevant shapes
collection = getattr(project.collections, shape_type)
if not collection:
return []
check_combinations = {}
list_shapes_dict = []
for shape in collection:
_relevant_properties = ''
_dict = {}
material = shape.material
geometry = shape.geometry
# Check if the material is already in the list (without looking at added mass)
material_check = [fem_compare_objects(
material, check_material, list_excluded_keys=["added_mass"]) for check_material in check_combinations]
# Additional check for geometry (only for entry with the same material)
geometry_check = [geometry in check_combinations[check_material]
for check_material, correct_material in zip(check_combinations, material_check) if correct_material]
# if both checks are true, skip shape
if any(material_check) and any(geometry_check):
continue
# Get shape type and properties
if isinstance(material, Concrete):
_dict['type'] = f"Concrete {_table_dict[shape_type]}"
if isinstance(geometry.geometry_model, IsotropicThickness):
_relevant_properties += f'Thickness = {int(geometry.geometry_model.thickness*1000)} mm'
# Find relevant properties
# Ugly regex that finds strength classes like 'S355', 'B500', 'C14' and 'C20/25'
strength_class_match = re.search(r'-([CSB]\d+/?\d+)', material.name)
if strength_class_match:
strength_class = strength_class_match.group(1)
_relevant_properties += strength_class
# Fill in concrete class for standard LIN-BETON (when no strength class is found)
elif 'LIN-BETON' in material.name:
if _relevant_properties:
_relevant_properties += '\n'
_relevant_properties += 'C20/25'
# Exclude dummy plates from table
elif 'DUMMY' in material.name:
continue
elif isinstance(material, Timber):
if 'PLANKEN' in material.name:
_dict['type'] = f"Timber {_table_dict[shape_type]} with joists and planks"
properties = viia_get_timberfloor_dimensions(shape=shape)['thickness']
_relevant_properties += f'Plank thickness = {int(properties*1000)} mm'
elif 'PLATEN' in material.name:
_dict['type'] = f"Timber {_table_dict[shape_type]} with joists and plates"
properties = viia_get_timberfloor_dimensions(shape=shape)['thickness']
thickness = int(properties*1000)
_relevant_properties += f'Plate thickness = {thickness} mm'
else:
_dict['type'] = f"{material.name} {_table_dict[shape_type]}"
project.write_log(f"WARNING: No acceptance criteria found for shape {shape.name}.")
_dict['relevant_properties'] = _relevant_properties
# Get the required limits
_dict['in_plane_shear_limit'] = ''
limit = viia_find_limits_NLTH(project=project, shape=shape)
if limit and 'N_in' in limit:
_dict['in_plane_shear_limit'] = round(limit['N_in'] / 1000, 1)
list_shapes_dict.append(_dict)
if material not in check_combinations:
check_combinations[material] = []
check_combinations[material].append(geometry)
return list_shapes_dict
def _get_acceptance_criteria_beams_columns(project: ViiaProject, shape_type: str = 'beams') -> List[Dict[str, str]]:
"""
Function to collect the acceptance criteria per shape in the model of the object, for which a NLTH analysis is
performed.
Input:
- project (obj): VIIA project object containing collections of fem objects and project variables.
- shape_type (string): The type of shape that should be considered. Possible are 'floors', 'roofs', 'beams' and
'columns'. Default is 'floors'.
Output:
- Returns a list with dictionaries with the acceptance criteria per shape.
"""
shape_types = ['beams', 'columns']
if shape_type not in shape_types:
raise ValueError(
f"ERROR: Provided input for shape_type should be one of {shape_types}. Provided was: {shape_type}")
_table_dict = {'beams': 'beam', 'columns': 'column'}
#Collect the relevant shapes
collection = getattr(project.collections, shape_type)
if not collection:
return []
check_combinations = []
list_shapes_dict = []
for shape in collection:
_relevant_properties = ''
_dict = {}
material = shape.material
# If we have the material in the list already, skip
if any(
[fem_compare_objects(
material, check_material, list_excluded_keys=["added_mass"]) for check_material in check_combinations]
):
continue
# Concrete materials
if isinstance(material, Concrete):
_dict['type'] = f"Concrete {_table_dict[shape_type]}"
# Find relevant properties
# Ugly regex that finds strength classes like 'S355', 'B500', 'C14' and 'C20/25'
strength_class_match = re.search(r'-([CSB]\d+/?\d+)', material.name)
if strength_class_match:
strength_class = strength_class_match.group(1)
_relevant_properties += strength_class
# Fill in concrete class for standard LIN-BETON (when no strength class is found)
elif 'LIN-BETON' in material.name:
_relevant_properties += 'C20/25'
_dict['relevant_properties'] = _relevant_properties
#Find limit concrete materials
_dict['limit'] = ''
limit = viia_find_limits_NLTH(project=project, shape=shape)
if limit and shape_type == 'beams' and 'f_t' in limit:
_dict['limit'] = "{:.1f}".format(limit['f_t']/1E6) + " N/mm\u00b2" #N/m2 to N/mm2
elif limit and shape_type == 'columns':
_dict['limit'] = "{:.1f}".format(-limit['f_c']/1E6) + " N/mm\u00b2" #N/m2 to N/mm2
# Timber materials
elif isinstance(material, Timber):
_dict['type'] = f"Timber {_table_dict[shape_type]}"
# Find relevant properties
# Ugly regex that finds strength classes like 'S355', 'B500', 'C14' and 'C20/25'
strength_class_match = re.search(r'-([CSB]\d+/?\d+)', material.name)
if strength_class_match:
strength_class = strength_class_match.group(1)
_relevant_properties += strength_class
# Catch laminated timber
elif 'LAM' in material.name:
_dict['type'] = "Laminated " + _dict['type'].lower()
_relevant_properties += f'GL24C'
# Fill in timber class for standard LIN-HOUT (when no strength class is found)
elif 'LIN-HOUT' in material.name and 'PLAAT' not in material.name:
_relevant_properties += 'C18'
_dict['relevant_properties'] = _relevant_properties
# Find limit timber materials
_dict['limit'] = ''
limit = viia_find_limits_NLTH(project=project, shape=shape)
if limit and shape_type == 'beams':
_dict['limit'] = "{:.1f}".format(limit['f_b'] / 1E6) + " N/mm\u00b2" # N/m2 to N/mm2
elif limit and shape_type == 'columns':
_dict['limit'] = "{:.1f}".format(-limit['f_b']/1E6) + " N/mm\u00b2" #N/m2 to N/mm2
# Steel materials
elif isinstance(material, Steel):
_dict['type'] = f"Steel {_table_dict[shape_type]}"
# Find relevant properties
# Ugly regex that finds strength classes like 'S355', 'B500', 'C14' and 'C20/25'
strength_class_match = re.search(r'-([CSB]\d+/?\d+)', material.name)
if strength_class_match:
strength_class = strength_class_match.group(1)
_relevant_properties += strength_class
# Fill in steel class for standard LIN-STAAL (when no strength class is found)
elif 'LIN-STAAL' in material.name:
_relevant_properties += 'S235'
_dict['relevant_properties'] = _relevant_properties
# Find limit steel materials
_dict['limit'] = ''
limit = viia_find_limits_NLTH(project=project, shape=shape)
if limit:
_dict['limit'] = "{:.1f}".format(limit['f_vm'] / 1E6) + " N/mm\u00b2" # N/m2 to N/mm2
elif isinstance(material, Masonry):
_dict['type'] = f"Masonry {_table_dict[shape_type]}"
_dict['relevant_properties'] = _relevant_properties
_dict['limit'] = ''
project.write_log(f"WARNING: No acceptance criteria found for shape {shape.name}.")
# Fallback, show raw name of material in table
else:
_dict['type'] = f"{material.name} {_table_dict[shape_type]}"
_dict['relevant_properties'] = _relevant_properties
_dict['limit'] = ''
project.write_log(f"WARNING: No acceptance criteria found for shape {shape.name}.")
# Add acceptance criteria to the list
list_shapes_dict.append(_dict)
# Add material to checked materials
check_combinations.append(material)
return list_shapes_dict
### ===================================================================================================================
### 3. End of script
### ===================================================================================================================