#!/usr/bin/env python3 # -*- coding: utf-8 -*- """© Ihor Mirzov, 2019-2023 Distributed under GNU General Public License v3.0 TODO Possible comments and empty lines TODO Parse mesh from INCLUDEs TODO Newly created elset is not highlighted Parses finite element mesh from the CalculiX .inp-file. Reads nodes coordinates, elements composition, node and element sets and surfaces. ifile - path to input file icode - piece of input code blocks - keyword blocks old - mesh to be reparsed TODO Only file list is logged. No parser messages. """ # Standard modules import os import re import sys import logging import textwrap import traceback # My modules sys_path = os.path.abspath(__file__) sys_path = os.path.dirname(sys_path) sys_path = os.path.join(sys_path, '..', '..') sys_path = os.path.normpath(sys_path) sys_path = os.path.realpath(sys_path) if sys_path not in sys.path: sys.path.insert(0, sys_path) import importer from utils import tests import log class Mesh: def __init__(self, ifile=None, icode=None, blocks=None, old=None): self.nodes = {} # all mesh nodes with coordinates self.nsets = {} # node sets self.elements = {} # all mesh elements composition self.elsets = {} # element sets self.surfaces = {} # with corresponding nodes and element faces self.duplicated_warnings = { 'NSET':[], 'ELSET':[]} self.warning_counter = 0 # Mesh being reparsed self.old = old if not old: self.old = self # Get lines from INP source lines = [] if ifile is not None: # whole the .inp-file lines = importer.read_lines(ifile) elif icode is not None: lines = icode # some piece of INP code elif blocks is not None: for b in blocks: lines.extend(b.get_inp_code()) # keyword blocks if not len(lines): self.warn('Nothing to parse!') return # Call parse methods for everything for mname in ['nodes', 'nsets', 'elements', 'elsets', 'surfaces']: method = self.__dict__[mname] try: getattr(self, 'parse_' + mname)(lines) except: msg = 'Can\'t parse {}\n'.format(mname) \ + traceback.format_exc() logging.error(msg) msg_text = [] for mname in ['nodes', 'nsets', 'elements', 'elsets', 'surfaces']: method = self.__dict__[mname] msg_text.append('{} {}'.format(len(method), mname)) msg_text = 'Mesh parser: ' + ', '.join(msg_text) logging.info(msg_text) def warn(self, msg): """Log some warning.""" if self.warning_counter < 10: logging.warning(msg) if self.warning_counter == 10: logging.warning('Message limit reached!') self.warning_counter += 1 # # Implement case insensetivity for sets dictionaries # def get_set_by_name(self, sets, name): # for s in sets.keys(): # if s.upper() == name.upper(): # return sets[s] # logging.error('There is no set {}.'.format(name)) def parse_nodes(self, lines): """Parse nodes with coordinates - *NODE keyword.""" regex1 = r'^\*[\w\s-]+' regex2 = r'NSET\s*=\s*([\w\!\#\%\$\&\"\'\(\)\*\=\+\-\.\/\:\;\<\>\?\@\[\]\^\_\`\{\\\|\}\~]*)' for i in range(len(lines)): match = re.search(regex1, lines[i]) if match is None: continue keyword_name = match.group(0) if keyword_name.upper() != '*NODE': continue nodes = [] duplicated_nodes = [] no_coordinates_nodes = [] lead_line = lines[i] match = re.search(regex2, lead_line.upper()) # Read the whole block while i+1 3: coords = coords[:3] # TODO Support Abaqus syntax self.warn('Direction cosines are not supported.') # if len(coords) == 2: # in 2D case add Z coord equal to zero # coords.append(0) # Create NODE node = NODE(num, coords) # Check duplicates and nodes without coordinates if num in self.nodes: duplicated_nodes.append(num) del node else: nodes.append(node) self.nodes[num] = node # if len(coords): # nodes.append(node) # self.nodes[num] = node # else: # no_coordinates_nodes.append(num) # del node i += 1 # Warn about duplicated nodes if len(duplicated_nodes): msg_text = 'Duplicated nodes: {}.'.format(duplicated_nodes) for msg_line in textwrap.wrap(msg_text, width=40): self.warn(msg_line) # Warn about nodes without coordinates if len(no_coordinates_nodes): msg_text = 'Nodes without coordinates: {}.'.format(no_coordinates_nodes) for msg_line in textwrap.wrap(msg_text, width=40): self.warn(msg_line) # If all nodes are named as a set if match is not None: name = lead_line[match.start(1):match.end(1)] self.create_or_extend_set(self.nsets, name, nodes, NSET) # do not return to parse few *NODE sections def parse_nsets(self, lines): """Parse node sets - *NSET keyword.""" rex = r'(\*NSET)\s*,.*NSET\s*=\s*' \ + r'([\w\!\#\%\$\&\"\'\(\)\*\=\+\-\.\/\:\;\<\>\?\@\[\]\^\_\`\{\\\|\}\~]*)' for i in range(len(lines)): match = re.search(rex, lines[i].upper()) if match is None: continue # name = lines[i][match.start(2):match.end(2)] # node set name name = match.group(2) nodes = [] sets_with_non_existent_nodes = {} duplicated_nodes = [] if not 'GENERATE' in lines[i].upper(): while i+1\?\@\[\]\^\_\`\{\\\|\}\~]*)' for i in range(len(lines)): lead_line = lines[i].upper() match = re.search(regex1, lead_line) if not match: continue if match.group(0) != '*ELEMENT': continue match = re.search(regex2, lead_line) # element type etype = match.group(1) amount = self.amount_of_nodes(etype) elements = [] duplicated_elements = [] # Read the whole block while i+1\?\@\[\]\^\_\`\{\\\|\}\~]*)' for i in range(len(lines)): match = re.search(regex, lines[i].upper()) if match is None: continue name = lines[i][match.start(2):match.end(2)] # element set name elements = [] sets_with_non_existent_elements = {} if not 'GENERATE' in lines[i].upper(): while i+1\?\@\[\]\^\_\`\{\\\|\}\~]*)', lines[i].upper()) if match is not None: name = lines[i][match.start(1):match.end(1)] skip = False # Surface type - optional attribute stype = 'ELEMENT' # 'ELEMENT' or 'NODE' match = re.search('\*SURFACE\s*,.*TYPE\s*=\s*(\w*)', lines[i].upper()) if match is not None: stype = lines[i][match.start(1):match.end(1)] if not skip: if name + stype in self.surfaces: msg_text = 'Duplicated surface name {}.'.format(name) self.warn(msg_text) items = [] while i+1