/* // // Copyright (c) 1993-2018 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// */ //////////////////////////////////////////////////////////////// // // example_test.cpp // // Example program using the Rhino file IO toolkit. The program reads // Rhino 3dm model files and verifies they can be saved and reread with // no changes to core model content. The program is a console application // that takes options, file names, and directory names as command line // arguments. // //////////////////////////////////////////////////////////////////////// #include "../opennurbs_public_examples.h" class Internal_CTestContext { public: enum : unsigned int { sizeof_a = 4088 }; Internal_CTestContext() { m_a = onmalloc(sizeof_a); } ~Internal_CTestContext() { onfree(m_a); } void SetInitialDirecory( const char* initial_directory, unsigned int counter ) { m_initial_directory = Internal_CleanPath(initial_directory); m_initial_directory_length = m_initial_directory.Length(); m_initial_directory_counter = counter; } void* m_a = nullptr; ON__INT64 m_min_delta_time = 0; ON__INT64 m_max_delta_time = 0; unsigned int m_file_count = 0; unsigned int m_directory_count = 0; unsigned int m_file_open_error_count = 0; unsigned int m_max_directory_tree_depth = 0; unsigned int m_max_file_count = 0; const ON_String TextLogPathFromFullPath( const char* full_path ) const; private: ON_String m_initial_directory; unsigned int m_initial_directory_counter = 0; int m_initial_directory_length = 0; Internal_CTestContext(const Internal_CTestContext&) = delete; Internal_CTestContext operator=(const Internal_CTestContext&) = delete; const ON_String Internal_CleanPath(const char* dirty_path) const { const char* volume = 0; const char* path = 0; // Use local path in case drive, dir, file_name_stem or ext are being reused. on_splitpath(dirty_path, &volume, &path, nullptr, nullptr); ON_String clean_path(path); if (clean_path.IsEmpty()) return ON_String(dirty_path); clean_path.Replace(ON_String::Backslash, ON_String::Slash); if (nullptr != volume && volume < path) { ON_String clean_volume(volume, (int)(path - volume)); return (clean_volume + clean_path); } return clean_path; } }; const ON_String Internal_CTestContext::TextLogPathFromFullPath(const char* full_path) const { // replace initial directory with and use / for // the directory separator so that output files are standardized. ON_String text_log_path; ON_String path1 = Internal_CleanPath(full_path); if (m_initial_directory_length > 0 && ON_String::EqualOrdinal(m_initial_directory, m_initial_directory_length, path1, m_initial_directory_length, true) ) { text_log_path = (m_initial_directory_counter>0) ? ON_String::FormatToString("", m_initial_directory_counter) : ON_String(""); text_log_path += static_cast(path1) + m_initial_directory_length; } else { text_log_path = path1; } return text_log_path; } static const ONX_ErrorCounter Internal_TestModelRead( ON_TextLog& text_log, const ON_String text_log_3dm_file_name, ON_BinaryArchive& source_archive, bool bVerbose ) { ONX_ModelTest::Type test_type = ONX_ModelTest::Type::ReadWriteReadCompare; ///// // // Read the orginal file // text_log.PushIndent(); ONX_ModelTest test; const ON_wString wide_text_log_3dm_file_name(text_log_3dm_file_name); test.ReadTest(source_archive, test_type, true, wide_text_log_3dm_file_name, &text_log); text_log.PrintNewLine(); text_log.Print("Test Results:\n"); text_log.PushIndent(); test.Dump(text_log); text_log.PopIndent(); ONX_ErrorCounter error_counter = test.ErrorCounter(); const ONX_Model* source_model = test.SourceModel().get(); if (nullptr == source_model) { text_log.PopIndent(); return error_counter; } const bool bCompareTestFailed = ONX_ModelTest::Result::Fail == test.TestResult(ONX_ModelTest::Type::ReadWriteReadCompare); if ( bVerbose || bCompareTestFailed ) { for (int i = 0; i < 2; i++) { if (0 == i) test.DumpSourceModel(); else test.DumpReadWriteReadModel(); if (false == bCompareTestFailed) break; } } text_log.PrintNewLine(); const ON_ModelComponent::Type component_types[] = { // TODO uncomment types as components are derived from ON_ModelComponent ON_ModelComponent::Type::Image, //ON_ModelComponent::Type::TextureMapping, ON_ModelComponent::Type::RenderMaterial, ON_ModelComponent::Type::LinePattern, ON_ModelComponent::Type::Layer, //ON_ModelComponent::Type::Group, ON_ModelComponent::Type::TextStyle, ON_ModelComponent::Type::DimStyle, ON_ModelComponent::Type::RenderLight, ON_ModelComponent::Type::HatchPattern, ON_ModelComponent::Type::InstanceDefinition, ON_ModelComponent::Type::ModelGeometry, //ON_ModelComponent::Type::HistoryRecord }; const unsigned int component_type_count = (unsigned int)(sizeof(component_types) / sizeof(component_types[0])); const ONX_Model& model = *source_model; error_counter.ClearLibraryErrorsAndWarnings(); const ON_ComponentManifest& manifest = model.Manifest(); ON_SerialNumberMap it_map; for (unsigned int i = 0; i < component_type_count; i++) { const ON_ModelComponent::Type component_type = component_types[i]; const bool bUniqueNameRequired = ON_ModelComponent::UniqueNameRequired(component_type); const bool bEmbeddedFileComponent = (ON_ModelComponent::Type::Image == component_type); ONX_ModelComponentIterator it(model,component_type); unsigned int it_count = 0; for (ON_ModelComponentReference mcr = it.FirstComponentReference(); false == mcr.IsEmpty(); mcr = it.NextComponentReference()) { const ON_ModelComponent* model_component = mcr.ModelComponent(); if (nullptr == model_component) { ON_ERROR("Iterator returned nullptr mcr.ModelComponent()"); continue; } if (model_component->ComponentType() != component_type) { ON_ERROR("Iterator returned wrong mcr.ModelComponent()->ComponentType()"); continue; } const ON__UINT64 model_component_sn = model_component->RuntimeSerialNumber(); const ON_UUID model_component_id = model_component->Id(); if (0 == model_component_sn) { ON_ERROR("Iterator mcr.ModelComponent()->RuntimeSerialNumber() is zero."); continue; } if (ON_nil_uuid == model_component_id) { ON_ERROR("Iterator mcr.ModelComponent()->Id() is nil."); continue; } const ON_SerialNumberMap::SN_ELEMENT* e_sn = it_map.FindSerialNumber(model_component_sn); if (nullptr != e_sn) { ON_ERROR("Iterator returned component serial number twice."); continue; } const ON_SerialNumberMap::SN_ELEMENT* e_id = it_map.FindId(model_component_id); if (nullptr != e_id) { ON_ERROR("Iterator returned component id twice."); continue; } ON_SerialNumberMap::SN_ELEMENT* e = it_map.AddSerialNumberAndId(model_component_sn,model_component_id); if (nullptr == e) { ON_ERROR("ON_SerialNumberMap failed to add sn and id."); continue; } if (e->m_value.m_u_type != 0) { ON_ERROR("ON_SerialNumberMap error."); continue; } e->m_value.m_u_type = 1; e->m_value.m_u.ptr = (void*)model_component; const ON_ComponentManifestItem& item = manifest.ItemFromComponentRuntimeSerialNumber(model_component_sn); if (item.IsUnset()) { ON_ERROR("Iterator returned item not in manifest."); continue; } if (model_component_sn != item.ComponentRuntimeSerialNumber()) { ON_ERROR("manifest.ItemFromComponentRuntimeSerialNumber() error."); continue; } if (model_component_id != item.Id()) { ON_ERROR("item has wrong id."); continue; } const ON_ComponentManifestItem& item_id = manifest.ItemFromId(model_component_id); if (&item != &item_id) { ON_ERROR("manifest.ItemFromId() failed."); continue; } if (bUniqueNameRequired || bEmbeddedFileComponent) { const ON_Bitmap* embedded_file_reference = bEmbeddedFileComponent ? ON_Bitmap::Cast(model_component) : nullptr; const ON_NameHash full_path_hash = (nullptr != embedded_file_reference) ? ON_NameHash::CreateFilePathHash(embedded_file_reference->FileReference()) : ON_NameHash::EmptyNameHash; const ON_NameHash name_hash =bEmbeddedFileComponent ? full_path_hash : model_component->NameHash(); if (false == name_hash.IsValidAndNotEmpty()) { ON_ERROR("model compoent name is not valid."); continue; } if (name_hash != item.NameHash()) { ON_ERROR("item has wrong name hash."); continue; } const ON_ComponentManifestItem& item_name = manifest.ItemFromNameHash(component_type,name_hash); if (&item != &item_name) { ON_ERROR("manifest.ItemFromNameHash() failed."); continue; } } it_count++; } if (it_count != manifest.ActiveAndDeletedComponentCount(component_type)) { ON_ERROR("iterator count != manifest.ActiveAndDeletedComponentCount(component_type)"); continue; } } text_log.PopIndent(); error_counter.AddLibraryErrorsAndWarnings(); return error_counter; } static const ONX_ErrorCounter Internal_TestFileRead( ON_TextLog& text_log, const ON_String fullpath, const ON_String text_log_path, bool bVerbose ) { FILE* fp = nullptr; fp = ON_FileStream::Open3dmToRead(fullpath); ONX_ErrorCounter error_counter; error_counter.ClearLibraryErrorsAndWarnings(); const ON_String path_to_print = (text_log_path.IsNotEmpty()) ? text_log_path : fullpath; for (;;) { if (nullptr == fp) { text_log.Print( "Skipped file: %s\n", static_cast(path_to_print) ); error_counter.IncrementFailureCount(); break; } text_log.Print( "File name: %s\n", static_cast(path_to_print) ); ON_BinaryFile source_archive(ON::archive_mode::read3dm, fp); const ON_wString wide_full_path(fullpath); source_archive.SetArchiveFullPath(static_cast(wide_full_path)); error_counter += Internal_TestModelRead(text_log, path_to_print, source_archive, bVerbose); break; } if ( nullptr != fp ) { ON_FileStream::Close(fp); fp = nullptr; } return error_counter; } static int Internal_ComparePath(const ON_String* lhs, const ON_String* rhs) { // sort nullptr to end of list. if (lhs == rhs) return 0; if (nullptr == lhs) return 1; if (nullptr == rhs) return -1; int rc = ON_String::CompareOrdinal(static_cast(*lhs), static_cast(*rhs), true); if ( 0 == rc ) rc = ON_String::CompareOrdinal(static_cast(*lhs), static_cast(*rhs), false); return rc; } static const ONX_ErrorCounter Internal_TestReadFolder( ON_TextLog& text_log, const char* directory_path, unsigned int directory_tree_depth, bool bVerbose, Internal_CTestContext& test_context ) { ONX_ErrorCounter error_counter; error_counter.ClearLibraryErrors(); if (nullptr == directory_path || 0 == directory_path[0]) { text_log.Print("Empty directory name.\n"); } ON_FileIterator fit; if (false == fit.Initialize(directory_path)) { text_log.Print( "Invalid directory name: %s\n", directory_path ); error_counter.IncrementFailureCount(); return error_counter; } const ON_String text_log_directory_name = (directory_tree_depth <= 1) ? ON_String(directory_path) : test_context.TextLogPathFromFullPath(directory_path); text_log.Print( "Directory name: %s\n", static_cast(text_log_directory_name) ); text_log.PushIndent(); ON_ClassArray< ON_String > sub_directories(32); ON_ClassArray< ON_String > skipped_files(32); ON_ClassArray< ON_String > tested_files(32); for ( bool bHaveItem = fit.FirstItem(); bHaveItem; bHaveItem = fit.NextItem() ) { if ( test_context.m_max_file_count > 0 && test_context.m_file_count >= test_context.m_max_file_count) break; if (fit.CurrentItemIsDirectory()) { if (directory_tree_depth < test_context.m_max_directory_tree_depth) { sub_directories.Append(fit.CurrentItemFullPathName()); } continue; } ON_String fullpath( fit.CurrentItemFullPathName() ); if (ON_FileStream::Is3dmFile(fullpath, false)) tested_files.Append(fullpath); else skipped_files.Append(fullpath); } // sort file and folder names so test order depends only on content. // This is required so results from different computers can be compared. sub_directories.QuickSort(Internal_ComparePath); skipped_files.QuickSort(Internal_ComparePath); tested_files.QuickSort(Internal_ComparePath); if (skipped_files.Count() > 0) { text_log.PrintNewLine(); for (int i = 0; i < skipped_files.Count(); i++) { const ON_String path_to_print = test_context.TextLogPathFromFullPath(skipped_files[i]); text_log.Print( "Skipped file: %s\n", static_cast(path_to_print) ); } text_log.PrintNewLine(); } for ( int i = 0; i < tested_files.Count(); i++ ) { const ON_String full_path = tested_files[i]; const ON_String path_to_print = test_context.TextLogPathFromFullPath(full_path); test_context.m_file_count++; const ONX_ErrorCounter file_error_counter = Internal_TestFileRead(text_log, full_path, path_to_print, bVerbose); error_counter += file_error_counter; } for (int i = 0; i < sub_directories.Count(); i++) { if (test_context.m_max_file_count > 0 && test_context.m_file_count >= test_context.m_max_file_count) break; const ON_String sub_directory_path = sub_directories[i]; test_context.m_directory_count++; error_counter += Internal_TestReadFolder(text_log, sub_directory_path, directory_tree_depth + 1, bVerbose, test_context); } text_log.PopIndent(); return error_counter; } static ONX_ErrorCounter Internal_InvalidArg(const ON_String arg, ON_TextLog& text_log) { ONX_ErrorCounter err = ONX_ErrorCounter::Zero; text_log.Print("Invalid command line parameter: \"%s\"\n", static_cast(arg)); err.IncrementFailureCount(); return err; } static ONX_ErrorCounter Internal_Test( unsigned int max_directory_tree_depth, ON_String full_path, bool bVerbose, ON_TextLog& text_log, unsigned int& directory_counter, unsigned int& file_count, unsigned int& folder_count ) { ONX_ErrorCounter err = ONX_ErrorCounter::Zero; if (ON_FileSystem::IsFile(full_path)) { if (ON_FileStream::Is3dmFile(full_path, false)) { text_log.Print("Testing 3dm file: %s\n", static_cast(full_path)); err = Internal_TestFileRead(text_log, full_path, ON_String::EmptyString, bVerbose); file_count++; } } else if (ON_FileSystem::IsDirectory(full_path)) { if ( max_directory_tree_depth > 0 ) { text_log.Print("Testing 3dm files in folder: %s\n", static_cast(full_path)); Internal_CTestContext test_context; directory_counter++; test_context.SetInitialDirecory(full_path,directory_counter); test_context.m_max_directory_tree_depth = max_directory_tree_depth; err = Internal_TestReadFolder(text_log, full_path, 1, bVerbose, test_context); file_count += test_context.m_file_count; folder_count += test_context.m_directory_count; } } else { err += Internal_InvalidArg(full_path,text_log); } return err; } static ON_String Internal_PlatformId(bool bVerbose) { const ON_String runtime = #if defined(ON_RUNTIME_WIN) bVerbose ? "Windows" : "Win" #elif defined(ON_RUNTIME_APPLE_IOS) bVerbose ? "Apple iOS" : "iOS" #elif defined(ON_RUNTIME_APPLE_MACOS) bVerbose ? "Apple Mac OS" : "MacOS" #elif defined(ON_RUNTIME_APPLE) "Apple" #elif defined(ON_RUNTIME_ANDROID) "Android" #elif defined(ON_RUNTIME_LINUX) "Linux" #else "Runtime" #endif ; const ON_String platform = #if defined(ON_64BIT_RUNTIME) bVerbose ? " 64 bit" : "64" #elif defined(ON_32BIT_RUNTIME) bVerbose ? " 32 bit" : "32" #else "" #endif ; const ON_String configuration = #if defined(ON_DEBUG) bVerbose ? " Debug" : "Debug" #else bVerbose ? " Release" : "Release" #endif ; ON_String platform_id(runtime+platform+configuration); return platform_id; } static ON_String Internal_DefaultOutFileName( const ON_String exe_stem ) { ON_String default_file_name(exe_stem); default_file_name.TrimLeftAndRight(); if (default_file_name.IsEmpty()) default_file_name = "example_test"; default_file_name += "_log"; const ON_String platform_id = Internal_PlatformId(false); if (platform_id.IsNotEmpty()) default_file_name += ON_String(ON_String("_") + platform_id); default_file_name += ".txt"; return default_file_name; } static void Internal_PrintHelp( const ON_String example_test_exe_stem, ON_TextLog& text_log ) { const ON_String default_out_filename = Internal_DefaultOutFileName(example_test_exe_stem); text_log.Print("\n"); text_log.Print("SYNOPSIS:\n"); text_log.Print(" %s [-out[:outfilename.txt]] [-recursive[:N]] [-verbose] \n", static_cast(example_test_exe_stem) ); text_log.Print("\n"); text_log.Print("DESCRIPTION:\n"); text_log.Print(" If a file is listed, it is read as an opennurbs model file.\n"); text_log.Print(" If a folder is listed, all .3dm files in that folder are read\n"); text_log.Print(" as opennurbs model files.\n"); text_log.Print("\n"); text_log.Print(" Available options:\n"); text_log.Print(" -help\n"); text_log.Print(" Displays this message.\n"); text_log.Print(" -out\n"); text_log.Print(" If -out is not present, output is written to stdout.\n"); text_log.Print(" If :outfilename.txt is appended to -out, output is written to the specifed file.\n"); text_log.Print(" If :stdout is appended to -out, output is sent to stdout.\n"); text_log.Print(" If :null is appended to -out, no output is written.\n"); text_log.Print(" Otherise output is written to %s.\n",static_cast(default_out_filename)); text_log.Print(" -recursive\n"); text_log.Print(" -r\n"); text_log.Print(" Recursivly reads files in subfolders.\n"); text_log.Print(" If a :N is appended to the option, N limits the recursion depth.\n"); text_log.Print(" -recursive:1 reads all 3dm files in the folder and does not recurse.\n"); text_log.Print(" -verbose\n"); text_log.Print(" -v\n"); text_log.Print(" For each 3dm file, a text summary of the 3dm file contents is created\n"); text_log.Print(" in the folder containing the 3dm file.\n"); text_log.Print("\n"); text_log.Print("RETURNS:\n"); text_log.Print(" 0: All tested files passed.\n"); text_log.Print(" >0: Number of failures, errors, or warnings. See output for details.\n"); text_log.Print("\n"); text_log.Print("EXAMPLE:\n"); text_log.Print(" %s -out:mytestresults.txt -resursive:2 .../example_files\n",static_cast(example_test_exe_stem)); text_log.Print(" with read all the opennurbs .3dm files in the .../example_files folder\n"); text_log.Print(" and immediate subfolders of .../example_files. Output will be saved in mytestresults.txt.\n"); } static void Internal_PrintHelp( const ON_String example_test_exe_stem, ON_TextLog* text_log_ptr ) { ON_TextLog print_to_stdout; if (nullptr == text_log_ptr) { print_to_stdout.SetIndentSize(2); text_log_ptr = &print_to_stdout; } return Internal_PrintHelp(example_test_exe_stem, *text_log_ptr); } static char Internal_ParseOptionTailSeparator() { return ':'; } static const char* Internal_ParseOptionHead( const char* s, const char* option_name0, const char* option_name1 = nullptr, const char* option_name2 = nullptr ) { if (nullptr == s || 0 == s[0]) return nullptr; if ('-' != s[0] ) { #if defined(ON_OS_WINDOWS) if ( '/' != s[0] ) #endif return nullptr; } s++; if (0 == s[0]) return nullptr; const char* option_names[] = { option_name0, option_name1, option_name2 }; const size_t option_name_count = sizeof(option_names) / sizeof(option_names[0]); for (size_t i = 0; i < option_name_count; i++) { const char* option_name = option_names[i]; const int option_name_length = ON_String::Length(option_name); if (option_name_length < 1) continue; if (ON_String::EqualOrdinal(option_name, option_name_length, s, option_name_length, true)) { const char* tail = s + option_name_length; if (0 == tail[0] || Internal_ParseOptionTailSeparator() == tail[0]) return tail; } } return nullptr; } static const ON_String Internal_ParseOptionTail( const char* s ) { for (;;) { if (nullptr == s || Internal_ParseOptionTailSeparator() != s[0] || s[1] <= ON_String::Space) break; ON_String tail(s+1); tail.TrimRight(); int len = tail.Length(); if (len < 1) break; if (len >= 2) { bool bTrim = false; if ('"' == tail[0] && '"' == tail[len - 1]) bTrim = true; else if ('\'' == tail[0] && '\'' == tail[len - 1]) bTrim = true; if (bTrim) { tail = ON_String(s + 2, len - 2); if (tail.Length() < 1) break; } } return tail; } return ON_String::EmptyString; } static bool Internal_ParseArg_HELP(const ON_String arg) { const char* tail = Internal_ParseOptionHead(static_cast(arg), "help", "h", "?" ); return (nullptr != tail && 0 == tail[0]); } static bool Internal_ParseArg_VERBOSE(const ON_String arg) { const char* tail = Internal_ParseOptionHead(static_cast(arg), "verbose", "v" ); return (nullptr != tail && 0 == tail[0]); } static bool Internal_ParseArg_OUT(const ON_String exe_stem, const ON_String arg, ON_String& output_file_path) { const char* tail = Internal_ParseOptionHead(static_cast(arg), "out" ); if (nullptr == tail) return false; if (0 == tail[0]) { output_file_path = Internal_DefaultOutFileName(exe_stem); } else if (Internal_ParseOptionTailSeparator() == tail[0]) { output_file_path = ON_FileSystemPath::ExpandUser(Internal_ParseOptionTail(tail)); output_file_path.TrimLeftAndRight(); if ( false == output_file_path.EqualOrdinal("stdout",true) && false == output_file_path.EqualOrdinal("stderr",true) && false == output_file_path.EqualOrdinal("null",true) && false == output_file_path.EqualOrdinal("dev/null",true) && ON_FileSystem::IsDirectory(output_file_path) ) { const ON_wString wide_dir(output_file_path); const ON_wString wide_fname(Internal_DefaultOutFileName(exe_stem)); output_file_path = ON_String(ON_FileSystemPath::CombinePaths(wide_dir, false, wide_fname, true, false)); } } else { output_file_path = ON_String::EmptyString; } return true; } static bool Internal_ParseArg_RECURSE(const ON_String arg, unsigned int& N) { const char* tail = Internal_ParseOptionHead(static_cast(arg), "recursive", "r" ); if (nullptr == tail) return false; if (0 == tail[0]) { N = 16; // sanity limit for default directory recursion depth; return true; } if (Internal_ParseOptionTailSeparator() == tail[0]) { const ON_String num = Internal_ParseOptionTail(tail); if (num.IsNotEmpty()) { unsigned int u = 0; const char* s1 = ON_String::ToNumber(num, u, &u); if (nullptr != s1 && s1 > static_cast(num) && u >= 1 && 0 == s1[0]) { N = u; return true; } } } N = 0; return false; } static const ON_String Internal_ParseArg_PATH(const ON_String arg, unsigned int max_directory_tree_depth) { ON_String arg_full_path = ON_FileSystemPath::ExpandUser(static_cast(arg)); arg_full_path.TrimLeftAndRight(); if (ON_FileSystem::IsFile(arg_full_path)) return arg_full_path; if (max_directory_tree_depth > 0) { if (arg_full_path.Length() != 1 || false == ON_FileSystemPath::IsDirectorySeparator(arg_full_path[0], true)) { const char dir_seps[3] = { ON_FileSystemPath::DirectorySeparatorAsChar, ON_FileSystemPath::AlternateDirectorySeparatorAsChar, 0 }; arg_full_path.TrimRight(dir_seps); } if (ON_FileSystem::IsDirectory(arg_full_path)) return arg_full_path; } return ON_String::EmptyString; } static void Internal_PrintIntroduction( const ON_String& example_test_exe_path, ON_TextLog& text_log ) { text_log.Print("Executable: %s\n", static_cast(example_test_exe_path)); text_log.Print("Compiled: " __DATE__ " " __TIME__ "\n"); const ON_String platform_name = Internal_PlatformId(true); ON::Version(); text_log.Print("Platform: %s\n", static_cast(platform_name)); text_log.Print( "Opennurbs version: %u.%u %04u-%02u-%02u %02u:%02u (%u)\n", ON::VersionMajor(), ON::VersionMinor(), ON::VersionYear(), ON::VersionMonth(), ON::VersionDayOfMonth(), ON::VersionHour(), ON::VersionMinute(), ON::VersionBranch() ); text_log.Print("Current time: "); text_log.PrintCurrentTime(); text_log.Print(" UCT\n"); } int main( int argc, const char *argv[] ) { // If you are using OpenNURBS as a Windows DLL, then you MUST use // ON::OpenFile() to open the file. If you are not using OpenNURBS // as a Windows DLL, then you may use either ON::OpenFile() or fopen() // to open the file. ON_ErrorLog verbose_log; verbose_log.EnableLogging(); ON_String arg(nullptr != argv ? argv[0] : ((const char*)nullptr)); arg.TrimLeftAndRight(); const ON_String example_test_exe_path(arg); ON_String example_test_exe_stem; ON_FileSystemPath::SplitPath(example_test_exe_path, nullptr, nullptr, &example_test_exe_stem, nullptr); if (example_test_exe_stem.IsEmpty()) example_test_exe_stem = "example_test"; ON_TextLog print_to_stdout; print_to_stdout.SetIndentSize(2); int argi; if ( argc < 2 ) { Internal_PrintHelp(example_test_exe_stem, print_to_stdout); return 0; } // Call once in your application to initialze opennurbs library ON::Begin(); // default text_log is to stdout ON_TextLog* text_log = &print_to_stdout; FILE* text_log_fp = nullptr; unsigned int maximum_directory_depth = 0; bool bVerbose = false; // true = output will include a full listing of all 3dm file contents. ONX_ErrorCounter err; unsigned int file_count = 0; unsigned int folder_count = 0; unsigned int directory_arg_counter = 0; bool bPrintIntroduction = true; for ( argi = 1; argi < argc; argi++ ) { arg = argv[argi]; arg.TrimLeftAndRight(); if (arg.IsEmpty()) continue; if (argi + 1 == argc && Internal_ParseArg_HELP(arg)) { Internal_PrintHelp(example_test_exe_stem, text_log); if ( 0 == folder_count && 0 == file_count && 0 == err.TotalCount() ) return 0; continue; } // check for -out: option ON_String output_file_name; if ( Internal_ParseArg_OUT(example_test_exe_stem, arg, output_file_name) ) { // need a new introduction when output destination changes. bPrintIntroduction = true; // change destination of text_log file if ( text_log != &print_to_stdout && text_log != &ON_TextLog::Null ) { delete text_log; } if ( text_log_fp ) { ON::CloseFile(text_log_fp); text_log_fp = nullptr; } text_log = &print_to_stdout; if (output_file_name.IsEmpty() || output_file_name.EqualOrdinal("stdout", true)) continue; if (output_file_name.EqualOrdinal("null", true) || output_file_name.EqualOrdinal("dev/null", true)) { text_log = &ON_TextLog::Null; continue; } FILE* text_fp = ON::OpenFile(output_file_name,"w"); if (nullptr == text_fp) { err += Internal_InvalidArg(arg, *text_log); break; } text_log_fp = text_fp; text_log = new ON_TextLog(text_log_fp); text_log->SetIndentSize(2); continue; } // check for -recursive: option if (Internal_ParseArg_RECURSE(arg, maximum_directory_depth)) { continue; } if (Internal_ParseArg_VERBOSE(arg)) { bVerbose = true; continue; } if (bPrintIntroduction) { bPrintIntroduction = false; Internal_PrintIntroduction(example_test_exe_path, *text_log); } const ON_String arg_full_path = Internal_ParseArg_PATH(arg,maximum_directory_depth); if (arg_full_path.IsEmpty()) { err += Internal_InvalidArg(arg, *text_log); break; } err += Internal_Test( maximum_directory_depth, arg_full_path, bVerbose, *text_log, directory_arg_counter, file_count, folder_count ); } if (bPrintIntroduction) { bPrintIntroduction = false; Internal_PrintIntroduction(example_test_exe_path, *text_log); } if (folder_count > 0) { text_log->Print( "Tested %u 3dm files in %u folders.", file_count, folder_count ); } else { text_log->Print( "Tested %u 3dm files.", file_count ); } if (err.FailureCount() > 0) text_log->Print(" Failures. "); else if (err.ErrorCount() > 0) { text_log->Print(" Errors:\n"); for (int vbli = 0; vbli < verbose_log.Count(); vbli++) { text_log->Print(" !! "); text_log->Print(verbose_log.Event(vbli).Description()); text_log->Print("\n"); } } else if (err.FailureCount() > 0) text_log->Print(" Warnings. "); else text_log->Print(" All tests passed. "); err.Dump(*text_log); text_log->PrintNewLine(); if ( text_log != &print_to_stdout && text_log != &ON_TextLog::Null ) { delete text_log; } text_log = nullptr; if ( text_log_fp ) { // close the text text_log file ON::CloseFile( text_log_fp ); text_log_fp = 0; } // OPTIONAL: Call just before your application exits to clean // up opennurbs class definition information. // Opennurbs will not work correctly after ON::End() // is called. ON::End(); return err.TotalCount(); }