From 300c7ba0ed144f4dffea60e0d77de9fcf2bdb8ad Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 01:54:10 +0200 Subject: [PATCH 1/8] add preparser to julia native --- src/abaqusreader.jl | 82 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/abaqusreader.jl b/src/abaqusreader.jl index 87c7d4b..fc90467 100644 --- a/src/abaqusreader.jl +++ b/src/abaqusreader.jl @@ -1,5 +1,87 @@ isabaquskeyword(l) = startswith(l, "*") +struct AbaqusKeyword + keyword::String + parameters::Vector + data::Vector{Vector} +end + +function parse_abaqus(s) + parsed = tryparse(Int, s) + !isnothing(parsed) && return parsed + parsed = tryparse(Float64, s) + !isnothing(parsed) && return parsed + startswith(s, '"') && endswith(s, '"') && return s[2:end-1] + return s +end + +# skips comment lines and supports continuation +# removes whitespace splits at comma and capitalizes everything not in quotes +# parses sections to Ints or Floats where possible +function readline_abaqus(f) + line = readline(f) + while startswith(line, "**") + line = strip(readline(f)) + end + while endswith(line, ",") + eof(f) && throw(InvalidFileContent("Reached end of file on line continuation")) + next = strip(readline(f)) + startswith(next, "**") && continue + line *= next + end + quoted_split = split(line, '"') # even indices -> quoted; odd inices not quoted + sections = String[""] + for (i, s) in pairs(quoted_split) + if isodd(i) + s = replace(s, " " => "") + s = uppercase(s) + s_split = split(s, ',') + sections[end] *= first(s_split) + append!(sections, s_split[2:end]) + else + sections[end] *= s + end + end + sections_parsed = Vector(undef, length(sections)) + for (i, s) in pairs(sections) + parsed = parse_abaqus(s) + if parsed != s || !contains(s, '=') + sections_parsed[i] = parsed + else contains(s, '=') + s_split = split(s, '=', limit=2) + sections_parsed[i] = first(s_split) => parse_abaqus(last(s_split)) + end + end + # skip over comments after reading the line + # to make eof work as expected + while true + mark(f) + startswith(readline(f), "**") || break + end + reset(f) + + return sections_parsed +end + +function read_keywords(filename) + keywords = AbaqusKeyword[] + + open(filename) do f + while !eof(f) + if peek(f, Char) == '*' + line = readline_abaqus(f) + keyword = AbaqusKeyword(line[1], line[2:end], Vector[]) + push!(keywords, keyword) + else + line = readline_abaqus(f) + push!(keywords[end].data, line) + end + end + end + + return keywords +end + function join_multiline_elementdata(element_data::Vector{<:AbstractString}) fixed_element_data = copy(element_data) i = 0 From 3720f65ebf2f9be08031f3018478d72c4cfa0d58 Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 02:09:15 +0200 Subject: [PATCH 2/8] fix error when first line is commented --- src/abaqusreader.jl | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/abaqusreader.jl b/src/abaqusreader.jl index fc90467..d202cec 100644 --- a/src/abaqusreader.jl +++ b/src/abaqusreader.jl @@ -15,6 +15,14 @@ function parse_abaqus(s) return s end +function skipcomments(f) + while true + mark(f) + startswith(readline(f), "**") || break + end + reset(f) +end + # skips comment lines and supports continuation # removes whitespace splits at comma and capitalizes everything not in quotes # parses sections to Ints or Floats where possible @@ -54,12 +62,7 @@ function readline_abaqus(f) end # skip over comments after reading the line # to make eof work as expected - while true - mark(f) - startswith(readline(f), "**") || break - end - reset(f) - + skipcomments(f) return sections_parsed end @@ -67,6 +70,7 @@ function read_keywords(filename) keywords = AbaqusKeyword[] open(filename) do f + skipcomments(f) while !eof(f) if peek(f, Char) == '*' line = readline_abaqus(f) From 6f4bf486f829f97760a856f744f5c1b2b60b9d4d Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 03:14:02 +0200 Subject: [PATCH 3/8] fix quoted numbers --- src/abaqusreader.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/abaqusreader.jl b/src/abaqusreader.jl index d202cec..b8cb1b7 100644 --- a/src/abaqusreader.jl +++ b/src/abaqusreader.jl @@ -47,7 +47,7 @@ function readline_abaqus(f) sections[end] *= first(s_split) append!(sections, s_split[2:end]) else - sections[end] *= s + sections[end] *= '"' * s * '"' end end sections_parsed = Vector(undef, length(sections)) From b7599850659460d0668d58449bb85648b239d13a Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 19:35:43 +0200 Subject: [PATCH 4/8] improve .inp file parser --- Project.toml | 2 + src/FerriteMeshParser.jl | 3 ++ src/abaqusreader.jl | 89 ++++++++++++++++++++++------------------ 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/Project.toml b/Project.toml index 4c9ce71..36dba13 100644 --- a/Project.toml +++ b/Project.toml @@ -5,10 +5,12 @@ version = "0.2.0" [deps] Ferrite = "c061ca5d-56c9-439f-9c0e-210fe06d3992" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" [compat] Aqua = "0.6" Ferrite = "0.3, 1" +OrderedCollections = "1" julia = "1" [extras] diff --git a/src/FerriteMeshParser.jl b/src/FerriteMeshParser.jl index c5801dd..acf1a92 100644 --- a/src/FerriteMeshParser.jl +++ b/src/FerriteMeshParser.jl @@ -3,6 +3,9 @@ using Ferrite: Ferrite, Grid, Node, Vec, getcells, getnodes, getcoordinates, getncells +using OrderedCollections: + LittleDict + # Convenience when debugging const DEBUG_PARSE = false diff --git a/src/abaqusreader.jl b/src/abaqusreader.jl index b8cb1b7..ffeafde 100644 --- a/src/abaqusreader.jl +++ b/src/abaqusreader.jl @@ -1,11 +1,20 @@ isabaquskeyword(l) = startswith(l, "*") -struct AbaqusKeyword +struct AbaqusInpBlock keyword::String - parameters::Vector + parameters::LittleDict{String} data::Vector{Vector} end +struct AbaqusInp + blocks::Vector{AbaqusInpBlock} +end + +inpblocks(inp::AbaqusInp) = inp.blocks +keyword(datablock::AbaqusInpBlock) = datablock.keyword +datalines(datablock::AbaqusInpBlock) = datablock.data +parameters(datablock::AbaqusInpBlock) = datablock.parameters + function parse_abaqus(s) parsed = tryparse(Int, s) !isnothing(parsed) && return parsed @@ -15,75 +24,77 @@ function parse_abaqus(s) return s end -function skipcomments(f) - while true - mark(f) - startswith(readline(f), "**") || break - end - reset(f) -end - -# skips comment lines and supports continuation -# removes whitespace splits at comma and capitalizes everything not in quotes -# parses sections to Ints or Floats where possible -function readline_abaqus(f) +# skips comment lines and supports line continuation +# removes leading and trailing whitespace +function eatline_abaqus(f) line = readline(f) while startswith(line, "**") line = strip(readline(f)) + isodd(count('"', line)) && throw(InvalidFileContent("Quoted strings cannot span multiple lines!")) end while endswith(line, ",") - eof(f) && throw(InvalidFileContent("Reached end of file on line continuation")) + eof(f) && throw(InvalidFileContent("Reached end of file on line continuation!")) next = strip(readline(f)) + isodd(count('"', line)) && throw(InvalidFileContent("Quoted strings cannot span multiple lines!")) startswith(next, "**") && continue line *= next end + return line +end + +# removes whitespace, splits at comma, and uppercases everything +# while reserving quoted strings +function clean_and_split(line) quoted_split = split(line, '"') # even indices -> quoted; odd inices not quoted sections = String[""] for (i, s) in pairs(quoted_split) if isodd(i) s = replace(s, " " => "") s = uppercase(s) - s_split = split(s, ',') + s_split = split(s, ',', keepempty=true) sections[end] *= first(s_split) append!(sections, s_split[2:end]) else sections[end] *= '"' * s * '"' end end - sections_parsed = Vector(undef, length(sections)) - for (i, s) in pairs(sections) - parsed = parse_abaqus(s) - if parsed != s || !contains(s, '=') - sections_parsed[i] = parsed - else contains(s, '=') - s_split = split(s, '=', limit=2) - sections_parsed[i] = first(s_split) => parse_abaqus(last(s_split)) + return sections +end + +function parsekeywordline(line) + sections = clean_and_split(line) + parameters = LittleDict{String, Any}() + for parameter in sections[2:end] + if contains(parameter, '=') + (key, value) = split(parameter, '=') + parameters[key] = parse_abaqus(value) + else + parameters[parameter] = nothing end end - # skip over comments after reading the line - # to make eof work as expected - skipcomments(f) - return sections_parsed + return AbaqusInpBlock(sections[1], parameters, Vector[]) end -function read_keywords(filename) - keywords = AbaqusKeyword[] +function parsedataline(line) + sections = clean_and_split(line) + return parse_abaqus.(sections) +end +function parse_abaqus_inp(filename) + keywords = AbaqusInpBlock[] open(filename) do f - skipcomments(f) while !eof(f) - if peek(f, Char) == '*' - line = readline_abaqus(f) - keyword = AbaqusKeyword(line[1], line[2:end], Vector[]) - push!(keywords, keyword) + line = eatline_abaqus(f) + line == "" && continue + if isabaquskeyword(line) + push!(keywords, parsekeywordline(line)) else - line = readline_abaqus(f) - push!(keywords[end].data, line) + isempty(keywords) && throw(InvalidFileContent("The first non-comment line must be a keyword line!")) + push!(keywords[end].data, parsedataline(line)) end end end - - return keywords + return AbaqusInp(keywords) end function join_multiline_elementdata(element_data::Vector{<:AbstractString}) From f341543bbbd21a35d2ae292b3bef6dad4939811d Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 19:36:27 +0200 Subject: [PATCH 5/8] use .inp file parser in read_mesh --- src/abaqusreader.jl | 155 +++++++++++++------------------------------- 1 file changed, 46 insertions(+), 109 deletions(-) diff --git a/src/abaqusreader.jl b/src/abaqusreader.jl index ffeafde..25136c5 100644 --- a/src/abaqusreader.jl +++ b/src/abaqusreader.jl @@ -97,77 +97,37 @@ function parse_abaqus_inp(filename) return AbaqusInp(keywords) end -function join_multiline_elementdata(element_data::Vector{<:AbstractString}) - fixed_element_data = copy(element_data) - i = 0 - i_fixed = 0 - nrows = length(element_data) - while i < nrows - i += 1 - length(element_data[i])==0 && continue - i_fixed += 1 - fixed_element_data[i_fixed] = element_data[i] - while endswith(fixed_element_data[i_fixed], ',') && i < nrows - i += 1 - fixed_element_data[i_fixed] = fixed_element_data[i_fixed]*element_data[i] - end - end - return fixed_element_data[1:i_fixed] -end - -function read_abaqus_nodes!(f, node_numbers::Vector{Int}, coord_vec::Vector{Float64}) - local coords - node_data = readlinesuntil(f; stopsign='*') - for nodeline in node_data - node = split(nodeline, ',', keepempty = false) - length(node) == 0 && continue - push!(node_numbers, parse(Int, node[1])) - coords = parse.(Float64, node[2:end]) - append!(coord_vec, coords) +function read_abaqus_nodes!(inpblock::AbaqusInpBlock, node_numbers, coord_vec) + for dataline in datalines(inpblock) + push!(node_numbers, dataline[1]) + append!(coord_vec, dataline[2:end]) end - return length(coords) + return length(datalines(inpblock)[1]) - 1 end -function read_abaqus_elements!(f, topology_vectors, element_number_vectors, element_type::AbstractString, element_set="", element_sets=nothing) - if !haskey(topology_vectors, element_type) - topology_vectors[element_type] = Int[] - element_number_vectors[element_type] = Int[] +function read_abaqus_elements!(inpblock::AbaqusInpBlock, topology_vectors, element_number_vectors, element_sets) + topology_vec = get!(topology_vectors, parameters(inpblock)["TYPE"], Int[]) + element_numbers = get!(element_number_vectors, parameters(inpblock)["TYPE"], Int[]) + element_numers_new = Int[] + for dataline in datalines(inpblock) + push!(element_numers_new, dataline[1]) + append!(topology_vec, dataline[2:end]) end - topology_vec = topology_vectors[element_type] - element_numbers = element_number_vectors[element_type] - element_numbers_new = Int[] - element_data_raw = readlinesuntil(f; stopsign='*') - element_data = join_multiline_elementdata(element_data_raw) - for elementline in element_data - element = split(elementline, ',', keepempty = false) - length(element) == 0 && continue - n = parse(Int, element[1]) - push!(element_numbers_new, n) - vertices = [parse(Int, element[i]) for i in 2:length(element)] - append!(topology_vec, vertices) - end - append!(element_numbers, element_numbers_new) - if element_set != "" - element_sets[element_set] = copy(element_numbers_new) + append!(element_numbers, element_numers_new) + elset = get(parameters(inpblock), "ELSET", nothing) + if !isnothing(elset) + element_sets[elset] = element_numers_new end end -function read_abaqus_set!(f, sets, setname::AbstractString) - if endswith(setname, "generate") - split_line = split(strip(eat_line(f)), ",", keepempty = false) - start, stop, step = [parse(Int, x) for x in split_line] +function read_abaqus_set!(inpblock::AbaqusInpBlock, sets) + if haskey(parameters(inpblock), "GENERATE") + start, stop, step = only(datalines(inpblock)) indices = collect(start:step:stop) - setname = split(setname, [','])[1] else - data = readlinesuntil(f; stopsign='*') - indices = Int[] - for line in data - indices_str = split(line, ',', keepempty = false) - for v in indices_str - push!(indices, parse(Int, v)) - end - end + indices = reduce(vcat, datalines(inpblock)) end + setname = parameters(inpblock)[keyword(inpblock)[2:end]] sets[setname] = indices end @@ -184,54 +144,31 @@ function read_mesh(filename, ::AbaqusMeshFormat) part_counter = 0 instance_counter = 0 - open(filename) do f - while !eof(f) - header = eat_line(f) - if header == "" - continue - end - DEBUG_PARSE && println("H: $header") - if startswith(lowercase(header), "*node") - DEBUG_PARSE && println("Reading nodes") - read_dim = read_abaqus_nodes!(f, node_numbers, coord_vec) - dim == 0 && (dim = read_dim) # Set dim if not yet set - read_dim != dim && throw(DimensionMismatch("Not allowed to mix nodes in different dimensions")) - elseif startswith(lowercase(header), "*element") - if ((m = match(r"\*Element, type=(.*), ELSET=(.*)"i, header)) !== nothing) - DEBUG_PARSE && println("Reading elements with elset") - read_abaqus_elements!(f, topology_vectors, element_number_vectors, m.captures[1], m.captures[2], elementsets) - elseif ((m = match(r"\*Element, type=(.*)"i, header)) !== nothing) - DEBUG_PARSE && println("Reading elements without elset") - read_abaqus_elements!(f, topology_vectors, element_number_vectors, m.captures[1]) - end - elseif ((m = match(r"\*Elset, elset=(.*)"i, header)) !== nothing) - DEBUG_PARSE && println("Reading elementset") - read_abaqus_set!(f, elementsets, m.captures[1]) - elseif ((m = match(r"\*Nset, nset=(.*)"i, header)) !== nothing) - DEBUG_PARSE && println("Reading nodeset") - read_abaqus_set!(f, nodesets, m.captures[1]) - elseif startswith(lowercase(header), "*part") - DEBUG_PARSE && println("Increment part counter") - part_counter += 1 - elseif startswith(lowercase(header), "*instance") - DEBUG_PARSE && println("Increment instance counter") - instance_counter += 1 - discardlinesuntil(f, stopsign='*') # Instances contain translations, or start with *Node if independent mesh - elseif isabaquskeyword(header) # Ignore unused keywords - DEBUG_PARSE && println("Discarding keyword content") - discardlinesuntil(f, stopsign='*') - else - eof(f) && break # discardlinesuntil will stop at eof, and last line read again and incorrectly considered a "header" - throw(InvalidFileContent("Unknown header, \"$header\", in file \"$filename\". Could also indicate an incomplete file")) - end + inp = parse_abaqus_inp(filename) + + for inpblock in inpblocks(inp) + if keyword(inpblock) == "*NODE" + read_dim = read_abaqus_nodes!(inpblock, node_numbers, coord_vec) + dim == 0 && (dim = read_dim) # Set dim if not yet set + read_dim != dim && throw(DimensionMismatch("Not allowed to mix nodes in different dimensions")) + elseif keyword(inpblock) == "*ELEMENT" + read_abaqus_elements!(inpblock, topology_vectors, element_number_vectors, elementsets) + elseif keyword(inpblock) == "*ELSET" + read_abaqus_set!(inpblock, elementsets) + elseif keyword(inpblock) == "*NSET" + read_abaqus_set!(inpblock, nodesets) + elseif keyword(inpblock) == "*PART" + part_counter += 1 + elseif keyword(inpblock) == "*INSTANCE" + instance_counter += 1 end + end - if part_counter > 1 || instance_counter > 1 - msg = "Multiple parts or instances are not supported\n" - msg *= "Tip: If you want a single grid, merge parts and differentiated by Abaqus sets\n" - msg *= " If you want multiple grids, split into multiple input files" - throw(InvalidFileContent(msg)) - end + if part_counter > 1 || instance_counter > 1 + msg = "Multiple parts or instances are not supported\n" + msg *= "Tip: If you want a single grid, merge parts and differentiated by Abaqus sets\n" + msg *= " If you want multiple grids, split into multiple input files" + throw(InvalidFileContent(msg)) end elements = Dict{String, RawElements}() @@ -239,9 +176,9 @@ function read_mesh(filename, ::AbaqusMeshFormat) topology_vec = topology_vectors[element_type] element_numbers = element_number_vectors[element_type] n_elements = length(element_numbers) - topology_matrix = reshape(topology_vec, length(topology_vec) ÷ n_elements, n_elements) + topology_matrix = reshape(topology_vec, :, n_elements) elements[element_type] = RawElements(numbers=element_numbers, topology=topology_matrix) end - nodes = RawNodes(numbers=node_numbers, coordinates=reshape(coord_vec, dim, length(coord_vec) ÷ dim)) + nodes = RawNodes(numbers=node_numbers, coordinates=reshape(coord_vec, dim, :)) return RawMesh(elements=elements, nodes=nodes, nodesets=nodesets, elementsets=elementsets) end \ No newline at end of file From 9ed95b9f8d749a186ed12a109ab1229d86a431ca Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 19:37:09 +0200 Subject: [PATCH 6/8] adjust tests; uppercase setnames --- test/runtests.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 97344a5..a0e7ffb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -72,9 +72,9 @@ end @testset "facet set generation" begin filename = gettestfile("compact_tension.inp") grid = get_ferrite_grid(filename) - face_set = create_facetset(grid, getnodeset(grid, "Hole")) - @test getfacetset(grid, "Hole") == face_set - @test face_set == create_facetset(grid, getnodeset(grid, "Hole"), getcellset(grid, "Hole")) # Test that including cells doesn't change the created sets + face_set = create_facetset(grid, getnodeset(grid, "HOLE")) + @test getfacetset(grid, "HOLE") == face_set + @test face_set == create_facetset(grid, getnodeset(grid, "HOLE"), getcellset(grid, "HOLE")) # Test that including cells doesn't change the created sets end @testset "exceptions" begin @@ -100,7 +100,7 @@ end @test contains(String(take!(io)), test_string) grid = get_ferrite_grid(gettestfile("compact_tension.inp")) - nset = getnodeset(grid, "Hole") + nset = getnodeset(grid, "HOLE") @test_throws ErrorException("create_faceset is no longer supported, use create_facetset instead") create_faceset(grid, nset) end @@ -131,12 +131,12 @@ end @testset "sets" begin filename = gettestfile("generated_set.inp") grid = get_ferrite_grid(filename) - @test getcellset(grid, "lower") == Set(1:8) - @test getnodeset(grid, "lower") == Set((1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 20, 21, 22)) + @test getcellset(grid, "LOWER") == Set(1:8) + @test getnodeset(grid, "LOWER") == Set((1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 20, 21, 22)) filename = gettestfile("2D_UnitArea_Quadratic.inp") grid = get_ferrite_grid(filename) - @test getcellset(grid, "mysetname") == Set((1,)) + @test getcellset(grid, "MYSETNAME") == Set((1,)) end From e7076bc5a1730134b6a39874bda1ed00121f378b Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 20:41:50 +0200 Subject: [PATCH 7/8] debug output --- src/abaqusreader.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/abaqusreader.jl b/src/abaqusreader.jl index 25136c5..6ca3850 100644 --- a/src/abaqusreader.jl +++ b/src/abaqusreader.jl @@ -39,6 +39,7 @@ function eatline_abaqus(f) startswith(next, "**") && continue line *= next end + DEBUG_PARSE && println("Ate: " * line) return line end @@ -148,18 +149,24 @@ function read_mesh(filename, ::AbaqusMeshFormat) for inpblock in inpblocks(inp) if keyword(inpblock) == "*NODE" + DEBUG_PARSE && println("Reading nodes") read_dim = read_abaqus_nodes!(inpblock, node_numbers, coord_vec) dim == 0 && (dim = read_dim) # Set dim if not yet set read_dim != dim && throw(DimensionMismatch("Not allowed to mix nodes in different dimensions")) elseif keyword(inpblock) == "*ELEMENT" + DEBUG_PARSE && println("Reading elements") read_abaqus_elements!(inpblock, topology_vectors, element_number_vectors, elementsets) elseif keyword(inpblock) == "*ELSET" + DEBUG_PARSE && println("Reading elementset") read_abaqus_set!(inpblock, elementsets) elseif keyword(inpblock) == "*NSET" + DEBUG_PARSE && println("Reading nodeset") read_abaqus_set!(inpblock, nodesets) elseif keyword(inpblock) == "*PART" + DEBUG_PARSE && println("Increment part counter") part_counter += 1 elseif keyword(inpblock) == "*INSTANCE" + DEBUG_PARSE && println("Increment instance counter") instance_counter += 1 end end From fed726fe4629cf16c2b305f8864140f31b5d68e9 Mon Sep 17 00:00:00 2001 From: Joroks Date: Sun, 14 Jul 2024 20:42:59 +0200 Subject: [PATCH 8/8] fixup --- src/abaqusreader.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/abaqusreader.jl b/src/abaqusreader.jl index 6ca3850..cac0b41 100644 --- a/src/abaqusreader.jl +++ b/src/abaqusreader.jl @@ -37,6 +37,7 @@ function eatline_abaqus(f) next = strip(readline(f)) isodd(count('"', line)) && throw(InvalidFileContent("Quoted strings cannot span multiple lines!")) startswith(next, "**") && continue + startswith(next, '*') && throw(InvalidFileContent("Ran into new keyword during line continuation")) line *= next end DEBUG_PARSE && println("Ate: " * line)