diff --git a/packages/seacas/applications/ejoin/EJ_SystemInterface.C b/packages/seacas/applications/ejoin/EJ_SystemInterface.C index 25d0b6e71f..8b7452795e 100644 --- a/packages/seacas/applications/ejoin/EJ_SystemInterface.C +++ b/packages/seacas/applications/ejoin/EJ_SystemInterface.C @@ -55,7 +55,7 @@ void SystemInterface::enroll_options() options_.enroll("version", GetLongOption::NoValue, "Print version and exit", nullptr); options_.enroll("output", GetLongOption::MandatoryValue, "Name of output file to create", - "ejoin-out.e"); + "ejoin-out.e", nullptr, true); options_.enroll( "extract_blocks", GetLongOption::MandatoryValue, @@ -75,6 +75,19 @@ void SystemInterface::enroll_options() "\t\t\t '-omit_blocks p1:1:3:4,p2:2:3:4,p5:8'", nullptr); + options_.enroll("omit_assemblies", GetLongOption::MandatoryValue, + "If no value, then don't transfer any assemblies to output file.\n" + "\t\tIf just p#,p#,... specified, then omit assemblies on specified parts\n" + "\t\tIf p#:id1:id2,p#:id2,id4... then omit the assemblies with the specified\n" + "\t\tid in the specified parts.", + nullptr, "ALL"); + + options_.enroll( + "omit_part_assemblies", GetLongOption::NoValue, + "Do not create an assembly for each input part containing the blocks in that part.\n" + "\t\tDefault is to create the part assemblies.", + nullptr); + options_.enroll("omit_nodesets", GetLongOption::OptionalValue, "If no value, then don't transfer any nodesets to output file.\n" "\t\tIf just p#,p#,... specified, then omit sets on specified parts\n" @@ -94,7 +107,7 @@ void SystemInterface::enroll_options() "\t\tcreate a nodeset containing the nodes of that part\n" "\t\tand output the nodal fields as nodeset fields instead of nodal fields.\n" "\t\tFormat is comma-separated list of parts (1-based), or ALL", - nullptr); + nullptr, nullptr, true); options_.enroll("match_node_ids", GetLongOption::NoValue, "Combine nodes if their global ids match.", nullptr); @@ -115,19 +128,20 @@ void SystemInterface::enroll_options() #endif options_.enroll("tolerance", GetLongOption::MandatoryValue, - "Maximum distance between two nodes to be considered colocated.", nullptr); + "Maximum distance between two nodes to be considered colocated.", nullptr, + nullptr, true); options_.enroll( "block_prefix", GetLongOption::MandatoryValue, "Prefix used on the input block names of second and subsequent meshes to make them\n" - "\t\tunique. Default is 'p'. Example: block1, p1_block1, p2_block1.", + "\t\tunique. Default is 'p'. Example: block1, p2_block1, p3_block1.", "p"); options_.enroll("offset", GetLongOption::MandatoryValue, "Comma-separated x,y,z offset for coordinates of second and subsequent meshes.\n" "\t\tThe offset will be multiplied by the part number-1 so:\n" "\t\tP1: no offset; P2: 1x, 1y, 1z; P3: 2x, 2y, 2z; P(n+1): nx, ny, nz", - nullptr); + nullptr, nullptr, true); options_.enroll("steps", GetLongOption::MandatoryValue, "Specify subset of timesteps to transfer to output file.\n" @@ -171,7 +185,7 @@ void SystemInterface::enroll_options() "Ignore the element id maps on the input database and just use a 1..#elements id " "map on output.\n" "\t\tMuch faster for large models if do not need specific element global ids", - nullptr); + nullptr, nullptr, true); options_.enroll("netcdf4", GetLongOption::NoValue, "Create output database using the HDF5-based " @@ -249,6 +263,7 @@ bool SystemInterface::parse_options(int argc, char **argv) blockInclusions_.resize(part_count); nsetOmissions_.resize(part_count); ssetOmissions_.resize(part_count); + assemblyOmissions_.resize(part_count); // Get options from environment variable also... char *options = getenv("EJOIN_OPTIONS"); @@ -303,6 +318,17 @@ bool SystemInterface::parse_options(int argc, char **argv) } } + { + const char *temp = options_.retrieve("omit_assemblies"); + if (temp != nullptr) { + parse_omissions(temp, &assemblyOmissions_, "assembly", true); + } + } + + if (options_.retrieve("omit_part_assemblies") != nullptr) { + createAssemblies_ = false; + } + { const char *temp = options_.retrieve("extract_blocks"); if (temp != nullptr) { diff --git a/packages/seacas/applications/ejoin/EJ_SystemInterface.h b/packages/seacas/applications/ejoin/EJ_SystemInterface.h index e0fff14fd0..a8bf2120d9 100644 --- a/packages/seacas/applications/ejoin/EJ_SystemInterface.h +++ b/packages/seacas/applications/ejoin/EJ_SystemInterface.h @@ -1,4 +1,4 @@ -// Copyright(C) 1999-2022 National Technology & Engineering Solutions +// Copyright(C) 1999-, 20232023 National Technology & Engineering Solutions // of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with // NTESS, the U.S. Government retains certain rights in this software. // @@ -29,10 +29,12 @@ class SystemInterface bool omit_nodesets() const { return omitNodesets_; } bool omit_sidesets() const { return omitSidesets_; } bool convert_nodes_to_nodesets(int part_number) const; - bool disable_field_recognition() const { return disableFieldRecognition_; } - bool ints64bit() const { return ints64bit_; } - bool use_netcdf4() const { return useNetcdf4_; } - bool ignore_element_ids() const { return ignoreElementIds_; } + bool create_assemblies() const { return createAssemblies_; } + + bool disable_field_recognition() const { return disableFieldRecognition_; } + bool ints64bit() const { return ints64bit_; } + bool use_netcdf4() const { return useNetcdf4_; } + bool ignore_element_ids() const { return ignoreElementIds_; } int compression_level() const { return compressionLevel_; } bool zlib() const { return zlib_; } @@ -53,6 +55,7 @@ class SystemInterface const Omissions &block_omissions() const { return blockOmissions_; } const Omissions &nset_omissions() const { return nsetOmissions_; } const Omissions &sset_omissions() const { return ssetOmissions_; } + const Omissions &assembly_omissions() const { return assemblyOmissions_; } const std::string &block_prefix() const { return blockPrefix_; } @@ -96,6 +99,7 @@ class SystemInterface bool ignoreElementIds_{false}; bool zlib_{true}; bool szip_{false}; + bool createAssemblies_{true}; std::string blockPrefix_{"p"}; @@ -104,6 +108,7 @@ class SystemInterface Omissions blockInclusions_; Omissions blockOmissions_; + Omissions assemblyOmissions_; Omissions nsetOmissions_; Omissions ssetOmissions_; diff --git a/packages/seacas/applications/ejoin/EJ_Version.h b/packages/seacas/applications/ejoin/EJ_Version.h index 56831f6705..02547db24a 100644 --- a/packages/seacas/applications/ejoin/EJ_Version.h +++ b/packages/seacas/applications/ejoin/EJ_Version.h @@ -9,6 +9,6 @@ static std::array qainfo{ "ejoin", - "2022/01/24", - "1.5.7", + "2023/05/26", + "1.6.0", }; diff --git a/packages/seacas/applications/ejoin/EJoin.C b/packages/seacas/applications/ejoin/EJoin.C index debd9f0640..32b91d26fe 100644 --- a/packages/seacas/applications/ejoin/EJoin.C +++ b/packages/seacas/applications/ejoin/EJoin.C @@ -80,6 +80,7 @@ namespace { const std::vector &local_node_map, SystemInterface &interFace); void process_nset_omissions(RegionVector &part_mesh, const Omissions &omit); void process_sset_omissions(RegionVector &part_mesh, const Omissions &omit); + void process_assembly_omissions(RegionVector &part_mesh, const Omissions &omit); int count_omissions(Ioss::Region *region) { @@ -123,7 +124,9 @@ namespace { } // namespace namespace { - void transfer_elementblock(Ioss::Region ®ion, Ioss::Region &output_region, bool debug); + void transfer_elementblock(Ioss::Region ®ion, Ioss::Region &output_region, + bool create_assemblies, bool debug); + void transfer_assembly(Ioss::Region ®ion, Ioss::Region &output_region, bool debug); void transfer_nodesets(Ioss::Region ®ion, Ioss::Region &output_region, bool debug); void transfer_sidesets(Ioss::Region ®ion, Ioss::Region &output_region, bool debug); void create_nodal_nodeset(Ioss::Region ®ion, Ioss::Region &output_region, bool debug); @@ -240,6 +243,7 @@ int main(int argc, char *argv[]) process_nset_omissions(part_mesh, interFace.nset_omissions()); process_sset_omissions(part_mesh, interFace.sset_omissions()); + process_assembly_omissions(part_mesh, interFace.assembly_omissions()); double time = 0.0; @@ -404,7 +408,7 @@ double ejoin(SystemInterface &interFace, std::vector &part_mesh, // Add element blocks, nodesets, sidesets for (size_t p = 0; p < part_count; p++) { - transfer_elementblock(*part_mesh[p], output_region, false); + transfer_elementblock(*part_mesh[p], output_region, interFace.create_assemblies(), false); if (interFace.convert_nodes_to_nodesets(p + 1)) { create_nodal_nodeset(*part_mesh[p], output_region, false); } @@ -414,6 +418,7 @@ double ejoin(SystemInterface &interFace, std::vector &part_mesh, if (!interFace.omit_sidesets()) { transfer_sidesets(*part_mesh[p], output_region, false); } + transfer_assembly(*part_mesh[p], output_region, false); } if (!interFace.information_record_parts().empty()) { @@ -565,11 +570,19 @@ namespace { return omitted; } - void transfer_elementblock(Ioss::Region ®ion, Ioss::Region &output_region, bool debug) + void transfer_elementblock(Ioss::Region ®ion, Ioss::Region &output_region, + bool create_assemblies, bool debug) { static int used_blocks = 0; const std::string &prefix = region.name(); + Ioss::Assembly *assem = nullptr; + if (create_assemblies) { + assem = new Ioss::Assembly(output_region.get_database(), region.name()); + output_region.add(assem); + assem->property_add(Ioss::Property("name_in_output", region.name())); + } + const Ioss::ElementBlockContainer &ebs = region.get_element_blocks(); for (const auto &eb : ebs) { if (!entity_is_omitted(eb)) { @@ -581,6 +594,8 @@ namespace { exit(EXIT_FAILURE); } } + eb->property_add(Ioss::Property("name_in_output", name)); + if (debug) { fmt::print(stderr, "{}, ", name); } @@ -601,6 +616,54 @@ namespace { // Set the new property ebn->property_add(Ioss::Property("original_topology_type", oes)); } + + if (create_assemblies) { + assem->add(ebn); + } + } + } + } + } + + void transfer_assembly(Ioss::Region ®ion, Ioss::Region &output_region, bool debug) + { + // All assemblies on the input parts will be transferred to the output mesh + // Possibly renamed if a name conflict + // Also need to update names of the entities in the assembly since they were possibly + // renamed on the output... + // TODO: handle renamed nested assemblies... + const std::string &prefix = region.name(); + + const Ioss::AssemblyContainer &assems = region.get_assemblies(); + for (const auto &as : assems) { + if (!entity_is_omitted(as)) { + std::string name = as->name(); + if (output_region.get_assembly(name) != nullptr) { + name = prefix + "_" + as->name(); + if (output_region.get_assembly(name) != nullptr) { + fmt::print(stderr, "ERROR: Duplicate assemblies named '{}'\n", name); + exit(EXIT_FAILURE); + } + } + as->property_add(Ioss::Property("name_in_output", name)); + + if (debug) { + fmt::print(stderr, "{}, ", name); + } + size_t num_members = as->entity_count(); + if (num_members > 0) { + auto member_type = as->get_member_type(); + auto asn = new Ioss::Assembly(output_region.get_database(), name); + output_region.add(asn); + + const auto &members = as->get_members(); + for (const auto &member : members) { + auto output_name = member->get_property("name_in_output").get_string(); + + auto *entity = output_region.get_entity(output_name, member_type); + assert(entity != nullptr); + asn->add(entity); + } } } } @@ -621,6 +684,7 @@ namespace { exit(EXIT_FAILURE); } } + fs->property_add(Ioss::Property("name_in_output", name)); if (debug) { fmt::print(stderr, "{}, ", name); } @@ -633,6 +697,7 @@ namespace { if (debug) { fmt::print(stderr, "{}, ", fbname); } + fb->property_add(Ioss::Property("name_in_output", fbname)); std::string fbtype = fb->topology()->name(); std::string partype = fb->parent_element_topology()->name(); size_t num_side = fb->entity_count(); @@ -747,6 +812,7 @@ namespace { exit(EXIT_FAILURE); } } + ns->property_add(Ioss::Property("name_in_output", name)); if (debug) { fmt::print(stderr, "{}, ", name); } @@ -1493,4 +1559,28 @@ namespace { } } } + + void process_assembly_omissions(RegionVector &part_mesh, const Omissions &omit) + { + size_t part_count = part_mesh.size(); + for (size_t p = 0; p < part_count; p++) { + if (!omit[p].empty()) { + // Get the assemblies for this part and set the "omitted" property on the assembly + if (omit[p][0] == "ALL") { + const auto &assemblies = part_mesh[p]->get_assemblies(); + for (auto &as : assemblies) { + as->property_add(Ioss::Property(std::string("omitted"), 1)); + } + } + else { + for (const auto &omitted : omit[p]) { + auto *as = part_mesh[p]->get_assembly(omitted); + if (as != nullptr) { + as->property_add(Ioss::Property(std::string("omitted"), 1)); + } + } + } + } + } + } } // namespace diff --git a/packages/seacas/libraries/exodus/include/exodusII.h b/packages/seacas/libraries/exodus/include/exodusII.h index 33bcca575a..92ea5736a2 100644 --- a/packages/seacas/libraries/exodus/include/exodusII.h +++ b/packages/seacas/libraries/exodus/include/exodusII.h @@ -52,12 +52,12 @@ #endif /* EXODUS version number */ -#define EXODUS_VERSION "8.20" +#define EXODUS_VERSION "8.21" #define EXODUS_VERSION_MAJOR 8 -#define EXODUS_VERSION_MINOR 20 +#define EXODUS_VERSION_MINOR 21 #define EXODUS_RELEASE_DATE "May 1, 2023" -#define EX_API_VERS 8.20f +#define EX_API_VERS 8.21f #define EX_API_VERS_NODOT (100 * EXODUS_VERSION_MAJOR + EXODUS_VERSION_MINOR) #define EX_VERS EX_API_VERS diff --git a/packages/seacas/libraries/exodus/src/ex_put_name.c b/packages/seacas/libraries/exodus/src/ex_put_name.c index e860663e94..9276f6ce8c 100644 --- a/packages/seacas/libraries/exodus/src/ex_put_name.c +++ b/packages/seacas/libraries/exodus/src/ex_put_name.c @@ -1,5 +1,5 @@ /* - * Copyright(C) 1999-2020, 2022 National Technology & Engineering Solutions + * Copyright(C) 1999-2020, 2022, 2023 National Technology & Engineering Solutions * of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with * NTESS, the U.S. Government retains certain rights in this software. * @@ -28,6 +28,51 @@ #include "exodusII.h" // for ex_err, etc #include "exodusII_int.h" // for EX_FATAL, ex__id_lkup, etc +int ex__put_assembly_name(int exoid, ex_entity_type obj_type, ex_entity_id entity_id, + const char *name) +{ + /* Internal function to handle renaming of an existing assembly. + Note that assembly must exist or an error will be returned. + */ + /* See if an assembly with this id has already been defined or exists on file... */ + int entlst_id = 0; + char errmsg[MAX_ERR_LENGTH]; + if (nc_inq_varid(exoid, VAR_ENTITY_ASSEMBLY(entity_id), &entlst_id) == NC_NOERR) { + int status; + if ((status = nc_redef(exoid)) != NC_NOERR) { + snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: failed to put file id %d into define mode", exoid); + ex_err_fn(exoid, __func__, errmsg, status); + EX_FUNC_LEAVE(EX_FATAL); + } + size_t length = strlen(name) + 1; + if ((status = nc_put_att_text(exoid, entlst_id, EX_ATTRIBUTE_NAME, length, name)) != NC_NOERR) { + snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: failed to store assembly name %s in file id %d", + name, exoid); + ex_err_fn(exoid, __func__, errmsg, status); + if ((status = ex__leavedef(exoid, __func__)) != NC_NOERR) { + snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: failed to exit define mode in file id %d", exoid); + ex_err_fn(exoid, __func__, errmsg, status); + EX_FUNC_LEAVE(EX_FATAL); + } + EX_FUNC_LEAVE(EX_FATAL); + } + /* Update the maximum_name_length attribute on the file. */ + ex__update_max_name_length(exoid, length - 1); + if ((status = ex__leavedef(exoid, __func__)) != NC_NOERR) { + snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: failed to exit define mode in file id %d", exoid); + ex_err_fn(exoid, __func__, errmsg, status); + EX_FUNC_LEAVE(EX_FATAL); + } + EX_FUNC_LEAVE(EX_NOERR); + } + + /* Assembly not found... */ + snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: %s id %" PRId64 " not found in file id %d", + ex_name_of_object(obj_type), entity_id, exoid); + ex_err_fn(exoid, __func__, errmsg, EX_LOOKUPFAIL); + EX_FUNC_LEAVE(EX_FATAL); +} + /*! * writes the name of the specified entity to the database. The entity * with id `entity_id` must exist before calling ex_put_name(). @@ -51,12 +96,7 @@ int ex_put_name(int exoid, ex_entity_type obj_type, ex_entity_id entity_id, cons } switch (obj_type) { - case EX_ASSEMBLY: - snprintf(errmsg, MAX_ERR_LENGTH, - "ERROR: Assembly name is written using `ex_put_assembly()` function"); - ex_err_fn(exoid, __func__, errmsg, EX_BADPARAM); - EX_FUNC_LEAVE(EX_FATAL); - break; + case EX_ASSEMBLY: return ex__put_assembly_name(exoid, obj_type, entity_id, name); break; case EX_EDGE_BLOCK: vobj = VAR_NAME_ED_BLK; break; case EX_FACE_BLOCK: vobj = VAR_NAME_FA_BLK; break; case EX_ELEM_BLOCK: vobj = VAR_NAME_EL_BLK; break; diff --git a/packages/seacas/libraries/ioss/src/Ioss_Region.C b/packages/seacas/libraries/ioss/src/Ioss_Region.C index 06bf75fdde..f48f5fcc52 100644 --- a/packages/seacas/libraries/ioss/src/Ioss_Region.C +++ b/packages/seacas/libraries/ioss/src/Ioss_Region.C @@ -1883,7 +1883,8 @@ namespace Ioss { std::ostringstream errmsg; fmt::print( errmsg, - "ERROR: There are multiple ({}) blocks and/or sets with the name '{}' defined in the " + "ERROR: There are multiple ({}) blocks, sets, assemblies and/or blobs with the name '{}' " + "defined in the " "database file '{}'.\n" "\tThis is allowed in general, but this application uses an API function (get_entity) " "that does not support duplicate names.", @@ -2732,7 +2733,8 @@ namespace Ioss { int64_t id2 = old_ge->get_optional_property(id_str(), 0); std::ostringstream errmsg; fmt::print(errmsg, - "ERROR: There are multiple blocks or sets with the same name defined in the " + "ERROR: There are multiple blocks, sets, assemblies, and/or blobs with the same " + "name defined in the " "database file '{}'.\n" "\tBoth {} {} and {} {} are named '{}'. All names must be unique.", filename, entity->type_string(), id1, old_ge->type_string(), id2, name);