# -*- coding: utf-8 -*-
# Abaqus/CAE script.
#
# This script imports an existing, translated Abaqus model containing
# the complete vehicle mesh and explicit SPH explosive particles.
# It does not reconstruct missing geometry from paper illustrations.
#
# REQUIRED INPUT:
# An Abaqus-format INP translated and verified from the original
# paper model, with material definitions, sections, interactions,
# explosive initialization, ground and analysis steps.
#
# An LS-DYNA .k file cannot be used directly as an Abaqus input file.
# The paper's explosive burn and contact definitions require an
# independently verified translation.
#
# Importing a model does NOT establish equivalence to the paper.
# No computation or result-database access is performed here.

from abaqus import mdb
from abaqusConstants import *
import os
import re

# Change this to the location of the complete translated source model.
SOURCE_INP = os.path.abspath("LAV_original_translated.inp")
OUTPUT_DIR = os.path.abspath("LAV_checked_input")
BASE_NAME = "LAV_Original_Imported"


def read_input_tree(path, active=None):
    """Read the input and included files for preliminary checks."""
    if active is None:
        active = set()
    path = os.path.realpath(path)
    if path in active:
        raise RuntimeError("Circular INCLUDE detected: " + path)
    if not os.path.isfile(path):
        raise RuntimeError("Missing input file: " + path)

    active.add(path)
    with open(path, "r") as stream:
        lines = stream.readlines()

    output = []
    for line in lines:
        stripped = line.strip()
        if stripped.upper().startswith("*INCLUDE"):
            match = re.search(
                r"\bINPUT\s*=\s*(.+?)(?:,\s*\w+\s*=|$)",
                stripped, re.IGNORECASE
            )
            if match is None:
                raise RuntimeError("Unrecognized INCLUDE: " + stripped)
            relative = match.group(1).strip().strip("\"'")
            include_path = os.path.join(os.path.dirname(path), relative)
            output.append(read_input_tree(include_path, active))
        else:
            output.append(line)
    active.remove(path)
    return "".join(output)


def choose_name():
    index = 1
    while True:
        name = "%s_%02d" % (BASE_NAME, index)
        paths = [
            os.path.join(OUTPUT_DIR, name + extension)
            for extension in (".cae", ".inp")
        ]
        if (name not in mdb.models and name not in mdb.jobs
                and not any(os.path.exists(p) for p in paths)):
            return name
        index += 1


if not os.path.isfile(SOURCE_INP):
    raise RuntimeError(
        "Original model is unavailable. Supply the complete translated "
        "Abaqus INP at: %s. The supplied paper alone does not specify "
        "the full vehicle geometry, thickness map and connections. "
        "No substitute vehicle or arbitrary blast load was generated."
        % SOURCE_INP
    )

source_text = read_input_tree(SOURCE_INP)
keyword_lines = [
    line.strip().upper()
    for line in source_text.splitlines()
    if line.lstrip().startswith("*")
    and not line.lstrip().startswith("**")
]
keywords = "\n".join(keyword_lines)

element_types = set()
for line in keyword_lines:
    if line.startswith("*ELEMENT,") or line.startswith("*ELEMENT "):
        match = re.search(r"\bTYPE\s*=\s*([A-Z0-9]+)", line)
        if match:
            element_types.add(match.group(1))

requirements = [
    ("explicit SPH particles PC3D", "PC3D" in element_types),
    ("vehicle shell elements",
     bool(element_types.intersection(("S3", "S3R", "S4", "S4R")))),
    ("explicit dynamics step",
     any(line.startswith("*DYNAMIC") and "EXPLICIT" in line
         for line in keyword_lines)),
    ("JWL equation of state",
     any(line.startswith("*EOS") and "JWL" in line
         for line in keyword_lines)),
    ("shell section assignment", "*SHELL SECTION" in keywords),
    ("initial conditions", "*INITIAL CONDITIONS" in keywords),
]

missing = [label for label, passed in requirements if not passed]
if missing:
    raise RuntimeError(
        "Source model failed preliminary checks: " + "; ".join(missing)
    )

if not os.path.isdir(OUTPUT_DIR):
    os.makedirs(OUTPUT_DIR)

name = choose_name()

# ModelFromInputFile creates the CAE model, parts and imported meshes.
# Review all Abaqus import warnings: some keywords may not be supported
# by the CAE importer and must not be silently omitted.
model = mdb.ModelFromInputFile(name=name, inputFileName=SOURCE_INP)

counts = {}
for part in model.parts.values():
    for element in part.elements:
        key = str(element.type).upper()
        counts[key] = counts.get(key, 0) + 1

if counts.get("PC3D", 0) == 0:
    raise RuntimeError(
        "CAE import did not retain PC3D particles. Stop and review "
        "the importer warnings; do not calculate this imported model."
    )

shell_count = sum(
    counts.get(key, 0) for key in ("S3", "S3R", "S4", "S4R")
)
if shell_count == 0:
    raise RuntimeError("CAE import did not retain the vehicle shell mesh.")

model.keywordBlock.synchVersions(storeNodesAndElements=False)
imported_keywords = "\n".join(model.keywordBlock.sieBlocks).upper()
if not re.search(r"\*EOS[^\n]*JWL", imported_keywords):
    raise RuntimeError(
        "JWL definition was not retained during import. "
        "A verified keyword translation is required."
    )

# Keep existing source loads, interactions and output definitions.
# Do not replace explosive loading with an arbitrary pressure pulse.
# Request contour variables in each imported explicit dynamics step.
for step_name, step in model.steps.items():
    if step_name == "Initial":
        continue
    if step.__class__.__name__ == "ExplicitDynamicsStep":
        request_name = "Contour_" + step_name
        if request_name not in model.fieldOutputRequests:
            model.FieldOutputRequest(
                name=request_name,
                createStepName=step_name,
                variables=("S", "U", "V"),
                numIntervals=60
            )

model.rootAssembly.regenerate()
os.chdir(OUTPUT_DIR)

job = mdb.Job(
    name=name,
    model=name,
    type=ANALYSIS,
    description="Imported vehicle/SPH model; source verification required",
    numCpus=4,
    numDomains=4,
    parallelizationMethodExplicit=DOMAIN,
    multiprocessingMode=DEFAULT,
    explicitPrecision=DOUBLE,
    nodalOutputPrecision=SINGLE
)
job.writeInput()
mdb.saveAs(pathName=os.path.join(OUTPUT_DIR, name + ".cae"))

print("Imported shell elements: %d" % shell_count)
print("Imported PC3D particles: %d" % counts["PC3D"])
print("Paper reference: 169052 shells; 29316 explosive SPH particles.")
print("Counts alone do not establish agreement with the paper.")
print("Review import warnings and compare written INP with source.")
print("Existing CAE job: " + name)
print("Input written; calculation has not been started.")
print('SOUTHWEITION_JOB_PREPARED')
import os as _sw_os
try:
    print('SOUTHWEITION_CWD ' + _sw_os.getcwd())
    def _sw_job_model(_job):
        _model = getattr(_job, 'model', '')
        try:
            return str(_model.name)
        except Exception:
            return str(_model or '')
    _sw_models = list(mdb.models.keys())
    _sw_latest_model = _sw_models[-1] if _sw_models else ''
    _sw_name = ''
    for _cand in reversed(list(mdb.jobs.keys())):
        if _sw_latest_model and _sw_job_model(mdb.jobs[_cand]) == _sw_latest_model:
            _sw_name = str(_cand)
            break
    if not _sw_name:
        _sw_names = list(mdb.jobs.keys())
        if _sw_names:
            _sw_name = str(_sw_names[-1])
    if _sw_name:
        try:
            mdb.jobs[_sw_name].setValues(numCpus=4, numDomains=4)
        except Exception:
            pass
        mdb.jobs[_sw_name].writeInput(consistencyChecking=OFF)
        print('SOUTHWEITION_JOB ' + str(_sw_name))
        print('SOUTHWEITION_MODEL ' + str(_sw_latest_model))
        print('SOUTHWEITION_INP ' + _sw_os.path.abspath(str(_sw_name) + '.inp'))
    print('SOUTHWEITION_JOB_PREPARED')
except Exception:
    print('SOUTHWEITION_JOB_PREPARED')

