diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index 8d386b8b04..65de7f2a30 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -113,7 +113,7 @@ bool rename_file(const std::string& old_filename, const std::string& new_filenam } static std::stringstream log_stream; -void write_log(); +void write_log(bool); /// @todo make the filters non-global IfcGeom::entity_filter entity_filter; // Entity filter is used always by default. @@ -152,13 +152,16 @@ bool init_input_file(const std::string& filename, IfcParse::IfcFile& ifc_file, b int main(int argc, char** argv) { + std::string log_format; po::options_description generic_options("Command line options"); generic_options.add_options() ("help,h", "display usage information") ("version", "display version information") - ("verbose,v", "more verbose output") - ("yes,y", "answer 'yes' automatically to possible confirmation queries (e.g. overwriting an existing output file)") - ("no-progress", "Suppress possible progress bar type of prints that use carriage return."); + ("verbose,v", "more verbose log messages") + ("quiet,q", "less status and progress output") + ("yes,y", "answer 'yes' automatically to possible confirmation queries (e.g. overwriting an existing output file)") + ("no-progress", "suppress possible progress bar type of prints that use carriage return") + ("log-format", po::value(&log_format), "log format: plain or json"); po::options_description fileio_options; fileio_options.add_options() @@ -174,7 +177,7 @@ int main(int argc, char** argv) inclusion_traverse_filter include_traverse_filter; exclusion_filter exclude_filter; exclusion_traverse_filter exclude_traverse_filter; - std::string filter_filename; + std::string filter_filename; po::options_description geom_options("Geometry options"); geom_options.add_options() @@ -336,7 +339,36 @@ int main(int argc, char** argv) po::notify(vmap); - print_version(); + const bool mmap = vmap.count("mmap") != 0; + const bool verbose = vmap.count("verbose") != 0; + const bool no_progress = vmap.count("no-progress") != 0; + const bool quiet = vmap.count("quiet") != 0; + const bool weld_vertices = vmap.count("weld-vertices") != 0; + const bool use_world_coords = vmap.count("use-world-coords") != 0; + const bool convert_back_units = vmap.count("convert-back-units") != 0; + const bool sew_shells = vmap.count("sew-shells") != 0; +#if OCC_VERSION_HEX < 0x60900 + const bool merge_boolean_operands = vmap.count("merge-boolean-operands") != 0; +#endif + const bool disable_opening_subtractions = vmap.count("disable-opening-subtractions") != 0; + const bool include_plan = vmap.count("plan") != 0; + const bool include_model = vmap.count("model") != 0 || (!include_plan); + const bool enable_layerset_slicing = vmap.count("enable-layerset-slicing") != 0; + const bool use_element_names = vmap.count("use-element-names") != 0; + const bool use_element_guids = vmap.count("use-element-guids") != 0; + const bool use_material_names = vmap.count("use-material-names") != 0; + const bool use_element_types = vmap.count("use-element-types") != 0; + const bool use_element_hierarchy = vmap.count("use-element-hierarchy") != 0; + const bool no_normals = vmap.count("no-normals") != 0; + const bool center_model = vmap.count("center-model") != 0; + const bool model_offset = vmap.count("model-offset") != 0; + const bool site_local_placement = vmap.count("site-local-placement") != 0; + const bool building_local_placement = vmap.count("building-local-placement") != 0; + const bool generate_uvs = vmap.count("generate-uvs") != 0; + + if (!quiet || vmap.count("version")) { + print_version(); + } if (vmap.count("version")) { return EXIT_SUCCESS; @@ -349,31 +381,6 @@ int main(int argc, char** argv) print_usage(); return EXIT_FAILURE; } - const bool mmap = vmap.count("mmap") != 0; - const bool verbose = vmap.count("verbose") != 0; - const bool no_progress = vmap.count("no-progress") != 0; - const bool weld_vertices = vmap.count("weld-vertices") != 0; - const bool use_world_coords = vmap.count("use-world-coords") != 0; - const bool convert_back_units = vmap.count("convert-back-units") != 0; - const bool sew_shells = vmap.count("sew-shells") != 0; -#if OCC_VERSION_HEX < 0x60900 - const bool merge_boolean_operands = vmap.count("merge-boolean-operands") != 0; -#endif - const bool disable_opening_subtractions = vmap.count("disable-opening-subtractions") != 0; - const bool include_plan = vmap.count("plan") != 0; - const bool include_model = vmap.count("model") != 0 || (!include_plan); - const bool enable_layerset_slicing = vmap.count("enable-layerset-slicing") != 0; - const bool use_element_names = vmap.count("use-element-names") != 0; - const bool use_element_guids = vmap.count("use-element-guids") != 0 ; - const bool use_material_names = vmap.count("use-material-names") != 0; - const bool use_element_types = vmap.count("use-element-types") != 0; - const bool use_element_hierarchy = vmap.count("use-element-hierarchy") != 0; - const bool no_normals = vmap.count("no-normals") != 0 ; - const bool center_model = vmap.count("center-model") != 0 ; - const bool model_offset = vmap.count("model-offset") != 0 ; - const bool site_local_placement = vmap.count("site-local-placement") != 0 ; - const bool building_local_placement = vmap.count("building-local-placement") != 0 ; - const bool generate_uvs = vmap.count("generate-uvs") != 0 ; #ifdef HAVE_ICU if (!unicode_mode.empty()) { @@ -438,12 +445,25 @@ int main(int argc, char** argv) Logger::SetOutput(&std::cout, &log_stream); Logger::Verbosity(verbose ? Logger::LOG_NOTICE : Logger::LOG_ERROR); + if (vmap.count("log-format") == 1) { + boost::to_lower(log_format); + if (log_format == "plain") { + Logger::OutputFormat(Logger::FMT_PLAIN); + } else if (log_format == "json") { + Logger::OutputFormat(Logger::FMT_JSON); + } else { + std::cerr << "[Error] --log-format should be either plain or json" << std::endl; + print_usage(); + return EXIT_FAILURE; + } + } + IfcParse::IfcFile ifc_file; if (output_extension == ".xml") { int exit_code = EXIT_FAILURE; try { - if (init_input_file(input_filename, ifc_file, no_progress, mmap)) { + if (init_input_file(input_filename, ifc_file, no_progress || quiet, mmap)) { XmlSerializer s(output_temp_filename); s.setFile(&ifc_file); Logger::Status("Writing XML output..."); @@ -455,7 +475,7 @@ int main(int argc, char** argv) } catch (const std::exception& e) { Logger::Error(e); } - write_log(); + write_log(!quiet); return exit_code; } @@ -548,7 +568,7 @@ int main(int argc, char** argv) } } else { std::cerr << "[Error] Unknown output filename extension '" + output_extension + "'\n"; - write_log(); + write_log(!quiet); print_usage(); return EXIT_FAILURE; } @@ -557,7 +577,7 @@ int main(int argc, char** argv) if (use_element_hierarchy && output_extension != ".dae") { std::cerr << "[Error] --use-element-hierarchy can be used only with .dae output.\n"; - write_log(); + write_log(!quiet); print_usage(); delete serializer; std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ @@ -582,14 +602,14 @@ int main(int argc, char** argv) if (!serializer->ready()) { delete serializer; std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ - write_log(); + write_log(!quiet); return EXIT_FAILURE; } time_t start,end; time(&start); - if (!init_input_file(input_filename, ifc_file, no_progress, mmap)) { + if (!init_input_file(input_filename, ifc_file, no_progress || quiet, mmap)) { return EXIT_FAILURE; } @@ -600,7 +620,7 @@ int main(int argc, char** argv) Logger::Error("No geometrical entities found"); delete serializer; std::remove(output_temp_filename.c_str()); /**< @todo Windows Unicode support */ - write_log(); + write_log(!quiet); return EXIT_FAILURE; } @@ -614,7 +634,7 @@ int main(int argc, char** argv) serializer->writeHeader(); - int old_progress = -1; + int old_progress = quiet ? 0 : -1; if (center_model || model_offset) { double* offset = serializer->settings().offset; @@ -643,7 +663,9 @@ int main(int argc, char** argv) Logger::Notice(msg.str()); } - Logger::Status("Creating geometry..."); + if (!quiet) { + Logger::Status("Creating geometry..."); + } // The functions IfcGeom::Iterator::get() and IfcGeom::Iterator::next() // wrap an iterator of all geometrical products in the Ifc file. @@ -670,14 +692,28 @@ int main(int argc, char** argv) } if (!no_progress) { - const int progress = context_iterator.progress() / 2; - if (old_progress != progress) Logger::ProgressBar(progress); - old_progress = progress; + if (quiet) { + const int progress = context_iterator.progress(); + for (; old_progress < progress; ++old_progress) { + std::cout << "."; + } + std::cout << std::flush; + } else { + const int progress = context_iterator.progress() / 2; + if (old_progress != progress) Logger::ProgressBar(progress); + old_progress = progress; + } } } while (++num_created, context_iterator.next()); - Logger::Status("\rDone creating geometry (" + boost::lexical_cast(num_created) + - " objects) "); + if (!no_progress && quiet) { + for (; old_progress < 100; ++old_progress) { + std::cout << "."; + } + } else { + Logger::Status("\rDone creating geometry (" + boost::lexical_cast(num_created) + + " objects) "); + } serializer->finalize(); delete serializer; @@ -690,34 +726,39 @@ int main(int argc, char** argv) output_temp_filename + "' for the conversion result."); } - write_log(); + write_log(!quiet); time(&end); - int seconds = (int)difftime(end, start); - std::stringstream msg; - int minutes = seconds / 60; - seconds = seconds % 60; - msg << "\nConversion took"; - if (minutes > 0) { - msg << " " << minutes << " minute"; - if (minutes > 1) { + if (!quiet) { + int seconds = (int)difftime(end, start); + std::stringstream msg; + int minutes = seconds / 60; + seconds = seconds % 60; + msg << "\nConversion took"; + if (minutes > 0) { + msg << " " << minutes << " minute"; + if (minutes > 1) { + msg << "s"; + } + } + msg << " " << seconds << " second"; + if (seconds > 1) { msg << "s"; } + Logger::Status(msg.str()); } - msg << " " << seconds << " second"; - if (seconds > 1) { - msg << "s"; - } - Logger::Status(msg.str()); return successful ? EXIT_SUCCESS : EXIT_FAILURE; } -void write_log() { +void write_log(bool header) { std::string log = log_stream.str(); if (!log.empty()) { - std::cout << "\nLog:\n" << log << std::endl; + if (header) { + std::cout << "\nLog:\n"; + } + std::cout << log << std::endl; } } diff --git a/src/ifcparse/IfcLogger.cpp b/src/ifcparse/IfcLogger.cpp index 4fe538365e..2fcc9d3c2f 100644 --- a/src/ifcparse/IfcLogger.cpp +++ b/src/ifcparse/IfcLogger.cpp @@ -23,13 +23,47 @@ #include #include +#include +#include +#include + #include #include +using boost::property_tree::ptree; + +namespace { + static const char* severity_strings[] = {"Notice", "Warning", "Error"}; + + void plain_text_message(std::ostream& os, const boost::optional& current_product, Logger::Severity type, const std::string& message, IfcEntityInstanceData* entity) { + os << "[" << severity_strings[type] << "] "; + if (current_product) { + os << "{" << (*current_product)->GlobalId() << "} "; + } + os << message << std::endl; + if (entity) { + os << entity->toString() << std::endl; + } + } + + void json_message(std::ostream& os, const boost::optional& current_product, Logger::Severity type, const std::string& message, IfcEntityInstanceData* entity) { + ptree pt; + pt.put("level", severity_strings[type]); + if (current_product) { + pt.put("product", (**current_product).entity->toString()); + } + pt.put("message", message); + if (entity) { + pt.put("instance", entity); + } + boost::property_tree::write_json(os, pt, false); + } +} void Logger::SetProduct(boost::optional product) { current_product = product; } + void Logger::SetOutput(std::ostream* l1, std::ostream* l2) { log1 = l1; log2 = l2; @@ -39,13 +73,12 @@ void Logger::SetOutput(std::ostream* l1, std::ostream* l2) { } void Logger::Message(Logger::Severity type, const std::string& message, IfcEntityInstanceData* entity) { - if ( log2 && type >= verbosity ) { - (*log2) << "[" << severity_strings[type] << "] "; - if ( current_product ) { - (*log2) << "{" << (*current_product)->GlobalId() << "} "; + if (log2 && type >= verbosity) { + if (format == FMT_PLAIN) { + plain_text_message(*log2, current_product, type, message, entity); + } else if (format == FMT_JSON) { + json_message(*log2, current_product, type, message, entity); } - (*log2) << message << std::endl; - if ( entity ) (*log2) << entity->toString() << std::endl; } } @@ -54,26 +87,32 @@ void Logger::Message(Logger::Severity type, const std::exception& exception, Ifc } void Logger::Status(const std::string& message, bool new_line) { - if ( log1 ) { + if (log1) { (*log1) << message; if ( new_line ) (*log1) << std::endl; else (*log1) << std::flush; } } + void Logger::ProgressBar(int progress) { - if ( log1 ) { + if (log1) { Status("\r[" + std::string(progress,'#') + std::string(50 - progress,' ') + "]", false); } } + std::string Logger::GetLog() { return log_stream.str(); } + void Logger::Verbosity(Logger::Severity v) { verbosity = v; } Logger::Severity Logger::Verbosity() { return verbosity; } +void Logger::OutputFormat(Format f) { format = f; } +Logger::Format Logger::OutputFormat() { return format; } + std::ostream* Logger::log1 = 0; std::ostream* Logger::log2 = 0; std::stringstream Logger::log_stream; Logger::Severity Logger::verbosity = Logger::LOG_NOTICE; -const char* Logger::severity_strings[] = { "Notice","Warning","Error" }; +Logger::Format Logger::format = Logger::FMT_PLAIN; boost::optional Logger::current_product; \ No newline at end of file diff --git a/src/ifcparse/IfcLogger.h b/src/ifcparse/IfcLogger.h index f5e6f9c0ee..ce13ee7558 100644 --- a/src/ifcparse/IfcLogger.h +++ b/src/ifcparse/IfcLogger.h @@ -40,20 +40,26 @@ class IFC_PARSE_API Logger { public: typedef enum { LOG_NOTICE, LOG_WARNING, LOG_ERROR } Severity; + typedef enum { FMT_PLAIN, FMT_JSON } Format; private: static std::ostream* log1; static std::ostream* log2; static std::stringstream log_stream; static Severity verbosity; - static const char* severity_strings[]; + static Format format; static boost::optional current_product; public: static void SetProduct(boost::optional product); /// Determines to what stream respectively progress and errors are logged static void SetOutput(std::ostream* l1, std::ostream* l2); + /// Determines the types of log messages to get logged static void Verbosity(Severity v); static Severity Verbosity(); + + /// Determines output format: plain text or sequence of JSON objects + static void OutputFormat(Format f); + static Format OutputFormat(); /// Log a message to the output stream static void Message(Severity type, const std::string& message, IfcEntityInstanceData* entity=0);