Source code for upxo.meshing.writer_ABQ

"""
Low-level Abaqus keyword writers for UPXO meshes.

Helpers to write ``*Elset`` / ``*Nset`` blocks. 2D conformal INP export is
``export_confmesh2d_inp``. 3D partitioned export lives in
``confMesh3d.export``, ``fm_steel_3d.mesh_exporter_3d``, and
``twinned_simple_3d.abaqus_exporter_3d``.
"""

import numpy as np


# -------------------------------------------------
# Utilities
# -------------------------------------------------


[docs] def write_elset(f, name, elem_ids, per_line=16): """ Writes an Abaqus *Elset. """ elem_ids = np.asarray(elem_ids, dtype=int) f.write(f"*Elset, elset={name}\n") for i in range(0, len(elem_ids), per_line): line = ", ".join(map(str, elem_ids[i:i+per_line])) f.write(line + "\n")
[docs] def write_nset(f, name, node_ids, per_line=16): """Write an Abaqus *Nset.""" node_ids = np.asarray(node_ids, dtype=int) f.write(f"*Nset, nset={name}\n") for i in range(0, len(node_ids), per_line): f.write(", ".join(map(str, node_ids[i:i + per_line])) + "\n")
[docs] def summarize_inp(path): """Return which standard Abaqus keyword blocks are present in ``path``.""" from pathlib import Path text = Path(path).read_text(encoding='utf-8') keys = ( '*Node', '*Element', '*Elset', '*Nset', '*Solid Section', '*Material', ) return {k: k.lower() in text.lower() for k in keys}
def _abaqus_shell_type(eltype, n_nodes, plane): """Return ``(Abaqus *Element type, nodes written)`` for a 2D connectivity. Linear: CPS3/CPS4 or CPE3/CPE4. Quadratic: CPS6/CPE6 (6-node tri) and CPS8/CPE8 (8-node serendipity quad). Gmsh 6-node tri / 8-node quad node order matches Abaqus (corners then mid-side nodes). """ if plane not in ('stress', 'strain'): raise ValueError("plane must be 'stress' or 'strain'") s = plane == 'stress' if eltype == 'triangle': if n_nodes >= 6: return ('CPS6' if s else 'CPE6'), 6 return ('CPS3' if s else 'CPE3'), 3 if eltype == 'quad': if n_nodes >= 8: return ('CPS8' if s else 'CPE8'), 8 return ('CPS4' if s else 'CPE4'), 4 raise ValueError(f"Unsupported element type {eltype!r}")
[docs] def export_confmesh2d_inp( path, nodes, elConn, elsets_eltype, nsets=None, plane='stress', thickness=1.0, write_sections=True, heading=None, material_format='isotropic', grain_euler_deg=None, n_depvar=1, elastic_constants=(210000., 0.3)): """Write a 2D conformal mesh to an Abaqus ``.inp``. Compacts sparse node tags to 1..N. ``plane='stress'`` → CPS3/CPS6/CPS4/CPS8, ``plane='strain'`` → CPE3/CPE6/CPE4/CPE8, chosen from connectivity width. ``material_format`` controls the ``*Material`` block written per elset (only used when ``write_sections=True``): * ``'isotropic'`` (default) -- dummy isotropic ``*Elastic`` section (``elastic_constants``), unchanged from before this parameter existed. * ``'bunge_euler'`` -- one ``*Material`` per grain carrying its grain-averaged Bunge-Euler angles (degrees) as ``*User Material, constants=3`` plus a ``*Depvar`` block, for a crystal-plasticity UMAT. Mirrors the convention used by ``pxtal.twinned_simple_3d.abaqus_exporter_3d.AbaqusExporter3D``. Requires ``grain_euler_deg``: dict ``{grain_id: (phi1, Phi, phi2)}`` in degrees, keyed by the grain id embedded in each elset name (the part after the last ``.`` in the un-sanitised elset name, e.g. ``grain.42`` -> ``42``). """ from pathlib import Path nodes = np.asarray(nodes) valid = ~np.isnan(nodes[:, 0]) old_ids = np.where(valid)[0] if old_ids.size == 0: raise RuntimeError("No mesh nodes to export.") remap = np.zeros(int(old_ids.max()) + 1, dtype=int) remap[old_ids] = np.arange(1, old_ids.size + 1) xy = nodes[old_ids, :2] path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) nsets = nsets or {} elsets_eltype = elsets_eltype or {} with path.open('w', encoding='utf-8') as f: f.write("*Heading\n") f.write((heading or "UPXO confMesh2dGMSH conformal 2D mesh") + "\n") f.write("*Preprint, echo=NO, model=NO, history=NO, contact=NO\n") f.write("*Node\n") for nid, (x, y) in zip(remap[old_ids], xy): f.write(f"{nid}, {x:.8g}, {y:.8g}, 0.0\n") eid_offset = 0 global_elsets = {} for eltype in ('triangle', 'quad'): conn = elConn.get(eltype) if elConn else None if conn is None or len(conn) == 0: continue n_nodes = int(np.asarray(conn).shape[1]) abq_type, n_write = _abaqus_shell_type(eltype, n_nodes, plane) f.write(f"*Element, type={abq_type}\n") for local_i, nds in enumerate(conn): eid = eid_offset + local_i + 1 nstr = ", ".join(str(int(remap[n])) for n in nds[:n_write]) f.write(f"{eid}, {nstr}\n") for name, local_ids in elsets_eltype.get(eltype, {}).items(): gids = np.asarray(local_ids, dtype=int) + eid_offset + 1 if name in global_elsets: global_elsets[name] = np.concatenate( (global_elsets[name], gids)) else: global_elsets[name] = gids eid_offset += len(conn) for name, eids in global_elsets.items(): abq_name = name.replace('.', '_').replace('-', '_').upper() write_elset(f, abq_name, eids) for key in ( 'LEFT', 'RIGHT', 'BOTTOM', 'TOP', 'BOTTOM_LEFT', 'BOTTOM_RIGHT', 'TOP_LEFT', 'TOP_RIGHT', 'GB', ): ids = nsets.get(key) if ids is None or len(ids) == 0: continue write_nset(f, f"NS_{key}", remap[np.asarray(ids, dtype=int)]) if write_sections: if material_format == 'bunge_euler': if grain_euler_deg is None: raise ValueError( "material_format='bunge_euler' requires grain_euler_deg " "(dict {grain_id: (phi1, Phi, phi2)} in degrees).") f.write("** One *Material per grain " "(Bunge-Euler angles in degrees).\n") f.write("** Replace with full CPFEM constitutive block as " "needed.\n") for name in global_elsets: abq_name = name.replace('.', '_').replace('-', '_').upper() gid = int(name.rsplit('.', 1)[-1]) phi1, Phi, phi2 = grain_euler_deg[gid] f.write(f"*Material, name=MAT_{abq_name}\n") f.write(f"** Bunge-Euler (deg): phi1={phi1:.4f}, " f"Phi={Phi:.4f}, phi2={phi2:.4f}\n") f.write("*User Material, constants=3\n") f.write(f"{phi1:.6f}, {Phi:.6f}, {phi2:.6f}\n") f.write(f"*Depvar\n{n_depvar},\n") f.write( f"*Solid Section, elset={abq_name}, material=MAT_{abq_name}\n" ) f.write(f"{thickness},\n") else: f.write("** Dummy isotropic sections — replace before a real job\n") e_mod, nu = elastic_constants for name in global_elsets: abq_name = name.replace('.', '_').replace('-', '_').upper() f.write(f"*Material, name=MAT_{abq_name}\n") f.write(f"*Elastic\n{e_mod}, {nu}\n") f.write( f"*Solid Section, elset={abq_name}, material=MAT_{abq_name}\n" ) f.write(f"{thickness},\n") return str(path)
[docs] def write_grain_elsets(f, elsets_dict, prefix="Grain"): """ Writes all grain-wise elsets from dictionary. """ grain_block = elsets_dict.get("basic", {}) for gid_key, elem_ids in grain_block.items(): gid = gid_key.replace("gid_", "") elset_name = f"{prefix}{gid}_set" write_elset(f, elset_name, elem_ids)
# ------------------------------------------------- # Example / self-test: writes a minimal job1.inp demonstrating write_elset # and write_grain_elsets. Only runs when this file is executed directly, # not on import (an earlier version ran this unconditionally at import # time, writing job1.inp to the caller's CWD as an import side effect). # ------------------------------------------------- if __name__ == "__main__": heading = """\ *Heading Job-1 ** Job name : Job-1 ** Generated by : UPXO 1.0 *Preprint, echo=NO, model=NO, history=NO, contact=NO """ nodes = np.array([ [1, 0.0, 0.0, 0.0], [2, 1.0, 0.0, 0.0], [3, 1.0, 1.0, 0.0], ]) elements = np.array([ [1, 1, 2, 3], [2, 2, 3, 4], ]) nset = np.array([1, 2, 5, 7, 9]) elsets_dict = { "basic": { "gid_1": [1, 2, 3, 4, 5, 6], "gid_2": [7, 9, 10, 14, 15, 18], } } with open("job1.inp", "w", encoding="utf-8") as f: # Heading f.write(heading) # -------------------- # Nodes # -------------------- f.write("*Node\n") np.savetxt(f, nodes, fmt="%d, %.6f, %.6f, %.6f") # -------------------- # Elements # -------------------- f.write("*Element, type=CPS3\n") np.savetxt(f, elements, fmt="%d, %d, %d, %d") # -------------------- # Node set # -------------------- f.write("*Nset, nset=LEFT\n") for i in range(0, len(nset), 16): f.write(", ".join(map(str, nset[i:i+16])) + "\n") # -------------------- # Grain element sets # -------------------- f.write("**\n") f.write("** Each Grain is made up of multiple elements\n") f.write("**\n") write_grain_elsets(f, elsets_dict) # ======================================================================= # ======================================================================= # ======================================================================= # ======================================================================= # ======================================================================= ''' *Heading Job-1 ** Job name : Job-1 ** Generated by : ImportExport Version 6.5.167.2b16e61e0 *Preprint, echo = NO, model = NO, history = NO, contact = NO ** ** ----------------------------Geometry---------------------------- ** *Include, Input = sunil_nodes.inp *Include, Input = sunil_elems.inp *Include, Input = sunil_elset.inp *Include, Input = sunil_sects.inp ** ** ---------------------------------------------------------------- ** ''' # NODES ''' ** Generated by : ImportExport Version 6.5.167.2b16e61e0 ** ---------------------------------------------------------------- ** *Node 1, 0.000000, 0.000000, 0.000000 2, 1.000000, 0.000000, 0.000000 ''' # ELEMENTS ''' ** Generated by : ImportExport Version 6.5.167.2b16e61e0 ** ---------------------------------------------------------------- ** *Element, type=C3D8 1, 123, 2, 1, 122, 134, 13, 12, 133 2, 124, 3, 2, 123, 135, 14, 13, 134 3, 125, 4, 3, 124, 136, 15, 14, 135 ''' # ELSETS ''' ** Generated by : ImportExport Version 6.5.167.2b16e61e0 ** ---------------------------------------------------------------- ** ** The element sets *Elset, elset=cube, generate 1, 1000, 1 ** ** Each Grain is made up of multiple elements ** *Elset, elset=Grain1_set 654, 655, 656, 657, 663, 664, 665, 666, 667, 672, 673, 674, 675, 676, 744, 745, 753, 754, 755, 756, 757, 763, 764, 765, 766, 767, 771, 772, 773, 774, 775, 776, 777, 781, 782, 784, 786, 787, 791, 792, 843, 844, 845, 846, 853, 854, 855, 856, 857, 861, 863, 864, 865, 866, 867, 871, 872, 874, 875, 876, 881, 882, 884, 885, 886, 887, 891, 892, 893, 943, 944, 945, 946, 952, 953, 954, 955, 956, 961, 962, 963, 964, 965, 966, 971, 972, 973, 974, 975, 976, 981, 982, 983, 984, 985, 986, 987, 991, 992, 993, 994, 995, 996, 997 ''' # SECTIONS ''' ** Generated by : ImportExport Version 6.5.167.2b16e61e0 ** ---------------------------------------------------------------- ** ** Each section is a separate grain ** Section: Grain1 *Solid Section, elset=Grain1_set, material=Grain_Mat1 *Hourglass Stiffness 250 ** -------------------------------------- ** Section: Grain2 *Solid Section, elset=Grain2_set, material=Grain_Mat2 *Hourglass Stiffness 250 ''' # ---------------------------------------------- # *Part, name=DREAM3D # *Node # *Element, type=C3D8 # *Elset, elset=GRAIN-87 # 5603, 5604, 5623, 5624, 6003, 6004, 6023, 6024, 6025, 6026, 6027, 6028, 6047, 6048, 6403, 6404 # 6405, 6406, 6407, 6408, 6409, 6423, 6424, 6425, 6426, 6427, 6428, 6429, 6447, 6448, 6827, 6828 # 6829, 6848, 6849, 6868, 7228, 7229, 7248 # *Elset, elset=PHASE-1, generate # 1, 8000, 1 # ** Section: Section-64-GRAIN-64 # *Solid Section, elset=GRAIN-64, material=MATERIAL-GRAIN64 # , # *End Part # ---------------------------------------------- # *Assembly, name=Assembly # *Instance, name=DREAM3D-1, part=DREAM3D # *End Instance # *Nset, nset=_PickedSet4, internal, instance=DREAM3D-1, generate # 21, 9261, 21 # *End Assembly # ---------------------------------------------- # *Material, name=MATERIAL-GRAIN1 # *Depvar # 1, # *User Material, constants=6 # 26.534, 83.227, 38.07, 1., 2., 0. # ---------------------------------------------- # ** # ** STEP: Loading # ** # *Step, name=Loading, nlgeom=YES, inc=10000 # *Static # 0.01, 1., 1e-08, 1. # ---------------------------------------------- # ** # ** BOUNDARY CONDITIONS # ** ''' ** Name: xfix Type: Displacement/Rotation *Boundary Set-1, 1, 1 ** Name: yfix Type: Displacement/Rotation *Boundary Set-2, 2, 2 ** Name: zfix Type: Displacement/Rotation *Boundary Set-3, 3, 3 ''' # ---------------------------------------------- ''' ** ** STEP: Loading ** *Step, name=Loading, nlgeom=YES, inc=10000 *Static 0.01, 10., 1e-05, 1. ** ** BOUNDARY CONDITIONS ** ** Name: xpull Type: Displacement/Rotation *Boundary Set-4, 1, 1, 0.5 ** ** OUTPUT REQUESTS ** *Restart, write, frequency=0 ** ** FIELD OUTPUT: F-Output-2 ** *Output, field *Element Output, directions=YES SDV, ** ** FIELD OUTPUT: F-Output-1 ** *Output, field, variable=PRESELECT ** ** HISTORY OUTPUT: H-Output-1 ** *Output, history, variable=PRESELECT *End Step '''