mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 02:23:34 +00:00
Begin to implement IfcConvert enhancements discussed in #20. Remove C++-specific getters and setters and use the same set() and get() functions in in both C++ and Python. Enhance output of IfcConvert, especially in error situations (spamming a hundred lines of options always hides the original error message), utilize Boost's foreach macro to provide cleaner iteration code.
This commit is contained in:
+194
-94
@@ -26,16 +26,6 @@
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <time.h>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../ifcgeom/IfcGeomIterator.h"
|
||||
|
||||
#include "../ifcconvert/ColladaSerializer.h"
|
||||
#include "../ifcconvert/IgesSerializer.h"
|
||||
#include "../ifcconvert/StepSerializer.h"
|
||||
@@ -43,35 +33,68 @@
|
||||
#include "../ifcconvert/XmlSerializer.h"
|
||||
#include "../ifcconvert/SvgSerializer.h"
|
||||
|
||||
#include "../ifcgeom/IfcGeomIterator.h"
|
||||
|
||||
#include <IGESControl_Controller.hxx>
|
||||
#include <Standard_Version.hxx>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/optional/optional_io.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <time.h>
|
||||
|
||||
typedef double real_t; /**< @todo Will be configurable */
|
||||
#define INF std::numeric_limits<real_t>::infinity()
|
||||
real_t bounds_min[3] = { INF, INF, INF };
|
||||
real_t bounds_max[3] = { -INF, -INF, -INF };
|
||||
|
||||
#if USE_VLD
|
||||
#include <vld.h>
|
||||
#endif
|
||||
|
||||
static std::string DEFAULT_EXTENSION = "obj";
|
||||
const std::string DEFAULT_EXTENSION = "obj";
|
||||
|
||||
void printVersion() {
|
||||
std::cerr << "IfcOpenShell IfcConvert " << IFCOPENSHELL_VERSION << std::endl;
|
||||
void print_version()
|
||||
{
|
||||
/// @todo Why cerr used for info prints? Change to cout.
|
||||
std::cerr << "IfcOpenShell " << IfcSchema::Identifier << " IfcConvert " << IFCOPENSHELL_VERSION << std::endl;
|
||||
}
|
||||
|
||||
void printUsage(const boost::program_options::options_description& generic_options, const boost::program_options::options_description& geom_options) {
|
||||
printVersion();
|
||||
std::cerr << "Usage: IfcConvert [options] <input.ifc> [<output>]" << std::endl
|
||||
<< std::endl
|
||||
<< "Converts the geometry in an IFC file into one of the following formats:" << std::endl
|
||||
<< " .obj WaveFront OBJ (a .mtl file is also created)" << std::endl;
|
||||
#ifdef WITH_OPENCOLLADA
|
||||
std::cerr << " .dae Collada Digital Asset Exchange" << std::endl;
|
||||
void print_usage()
|
||||
{
|
||||
std::cerr << "Usage: IfcConvert [options] <input.ifc> [<output>]" << "\n"
|
||||
<< "\n"
|
||||
<< "Converts the geometry in an IFC file into one of the following formats:" << "\n"
|
||||
<< " .obj WaveFront OBJ (a .mtl file is also created)" << "\n"
|
||||
#ifdef WITH_OPENCOLLADA
|
||||
<< " .dae Collada Digital Assets Exchange" << "\n"
|
||||
#endif
|
||||
std::cerr << " .stp STEP Standard for the Exchange of Product Data" << std::endl
|
||||
<< " .igs IGES Initial Graphics Exchange Specification" << std::endl
|
||||
<< " .xml XML Property definitions and decomposition tree" << std::endl
|
||||
<< " .svg SVG Scalable Vector Graphics (2d floor plan)" << std::endl
|
||||
<< std::endl
|
||||
<< "Command line options" << std::endl << generic_options << std::endl
|
||||
<< "Advanced options" << std::endl << geom_options << std::endl;
|
||||
<< " .stp STEP Standard for the Exchange of Product Data" << "\n"
|
||||
<< " .igs IGES Initial Graphics Exchange Specification" << "\n"
|
||||
<< " .xml XML Property definitions and decomposition tree" << "\n"
|
||||
<< " .svg SVG Scalable Vector Graphics (2D floor plan)" << "\n"
|
||||
<< "\n"
|
||||
<< "If no output filename given, <input>." + DEFAULT_EXTENSION + " will be used as the output file.\n"
|
||||
<< "\n"
|
||||
<< "Run 'IfcConvert --help' for more information." << std::endl;
|
||||
}
|
||||
|
||||
void print_options(
|
||||
const boost::optional<boost::program_options::options_description>& generic_options,
|
||||
const boost::optional<boost::program_options::options_description>& geom_options,
|
||||
const boost::optional<boost::program_options::options_description>& serialization_options)
|
||||
{
|
||||
if (generic_options)
|
||||
std::cerr << "\nCommand line options\n" << generic_options;
|
||||
if (geom_options)
|
||||
std::cerr << "\nGeometry options\n" << geom_options;
|
||||
if (serialization_options)
|
||||
std::cerr << "\nSerialization options\n" << serialization_options;
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
|
||||
std::string change_extension(const std::string& fn, const std::string& ext) {
|
||||
@@ -89,7 +112,7 @@ void write_log();
|
||||
int main(int argc, char** argv) {
|
||||
boost::program_options::options_description generic_options;
|
||||
generic_options.add_options()
|
||||
("help", "display usage information")
|
||||
("help,h", "display usage information")
|
||||
("version", "display version information")
|
||||
("verbose,v", "more verbose output");
|
||||
|
||||
@@ -98,8 +121,8 @@ int main(int argc, char** argv) {
|
||||
("input-file", boost::program_options::value<std::string>(), "input IFC file")
|
||||
("output-file", boost::program_options::value<std::string>(), "output geometry file");
|
||||
|
||||
std::string bounds;
|
||||
std::vector<std::string> entity_vector;
|
||||
std::vector<std::string> entity_vector/*, names*/;
|
||||
//double deflection_tolerance;
|
||||
boost::program_options::options_description geom_options;
|
||||
geom_options.add_options()
|
||||
("plan",
|
||||
@@ -142,20 +165,48 @@ int main(int argc, char** argv) {
|
||||
("disable-opening-subtractions",
|
||||
"Specifies whether to disable the boolean subtraction of "
|
||||
"IfcOpeningElement Representations from their RelatingElements.")
|
||||
("bounds", boost::program_options::value<std::string>(&bounds),
|
||||
"Specifies the bounding rectangle, for example 512x512, to which the "
|
||||
"output will be scaled. Only used when converting to SVG.")
|
||||
("include",
|
||||
"Specifies that the entities listed after --entities are to be included")
|
||||
"Specifies that the entities listed after --entities or --names are to be included")
|
||||
("exclude",
|
||||
"Specifies that the entities listed after --entities are to be excluded")
|
||||
("entities", boost::program_options::value< std::vector<std::string> >(&entity_vector)->multitoken(),
|
||||
"Specifies that the entities listed after --entities or --names are to be excluded")
|
||||
("entities", boost::program_options::value< std::vector<std::string> >(&entity_vector)->multitoken(),
|
||||
"A list of entities that should be included in or excluded from the "
|
||||
"geometrical output, depending on whether --ignore or --include is "
|
||||
"specified. Defaults to IfcOpeningElement and IfcSpace to be excluded.");
|
||||
|
||||
"geometrical output, depending on whether --exclude or --include is specified. "
|
||||
" Defaults to IfcOpeningElement and IfcSpace to be excluded. "
|
||||
"The names are handled case-insensitively. Cannot be placed right before input file argument.")
|
||||
/*("names", boost::program_options::value< std::vector<std::string> >(&names)->multitoken(),
|
||||
"A list of names or wildcard patterns that should be included in or excluded from the "
|
||||
"geometrical output, depending on whether --exclude or --include is specified. "
|
||||
"The names are handled case-sensitively. Cannot be placed right before input file argument.")
|
||||
("no-normals",
|
||||
"Disables computation of normals. Saves time and file size and is useful "
|
||||
"in instances where you're going to recompute normals for the exported "
|
||||
"model in other modelling application in any case.")
|
||||
("deflection-tolerance", boost::program_options::value<double>(&deflection_tolerance),
|
||||
"Sets the deflection tolerance of the mesher, 1e-3 by default if not specified.")*/;
|
||||
|
||||
std::string bounds;
|
||||
boost::program_options::options_description serializer_options;
|
||||
serializer_options.add_options()
|
||||
("bounds", boost::program_options::value<std::string>(&bounds),
|
||||
"Specifies the bounding rectangle, for example 512x512, to which the "
|
||||
"output will be scaled. Only used when converting to SVG.")
|
||||
/*("use-names",
|
||||
"Use entity names instead of unique IDs for naming objects and materials "
|
||||
"upon serialization. Applicable for .obj and .dae output.")
|
||||
("use-guids",
|
||||
"Use entity GUIDs instead of unique IDs for naming objects upon serialization. "
|
||||
"Overrides possible usage of --use-names for objects but not for materials."
|
||||
"Applicable for .obj and .dae output.")
|
||||
("center-model",
|
||||
"Centers the models upon serialization by applying the center point of "
|
||||
"the scene bounds as an offset. Applicable only for .dae output currently.")
|
||||
("generate-uvs",
|
||||
"Generates UVs (texture coordinates) by using simple box projection. Requires normals. Not guaranteed to work "
|
||||
"properly if used with --weld-vertices. Applicable only for .dae output currently.")*/;
|
||||
|
||||
boost::program_options::options_description cmdline_options;
|
||||
cmdline_options.add(generic_options).add(fileio_options).add(geom_options);
|
||||
cmdline_options.add(generic_options).add(fileio_options).add(geom_options).add(serializer_options);
|
||||
|
||||
boost::program_options::positional_options_description positional_options;
|
||||
positional_options.add("input-file", 1);
|
||||
@@ -167,21 +218,30 @@ int main(int argc, char** argv) {
|
||||
options(cmdline_options).positional(positional_options).run(), vmap);
|
||||
} catch (const boost::program_options::unknown_option& e) {
|
||||
std::cerr << "[Error] Unknown option '" << e.get_option_name() << "'" << std::endl << std::endl;
|
||||
// Usage information will be emitted below
|
||||
print_usage();
|
||||
return 1;
|
||||
} catch (...) {
|
||||
// Catch other errors such as invalid command line syntax
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
boost::program_options::notify(vmap);
|
||||
|
||||
if (vmap.count("version")) {
|
||||
printVersion();
|
||||
return 0;
|
||||
} else if (vmap.count("help") || !vmap.count("input-file")) {
|
||||
printUsage(generic_options, geom_options);
|
||||
return vmap.count("help") ? 0 : 1;
|
||||
print_version();
|
||||
|
||||
if (vmap.count("version")) {
|
||||
return 0;
|
||||
} else if (vmap.count("help")) {
|
||||
print_usage();
|
||||
print_options(generic_options, geom_options, serializer_options);
|
||||
return 0;
|
||||
} else if (!vmap.count("input-file")) {
|
||||
std::cerr << "[Error] Input file not specified" << std::endl;
|
||||
print_usage();
|
||||
return 1;
|
||||
} else if (vmap.count("include") && vmap.count("exclude")) {
|
||||
std::cerr << "[Error] --include and --ignore can not be specified together" << std::endl;
|
||||
printUsage(generic_options, geom_options);
|
||||
std::cerr << "[Error] --include and --exclude can not be specified together" << std::endl;
|
||||
print_options(boost::none, geom_options, boost::none);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -197,6 +257,12 @@ int main(int argc, char** argv) {
|
||||
bool include_entities = vmap.count("include") != 0;
|
||||
const bool include_plan = vmap.count("plan") != 0;
|
||||
const bool include_model = vmap.count("model") != 0 || (!include_plan);
|
||||
//const bool use_names = vmap.count("use-names") != 0;
|
||||
//const bool use_guids = vmap.count("use-guids") != 0 ;
|
||||
//const bool no_normals = vmap.count("no-normals") != 0 ;
|
||||
//const bool center_model = vmap.count("center-model") != 0 ;
|
||||
//const bool generate_uvs = vmap.count("generate-uvs") != 0 ;
|
||||
//const bool deflection_tolerance_specified = vmap.count("deflection-tolerance") != 0 ;
|
||||
boost::optional<int> bounding_width, bounding_height;
|
||||
if (vmap.count("bounds") == 1) {
|
||||
int w, h;
|
||||
@@ -205,18 +271,14 @@ int main(int argc, char** argv) {
|
||||
bounding_height = h;
|
||||
} else {
|
||||
std::cerr << "[Error] Invalid use of --bounds" << std::endl;
|
||||
printUsage(generic_options, geom_options);
|
||||
print_options(boost::none, boost::none, serializer_options);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the set ifc types to be ignored from the command line.
|
||||
std::set<std::string> entities;
|
||||
for (std::vector<std::string>::const_iterator it = entity_vector.begin(); it != entity_vector.end(); ++it) {
|
||||
const std::string& mixed_case_type = *it;
|
||||
entities.insert(boost::to_lower_copy(mixed_case_type));
|
||||
}
|
||||
|
||||
std::set<std::string> entities(entity_vector.begin(), entity_vector.end());
|
||||
|
||||
const std::string input_filename = vmap["input-file"].as<std::string>();
|
||||
// If no output filename is specified a Wavefront OBJ file will be output
|
||||
// to maintain backwards compatibility with the obsolete IfcObj executable.
|
||||
@@ -225,7 +287,8 @@ int main(int argc, char** argv) {
|
||||
: change_extension(input_filename, DEFAULT_EXTENSION);
|
||||
|
||||
if (output_filename.size() < 5) {
|
||||
printUsage(generic_options, geom_options);
|
||||
std::cerr << "[Error] Invalid or unsupported output file given " << output_filename << std::endl;
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -234,12 +297,12 @@ int main(int argc, char** argv) {
|
||||
|
||||
// If no entities are specified these are the defaults to skip from output
|
||||
if (entity_vector.empty()) {
|
||||
entities.insert("IfcSpace");
|
||||
/// @todo Document in --help that SVG uses "--include --entities IfcSpace" by default.
|
||||
if (output_extension == ".svg") {
|
||||
entities.insert("ifcspace");
|
||||
include_entities = true;
|
||||
} else {
|
||||
entities.insert("ifcopeningelement");
|
||||
entities.insert("ifcspace");
|
||||
entities.insert("IfcOpeningElement");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +327,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
IfcGeom::IteratorSettings settings;
|
||||
|
||||
/// @todo Make APPLY_DEFAULT_MATERIALS configurable? Quickly tested setting this to false and using obj exporter caused the program to crash and burn.
|
||||
settings.set(IfcGeom::IteratorSettings::APPLY_DEFAULT_MATERIALS, true);
|
||||
settings.set(IfcGeom::IteratorSettings::USE_WORLD_COORDS, use_world_coords);
|
||||
settings.set(IfcGeom::IteratorSettings::WELD_VERTICES, weld_vertices);
|
||||
@@ -276,6 +339,13 @@ int main(int argc, char** argv) {
|
||||
settings.set(IfcGeom::IteratorSettings::DISABLE_OPENING_SUBTRACTIONS, disable_opening_subtractions);
|
||||
settings.set(IfcGeom::IteratorSettings::INCLUDE_CURVES, include_plan);
|
||||
settings.set(IfcGeom::IteratorSettings::EXCLUDE_SOLIDS_AND_SURFACES, !include_model);
|
||||
//settings.set(IfcConvertSettings::USE_NAMES, use_names);
|
||||
//settings.set(IfcConvertSettings::USE_GUIDS, use_guids);
|
||||
//settings.set(IfcGeom::IteratorSettings::NO_NORMALS, no_normals);
|
||||
//settings.set(IfcConvertSettings::CENTER_MODEL, center_model);
|
||||
//settings.set(IfcConvertSettings::GENERATE_UVS, generate_uvs);
|
||||
//if (deflection_tolerance_specified)
|
||||
// settings.set_deflection_tolerance(deflection_tolerance);
|
||||
|
||||
GeometrySerializer* serializer;
|
||||
if (output_extension == ".obj") {
|
||||
@@ -300,15 +370,12 @@ int main(int argc, char** argv) {
|
||||
settings.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
|
||||
serializer = new SvgSerializer(output_filename);
|
||||
if (bounding_width && bounding_height) {
|
||||
((SvgSerializer*) serializer)->setBoundingRectangle(
|
||||
static_cast<double>(*bounding_width),
|
||||
static_cast<double>(*bounding_height)
|
||||
);
|
||||
static_cast<SvgSerializer*>(serializer)->setBoundingRectangle(*bounding_width, *bounding_height);
|
||||
}
|
||||
} else {
|
||||
Logger::Message(Logger::LOG_ERROR, "Unknown output filename extension");
|
||||
write_log();
|
||||
printUsage(generic_options, geom_options);
|
||||
print_usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -316,16 +383,18 @@ int main(int argc, char** argv) {
|
||||
if (weld_vertices) {
|
||||
Logger::Message(Logger::LOG_NOTICE, "Weld vertices setting ignored when writing STEP or IGES files");
|
||||
}
|
||||
settings.disable_triangulation() = true;
|
||||
settings.set(IfcGeom::IteratorSettings::DISABLE_TRIANGULATION, true);
|
||||
}
|
||||
|
||||
IfcGeom::Iterator<double> context_iterator(settings, input_filename);
|
||||
IfcGeom::Iterator<real_t> context_iterator(settings, input_filename);
|
||||
|
||||
try {
|
||||
if (include_entities) {
|
||||
context_iterator.includeEntities(entities);
|
||||
//context_iterator.include_entity_names(names);
|
||||
} else {
|
||||
context_iterator.excludeEntities(entities);
|
||||
//context_iterator.exclude_entity_names(names);
|
||||
}
|
||||
} catch (const IfcParse::IfcException& e) {
|
||||
std::cout << "[Error] " << e.what() << std::endl;
|
||||
@@ -347,21 +416,18 @@ int main(int argc, char** argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
serializer->setFile(context_iterator.getFile());
|
||||
|
||||
if (convert_back_units) {
|
||||
serializer->setUnitNameAndMagnitude(context_iterator.getUnitName(), static_cast<const float>(context_iterator.getUnitMagnitude()));
|
||||
serializer->setUnitNameAndMagnitude(context_iterator.getUnitName(), static_cast<float>(context_iterator.getUnitMagnitude()));
|
||||
} else {
|
||||
serializer->setUnitNameAndMagnitude("METER", 1.0f);
|
||||
}
|
||||
|
||||
serializer->writeHeader();
|
||||
|
||||
std::set<std::string> materials;
|
||||
|
||||
int old_progress = -1;
|
||||
Logger::Status("Creating geometry...");
|
||||
|
||||
std::vector<IfcGeom::Element<real_t>* > geometries;
|
||||
// The functions IfcGeom::Iterator::get() and IfcGeom::Iterator::next()
|
||||
// wrap an iterator of all geometrical products in the Ifc file.
|
||||
// IfcGeom::Iterator::get() returns an IfcGeom::TriangulationElement or
|
||||
@@ -373,30 +439,65 @@ int main(int argc, char** argv) {
|
||||
// true return value guarantees that a successfully processed product is
|
||||
// available.
|
||||
do {
|
||||
const IfcGeom::Element<double>* geom_object = context_iterator.get();
|
||||
|
||||
if (serializer->isTesselated()) {
|
||||
serializer->write(static_cast<const IfcGeom::TriangulationElement<double>*>(geom_object));
|
||||
} else {
|
||||
serializer->write(static_cast<const IfcGeom::BRepElement<double>*>(geom_object));
|
||||
}
|
||||
|
||||
const int progress = context_iterator.progress() / 2;
|
||||
if (old_progress!= progress) Logger::ProgressBar(progress);
|
||||
old_progress = progress;
|
||||
|
||||
} while (context_iterator.next());
|
||||
IfcGeom::Element<real_t> *geom_object = context_iterator.get(true); // true == take ownership, we will clean up ourselves
|
||||
geometries.push_back(geom_object);
|
||||
const int progress = context_iterator.progress() / 2;
|
||||
if (old_progress != progress) Logger::ProgressBar(progress);
|
||||
old_progress = progress;
|
||||
|
||||
//if (center_model) {
|
||||
// const std::vector<real_t>& pos = geom_object->transformation().matrix().data();
|
||||
// bounds_min[0] = std::min(bounds_min[0], pos[9]);
|
||||
// bounds_min[1] = std::min(bounds_min[1], pos[10]);
|
||||
// bounds_min[2] = std::min(bounds_min[2], pos[11]);
|
||||
// bounds_max[0] = std::max(bounds_max[0], pos[9]);
|
||||
// bounds_max[1] = std::max(bounds_max[1], pos[10]);
|
||||
// bounds_max[2] = std::max(bounds_max[2], pos[11]);
|
||||
//}
|
||||
} while (context_iterator.next());
|
||||
|
||||
Logger::Status("\rDone creating geometry (" + boost::lexical_cast<std::string>(geometries.size()) +
|
||||
" objects) ");
|
||||
|
||||
//if (center_model) {
|
||||
// settings.offset[0] = -(bounds_min[0] + bounds_max[0]) * real_t(0.5);
|
||||
// settings.offset[1] = -(bounds_min[1] + bounds_max[1]) * real_t(0.5);
|
||||
// settings.offset[2] = -(bounds_min[2] + bounds_max[2]) * real_t(0.5);
|
||||
// //printf("Bounds min. (%g, %g, %g)\n", bounds_min[0], bounds_min[1], bounds_min[2]);
|
||||
// //printf("Bounds max. (%g, %g, %g)\n", bounds_max[0], bounds_max[1], bounds_max[2]);
|
||||
// printf("Using model offset (%g, %g, %g)\n", settings.offset[0], settings.offset[1], settings.offset[2]); //TODO Logger::Message(Logger::LOG_NOTICE, ...);
|
||||
//}
|
||||
|
||||
Logger::Status("Serializing geometry...");
|
||||
|
||||
//serializer->setSettings(settings);
|
||||
|
||||
if (serializer->isTesselated()) { // isTesselated() doesn't change at run-time
|
||||
foreach(const IfcGeom::Element<real_t>* geom, geometries) {
|
||||
serializer->write(static_cast<const IfcGeom::TriangulationElement<real_t>*>(geom));
|
||||
delete geom;
|
||||
}
|
||||
} else {
|
||||
foreach(const IfcGeom::Element<real_t>* geom, geometries) {
|
||||
serializer->write(static_cast<const IfcGeom::BRepElement<real_t>*>(geom));
|
||||
delete geom;
|
||||
}
|
||||
}
|
||||
|
||||
serializer->finalize();
|
||||
delete serializer;
|
||||
|
||||
Logger::Status("\rDone creating geometry ");
|
||||
Logger::Status("\rDone serializing geometry ");
|
||||
|
||||
delete serializer;
|
||||
|
||||
write_log();
|
||||
|
||||
time(&end);
|
||||
int dif = (int) difftime (end,start);
|
||||
printf ("\nConversion took %d seconds\n", dif );
|
||||
int seconds = (int)difftime(end, start);
|
||||
if (seconds < 60)
|
||||
printf("\nConversion took %d seconds\n", seconds); // TODO Logger::Message(Logger::LOG_NOTICE, ...);
|
||||
else
|
||||
printf("\nConversion took %d minute(s) %d seconds\n", seconds/60, seconds%60); // TODO Logger::Message(Logger::LOG_NOTICE, ...);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -404,7 +505,6 @@ int main(int argc, char** argv) {
|
||||
void write_log() {
|
||||
std::string log = log_stream.str();
|
||||
if (!log.empty()) {
|
||||
std::cerr << std::endl << "Log:" << std::endl;
|
||||
std::cerr << log << std::endl;
|
||||
std::cerr << "\n" << "Log:\n" << log << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user