mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-12 14:23:20 +00:00
feat(legacy/io): vendor Abaqus model AST types
Restore mutable `Model`/`Mesh` Dict containers for `.inp` ingestion. - Include section parsers and state machine helpers from the historical reader.
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
# This file is a part of JuliaFEM.
|
||||
# License is MIT: see https://github.com/JuliaFEM/AbaqusReader.jl/blob/master/LICENSE
|
||||
|
||||
import Base.parse
|
||||
import Base: getindex, length
|
||||
|
||||
### Model definitions for ABAQUS data model
|
||||
|
||||
abstract type AbstractMaterial end
|
||||
abstract type AbstractMaterialProperty end
|
||||
abstract type AbstractProperty end
|
||||
abstract type AbstractStep end
|
||||
abstract type AbstractBoundaryCondition end
|
||||
abstract type AbstractOutputRequest end
|
||||
|
||||
mutable struct Mesh
|
||||
nodes::Dict{Int,Vector{Float64}}
|
||||
node_sets::Dict{String,Vector{Int}}
|
||||
elements::Dict{Int,Vector{Int}}
|
||||
element_types::Dict{Int,Symbol}
|
||||
element_sets::Dict{String,Vector{Int}}
|
||||
surface_sets::Dict{String,Vector{Tuple{Int,Symbol}}}
|
||||
surface_types::Dict{String,Symbol}
|
||||
end
|
||||
|
||||
function Mesh(d::Dict{String,Dict})
|
||||
return Mesh(d["nodes"], d["node_sets"], d["elements"],
|
||||
d["element_types"], d["element_sets"],
|
||||
d["surface_sets"], d["surface_types"])
|
||||
end
|
||||
|
||||
mutable struct Model
|
||||
path::String
|
||||
name::String
|
||||
mesh::Mesh
|
||||
materials::Dict{Symbol,AbstractMaterial}
|
||||
properties::Vector{AbstractProperty}
|
||||
boundary_conditions::Vector{AbstractBoundaryCondition}
|
||||
steps::Vector{AbstractStep}
|
||||
end
|
||||
|
||||
mutable struct SolidSection <: AbstractProperty
|
||||
element_set::Symbol
|
||||
material_name::Symbol
|
||||
end
|
||||
|
||||
mutable struct Material <: AbstractMaterial
|
||||
name::Symbol
|
||||
properties::Vector{AbstractMaterialProperty}
|
||||
end
|
||||
|
||||
mutable struct Elastic <: AbstractMaterialProperty
|
||||
E::Float64
|
||||
nu::Float64
|
||||
end
|
||||
|
||||
mutable struct Step <: AbstractStep
|
||||
kind::Union{Symbol,Nothing} # STATIC, ... (was Nullable{Symbol} in Julia 0.x)
|
||||
boundary_conditions::Vector{AbstractBoundaryCondition}
|
||||
output_requests::Vector{AbstractOutputRequest}
|
||||
end
|
||||
|
||||
mutable struct BoundaryCondition <: AbstractBoundaryCondition
|
||||
kind::Symbol # BOUNDARY, CLOAD, DLOAD, DSLOAD, ...
|
||||
data::Vector
|
||||
options::Dict
|
||||
end
|
||||
|
||||
mutable struct OutputRequest <: AbstractOutputRequest
|
||||
kind::Symbol # NODE, EL, SECTION, ...
|
||||
data::Vector
|
||||
options::Dict
|
||||
target::Symbol # PRINT, FILE
|
||||
end
|
||||
|
||||
### Utility functions to parse ABAQUS .inp file to data model
|
||||
|
||||
mutable struct Keyword
|
||||
name::String
|
||||
options::Vector{Union{String,Pair}}
|
||||
end
|
||||
|
||||
mutable struct AbaqusReaderState
|
||||
section::Union{Keyword,Nothing} # was Nullable{Keyword}
|
||||
material::Union{AbstractMaterial,Nothing} # was Nullable
|
||||
property::Union{AbstractProperty,Nothing} # was Nullable
|
||||
step::Union{AbstractStep,Nothing} # was Nullable
|
||||
data::Vector{String}
|
||||
end
|
||||
|
||||
function get_data(state::AbaqusReaderState)
|
||||
data = []
|
||||
for row in state.data
|
||||
row = strip(row, [' ', ','])
|
||||
col = split(row, ',')
|
||||
col = map(Meta.parse, col)
|
||||
push!(data, col)
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
function get_options(state::AbaqusReaderState)
|
||||
return Dict(state.section.options)
|
||||
end
|
||||
|
||||
function get_option(state::AbaqusReaderState, what::String)
|
||||
return get_options(state)[what]
|
||||
end
|
||||
|
||||
function length(state::AbaqusReaderState)
|
||||
return length(state.data)
|
||||
end
|
||||
|
||||
function is_comment(line)
|
||||
return startswith(line, "**")
|
||||
end
|
||||
|
||||
function is_keyword(line)
|
||||
return startswith(line, "*") && !is_comment(line)
|
||||
end
|
||||
|
||||
function parse_keyword(line; uppercase_keyword=true)
|
||||
args = split(line, ",")
|
||||
args = map(String, map(strip, args))
|
||||
keyword_name = strip(args[1], '*')
|
||||
if uppercase_keyword
|
||||
keyword_name = uppercase(keyword_name)
|
||||
end
|
||||
keyword = Keyword(keyword_name, [])
|
||||
for option in args[2:end]
|
||||
pair = map(String, split(option, "="))
|
||||
if uppercase_keyword
|
||||
pair[1] = uppercase(pair[1])
|
||||
end
|
||||
if length(pair) == 1
|
||||
push!(keyword.options, pair[1])
|
||||
elseif length(pair) == 2
|
||||
push!(keyword.options, pair[1] => pair[2])
|
||||
else
|
||||
error("Keyword failure: $line, $option, $pair")
|
||||
end
|
||||
end
|
||||
return keyword
|
||||
end
|
||||
|
||||
function is_new_section(line)
|
||||
is_keyword(line) || return false
|
||||
section = parse_keyword(line)
|
||||
is_abaqus_keyword_registered(section.name) || return false
|
||||
return true
|
||||
end
|
||||
|
||||
# open_section! is called right after keyword is found
|
||||
function open_section! end
|
||||
|
||||
# close_section! is called at the end or section or before new keyword
|
||||
function close_section! end
|
||||
|
||||
function maybe_close_section!(model, state)
|
||||
global close_section!
|
||||
isnull(state.section) && return
|
||||
section_name = state.section.name
|
||||
@debug("Close section: $section_name")
|
||||
args = Tuple{Model,AbaqusReaderState,Type{Val{Symbol(section_name)}}}
|
||||
if hasmethod(close_section!, args)
|
||||
close_section!(model, state, Val{Symbol(section_name)})
|
||||
else
|
||||
@debug("no close_section! found for $section_name")
|
||||
end
|
||||
state.section = nothing
|
||||
end
|
||||
|
||||
function maybe_open_section!(model, state)
|
||||
global open_section!
|
||||
section_name = state.section.name
|
||||
section_options = state.section.options
|
||||
@debug("New section: $section_name with options $section_options")
|
||||
args = Tuple{Model,AbaqusReaderState,Type{Val{Symbol(section_name)}}}
|
||||
if hasmethod(open_section!, args)
|
||||
open_section!(model, state, Val{Symbol(section_name)})
|
||||
else
|
||||
@warn("no open_section! found for $section_name")
|
||||
end
|
||||
end
|
||||
|
||||
function new_section!(model, state, line::String)
|
||||
maybe_close_section!(model, state)
|
||||
state.data = []
|
||||
state.section = parse_keyword(line)
|
||||
maybe_open_section!(model, state)
|
||||
end
|
||||
|
||||
function process_line!(model, state, line::String)
|
||||
if isnull(state.section)
|
||||
@debug("section = nothing! line = $line")
|
||||
return
|
||||
end
|
||||
if is_keyword(line)
|
||||
@warn("missing keyword? line = $line")
|
||||
# close section, this is probably keyword and collecting data should stop.
|
||||
maybe_close_section!(model, state)
|
||||
return
|
||||
end
|
||||
push!(state.data, line)
|
||||
end
|
||||
|
||||
"""
|
||||
abaqus_read_model(filename::String)
|
||||
|
||||
Read ABAQUS model from file. Include also boundary conditions, load steps
|
||||
and so on. If only mesh is needed, it's better to use `abaqus_read_mesh`
|
||||
insted.
|
||||
"""
|
||||
function abaqus_read_model(fn::String)
|
||||
|
||||
model_path = dirname(fn)
|
||||
model_name = first(splitext(basename(fn)))
|
||||
mesh = Mesh(open(parse_abaqus, fn))
|
||||
materials = Dict()
|
||||
model = Model(model_path, model_name, mesh, materials, [], [], [])
|
||||
|
||||
state = AbaqusReaderState(nothing, nothing, nothing, nothing, [])
|
||||
|
||||
fid = open(fn)
|
||||
for line in eachline(fid)
|
||||
line = convert(String, strip(line))
|
||||
is_comment(line) && continue
|
||||
if is_new_section(line)
|
||||
new_section!(model, state, line)
|
||||
else
|
||||
process_line!(model, state, line)
|
||||
end
|
||||
end
|
||||
close(fid)
|
||||
maybe_close_section!(model, state)
|
||||
|
||||
return model
|
||||
end
|
||||
|
||||
### Code to parse ABAQUS .inp to data model
|
||||
|
||||
# add here only keywords when planning to define open_section! and/or
|
||||
# close_section!, i.e. actually parse keyword to model. little bit of
|
||||
# magic is happening here, but after calling macro there is typealias
|
||||
# defined i.e. typealias SOLID_SECTION Type{Val{Symbol("SOLID_SECTION")}}
|
||||
# and also is_keyword_registered("SOLID SECTION") returns true after
|
||||
# registration, also notice underscoring
|
||||
|
||||
SOLID_SECTION = register_abaqus_keyword("SOLID SECTION")
|
||||
|
||||
MATERIAL = register_abaqus_keyword("MATERIAL")
|
||||
ELASTIC = register_abaqus_keyword("ELASTIC")
|
||||
|
||||
STEP = register_abaqus_keyword("STEP")
|
||||
STATIC = register_abaqus_keyword("STATIC")
|
||||
END_STEP = register_abaqus_keyword("END STEP")
|
||||
|
||||
BOUNDARY = register_abaqus_keyword("BOUNDARY")
|
||||
CLOAD = register_abaqus_keyword("CLOAD")
|
||||
DLOAD = register_abaqus_keyword("DLOAD")
|
||||
DSLOAD = register_abaqus_keyword("DSLOAD")
|
||||
const BOUNDARY_CONDITIONS = Union{BOUNDARY,CLOAD,DLOAD,DSLOAD}
|
||||
|
||||
NODE_PRINT = register_abaqus_keyword("NODE PRINT")
|
||||
EL_PRINT = register_abaqus_keyword("EL PRINT")
|
||||
SECTION_PRINT = register_abaqus_keyword("SECTION PRINT")
|
||||
const OUTPUT_REQUESTS = Union{NODE_PRINT,EL_PRINT,SECTION_PRINT}
|
||||
|
||||
## Properties
|
||||
|
||||
function open_section!(model, state, ::SOLID_SECTION)
|
||||
element_set = Symbol(get_option(state, "ELSET"))
|
||||
material_name = Symbol(get_option(state, "MATERIAL"))
|
||||
property = SolidSection(element_set, material_name)
|
||||
state.property = property
|
||||
push!(model.properties, property)
|
||||
end
|
||||
|
||||
function close_section!(model, state, ::SOLID_SECTION)
|
||||
name = model.name
|
||||
state.property = nothing
|
||||
end
|
||||
|
||||
## Materials
|
||||
|
||||
function open_section!(model, state, ::MATERIAL)
|
||||
material_name = Symbol(get_option(state, "NAME"))
|
||||
material = Material(material_name, [])
|
||||
state.material = material
|
||||
model.materials[material_name] = material
|
||||
end
|
||||
|
||||
function close_section!(model, state, ::ELASTIC)
|
||||
name = model.name
|
||||
@assert length(state) == 1
|
||||
E, nu = first(get_data(state))
|
||||
material_property = Elastic(E, nu)
|
||||
material = state.material
|
||||
push!(material.properties, material_property)
|
||||
end
|
||||
|
||||
## Steps
|
||||
|
||||
function open_section!(model, state, ::STEP)
|
||||
name = model.name
|
||||
step_ = Step(nothing, Vector(), Vector())
|
||||
state.step = step_
|
||||
push!(model.steps, step_)
|
||||
end
|
||||
|
||||
function open_section!(model, state, ::STATIC)
|
||||
name = model.name
|
||||
isnull(state.step) && error("*STATIC outside *STEP ?")
|
||||
state.step.kind = :STATIC
|
||||
end
|
||||
|
||||
function open_section!(model, state, ::END_STEP)
|
||||
name = model.name
|
||||
state.step = nothing
|
||||
end
|
||||
|
||||
## Steps -- boundary conditions
|
||||
|
||||
function close_section!(model, state, ::BOUNDARY_CONDITIONS)
|
||||
kind = Symbol(state.section.name)
|
||||
data = get_data(state)
|
||||
options = get_options(state)
|
||||
bc = BoundaryCondition(kind, data, options)
|
||||
if isnull(state.step)
|
||||
push!(model.boundary_conditions, bc)
|
||||
else
|
||||
step_ = state.step
|
||||
push!(step_.boundary_conditions, bc)
|
||||
end
|
||||
end
|
||||
|
||||
## Steps -- output requests
|
||||
|
||||
function close_section!(model, state, ::OUTPUT_REQUESTS)
|
||||
name = model.name
|
||||
kind, target = map(Meta.parse, split(state.section.name, " "))
|
||||
data = get_data(state)
|
||||
options = get_options(state)
|
||||
request = OutputRequest(Symbol(kind), data, options, Symbol(target))
|
||||
step_ = state.step
|
||||
push!(step_.output_requests, request)
|
||||
end
|
||||
Reference in New Issue
Block a user