Source code for pycalculix.feamodel

""" This module stores the FeaModel class.

This class is the highest level object in a pycalculix program.
It stores all parts, loads, constraints, mesh, problem, and results_file
objects.
"""

import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection  # element plotting
import matplotlib.colors as colors
import matplotlib.cm as cmx
import subprocess # used to launch meshers cgx and gmsh
import os # used to delete files written by cgx
from numpy.core.function_base import linspace # need to make contours

from . import environment
from . import base_classes
from . import geometry
from . import material
from . import components
from . import connectors
from . import loads
from . import mesh
from . import partmodule
from . import problem
from . import selector

# element colors, 0-1, 0=black, 1=whate
ECOLOR = '.4'
FCOLOR = '.9'
CMAP = 'jet' # for results
GEOM_CMAP = 'Pastel1' # color map for parts or areas

[docs]class FeaModel(object): """Makes a FeaModel instance. Parts, area, lines, arcs, points, nodes, elements, faces, models, components, and loads are stored in this object. Args: model_name (str): project name for this FeaModel, this is a file prefix ccx (None or str): path to Calculix ccx solver, pass this when you want to overwite the default program location. None means the default envionment.CCX is used. cgx (None or str): path to Calculix cgx mesher, pass this when you want to overwite the default program location. None means the default envionment.CGX is used. gmsh (None or str): path to gmsh mesher, pass this when you want to overwite the default program location. None means the default envionment.GMSH is used. Attributes: fname (str): FeaModel project file name prefix points (Itemlist): list of all Point lines (Itemlist): list of all Line and Arc signlines (Itemlist): list of all SignLine and SignArc lineloops (Itemlist): list of all LineLoop, each contains SignLine SignArc areas (Itemlist):list of all Area, each contains LineLoop(s) parts (Itemlist): list of all Part matls (Itemlist): list of all materials components (Itemlist): list of all components loads (dict): a dictionary of loads | Key (float): the load time point | loads[time] = list of loads for that time step | See method set_time to change the current time. | Time = 0.0 stores constant loads, such as: | material, thickness contacts (Itemlist): list of contacts surfints (Itemlist): list of surface interactions problems (Itemlist): list of problems nodes (Meshlist): list of all mesh nodes eshape (str): element shape - 'quad': quadrilateral elements (Default) - 'tri': triangle elements eorder (int): element order, 1 or 2 - 1: elements meshed with corner nodes only - 2: (Default) elements meshed with corner and midside nodes elements (Meshlist): list of all mesh elements faces (list): list of all element faces, includes non-exterior ones view (Selector): currently selected items Attributes: parts: list of selected parts areas: list of selected areas lines: list of selected signed lines or arcs points: list of selected points elements: list of selected elements faces: list of selected faces nodes: list of selected nodes time (float): current model time value, defaults to 1.0 units (dict): a dict to store units for different fields Keys: - 'displ': displacement or location - 'force': force - 'stress': stress - 'temp': temperature - 'density': density (mass/volume) - 'time': time Returns: Text string describing the units for the given key field. See the set_units method. For example when units have been set to metric, the below values are returned. - 'dist' --> 'm' - 'density' --> 'kg/(m^3)' - 'time' --> 's' """ def __init__(self, model_name, ccx=None, cgx=None, gmsh=None): self.fname = model_name self.points = base_classes.Itemlist() self.lines = base_classes.Itemlist() self.signlines = base_classes.Itemlist() self.lineloops = base_classes.Itemlist() self.areas = base_classes.Itemlist() self.parts = base_classes.Itemlist() self.matls = base_classes.Itemlist() self.components = base_classes.Itemlist() self.loads = {} # store loads by time self.contacts = base_classes.Itemlist() self.surfints = base_classes.Itemlist() self.problems = base_classes.Itemlist() self.nodes = base_classes.Meshlist() self.elements = base_classes.Meshlist() self.faces = [] self.view = selector.Selector(self) self.time = 1.0 # 0 is model set-up, 1 is first step, etc self.eshape = 'quad' self.eorder = 2 self.units = {} # fix the paths to the needed programs, ccx, cgx, and gmsh if ccx != None: environment.CCX = ccx if cgx != None: environment.CGX = cgx if gmsh != None: environment.GMSH = gmsh
[docs] def set_ediv(self, items, ediv): """Sets the number of elements on the passed line. Args: items (str or SignLine or SignArc or list): lines to set ediv on - str: 'L0' - list of str ['L0', 'L1'] - list of SignLine or SignArc part.bottom or part.hole[-1] ediv (int): number of elements to mesh on the line """ items = self.get_items(items) for line in items: line.set_ediv(ediv)
[docs] def set_esize(self, items, esize): """Sets the element size on the passed line. Args: items (str or SignLine or SignArc or list): lines or points to set esize on - str: 'L0' - list of str ['L0', 'L1', 'P3'] - list of SignLine or SignArc part.bottom or part.hole[-1] esize (float): size of the mesh elements on the line """ items = self.get_items(items) for item in items: item.set_esize(esize)
[docs] def set_units(self, dist_unit='m', cfswitch=False): """Sets the units that will be displayed when plotting. Picks a unit set based on the passed distance unit. That unit set is printed to the console when set. Defaults to MKS units (meter-kilogram-second) ======== ===== ====== =========== =============== ==== Distance Force Stress Temperature Density Time ======== ===== ====== =========== =============== ==== 'm' 'N' 'Pa' 'K' 'kg/(m^3)' 's' 'mm' 'N' 'MPa' 'K' 'tonne/(mm^3)' 's' 'in' 'lbf' 'psi' 'R' 'slinch/(in^3)' 's' 'ft' 'lbf' 'psf' 'R' 'slug/(ft^3)' 's' ======== ===== ====== =========== =============== ==== See get_units method or returning text strings based on unit types. Args: dist_unit (str): string of distance unit. Options: - 'm': meter - 'mm': milimeter - 'in': inch - 'ft': foot cfswitch (bool): Celsius/Fahrenheit temperature switch. Default is False. If True, this switches temperature from K-->C or from R-->F Default keeps units as shown in above table. """ keys = ['dist', 'force', 'stress', 'temp', 'density', 'time'] m_newton = ['m', 'N', 'Pa', 'K', 'kg/(m^3)', 's'] mm_newton = ['mm', 'N', 'MPa', 'K', 'tonne/(mm^3)', 's'] in_lbf = ['in', 'lbf', 'psi', 'R', 'slinch/(in^3)', 's'] ft_lbf = ['ft', 'lbf', 'psf', 'R', 'slug/(ft^3)', 's'] unit_systems = [m_newton, mm_newton, in_lbf, ft_lbf] vals = [usys for usys in unit_systems if usys[0] == dist_unit][0] if cfswitch: # switches K-->C and R-->F newunit = {'K':'C', 'R':'F'} vals['temp'] = newunit[vals['temp']] # set values adict = dict(zip(keys, vals)) adict['displ'] = adict['dist'] print('================================================') print('Units have been set to %s_%s' % (adict['dist'], adict['force'])) for key in adict: print('For %s use %s' % (key, adict[key])) print('================================================') self.units = adict
[docs] def get_units(self, *args): """Returns units for the passed arguments. Accepts and returns a list of strings. Options for inputs: - 'dist' - 'displ' (same units as 'dist') - 'force' - 'stress' - 'temp' - 'density' - 'time' """ res = [] for arg in args: mystr = '' if arg in self.units: # check if the requested item is in the units dict mystr = ' ('+ self.units[arg] + ')' elif arg in base_classes.FIELDTYPE: # return units on results dict item, ex: stress, strain etc ftype = base_classes.FIELDTYPE[arg] if ftype in self.units: mystr = ' ('+ self.units[ftype] + ')' res.append(mystr) return res
[docs] def set_time(self, time): """Sets the time in the FeaModel (preprocessor). This time is used when setting loads. Args: time (float): the time to set """ self.time = time
[docs] def get_item(self, item): """Returns an item given a string identifying the item. Args: item (str): 'A0' or 'P0' or 'L0' etc. """ if item[0] == 'P': # get point items = self.points num = int(item[1:]) res = [a for a in items if a.id == num] return res[0] elif item[0] == 'L' or item[1] == 'L': # get line items = self.signlines num = int(item[1:]) items = [a for a in items if a.get_name() == item] return items[0] elif item[0] == 'A': # get area items = self.areas num = int(item[1:]) items = [a for a in items if a.id == num] return items[0] elif item[0] == 'E': # get element items = self.elements items = [a for a in items if a.get_name() == item] return items[0] elif item[0] == 'N': # get node items = self.nodes items = [a for a in items if a.get_name() == item] return items[0] else: print('Unknown item! Please pass the name of a point, line or area!')
[docs] def get_items(self, items): """Returns a list of correctly typed items. Input can be a single string or item, or a list of strings identifying items. """ res_items = base_classes.listify(items) for ind, item in enumerate(res_items): if isinstance(item, str): res_items[ind] = self.get_item(item) return res_items
[docs] def make_matl(self, name): """Makes and returns a new material. Args: name (str): the material's name """ mat = material.Material(name) self.matls.append(mat) return mat
[docs] def make_part(self): """Makes and returns a new part.""" #p = part.Part(self) #self.parts.append(p) return partmodule.Part(self)
[docs] def make_problem(self, problem_type='struct', parts='all'): """Makes and returns a new problem, which can be solved. Args: problem_type (str): problem type, options: 'struct': structural parts (str Part or list of Part): Parts the model will analyze. Options: - 'all': add all parts. This is the default. - Part: add the single part - list of Part: add these parts """ if parts == 'all': parts = self.parts prob = problem.Problem(self, problem_type, parts) return prob
[docs] def print_summary(self): """Prints a summary of the items in the database. """ items = ['parts', 'areas', 'signlines', 'points', 'elements', 'faces', 'nodes'] names = ['parts', 'areas', 'lines', 'points', 'elements', 'faces', 'nodes'] spacer = '----------------------------------' print(spacer) print("Items in the database:") for (item_str, name) in zip(items, names): items = getattr(self, item_str) print(' %s: %i' % (name, len(items))) print(spacer)
[docs] def plot_nodes(self, fname='', display=True, title='Nodes', nnum=False): """Plots the selected nodes. Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown title (str): the plot's title nnum (bool): if True node numbers are plotted """ nodes = self.view.nodes if len(nodes) > 0: # plotting elements fig = plt.figure() axis = fig.add_subplot(111) # plot nodes, this is quicker than individual plotting axs = [node.y for node in nodes] rads = [node.x for node in nodes] axis.scatter(axs, rads, s=7, color='black') if nnum: for node in nodes: node.label(axis) # set units [d_unit] = self.get_units('dist') plt.title(title) plt.xlabel('axial, y'+d_unit) plt.ylabel('radial, x'+d_unit) plt.axis('scaled') # extract max and min for plot window radials = [n.x for n in nodes] axials = [n.y for n in nodes] # finish pot base_classes.plot_set_bounds(plt, axials, radials) base_classes.plot_finish(plt, fname, display) else: # no elements exist or no elemnts are selected res = '' if len(self.nodes) == 0: res = 'No nodes exist! Try meshing your parts!' else: res = 'No nodes are selected! Select some!' print(res)
[docs] def plot_elements(self, fname='', display=True, title='Elements', enum=False, nshow=False, nnum=False): """Plots the selected elements. Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown title (str): the plot's title enum (bool): if True element numbers are plotted nshow (bool): True=plot nodes, False=don't plot them nnum (bool): if True node numbers are plotted """ nodes = self.view.nodes elements = self.view.elements if len(elements) > 0: # plotting elements fig = plt.figure() axis = fig.add_subplot(111) polys = [] for element in elements: poly = element.get_poly() polys.append(poly) coll = PatchCollection(polys, facecolors=FCOLOR, edgecolors=ECOLOR) axis.add_collection(coll) # plot element numbers if enum: for element in elements: element.label(axis) # plot nodes, this is quicker than individual plotting if nshow: axs = [node.y for node in nodes] rads = [node.x for node in nodes] axis.scatter(axs, rads, s=7, color='black') if nnum: for node in nodes: node.label(axis) # set units [d_unit] = self.get_units('dist') plt.title(title) plt.xlabel('axial, y'+d_unit) plt.ylabel('radial, x'+d_unit) plt.axis('scaled') # extract nodes for elements if no nodes are selected if len(nodes) == 0: tmp = set() for element in elements: tmp.update(element.nodes) nodes = list(tmp) # extract max and min for plot window radials = [n.x for n in nodes] axials = [n.y for n in nodes] # finish pot base_classes.plot_set_bounds(plt, axials, radials) base_classes.plot_finish(plt, fname, display) else: # no elements exist or no elemnts are selected res = '' if len(self.elements) == 0: res = 'No elements exist! Try meshing your parts!' else: res = 'No elements are selected! Select some!' print(res)
[docs] def plot_pressures(self, fname='', display=True): """Plots the load step pressures on the passed part(s) or selected elements. This is an element plot, with arrows showing pressure magnitude and directions. Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown. Default = True """ elements = self.view.elements faces = self.view.faces nodes = self.view.nodes # plot all elements and store length, length determins min pressure arrow if len(elements) > 0: # plotting elements fig = plt.figure() axis = fig.add_subplot(111, aspect='equal') # plot polys and calculate avg face length polys = [] face_len = [] for element in elements: face_len.append(element.face[1].length()) poly = element.get_poly() polys.append(poly) coll = PatchCollection(polys, edgecolors=ECOLOR, facecolors=FCOLOR) axis.add_collection(coll) face_len = sum(face_len)/len(face_len) # store pressures we'll want to plot: list of [face, pval] plist = [] for load in self.loads[self.time]: if load.ltype in ['press', 'press_fluid']: loadlist = load.get_list() for [face, pval] in loadlist: if face in faces: plist.append([face, pval]) pressures = [abs(pval) for [face, pval] in plist] # set max and min pressure bounds pmin = min(pressures) pmax = max(pressures) # extract nodes for elements if no nodes are selected if len(nodes) == 0: tmp = set() for element in elements: tmp.update(element.nodes) nodes = list(tmp) radials = [p.x for p in nodes] axials = [p.y for p in nodes] # arrow length = arrow_min + (pval-pmin)*mult arrow_min = face_len mult = 0 if pmax != pmin: adelta = max(axials) - min(axials) rdelta = max(radials) - min(radials) delta = max(adelta, rdelta) dist = delta*0.2 mult = dist/(pmax - pmin) # make tick list for later plot, and color map cbar_val = None tick_list = [] cmap = None if pmax != pmin: # we have a range of values we're plotting tick_list = linspace(pmin, pmax, 8) cmap = plt.get_cmap(CMAP) else: cbar_val = pmin pmax = pmin + 1.0 pmin = pmin - 1.0 tick_list = [pmin, cbar_val, pmax] # default 3 values to plot one solid color cmap = colors.ListedColormap(['b', 'b']) # default to plot one val # set color contours for arrows cnorm = colors.Normalize(vmin=pmin, vmax=pmax) scalarmap = cmx.ScalarMappable(norm=cnorm, cmap=cmap) scalarmap.set_array([]) # make arrows, store axials + radials for [face, pval] in plist: [face_point, unit] = face.get_mnorm() arrow_length = arrow_min + (abs(pval) - pmin)*mult other_point = face_point + unit*arrow_length radials.append(other_point.x) axials.append(other_point.y) # compression tail = other_point delta = face_point - other_point if pval < 0: # tension tail = face_point delta = other_point - face_point headwidth = face_len*0.2 headlength = face_len*0.3 colorval = scalarmap.to_rgba(abs(pval)) plt.arrow(tail.y, tail.x, delta.y, delta.x, color=colorval, head_width=headwidth, head_length=headlength, length_includes_head=True) # set the horizontal and vertical axes base_classes.plot_set_bounds(plt, axials, radials) # set units and titles [d_unit, p_unit, t_unit] = self.get_units('dist', 'stress', 'time') tstr = 'Pressures %s\nTime=%f%s' % (p_unit, self.time, t_unit) plt.title(tstr) plt.xlabel('axial, y'+d_unit) plt.ylabel('radial, x'+d_unit) # set the colorbar cbar = plt.colorbar(scalarmap, orientation='vertical', ticks=tick_list) if cbar_val != None: cbar.ax.set_yticklabels(['', str(cbar_val), '']) base_classes.plot_finish(plt, fname, display) else: res = '' if len(self.elements) == 0: res = 'No elements exist! Try meshing your parts!' else: res = 'No elements are selected! Select some!' print(res)
[docs] def plot_constraints(self, fname='', display=True): """Plots the constraints on the passed part(s) or selected items. This is an element and node plot, with arrows showing displacement constraints. Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown. Default = True """ elements = self.view.elements nodes = self.view.nodes # plot all elements if len(elements) > 0: # plotting elements fig = plt.figure() axis = fig.add_subplot(111, aspect='equal') # plot polys and calculate avg face length polys = [] face_len = [] for element in elements: face_len.append(element.face[1].length()) poly = element.get_poly() polys.append(poly) coll = PatchCollection(polys, edgecolors=ECOLOR, facecolors=FCOLOR) axis.add_collection(coll) face_len = sum(face_len)/len(face_len) # store displacements we'll plot: list of [node, dict ux,uy,uz] ulist = [] vals = [] for load in self.loads[self.time]: if load.ltype in ['ux', 'uy', 'uz']: alist = load.get_list() for nodelist in alist: node = nodelist[0] if node in nodes: ulist += [nodelist] vals += list(nodelist[1].values()) # check min and max bounds pmin = min(vals) pmax = max(vals) # make tick list for later plot, and color map cbar_val = None tick_list = [] cmap = None if pmax != pmin: # we have a range of values we're plotting tick_list = linspace(pmin, pmax, 8) cmap = plt.get_cmap(CMAP) else: cbar_val = pmin pmax = pmin + 1.0 pmin = pmin - 1.0 tick_list = [pmin, cbar_val, pmax] # default 3 values to plot one solid color cmap = colors.ListedColormap(['b', 'b']) # default to plot one val # set color contours for arrows cnorm = colors.Normalize(vmin=pmin, vmax=pmax) scalarmap = cmx.ScalarMappable(norm=cnorm, cmap=cmap) scalarmap.set_array([]) # make arrows for displacements # extract nodes for elements if no nodes are selected if len(nodes) == 0: tmp = set() for element in elements: tmp.update(element.nodes) nodes = list(tmp) radials = [p.x for p in nodes] axials = [p.y for p in nodes] pvect = {'ux':geometry.Point(1, 0, 0), 'uy':geometry.Point(0, 1, 0), 'uz':geometry.Point(0, 0, 1)} for [node, udict] in ulist: for (key, val) in udict.items(): headw = face_len*0.4 headl = headw point = geometry.Point(node.x, node.y, node.z) unit = pvect[key] # nonzero displacement, draw from node tail = point head = tail + unit*val store_point = head if val == 0: # zero displ, draw to node head = point tail = head - unit*face_len store_point = tail delta = head - tail radials.append(store_point.x) axials.append(store_point.y) colorVal = scalarmap.to_rgba(val) plt.arrow(tail.y, tail.x, delta.y, delta.x, color=colorVal, head_width=headw, head_length=headl, length_includes_head=True) # set the horizontal and vertical axes base_classes.plot_set_bounds(plt, axials, radials) # set units + titles [d_unit, t_unit] = self.get_units('dist', 'time') tstr = 'Constraints %s\nTime=%f%s' % (d_unit, self.time, t_unit) plt.title(tstr) plt.xlabel('axial, y'+d_unit) plt.ylabel('radial, x'+d_unit) # set the colorbar cbar = plt.colorbar(scalarmap, orientation='vertical', ticks=tick_list) if cbar_val != None: cbar.ax.set_yticklabels(['', str(cbar_val), '']) base_classes.plot_finish(plt, fname, display) else: res = '' if len(self.elements) == 0: res = 'No elements exist! Try meshing your parts!' else: res = 'No elements are selected! Select some!' print(res)
[docs] def plot_multiple(self, fname='', display=True, title='', styledict={'labels':['points', 'lines', 'areas', 'parts'], 'items':['points, lines, areas']}): """Plots items of type styledict['items'], labels styledict['labels'] Only the items currently selected will be plotted. """ # http://stackoverflow.com/questions/470690/how-to-automatically-generate-n-distinct-colors/4382138#4382138 # http://stackoverflow.com/questions/2328339/how-to-generate-n-different-colors-for-any-natural-number-n # start plotting fig = plt.figure() axis = fig.add_subplot(111) # check if we're plotting parts or areas and pick a color map colormaker, ids = None, None color_plot = False if 'parts' in styledict['items']: color_plot = True ids = [item.id for item in self.view.parts] if 'areas' in styledict['items']: styledict['items'].remove('areas') if 'areas' in styledict['items']: color_plot = True ids = [item.id for item in self.view.areas] if color_plot: cmap = plt.get_cmap(GEOM_CMAP) norm = colors.Normalize(vmin=min(ids), vmax=max(ids)) colormaker = cmx.ScalarMappable(norm=norm, cmap=cmap) # plot the items for item_type in ['points', 'lines', 'areas', 'parts']: plot_on = item_type in styledict['items'] label_on = item_type in styledict['labels'] items = getattr(self.view, item_type) if plot_on and label_on: for item in items: if color_plot and item_type in ['areas', 'parts']: color = colormaker.to_rgba(item.id) item.plot(axis, True, color) else: item.plot(axis, True) elif plot_on: for item in items: if color_plot and item_type in ['areas', 'parts']: color = colormaker.to_rgba(item.id) item.plot(axis, False, color) else: item.plot(axis, False) elif label_on: for item in items: item.label(axis) # set the horizontal and vertical axes points = self.view.points radials = [point.x for point in points] axials = [point.y for point in points] base_classes.plot_set_bounds(plt, axials, radials) # set units [d_unit] = self.get_units('dist') # show plot plt.title(title) plt.xlabel('axial, y'+d_unit) plt.ylabel('radial, x'+d_unit) axis.set_aspect('equal') base_classes.plot_finish(plt, fname, display)
[docs] def plot_parts(self, fname='', display=True, title='Parts', label=True): """Plots selected parts Defaults to displaying labels on: parts and filling all part areas. Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown. Default = True title (str): the plot's title label (bool): if True all Part objects will be labeled """ item = 'parts' styledict = {'items':[], 'labels':[]} if label: styledict['items'].append(item) styledict['labels'].append(item) else: styledict['items'].append(item) self.plot_multiple(fname, display, title, styledict)
[docs] def plot_areas(self, fname='', display=True, title='Areas', label=True): """Plots selected areas Defaults to displaying labels on: areas and filling all part areas. Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown. Default = True title (str): the plot's title label (bool): if True all areas will be labeled """ item = 'areas' styledict = {'items':[], 'labels':[]} if label: styledict['items'].append(item) styledict['labels'].append(item) else: styledict['items'].append(item) self.plot_multiple(fname, display, title, styledict)
[docs] def plot_lines(self, fname='', display=True, title='Lines', label=True): """Plots selected lines and arcs Defaults to displaying labels on: lines and arcs Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown. Default = True title (str): the plot's title label (bool): if True all lines and arcs will be labeled """ item = 'lines' styledict = {'items':[], 'labels':[]} if label: styledict['items'].append(item) styledict['labels'].append(item) else: styledict['items'].append(item) self.plot_multiple(fname, display, title, styledict)
[docs] def plot_points(self, fname='', display=True, title='Points', label=True): """Plots selected points Defaults to displaying labels on: points Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown. Default = True title (str): the plot's title label (bool): if True all points will be labeled """ item = 'points' styledict = {'items':[], 'labels':[]} if label: styledict['items'].append(item) styledict['labels'].append(item) else: styledict['items'].append(item) self.plot_multiple(fname, display, title, styledict)
[docs] def plot_geometry(self, fname='', display=True, title='Geometry', pnum=True, lnum=True, anum=True, afill=True): """Plots selected geometry items: areas, lines, points. Defaults to displaying labels on: areas, lines, and points, and filling in the areas. Args: fname (str): png image file prefix, if given, image will be saved display (bool): if True, interactive plot will be shown, if False plot will not be shown. Default = True title (str): the plot's title pnum (bool): if True all Point objects will be labeled lnum (bool): if True all Line and Arc objects will be labeled anum (bool): if True all Area objects will be labeled afill (bool): if True all Area objects will be filled """ styledict = {'items':[], 'labels':[]} if pnum: styledict['items'].append('points') styledict['labels'].append('points') if lnum: styledict['items'].append('lines') styledict['labels'].append('lines') if anum: styledict['labels'].append('areas') if afill: styledict['items'].append('areas') elif anum == False and afill: styledict['items'].append('areas') self.plot_multiple(fname, display, title, styledict)
@staticmethod def __get_cname(items): """Returns a component name prefix, for labeling lists of items. Args: items (list): a list of the items we'll be making a component of """ cname = '' if len(items) == 1: if items[0] == 'all': cname = 'all' else: cname = items[0].get_name() else: cname = items[0].get_name()+'-'+items[-1].get_name() return cname def __get_make_comp(self, comp): """Stores component if it doesn't exist, returns it if it does. Args: comp (Component): component we want to store/get """ if comp not in self.components: comp = self.components.append(comp) else: ind = self.components.index(comp) comp = self.components[ind] return comp def __get_make_surfint(self, surfint): """Stores surfac interaction if it doesn't exist, returns it if it does. Args: surfint (connectors.SurfaceInteraction): item to get or make """ items = [item for item in self.surfints if item.int_type == surfint.int_type] if surfint.int_type == 'LINEAR': for item in items: if item.k == surfint.k: return item elif surfint.type == 'EXPONENTIAL': for item in items: if item.c0 == surfint.c0 and item.p0 == surfint.p0: return item surfint = self.surfints.append(surfint) return surfint
[docs] def register(self, item): """Adds an item to the feamodel. Item is added to the correct list and its id is updated. Allowed Items: * Point * Line * Arc * SignLine * SignArc * LineLoop * Area * Part """ class_name = item.__class__.__name__ class_to_list = {'Point': 'points', 'Line': 'lines', 'Arc': 'lines', 'SignLine': 'signlines', 'SignArc': 'signlines', 'LineLoop': 'lineloops', 'Area': 'areas', 'Part': 'parts'} if class_name in class_to_list: list_name = class_to_list[class_name] getattr(self, list_name).append(item) else: print('ERROR: the item you passed must be a geometry item!') print('You passed a %s' % class_name) message = ("Allowed items: Point, Line, Arc, SignLine, SignArc, " "LineLoop, Area, Part") print(message)
def __add_load(self, load, time): """Adds a load to the FeaModel. Args: load (Load or Load_Linear): th eload to add time (float): the load's time point. The first time point is 1.0 """ if time in self.loads: self.loads[time].append(load) else: self.loads[time] = [load] return load
[docs] def scale(self, unitstr, point_first=None, point_last=None): """Scales the points from [point_first, ..., point_last] using unitstr If point_first and point_last are both None, all points are scaled. Args: unitstr (str): string scalar 'fromunit-tounit' using the below units: * mm, m, in, ft * Examples 'mm-m' 'm-in' 'ft-mm' 'in-ft' * Default value is '' and does not apply a scale factor point_first (Point or None or str): the first point to scale, string point names may be passed point_last (Point or None or str): the last point to scale, string point names may be passed """ # convert to points if passing in string point names 'P0' if isinstance(point_first, str): point_first = self.get_item(point_first) if isinstance(point_last, str): point_last = self.get_item(point_last) ind_first = 0 if point_first != None: ind_first = self.points.index(point_first) ind_last = -1 if point_last != None: ind_last = self.points.index(point_last) unit_size = {'m':1.0, 'mm':.001, 'ft':.3048, 'in':0.0254} units = unitstr.split('-') from_unit, to_unit = units[0], units[1] scalar = unit_size[from_unit]/unit_size[to_unit]
# scale all points in set # update all dependent arcs # update all dependent line loops # update all dependent areas # update all dependent parts
[docs] def set_gravity(self, grav, items): """Sets gravity on the elements in items. Assumes gravity acts in the -x direction with magnitude grav. Args: grav (float): gravity acceleration, MUST BE POSTIVE items (Area or Part or list): items gravity acts on - Area: gravity acts on elements in this area - Part: gravity acts on elements in this part - list of Part or Area: gravity acts on their elements """ items = self.get_items(items) ctype = 'elements' cname = self.__get_cname(items) # make compoenet comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # make load ltype = 'gravity' time = self.time load = loads.ConstLoad(ltype, comp, grav) self.__add_load(load, time) return load
[docs] def set_rpm(self, rpm, items): """Sets rpm rotation load on the items. Args: rpm (float): rotation per minute items (Area or Part or list): items rotation acts on - Area: rotation acts on elements in this area - Part: rotation acts on elements in this part - list of Part or Area: rotation acts on their elements """ # applies rpm to the items items = self.get_items(items) ctype = 'elements' cname = self.__get_cname(items) # make compoenet comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # make load ltype = 'rpm' time = self.time load = loads.ConstLoad(ltype, comp, rpm) self.__add_load(load, time) return load
[docs] def set_radps(self, radps, items): """Sets radians per second rotation load on the items. Args: radps (float): radians per second items (Area or Part or list): items rotation acts on - Area: rotation acts on elements in this area - Part: rotation acts on elements in this part - list of Part or Area: rotation acts on their elements """ items = self.get_items(items) ctype = 'elements' cname = self.__get_cname(items) # make compoenet comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # make load ltype = 'radps' time = self.time load = loads.ConstLoad(ltype, comp, radps) self.__add_load(load, time) return load
[docs] def set_fluid_press(self, items, rho, g, xo, po): """This sets a fluid presure load on items. This fluid pressure is dependednt on the x axis. g must be positive. - P = f(x) - P(xo) = po - P(x) = po + rho*g*(xo - x) Args: items (str or Line or Arc or list): items to set pressure on - str: string name of Line or Arc item, for example 'L0' - Line or Arc: set presssure on this - list or Line or Arc: set pressure on these rho (float): fluid density in mass/volume g (+float): gravity in dist/(t^2), MUST BE POSITIVE xo (float): sea level height, MUST BE POSITIVE po (float): sea level pressure, MUST BE POSITIVE """ items = self.get_items(items) ctype = 'faces' cname = self.__get_cname(items) # make compoenet comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # make load ltype = 'press_fluid' mult = rho*g load = loads.LinearLoad(ltype, comp, po, mult, xo) self.__add_load(load, self.time) return load
[docs] def set_load(self, ltype, items, lval, ldir=None): """Sets a pressure or force load on line(s). The force load is divided by the number of nodes and applied on each node. The pressure load is applied to the child faces under the line. Positive is compression, negative is tension. Args: ltype (str): 'press' or 'force' items (str or Line or Arc or list): items to set load on - str: string name of Line or Arc item, for example 'L0' - Line or Arc: set load on this - list or Line or Arc: set load on these lval (float): load value. - For ltype = 'press' this is in force/area units - For ltype = 'force' this is in force units ldir (None or str): load direction. Defaults to None - str: when ltype='load', we need to set ldir to 'x' or 'y' - None: when ltype='press' """ items = self.get_items(items) ctype = 'nodes' if ltype == 'press': ctype = 'faces' cname = self.__get_cname(items) # make compoenet comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # make load if ltype == 'force': ltype = 'f'+ldir # for example fx load = loads.ConstLoad(ltype, comp, lval) self.__add_load(load, self.time) return load
[docs] def set_constr(self, ltype, items, axis, val=0.0): """Sets a displacement constraint on the passed item(s). Args: ltype (str): 'fix' or 'displ' - 'fix': val arg should not be passed - 'displ': val arg must be passed items (str, SignLine, SignArc, Point or list): item(s) to apply the constraint on - str: this string must be a line or point name - SignLine or SignArc: the constraint will be applied to this item - Point: the constraint will be applied on this item - list of SignLine or SignArc: the constraint will be appplied on these - list of Point: the constraint will be appplied on these - list of str names: pass line or point names, constr applied on them axis (str): load axis, 'x' or 'y' val (float): displacement value, defaults to 0.0, only needs to be set when ltype = 'displ' """ items = self.get_items(items) cname = self.__get_cname(items) ctype = 'nodes' # make compoenet comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # make load ltype = 'u'+axis # for example ux load = loads.ConstLoad(ltype, comp, val) self.__add_load(load, self.time) return load
[docs] def set_contact_linear(self, master_lines, slave_lines, kval, many_si=False): """Sets contact between master and slave lines. Slave lines are on the more compliant or more curved object. Args: master_lines (list): list of SignLine or SignArc slave_lines (list): list of SignLine or SignArc kval (float): stiffness, 5 to 50 times the youngs modulus of the touching matl many_si (bool): True, make unique surface interaction for every contact False, use existing surface interaction if we can """ master_items = self.get_items(master_lines) master_cname = self.__get_cname(master_items) ctype = 'faces' master_comp = components.Component(master_items, ctype, master_cname) master_comp = self.__get_make_comp(master_comp) slave_items = self.get_items(slave_lines) slave_cname = self.__get_cname(slave_items) ctype = 'faces' slave_comp = components.Component(slave_items, ctype, slave_cname) slave_comp = self.__get_make_comp(slave_comp) surf_int = connectors.SurfaceInteraction('LINEAR', kval) if many_si: surf_int = self.surfints.append(surf_int) else: surf_int = self.__get_make_surfint(surf_int) cont = connectors.Contact(master_comp, slave_comp, surf_int, True) self.contacts.append(cont)
[docs] def set_eshape(self, eshape='quad', eorder=2): """Sets the element shape and order to use when meshing the model. Args: eshape (str): element shape - 'quad': quadrilaterials - 'tri': triangles eorder (int): element order, default=2 - 1: corner nodes only (3 and 4 noded elements) - 2: corder nodes and midside nodes (6 and 8 noded elements) """ self.eshape = eshape # quad or tri self.eorder = eorder # 1 or 2
[docs] def set_etype(self, etype, items, thick=None): """Sets the element type, and thickness on areas or parts. Args: etype (str): element type - 'plstress': plane stress - 'plstrain': plane strain - 'axisym': axisymmetric items (str or Area or Part or list): set element type on these - str: string name of Area or Part item. Example: 'A0', 'PART0' - Area: set element type on the elements in this area - Part: set element type on the elements in this part - list of Area or Part: set element type on their elements thick (float or None): element thickness - None: default, used for axisymmetric element type - float: thickness value to use for plane stress or plane strain elements """ items = self.get_items(items) # set the element types on the areas, this is used to fix # elements when importing them from the inp file for item in items: if isinstance(item, geometry.Area): item.set_etype(etype) if isinstance(item, partmodule.Part): for area in item.areas: area.set_etype(etype) # set a thickness component if needed if etype != 'axisym' and thick != None: # make component for element thickness cname = self.__get_cname(items) ctype = 'nodes' comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # add load ltype = 'nodal_thickness' time = 0.0 load = loads.ConstLoad(ltype, comp, thick) self.__add_load(load, time) return load return None
[docs] def set_matl(self, matl, items): """Sets the material on Part or Area items. Args: matl (Material): material to assign to items items (Part or Area or list): items that will have matl assigned - Part: assign matl to this - Area: assign matl to this - list of Area or Part: assign matl to these """ items = self.get_items(items) cname = self.__get_cname(items) # store the material if it's not already if matl not in self.matls: matl = self.matls.append(matl) # make component ctype = 'elements' comp = components.Component(items, ctype, cname) comp = self.__get_make_comp(comp) # make load ltype = 'matl' time = 0.0 load = loads.ConstLoad(ltype, comp, matl) self.__add_load(load, time) return load
def __read_inp(self, fname): """Reads in the mesh from a Calculix inp file. All nodes, elements, and faces are read. Child nodes, elements and faces are assigned to their geometry parents (points, lines, arcs, areas, parts. Args: fname (str): file name to read, must include '.inp' extension """ f = open(fname, 'r') mode = None set_name = None set_type = None items = [] # holder for nodes or elements in nsets or esets N = base_classes.Meshlist() # store nodes E = base_classes.Meshlist() # store elements, allows renumbering before putting int model F = [] # store faces sets = {'E':{}, 'N':{}} # store sets etype = '' Dict_NodeIDs={} Dict_ElemIDs={} # read in input file for line in f: if line[0] != '*': if mode == 'nmake': L = line.split(',') L = [a.strip() for a in L] (nnum, x, y, z) = (int(L[0]), float(L[1]), float(L[2]), float(L[3])) node = mesh.Node(nnum, x, y, z) N.append(node) Dict_NodeIDs[nnum]=node elif mode == 'emake': L = line.split(',') L = [int(a.strip()) for a in L] enum = L[0] nlist = [Dict_NodeIDs[a] for a in L[1:]] e = mesh.Element(enum, etype, nlist) faces = e.faces E.append(e) Dict_ElemIDs[enum]=e F += faces sets[set_type][set_name].append(e) elif mode == 'set': L = line.split(',') L = [a.strip() for a in L] L = [int(a) for a in L if a != ''] items = [] if set_type == 'E': items = [Dict_ElemIDs[a] for a in L if a in Dict_ElemIDs.keys()] elif set_type == 'N': items = [Dict_NodeIDs[a] for a in L] if items == [None]*len(items): pass # the elements were not found else: sets[set_type][set_name] += items # mode setting if '*Node' in line or '*NODE' in line: mode = 'nmake' elif '*Element' in line or '*ELEMENT' in line: L = line.split(',') # split it based on commas e = L[1].split('=') etype = e[1] # exclude T elements made in gmsh if etype[0] != 'T': e = L[2].split('=') set_name = e[1].strip() set_type = 'E' sets[set_type][set_name] = [] mode = 'emake' else: mode = None elif '*ELSET' in line: L = line.split(',') e = L[1].split('=') set_name = e[1].strip() set_type = 'E' sets[set_type][set_name] = [] mode = 'set' elif '*NSET' in line: L = line.split(',') e = L[1].split('=') set_name = e[1].strip() set_type = 'N' sets[set_type][set_name] = [] mode = 'set' f.close() # loop through sets and remove empty sets # store sets to delete todel = [] for (set_type, set_dict) in sets.items(): for (set_name, item_list) in set_dict.items(): if item_list == []: todel.append({'set_type':set_type, 'set_name':set_name}) # delete the empty sets for adict in todel: (set_type, set_name) = (adict['set_type'], adict['set_name']) del sets[set_type][set_name] #print('Empty set type:%s name:%s deleted' % (set_type, set_name)) # this resets the min element to number 1 if E.get_minid() > 1: E.set_minid(1) #----------------------------------- # Node and element assignment back onto parts, areas, lines, points #----------------------------------- # assign elements + nodes to feamodel self.elements = E self.faces = F # remove arc center nodes from imported node set # those nodes have no elements under them torem = [] for node in N: if len(node.elements) == 0: torem.append(node) for node in torem: N.remove(node) # assign nodes to feamodel self.nodes = N for part in self.parts: # assign part element and node sets pname = part.get_name() part.elements = sets['E'][pname] part.nodes = sets['N'][pname] # assign all nodes and elements to areas, fix the element types for area in part.areas: aname = area.get_name() area.elements = sets['E'][aname] area.nodes = sets['N'][aname] area.set_child_ccxtypes() #paint element types on elements # assign the child nodes to points pts = part.points for point in pts: ndist = [] for node in part.nodes: p_tmp = geometry.Point(node.x, node.y) dist = point - p_tmp dist = dist.length() ndist.append({'dist':dist, 'node':node}) # sort the list by dist, sorts low to high ndist = sorted(ndist, key=lambda k: k['dist']) point.nodes = [ndist[0]['node']] #print('Point %s = node %s' % (pt, pt.nodes)) # assign the nodes and n1 and faces to lines slines = part.signlines for sline in slines: lname = sline.get_name() area = sline.lineloop.parent nodes = sets['N'][sline.line.get_name()] n1 = [n for n in nodes if n.order == 1] sline.nodes = nodes sline.n1 = n1 # make a set of all faces that contain the line's node allfaces = set() for node in nodes: allfaces.update(node.faces) # make a set of all faces on the line only linefaces = set() for face in allfaces: if set(face.nodes).issubset(set(nodes)): linefaces.add(face) # and only the ones whose element centers are in the area faces = set() for face in linefaces: is_lineface = area.contains_point(face.element.center) if is_lineface: faces.add(face) sline.faces = list(faces) print('Elements: %i' % len(E)) print('Nodes: %i' % len(N)) print('Done reading Calculix/Abaqus .inp file')
[docs] def mesh(self, size=1.0, meshmode='fineness', mesher='gmsh'): """Meshes all parts. Args: size (float): - if meshmode == 'fineness' (default): - mesh size is adapted to geometry size - set size = 0.0001 - 1.0, to define how fine the mesh is. - Low numbers are very fine, higher numbers are coarser. - if meshmode == 'esize': - element size is kept constant - choose it depending on geometry size - it should be reduced e.g. at arcs with small radius, by calling line.esize function meshmode (str): - 'fineness': adapt mesh size to geometry - 'esize': keep explicitly defined element size meshmode is changed to 'esize' is used if esize property is set to points or lines mesher (str): the mesher to use - 'gmsh': mesh with Gmsh, this is reccomended, it allows holes - 'cgx': mesh with Calculix cgx, it doesn't allow holes """ #check if element size is set to points and change meshmode if necessary for pt in self.points: if pt.esize != None: if meshmode=='fineness': print('meshmode is changed to esize, because elementsize was defined on points!') meshmode = 'esize' #if meshmode esize is chosen: ediv's on lines and arcs are transformed to element sizes on start and end point if meshmode == 'esize': for line in self.lines: if line.ediv != None: line.pt(0).set_esize(line.length()/line.ediv) line.pt(1).set_esize(line.length()/line.ediv) if mesher == 'gmsh': self.__mesh_gmsh(size, meshmode) elif mesher == 'cgx': self.__mesh_cgx(size)
def __mesh_gmsh(self, size, meshmode, timeout=20): """Meshes all parts using the Gmsh mesher. Args: size (float): - if meshmode == 'fineness' (default): - mesh size is adapted to geometry size - set size = 0.0001 - 1.0, to define how fine the mesh is. - Low numbers are very fine, higher numbers are coarser. - if meshmode == 'esize': - element size is kept constant - choose it depending on geometry size meshmode (str): - 'fineness': adapt mesh size to geometry - 'esize': keep explicitly defined element size timeout (int): time in seconds before the process throws a subprocess.TimeoutExpired """ geo = [] ids = {} ids['line'] = {} ids['plane_surface'] = {} # write all points for pt in self.points: txtline = 'Point(%i) = {%f, %f, %f};' % (pt.id, pt.x, pt.y, 0.0) if meshmode == 'esize': #add element size to points if pt.esize == None: txtline = txtline.replace('}', ', %f}' % (size if self.eshape=='tri' else size*2.)) #txtline = txtline.replace('}', ', %f}' % (size)) else: txtline = txtline.replace('}', ', %f}' % (pt.esize if self.eshape=='tri' else pt.esize*2.)) #txtline = txtline.replace('}', ', %f}' % (pt.esize)) geo.append(txtline) # start storing an index number ind = self.points[-1].id + 1 # write all lines for line in self.lines: lnum = line.id ids['line'][lnum] = ind pt1 = line.pt(0).id pt2 = line.pt(1).id txtline = '' if isinstance(line, geometry.Arc): ctr = line.actr.id txtline = 'Circle(%i) = {%i, %i, %i};' % (ind, pt1, ctr, pt2) else: txtline = 'Line(%i) = {%i,%i};' % (ind, pt1, pt2) geo.append(txtline) # set division if we have it if line.ediv != None and meshmode=='fineness': ndiv = line.ediv+1 esize = line.length()/line.ediv if self.eshape == 'quad': ndiv = line.ediv/2+1 esize = esize*2 # this is needed because quad recombine # splits 1 element into 2 txtline = 'Transfinite Line{%i} = %i;' % (ind, ndiv) print('LINE ELEMENT SIZE: %f, MAKES %i ELEMENTS' % (line.length()/line.ediv, line.ediv)) geo.append(txtline) geo.append('Characteristic Length {%i,%i} = %f;' % (pt1, pt2, esize)) ind += 1 # write all areas for area in self.areas: if area.closed: aid = area.id aname = area.get_name() loop_ids = [] loops = [area.exlines] + area.holes for loop in loops: txtline = 'Line Loop(%i) = ' % (ind) loop_ids.append(str(ind)) line_ids = [] for sline in loop: lid = ids['line'][sline.line.id] prefix = '' if sline.sign == -1: prefix = '-' line_ids.append('%s%i' % (prefix, lid)) txtline = txtline + '{'+ ','.join(line_ids)+'};' geo.append(txtline) ind += 1 loop_ids = ','.join(loop_ids) geo.append('Plane Surface(%i) = {%s};' % (ind, loop_ids)) ids['plane_surface'][aid] = ind geo.append("Physical Surface('%s') = {%i};" % (aname, ind)) ind += 1 # write part area components for part in self.parts: # make components for each part txtline = "Physical Surface('%s') = " % (part.get_name()) area_ids = [] for area in part.areas: if area.closed: aid = ids['plane_surface'][area.id] area_ids.append(str(aid)) txtline = txtline + '{' + ','.join(area_ids) + '};' geo.append(txtline) # write all line componenets so we can get nodes out for line in self.lines: lid = ids['line'][line.id] txtline = "Physical Line('%s') = {%i};" % (line.get_name(), lid) geo.append(txtline) # write node componenets # ERROR: node list is not produced by gmsh for point in self.points: pname = point.get_name() txtline = "Physical Point('%s') = {%i};" % (pname, point.id) geo.append(txtline) # set the meshing options if meshmode == 'fineness': geo.append('Mesh.CharacteristicLengthFactor = '+str(size)+'; //mesh fineness') geo.append('Mesh.RecombinationAlgorithm = 1; //blossom') if self.eshape == 'quad': geo.append('Mesh.RecombineAll = 1; //turns on quads') geo.append('Mesh.SubdivisionAlgorithm = 1; // quadrangles only') #geo.append('Mesh.RecombinationAlgorithm = 1; //turns on blossom needed for quad') order = self.eorder geo.append('Mesh.CharacteristicLengthExtendFromBoundary = 1;') geo.append('Mesh.CharacteristicLengthMin = 0;') geo.append('Mesh.CharacteristicLengthMax = 1e+022;') # use this so small circles are meshed finely # this is broken in Gmsh 4.3.0 # geo.append('Mesh.CharacteristicLengthFromCurvature = 1;') geo.append('Mesh.CharacteristicLengthFromPoints = 1;') # geo.append('Mesh.Algorithm = 2; //delauny') #okay for quads geo.append('Mesh.Algorithm = 8; //delquad = delauny for quads') geo.append('Mesh.ElementOrder = ' +str(order) +'; //linear or second set here') if order == 2: geo.append('Mesh.SecondOrderIncomplete=1; //no face node w/ 2nd order') geo.append('Mesh.SaveGroupsOfNodes = 1; // save node groups') # write geo file to the local directory fname = self.fname+'.geo' fout = self.fname+'.inp' outfile = open(fname, 'w') for line in geo: #print (line) outfile.write(line+'\n') outfile.close() print('File: %s was written' % fname) # run file in bg mode, -2 is 2d mesh, makes required inp file runstr = "%s %s -2 -o %s" % (environment.GMSH, fname, fout) print(runstr) subprocess.check_call(runstr, timeout=timeout, shell=True) print('File: %s was written' % fout) print('Meshing done!') # write gmsh msh file, for manual checking only # not required by pycalculix runstr = "%s %s -2 -o %s" % (environment.GMSH, fname, self.fname+'.msh') subprocess.check_call(runstr, timeout=timeout, shell=True) print('File: %s.msh was written' % self.fname) # read in the calculix mesh self.__read_inp(self.fname+'.inp') def __mesh_cgx(self, size, meshmode, timeout=20): """Meshes all parts using the Calculix cgx mesher. Args: size (float): - if meshmode == 'fineness' (default): - mesh size is adapted to geometry size - set size = 0.0001 - 1.0, to define how fine the mesh is. - Low numbers are very fine, higher numbers are coarser. - if meshmode == 'esize': NOT TESTED WITH CGX - element size is kept constant - choose it depending on geometry size meshmode (str): - 'fineness': adapt mesh size to geometry - 'esize': keep explicitly defined element size NOT TESTED WITH CGX timeout (int): time in seconds before the process throws a subprocess.TimeoutExpired """ fbd = [] comps = [] cfiles = [] # Calculix CGX elements # axisymmetric cgx_elements = {} cgx_elements['tri2axisym'] = 'tr6c' cgx_elements['tri1axisym'] = 'tr3c' cgx_elements['quad2axisym'] = 'qu8c' cgx_elements['quad1axisym'] = 'qu4c' # plane stress cgx_elements['tri2plstress'] = 'tr6s' cgx_elements['tri1plstress'] = 'tr3s' cgx_elements['quad2plstress'] = 'qu8s' cgx_elements['quad1plstress'] = 'qu4s' # plane strain cgx_elements['tri2plstrain'] = 'tr6e' cgx_elements['tri1plstrain'] = 'tr3e' cgx_elements['quad2plstrain'] = 'qu8e' cgx_elements['quad1plstrain'] = 'qu4e' num = 1.0/size emult = int(round(num)) # this converts size to mesh multiplier # write all points for point in self.points: linestr = 'pnt %s %f %f %f' % (point.get_name(), point.x, point.y, 0.0) fbd.append(linestr) # gmsh can't make node componenets so don't do it in cgx #L = 'seta %s p %s' % (pt.get_name(), pt.get_name()) #comps.append(L) # write all lines for line in self.lines: lname = line.get_name() pt1 = line.pt(0).get_name() pt2 = line.pt(1).get_name() linestr = '' if isinstance(line, geometry.Arc): # line is arc pctr = line.actr.get_name() linestr = 'line %s %s %s %s' % (lname, pt1, pt2, pctr) else: # straight line linestr = 'line %s %s %s' % (lname, pt1, pt2) # set division if we have it if line.ediv != None: ndiv = self.eorder*line.ediv linestr += ' '+str(int(ndiv)) fbd.append(linestr) linestr = 'seta %s l %s' % (lname, lname) comps.append(linestr) cfiles.append(lname) # write all areas for area in self.areas: if area.closed: linestr = 'gsur '+area.get_name()+' + BLEND ' line_ids = [] for line in area.signlines: lname = '+ '+line.get_name() if line.sign == -1: lname = '- '+line.get_name()[1:] line_ids.append(lname) linestr = linestr + ' '.join(line_ids) fbd.append(linestr) # add area component, nodes + elements astr = 'seta %s s %s' % (area.get_name(), area.get_name()) fbd.append(astr) cfiles.append(area.get_name()) # write part area components for apart in self.parts: # make components for each part # seta P0 s s0 s1 line = 'seta %s s ' % apart.get_name() cfiles.append(apart.get_name()) area_ids = [] for area in apart.areas: if area.closed: area_ids.append(area.get_name()) line = line + ' '.join(area_ids) fbd.append(line) # mesh all areas for area in apart.areas: aname = area.get_name() estr = self.eshape+str(self.eorder)+area.etype cgx_etype = cgx_elements[estr] fbd.append('elty %s %s' % (aname, cgx_etype)) fbd.append('div all mult %i' % emult) fbd.append('mesh all') # save mesh file fbd.append('send all abq') # add line and area components fbd += comps # save component node and element sets for comp in cfiles: # select nodes under fbd.append('comp %s do' % (comp,)) fbd.append('send %s abq names' % (comp,)) # this orients the view correctly # this is the same as switching to the z+ orientation # y us axial, x is radial fbd.append('rot z') fbd.append('rot c -90') fbd.append('plot e all') # write fbd file to the local directory fname = self.fname+'.fbd' f = open(fname, 'w') for line in fbd: #print (line) f.write(line+'\n') f.close() print('File: %s was written' % fname) # run file in bg mode runstr = "%s -bg %s" % (environment.CGX, fname) p = subprocess.check_call(runstr, timeout=timeout, shell=True) print('Meshing done!') # assemble the output files into a ccx input file inp = [] files = ['all.msh'] files += [f+'.nam' for f in cfiles] for fname in files: infile = open(fname, 'r') for line in infile: # cgx adds E and N prfixes on sets after =, get rid of these if '=' in line and fname != 'all.msh': L = line.split('=') line = L[0] + '=' + L[1][1:] inp.append(line.strip()) else: inp.append(line.strip()) infile.close() # delete file os.remove(fname) # write out inp file fname = self.fname+'.inp' outfile = open(fname, 'w') for line in inp: #print (line) outfile.write(line+'\n') outfile.close() print('File: %s was written' % fname) # read in the calculix mesh self.__read_inp(fname)