diff --git a/example_test/example_test.cpp b/example_test/example_test.cpp index 9adafcbb..70bf3276 100644 --- a/example_test/example_test.cpp +++ b/example_test/example_test.cpp @@ -147,7 +147,8 @@ static const ONX_ErrorCounter Internal_TestModelRead( ONX_ErrorCounter error_counter = test.ErrorCounter(); - const ONX_Model* source_model = test.SourceModel().get(); + std::shared_ptr source_model_sp = test.SourceModel(); + const ONX_Model* source_model = source_model_sp.get(); if (nullptr == source_model) { text_log.PopIndent(); @@ -156,18 +157,46 @@ static const ONX_ErrorCounter Internal_TestModelRead( const bool bCompareTestFailed = ONX_ModelTest::Result::Fail == test.TestResult(ONX_ModelTest::Type::ReadWriteReadCompare); - if ( bVerbose || bCompareTestFailed ) + if (bCompareTestFailed) { - for (int i = 0; i < 2; i++) + // Dump the text used to generate the hashes that were different + // into 2 text files so a human can look at the diffs and decide + // what to do. + + ON_wString source_model_hash_log_filename; + ON_SHA1_Hash source_model_hash; + ON_wString copy_model_hash_log_filename; + ON_SHA1_Hash copy_model_hash; + const bool bDumpedHashLogs = test.DumpHashLogs( + source_model_hash_log_filename, + source_model_hash, + copy_model_hash_log_filename, + copy_model_hash + ); + if (bDumpedHashLogs) { - if (0 == i) - test.DumpSourceModel(); - else - test.DumpReadWriteReadModel(); - if (false == bCompareTestFailed) - break; + bVerbose = false; + text_log.PushIndent(); + text_log.PushIndent(); + text_log.Print(L"Compare these hash logs to see what changed in the temporary file.\n"); + text_log.PushIndent(); + text_log.Print(L"Source model hash log:\n"); + text_log.PushIndent(); + text_log.Print(L"\"%ls\"\n", static_cast(source_model_hash_log_filename)); + text_log.PopIndent(); + text_log.Print(L"Read-Write-Read model hash log:\n", static_cast(copy_model_hash_log_filename)); + text_log.PushIndent(); + text_log.Print(L"\"%ls\"\n", static_cast(copy_model_hash_log_filename)); + text_log.PopIndent(); + text_log.PopIndent(); + text_log.PopIndent(); + text_log.PopIndent(); } } + else if ( bVerbose ) + { + test.DumpSourceModel(); + } text_log.PrintNewLine(); @@ -404,14 +433,14 @@ static const ONX_ErrorCounter Internal_TestReadFolder( if (nullptr == directory_path || 0 == directory_path[0]) { - text_log.Print("Empty directory name.\n"); + text_log.Print("Empty folder name.\n"); } ON_FileIterator fit; if (false == fit.Initialize(directory_path)) { text_log.Print( - "Invalid directory name: %s\n", + "Invalid folder name: %s\n", directory_path ); error_counter.IncrementFailureCount(); @@ -423,7 +452,7 @@ static const ONX_ErrorCounter Internal_TestReadFolder( ? ON_String(directory_path) : test_context.TextLogPathFromFullPath(directory_path); text_log.Print( - "Directory name: %s\n", + "Folder name: %s\n", static_cast(text_log_directory_name) ); text_log.PushIndent(); @@ -523,7 +552,7 @@ static ONX_ErrorCounter Internal_Test( { if (ON_FileStream::Is3dmFile(full_path, false)) { - text_log.Print("Testing 3dm file: %s\n", static_cast(full_path)); + text_log.PrintNewLine(); // ("Testing 3dm file: %s\n", static_cast(full_path)); err = Internal_TestFileRead(text_log, full_path, ON_String::EmptyString, bVerbose); file_count++; } @@ -533,6 +562,7 @@ static ONX_ErrorCounter Internal_Test( if ( max_directory_tree_depth > 0 ) { text_log.Print("Testing 3dm files in folder: %s\n", static_cast(full_path)); + text_log.PrintNewLine(); Internal_CTestContext test_context; directory_counter++; test_context.SetInitialDirecory(full_path,directory_counter); @@ -599,16 +629,27 @@ 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 vN = ON_String::FormatToString("v%u", ON::VersionMajor()); - 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"; + ON_String file_name(exe_stem); + file_name.TrimLeftAndRight(); + if (file_name.IsEmpty()) + file_name = "example_test"; + file_name += "_log"; + + ON_String default_file_name = vN + file_name + ON_String(".txt"); + + const ON_wString desktop_folder = ON_FileSystemPath::PlatformPath(ON_FileSystemPath::PathId::DesktopDirectory); + if (desktop_folder.IsNotEmpty() && ON_FileSystem::IsDirectory(static_cast(desktop_folder))) + { + const ON_wString wdefault_file_name(default_file_name); + const ON_wString desktop_default_file_name = ON_FileSystemPath::CombinePaths( + static_cast(desktop_folder), false, + static_cast(wdefault_file_name), true, + false); + if (desktop_default_file_name.IsNotEmpty()) + default_file_name = ON_String(desktop_default_file_name); + } return default_file_name; } @@ -829,24 +870,23 @@ static bool Internal_ParseArg_RECURSE(const ON_String arg, unsigned int& N) } -static const ON_String Internal_ParseArg_PATH(const ON_String arg, unsigned int max_directory_tree_depth) +static const ON_String Internal_ParseArg_PATH(const ON_String arg, bool& bArgIsDirectory) { + bArgIsDirectory = false; 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)) { - 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; + const char dir_seps[3] = { ON_FileSystemPath::DirectorySeparatorAsChar, ON_FileSystemPath::AlternateDirectorySeparatorAsChar, 0 }; + arg_full_path.TrimRight(dir_seps); } + bArgIsDirectory = ON_FileSystem::IsDirectory(arg_full_path); + if (bArgIsDirectory) + return arg_full_path; return ON_String::EmptyString; } @@ -922,10 +962,11 @@ int main( int argc, const char *argv[] ) unsigned int directory_arg_counter = 0; bool bPrintIntroduction = true; + ON_String current_output_filename = ON_String::EmptyString; for ( argi = 1; argi < argc; argi++ ) { - arg = argv[argi]; + arg = (nullptr != argv) ? argv[argi] : ((const char*)nullptr); arg.TrimLeftAndRight(); if (arg.IsEmpty()) continue; @@ -952,14 +993,21 @@ int main( int argc, const char *argv[] ) } if ( text_log_fp ) { + if (current_output_filename.IsNotEmpty()) + { + print_to_stdout.Print("Results saved in %s\n", static_cast(current_output_filename)); + } ON::CloseFile(text_log_fp); text_log_fp = nullptr; + current_output_filename = ON_String::EmptyString; } 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)) { @@ -978,6 +1026,11 @@ int main( int argc, const char *argv[] ) text_log = new ON_TextLog(text_log_fp); text_log->SetIndentSize(2); + current_output_filename = output_file_name; + if (current_output_filename.IsNotEmpty()) + { + print_to_stdout.Print("Results will be saved in %s\n", static_cast(current_output_filename)); + } continue; } @@ -999,15 +1052,33 @@ int main( int argc, const char *argv[] ) Internal_PrintIntroduction(example_test_exe_path, *text_log); } - const ON_String arg_full_path = Internal_ParseArg_PATH(arg,maximum_directory_depth); + bool bArgIsDirectory = false; + const ON_String arg_full_path = Internal_ParseArg_PATH(arg, bArgIsDirectory); if (arg_full_path.IsEmpty()) { err += Internal_InvalidArg(arg, *text_log); break; } + const unsigned directory_recursion_depth = ((0 == maximum_directory_depth && bArgIsDirectory) ? 1 : maximum_directory_depth); + + if (current_output_filename.IsNotEmpty()) + { + if (bArgIsDirectory) + { + if (directory_recursion_depth > 1) + print_to_stdout.Print("Testing folder tree %s ...\n", static_cast(arg_full_path)); + else + print_to_stdout.Print("Testing folder %s ...\n", static_cast(arg_full_path)); + } + else + { + print_to_stdout.Print("Testing file %s ...\n", static_cast(arg_full_path)); + } + } + err += Internal_Test( - maximum_directory_depth, + directory_recursion_depth, arg_full_path, bVerbose, *text_log, @@ -1043,7 +1114,7 @@ int main( int argc, const char *argv[] ) text_log->Print(" Failures. "); else if (err.ErrorCount() > 0) { text_log->Print(" Errors:\n"); - for (int vbli = 0; vbli < verbose_log.Count(); vbli++) { + for (unsigned vbli = 0; vbli < verbose_log.Count(); vbli++) { text_log->Print(" !! "); text_log->Print(verbose_log.Event(vbli).Description()); text_log->Print("\n"); @@ -1056,18 +1127,18 @@ int main( int argc, const char *argv[] ) 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 ) { + if (current_output_filename.IsNotEmpty()) + { + print_to_stdout.Print("Results saved in %s\n", static_cast(current_output_filename)); + } // close the text text_log file ON::CloseFile( text_log_fp ); text_log_fp = 0; + current_output_filename = ON_String::EmptyString; } // OPTIONAL: Call just before your application exits to clean diff --git a/opennurbs_3dm_attributes.cpp b/opennurbs_3dm_attributes.cpp index 71cdd60b..a0ed640b 100644 --- a/opennurbs_3dm_attributes.cpp +++ b/opennurbs_3dm_attributes.cpp @@ -22,6 +22,9 @@ #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif +// TODO: This really needs to be a member of ON_3dmObjectAttributes but that would break the SDK. +std::recursive_mutex g_mutex; + class ON_3dmObjectAttributesPrivate { public: @@ -1076,6 +1079,7 @@ bool ON_3dmObjectAttributes::Internal_WriteV5( ON_BinaryArchive& file ) const // not actually needed when running Rhino because the RDK decal UI directly updates // the user data when changes are made. This is only needed when using ONX_Model and // File3dm outside of Rhino, in case the programmer sets a decal property. + std::lock_guard lg(g_mutex); const unsigned int archive_3dm_version = file.Archive3dmVersion(); m_private->m_decals.UpdateUserData(archive_3dm_version); } @@ -2519,22 +2523,49 @@ void ON_3dmObjectAttributes::SetObjectFrame(const ON_COMPONENT_INDEX& ci, const ON_MeshModifiers& ON_3dmObjectAttributes::MeshModifiers(void) const { + std::lock_guard lg(g_mutex); + if (nullptr == m_private) m_private = new ON_3dmObjectAttributesPrivate(this); return m_private->m_mesh_modifiers; } -const ON_SimpleArray& ON_3dmObjectAttributes::GetDecalArray(void) const +const ON_SimpleArray& ON_3dmObjectAttributes::GetDecalArray(void) const // Deprecated. { + std::vector> decals; + GetDecalArray(decals); + + static ON_SimpleArray dummy; + dummy.Destroy(); + + for (const auto& decal_sp : decals) + { + dummy.Append(decal_sp.get()); + } + + return dummy; +} + +void ON_3dmObjectAttributes::GetDecalArray(std::vector>& array_out) const +{ + std::lock_guard lg(g_mutex); + if (nullptr == m_private) m_private = new ON_3dmObjectAttributesPrivate(this); - return m_private->m_decals.GetDecalArray(); + array_out = m_private->m_decals.GetDecalArray(); } -ON_Decal* ON_3dmObjectAttributes::AddDecal(void) +ON_Decal* ON_3dmObjectAttributes::AddDecal(void) // Deprecated. { + return AddDecalEx().get(); +} + +const std::shared_ptr ON_3dmObjectAttributes::AddDecalEx(void) +{ + std::lock_guard lg(g_mutex); + if (nullptr == m_private) m_private = new ON_3dmObjectAttributesPrivate(this); @@ -2543,6 +2574,8 @@ ON_Decal* ON_3dmObjectAttributes::AddDecal(void) bool ON_3dmObjectAttributes::RemoveDecal(ON_Decal& decal) { + std::lock_guard lg(g_mutex); + if (nullptr == m_private) m_private = new ON_3dmObjectAttributesPrivate(this); @@ -2551,6 +2584,8 @@ bool ON_3dmObjectAttributes::RemoveDecal(ON_Decal& decal) void ON_3dmObjectAttributes::RemoveAllDecals(void) { + std::lock_guard lg(g_mutex); + if (nullptr == m_private) m_private = new ON_3dmObjectAttributesPrivate(this); diff --git a/opennurbs_3dm_attributes.h b/opennurbs_3dm_attributes.h index 833437bd..31838224 100644 --- a/opennurbs_3dm_attributes.h +++ b/opennurbs_3dm_attributes.h @@ -518,19 +518,26 @@ public: // Decals. + // This method is deprecated in favor of the one below. + ON_DEPRECATED const ON_SimpleArray& GetDecalArray(void) const; + /* Description: Get an array of decals that are stored on this attributes object. - Do not store or delete pointers from the array. + Param array_out is first cleared and then filled with shared pointers to decals (if any). + Do not store or delete raw pointers from the array. */ - const ON_SimpleArray& GetDecalArray(void) const; + void GetDecalArray(std::vector>& array_out) const; + + // This method is deprecated in favor of AddDecalEx(). + ON_DEPRECATED ON_Decal* AddDecal(void); /* Description: Add a new decal to this attributes object. The returned pointer points to an object that is owned by the attributes. Do not store or delete it. */ - ON_Decal* AddDecal(void); + const std::shared_ptr AddDecalEx(void); /* Description: diff --git a/opennurbs_archive.cpp b/opennurbs_archive.cpp index c338b1c4..2c09f65a 100644 --- a/opennurbs_archive.cpp +++ b/opennurbs_archive.cpp @@ -4620,6 +4620,24 @@ ON_BinaryArchive::WriteObject( const ON_Object& model_object ) return Internal_WriteObject(V2_text_dot); } break; + + case ON::object_type::subd_object: + { + if (m_3dm_version >= 60) + break; + const ON_SubD* subd = ON_SubD::Cast(&model_object); + if (nullptr == subd) + break; + + // Use a SubD mesh proxy for V5 and earlier file formats. + std::unique_ptr mesh(ON_SubDMeshProxyUserData::MeshProxyFromSubD(subd)); + if (nullptr == mesh) + return false; + + return Internal_WriteObject(*mesh.get()); + } + break; + default: break; } @@ -6578,65 +6596,46 @@ bool ON_BinaryArchive::EndRead3dmChunk(bool bSupressPartiallyReadChunkWarning) { // partially read chunk - happens when chunks are skipped or old code // reads a new minor version of a chunk whnich has added information. - if ( file_offset != c->m_start_offset ) + if ( file_offset != c->m_start_offset) { - if ( m_3dm_version != 1 || (m_error_message_mask&0x02) == 0 ) + for (;;) { - // when reading v1 files, there are some situations where - // it is reasonable to attempt to read 4 bytes at the end - // of a file. The above test prevents making a call - // to ON_WARNING() in these situations. - - unsigned int file_year = 0; - unsigned int file_month = 0; - unsigned int file_date = 0; - unsigned int file_major_version = 0; - const bool bHaveFileDate = ON_VersionNumberParse( - m_3dm_opennurbs_version, - &file_major_version, - 0, - &file_year, - &file_month, - &file_date, - 0 - ); - - const unsigned int file_ymd - = bHaveFileDate - ? ((file_year * 100 + file_month) * 100 + file_date) - : 0; - - unsigned int app_year = 0; - unsigned int app_month = 0; - unsigned int app_date = 0; - unsigned int app_major_version = 0; - const bool bHaveAppDate = ON_VersionNumberParse( - ON::Version(), - &app_major_version, - 0, - &app_year, - &app_month, - &app_date, - 0 - ); - - const unsigned int app_ymd - = bHaveAppDate - ? ((app_year * 100 + app_month) * 100 + app_date) - : 0; - - if (file_major_version <= app_major_version - && file_ymd <= app_ymd - ) + if (bSupressPartiallyReadChunkWarning) { - // We are reading a file written by this version or an - // earlier version of opennurbs. - // There should not be any partially read chunks. - if (!bSupressPartiallyReadChunkWarning) - { - ON_WARNING("ON_BinaryArchive::EndRead3dmChunk: partially read chunk - skipping bytes at end of current chunk."); - } + // The calling code expects there to be a partially read chunk. + break; } + + // The calling code had no reason to supress warnings about this chunk + // being partially read. + const bool bIsV1EndOfFile = this->Archive3dmVersion() == 1 && 0 != (m_error_message_mask & 0x02); + if (bIsV1EndOfFile) + { + // when reading v1 files, there are some situations where + // it is reasonable to attempt to read 4 bytes at the end + // of a file. The above test prevents making a call + // to ON_WARNING() in these situations. + break; + } + + // m_3dm_opennurbs_version = version of opennurbs that wrote this 3dm file. + // ON::Version() = this version of opennurbs. + if (ON_VersionNumberCompare(m_3dm_opennurbs_version, ON::Version(), 2) <= 0) + { + // We are reading a file that was written by this version or an earlier version of opennurbs. + // This chunk should have been completely read. + // Typically, this is a bug that can be fixed after carefully studying why it occured. + // Either there is a bug in the reading or writing of the chunk or new informaton was + // added at the end of a chunk and the opennurbs major version or YYMMDD was not + // correctly set. + // In rare cases, somebody did something more seriously wrong and likely harder to figure out. + // + // In any case, issue a warning and continue reading. This is not a fatal problem but + // it indicates information is being lost. + ON_WARNING("ON_BinaryArchive::EndRead3dmChunk: partially read chunk - skipping bytes at end of current chunk."); + } + + break; } } @@ -18424,12 +18423,12 @@ const void* ON_Read3dmBufferArchive::Buffer() const return (const void*)m_buffer; } -ON_Write3dmBufferArchive::ON_Write3dmBufferArchive( - size_t initial_sizeof_buffer, - size_t max_sizeof_buffer, - int archive_3dm_version, - unsigned int archive_opennurbs_version - ) +ON_Write3dmBufferArchive::ON_Write3dmBufferArchive( + size_t initial_sizeof_buffer, + size_t max_sizeof_buffer, + int archive_3dm_version, + unsigned int archive_opennurbs_version + ) : ON_BinaryArchive(ON::archive_mode::write3dm) , m_p(0) , m_buffer(0) diff --git a/opennurbs_decals.cpp b/opennurbs_decals.cpp index bbe05b96..8b2497f8 100644 --- a/opennurbs_decals.cpp +++ b/opennurbs_decals.cpp @@ -26,11 +26,6 @@ static ON_4dPoint UNSET_4D_POINT = ON_4dPoint(ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE); -ON_DECAL_CRC ON_DecalCRCFromNode(const ON_XMLNode& node) -{ - return ON_Decal::ComputeDecalCRC(0, node); -} - class ON_Decal::CImpl : public ON_InternalXMLImpl { public: @@ -147,6 +142,8 @@ void ON_Decal::CImpl::SetParameter(const wchar_t* param_name, const ON_XMLVarian ON_UUID ON_Decal::CImpl::TextureInstanceId(void) const { + std::lock_guard lg(_mutex); + if (!_cache.texture_instance_id_set) { _cache.texture_instance_id = GetParameter(ON_RDK_DECAL_TEXTURE_INSTANCE, ON_nil_uuid).AsUuid(); @@ -158,6 +155,8 @@ ON_UUID ON_Decal::CImpl::TextureInstanceId(void) const void ON_Decal::CImpl::SetTextureInstanceId(const ON_UUID& id) { + std::lock_guard lg(_mutex); + if (!_cache.texture_instance_id_set || (_cache.texture_instance_id != id)) { _cache.texture_instance_id = id; @@ -168,6 +167,8 @@ void ON_Decal::CImpl::SetTextureInstanceId(const ON_UUID& id) ON_Decal::Mappings ON_Decal::CImpl::Mapping(void) const { + std::lock_guard lg(_mutex); + if (Mappings::None == _cache.mapping) { const ON_wString s = GetParameter(ON_RDK_DECAL_MAPPING, ON_RDK_DECAL_MAPPING_UV).AsString(); @@ -179,6 +180,8 @@ ON_Decal::Mappings ON_Decal::CImpl::Mapping(void) const void ON_Decal::CImpl::SetMapping(Mappings m) { + std::lock_guard lg(_mutex); + if (_cache.mapping != m) { _cache.mapping = m; @@ -200,6 +203,8 @@ void ON_Decal::CImpl::SetMapping(Mappings m) ON_Decal::Projections ON_Decal::CImpl::Projection(void) const { + std::lock_guard lg(_mutex); + if (Projections::None == _cache.projection) { const ON_wString s = GetParameter(ON_RDK_DECAL_PROJECTION, ON_RDK_DECAL_PROJECTION_NONE).AsString(); @@ -215,6 +220,8 @@ ON_Decal::Projections ON_Decal::CImpl::Projection(void) const void ON_Decal::CImpl::SetProjection(Projections v) { + std::lock_guard lg(_mutex); + if (_cache.projection != v) { _cache.projection = v; @@ -235,6 +242,8 @@ void ON_Decal::CImpl::SetProjection(Projections v) bool ON_Decal::CImpl::MapToInside(void) const { + std::lock_guard lg(_mutex); + if (unset_bool == _cache.map_to_inside) { _cache.map_to_inside = GetParameter(ON_RDK_DECAL_MAP_TO_INSIDE_ON, false).AsBool() ? 1 : 0; @@ -245,6 +254,8 @@ bool ON_Decal::CImpl::MapToInside(void) const void ON_Decal::CImpl::SetMapToInside(bool b) { + std::lock_guard lg(_mutex); + const int i = b ? 1 : 0; if (_cache.map_to_inside != i) { @@ -255,6 +266,8 @@ void ON_Decal::CImpl::SetMapToInside(bool b) double ON_Decal::CImpl::Transparency(void) const { + std::lock_guard lg(_mutex); + if (ON_UNSET_VALUE == _cache.transparency) { _cache.transparency = GetParameter(ON_RDK_DECAL_TRANSPARENCY, 0.0).AsDouble(); @@ -265,6 +278,8 @@ double ON_Decal::CImpl::Transparency(void) const void ON_Decal::CImpl::SetTransparency(double v) { + std::lock_guard lg(_mutex); + if (_cache.transparency != v) { _cache.transparency = v; @@ -274,6 +289,8 @@ void ON_Decal::CImpl::SetTransparency(double v) ON_3dPoint ON_Decal::CImpl::Origin(void) const { + std::lock_guard lg(_mutex); + if (ON_3dPoint::UnsetPoint == _cache.origin) { _cache.origin = GetParameter(ON_RDK_DECAL_ORIGIN, ON_3dPoint::Origin).As3dPoint(); @@ -284,6 +301,8 @@ ON_3dPoint ON_Decal::CImpl::Origin(void) const void ON_Decal::CImpl::SetOrigin(const ON_3dPoint& pt) { + std::lock_guard lg(_mutex); + if (_cache.origin != pt) { _cache.origin = pt; @@ -293,6 +312,8 @@ void ON_Decal::CImpl::SetOrigin(const ON_3dPoint& pt) ON_3dVector ON_Decal::CImpl::VectorUp(void) const { + std::lock_guard lg(_mutex); + if (ON_3dVector::UnsetVector == _cache.vector_up) { _cache.vector_up = GetParameter(ON_RDK_DECAL_VECTOR_UP, ON_3dPoint::Origin).As3dPoint(); @@ -303,6 +324,8 @@ ON_3dVector ON_Decal::CImpl::VectorUp(void) const void ON_Decal::CImpl::SetVectorUp(const ON_3dVector& v) { + std::lock_guard lg(_mutex); + if (_cache.vector_up != v) { _cache.vector_up = v; @@ -312,6 +335,8 @@ void ON_Decal::CImpl::SetVectorUp(const ON_3dVector& v) ON_3dVector ON_Decal::CImpl::VectorAcross(void) const { + std::lock_guard lg(_mutex); + if (ON_3dVector::UnsetVector == _cache.vector_across) { _cache.vector_across = GetParameter(ON_RDK_DECAL_VECTOR_ACROSS, ON_3dPoint::Origin).As3dPoint(); @@ -322,6 +347,8 @@ ON_3dVector ON_Decal::CImpl::VectorAcross(void) const void ON_Decal::CImpl::SetVectorAcross(const ON_3dVector& v) { + std::lock_guard lg(_mutex); + if (_cache.vector_across != v) { _cache.vector_across = v; @@ -331,6 +358,8 @@ void ON_Decal::CImpl::SetVectorAcross(const ON_3dVector& v) double ON_Decal::CImpl::Height(void) const { + std::lock_guard lg(_mutex); + if (ON_UNSET_VALUE == _cache.height) { _cache.height = GetParameter(ON_RDK_DECAL_HEIGHT, 1.0).AsDouble(); @@ -341,6 +370,8 @@ double ON_Decal::CImpl::Height(void) const void ON_Decal::CImpl::SetHeight(double v) { + std::lock_guard lg(_mutex); + if (_cache.height != v) { _cache.height = v; @@ -350,6 +381,8 @@ void ON_Decal::CImpl::SetHeight(double v) double ON_Decal::CImpl::Radius(void) const { + std::lock_guard lg(_mutex); + if (ON_UNSET_VALUE == _cache.radius) { _cache.radius = GetParameter(ON_RDK_DECAL_RADIUS, 1.0).AsDouble(); @@ -360,6 +393,8 @@ double ON_Decal::CImpl::Radius(void) const void ON_Decal::CImpl::SetRadius(double v) { + std::lock_guard lg(_mutex); + if (_cache.radius != v) { _cache.radius = v; @@ -369,6 +404,8 @@ void ON_Decal::CImpl::SetRadius(double v) bool ON_Decal::CImpl::IsVisible(void) const { + std::lock_guard lg(_mutex); + if (unset_bool == _cache.visible) { _cache.visible = GetParameter(ON_RDK_DECAL_IS_VISIBLE, true).AsBool(); @@ -379,6 +416,8 @@ bool ON_Decal::CImpl::IsVisible(void) const void ON_Decal::CImpl::SetIsVisible(bool b) { + std::lock_guard lg(_mutex); + const int i = b ? 1 : 0; if (_cache.visible != i) { @@ -389,6 +428,8 @@ void ON_Decal::CImpl::SetIsVisible(bool b) void ON_Decal::CImpl::GetHorzSweep(double& sta, double& end) const { + std::lock_guard lg(_mutex); + if (ON_2dPoint::UnsetPoint == _cache.horz_sweep) { _cache.horz_sweep.x = GetParameter(ON_RDK_DECAL_HORZ_SWEEP_STA, 0.0).AsDouble(); @@ -401,6 +442,8 @@ void ON_Decal::CImpl::GetHorzSweep(double& sta, double& end) const void ON_Decal::CImpl::SetHorzSweep(double sta, double end) { + std::lock_guard lg(_mutex); + const auto sweep = ON_2dPoint(sta, end); if (_cache.horz_sweep != sweep) { @@ -412,6 +455,8 @@ void ON_Decal::CImpl::SetHorzSweep(double sta, double end) void ON_Decal::CImpl::GetVertSweep(double& sta, double& end) const { + std::lock_guard lg(_mutex); + if (ON_2dPoint::UnsetPoint == _cache.vert_sweep) { _cache.vert_sweep.x = GetParameter(ON_RDK_DECAL_VERT_SWEEP_STA, 0.0).AsDouble(); @@ -424,6 +469,8 @@ void ON_Decal::CImpl::GetVertSweep(double& sta, double& end) const void ON_Decal::CImpl::SetVertSweep(double sta, double end) { + std::lock_guard lg(_mutex); + const auto sweep = ON_2dPoint(sta, end); if (_cache.vert_sweep != sweep) { @@ -435,6 +482,8 @@ void ON_Decal::CImpl::SetVertSweep(double sta, double end) void ON_Decal::CImpl::GetUVBounds(double& min_u, double& min_v, double& max_u, double& max_v) const { + std::lock_guard lg(_mutex); + if (UNSET_4D_POINT == _cache.uv_bounds) { _cache.uv_bounds.x = GetParameter(ON_RDK_DECAL_MIN_U, 0.0).AsDouble(); @@ -451,6 +500,8 @@ void ON_Decal::CImpl::GetUVBounds(double& min_u, double& min_v, double& max_u, d void ON_Decal::CImpl::SetUVBounds(double min_u, double min_v, double max_u, double max_v) { + std::lock_guard lg(_mutex); + const auto bounds = ON_4dPoint(min_u, min_v, max_u, max_v); if (_cache.uv_bounds != bounds) { @@ -464,6 +515,8 @@ void ON_Decal::CImpl::SetUVBounds(double min_u, double min_v, double max_u, doub ON_XMLNode* ON_Decal::CImpl::FindCustomNodeForRenderEngine(const ON_UUID& renderEngineId) const { + std::lock_guard lg(_mutex); + ON_XMLNode* child_node = nullptr; auto it = Node().GetChildIterator(); while (nullptr != (child_node = it.GetNextChild())) @@ -521,6 +574,9 @@ const ON_Decal& ON_Decal::operator = (const ON_Decal& d) { if (this != &d) { + std::lock_guard lg1(_impl->_mutex); + std::lock_guard lg2(d._impl->_mutex); + _impl->Node() = d._impl->Node(); } @@ -531,6 +587,9 @@ bool ON_Decal::operator == (const ON_Decal& d) const { // This only checks if the basic parameters are equal. It ignores any custom data. + std::lock_guard lg1(_impl->_mutex); + std::lock_guard lg2(d._impl->_mutex); + if (TextureInstanceId() != d.TextureInstanceId()) return false; if (Mapping() != d.Mapping()) return false; if (Projection() != d.Projection()) return false; @@ -715,18 +774,10 @@ ON_UUID ON_Decal::Id(void) const return _impl->Id(); } -ON_DECAL_CRC ON_Decal::DecalCRC(void) const -{ - return ComputeDecalCRC(0, _impl->Node()); -} - -ON__UINT32 ON_Decal::DataCRC(ON__UINT32 current_remainder) const -{ - return ComputeDecalCRC(current_remainder, _impl->Node()); -} - void ON_Decal::GetCustomXML(const ON_UUID& renderEngineId, ON_XMLNode& custom_param_node) const { + std::lock_guard lg(_impl->_mutex); + custom_param_node.Clear(); custom_param_node.SetTagName(ON_RDK_DECAL_CUSTOM_PARAMS); @@ -760,7 +811,12 @@ bool ON_Decal::SetCustomXML(const ON_UUID& renderEngineId, const ON_XMLNode& cus } // Attach the new custom node and set its 'renderer' property to be the render engine id. - custom_node = _impl->Node().AttachChildNode(new ON_XMLNode(ON_RDK_DECAL_CUSTOM)); + { + std::lock_guard lg(_impl->_mutex); + + custom_node = _impl->Node().AttachChildNode(new ON_XMLNode(ON_RDK_DECAL_CUSTOM)); + } + ON_XMLProperty prop(ON_RDK_DECAL_CUSTOM_RENDERER, renderEngineId); custom_node->SetProperty(prop); @@ -779,9 +835,11 @@ void ON_Decal::AppendCustomXML(const ON_XMLNode& custom_node) ON_XMLNode* child = custom_node.FirstChild(); while (nullptr != child) { + std::lock_guard lg(_impl->_mutex); + _impl->Node().AttachChildNode(new ON_XMLNode(*child)); - child = custom_node.NextSibling(); + child = child->NextSibling(); } } @@ -999,7 +1057,7 @@ static void DecalUpdateCRC_Custom(const ON_XMLNode& decal_node, ON_DECAL_CRC& cr } } -ON_DECAL_CRC ON_Decal::ComputeDecalCRC(ON__UINT32 current_remainder, const ON_XMLNode& decal_node) // Static. +static ON_DECAL_CRC ComputeDecalCRC(ON__UINT32 current_remainder, const ON_XMLNode& decal_node) // Static. { // The CRC of a decal is a unique value based on its state. It's created by CRC-ing all the decal properties // that affect the decal's appearance. We do not include the 'IsTemporary' property in the CRC because whether @@ -1021,7 +1079,7 @@ ON_DECAL_CRC ON_Decal::ComputeDecalCRC(ON__UINT32 current_remainder, const ON_XM const ON_Decal::Mappings mapping = MappingFromString(d.Mapping().AsString()); - if (Mappings::UV == mapping) + if (ON_Decal::Mappings::UV == mapping) { DecalUpdateCRC(crc, d.MinU() ON_DECAL_PROP_NAME(L"min_u")); DecalUpdateCRC(crc, d.MinV() ON_DECAL_PROP_NAME(L"min_v")); @@ -1034,26 +1092,26 @@ ON_DECAL_CRC ON_Decal::ComputeDecalCRC(ON__UINT32 current_remainder, const ON_XM DecalUpdateCRC(crc, d.VectorUp() ON_DECAL_PROP_NAME(L"up")); DecalUpdateCRC(crc, d.VectorAcross() ON_DECAL_PROP_NAME(L"across")); - if ((Mappings::Cylindrical == mapping) || (Mappings::Spherical == mapping)) + if ((ON_Decal::Mappings::Cylindrical == mapping) || (ON_Decal::Mappings::Spherical == mapping)) { DecalUpdateCRC(crc, d.MapToInside() ON_DECAL_PROP_NAME(L"map_to_inside")); DecalUpdateCRC(crc, d.Radius() ON_DECAL_PROP_NAME(L"radius")); DecalUpdateCRC(crc, d.HorzSweepSta() ON_DECAL_PROP_NAME(L"horz_sweep_sta")); DecalUpdateCRC(crc, d.HorzSweepEnd() ON_DECAL_PROP_NAME(L"horz_sweep_end")); - if (Mappings::Cylindrical == mapping) + if (ON_Decal::Mappings::Cylindrical == mapping) { DecalUpdateCRC(crc, d.Height() ON_DECAL_PROP_NAME(L"height")); } else - if (Mappings::Spherical == mapping) + if (ON_Decal::Mappings::Spherical == mapping) { DecalUpdateCRC(crc, d.VertSweepSta() ON_DECAL_PROP_NAME(L"vert_sweep_sta")); DecalUpdateCRC(crc, d.VertSweepEnd() ON_DECAL_PROP_NAME(L"vert_sweep_end")); } } else - if (Mappings::Planar == mapping) + if (ON_Decal::Mappings::Planar == mapping) { DecalUpdateCRC(crc, d.Projection() ON_DECAL_PROP_NAME(L"projection")); } @@ -1069,6 +1127,25 @@ ON_DECAL_CRC ON_Decal::ComputeDecalCRC(ON__UINT32 current_remainder, const ON_XM return crc; } +ON_DECAL_CRC ON_DecalCRCFromNode(const ON_XMLNode& node) +{ + return ComputeDecalCRC(0, node); +} + +ON_DECAL_CRC ON_Decal::DecalCRC(void) const +{ + std::lock_guard lg(_impl->_mutex); + + return ComputeDecalCRC(0, _impl->Node()); +} + +ON__UINT32 ON_Decal::DataCRC(ON__UINT32 current_remainder) const +{ + std::lock_guard lg(_impl->_mutex); + + return ComputeDecalCRC(current_remainder, _impl->Node()); +} + // ON_DecalCollection ON_DecalCollection::~ON_DecalCollection() @@ -1078,7 +1155,9 @@ ON_DecalCollection::~ON_DecalCollection() int ON_DecalCollection::FindDecalIndex(const ON_UUID& id) const { - for (int i = 0; i < m_decals.Count(); i++) + std::lock_guard lg(_mutex); + + for (int i = 0; i < m_decals.size(); i++) { if (m_decals[i]->Id() == id) return i; @@ -1087,12 +1166,12 @@ int ON_DecalCollection::FindDecalIndex(const ON_UUID& id) const return -1; } -ON_Decal* ON_DecalCollection::AddDecal(void) +std::shared_ptr ON_DecalCollection::AddDecal(void) { // Ensure the array is populated before adding a new decal. GetDecalArray(); - ON_Decal* decal = nullptr; + std::shared_ptr decal; ON_XMLNode* decals_node = m_root_node.CreateNodeAtPath(ON_RDK_UD_ROOT ON_XML_SLASH ON_RDK_DECALS); if (nullptr != decals_node) @@ -1103,8 +1182,12 @@ ON_Decal* ON_DecalCollection::AddDecal(void) // Add the new decal. It stores a pointer to the new XML node. This is safe because // the decals have the same lifetime as the root node that owns the XML nodes. - decal = new ON_Decal(*this, *decal_node); - m_decals.Append(decal); + decal = std::make_shared(*this, *decal_node); + + { + std::lock_guard lg(_mutex); + m_decals.push_back(decal); + } SetChanged(); } @@ -1143,16 +1226,21 @@ bool ON_DecalCollection::RemoveDecal(const ON_Decal& decal) return false; // Delete it. - delete m_decals[index]; - m_decals.Remove(index); + { + std::lock_guard lg(_mutex); + m_decals.erase(m_decals.begin() + index); + } return true; } void ON_DecalCollection::RemoveAllDecals(void) { - m_root_node.Clear(); - m_root_node.CreateNodeAtPath(ON_RDK_UD_ROOT); + { + std::lock_guard lg(_mutex); + m_root_node.Clear(); + m_root_node.CreateNodeAtPath(ON_RDK_UD_ROOT); + } ClearDecalArray(); } @@ -1161,16 +1249,12 @@ void ON_DecalCollection::ClearDecalArray(void) { // 12th July 2023 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-75697 // Only call SetChanged() if a decal is actually deleted. - const int count = m_decals.Count(); - if (count > 0) + + std::lock_guard lg(_mutex); + + if (!m_decals.empty()) { - for (int i = 0; i < count; i++) - { - delete m_decals[i]; - } - - m_decals.Destroy(); - + m_decals.clear(); SetChanged(); } @@ -1179,14 +1263,15 @@ void ON_DecalCollection::ClearDecalArray(void) const ON_DecalCollection& ON_DecalCollection::operator = (const ON_DecalCollection& dc) { + std::lock_guard lg(_mutex); + ClearDecalArray(); - for (int i = 0; i < dc.m_decals.Count(); i++) + for (const auto& decal : dc.m_decals) { - ON_Decal* decal = dc.m_decals[i]; - if (nullptr != decal) + if (decal) { - m_decals.Append(new ON_Decal(*decal)); + m_decals.push_back(std::make_shared(*decal)); } } @@ -1195,22 +1280,20 @@ const ON_DecalCollection& ON_DecalCollection::operator = (const ON_DecalCollecti return *this; } -const ON_SimpleArray& ON_DecalCollection::GetDecalArray(void) const +const std::vector>& ON_DecalCollection::GetDecalArray(void) const { // 19th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-86089 - // The m_populated flag is an optimization designed to make sure we only populate the array when something - // changes. Unfortunately, in Rhino 8, I forgot to add code to detect when a decal changes externally to this - // class, and the array (which is essentially a cache) becomes invalid. This is fixed in Rhino 9 but the fix - // is too complicated to backport to Rhino 8, so to get around this I'm just going to remove the optimization. + // The m_populated flag is an optimization designed to make sure we only populate the array when + // something changes. Unfortunately, in Rhino 8, I forgot to add code to detect when a decal changes + // externally to this class, and the array (which is essentially a cache) becomes invalid. This is + // fixed in Rhino 9 but the fix is too complicated to backport to Rhino 8, so to get around this I'm + // just going to remove the optimization. + + std::lock_guard lg(_mutex); //if (!m_populated) // Always repopulate the array. { - for (int i = 0; i < m_decals.Count(); i++) - { - delete m_decals[i]; - } - - m_decals.Destroy(); + m_decals.clear(); Populate(); @@ -1225,6 +1308,8 @@ void ON_DecalCollection::Populate(void) const if (nullptr == m_attr) return; + std::lock_guard lg(_mutex); + if (GetEntireDecalXML(*m_attr, m_root_node)) { const wchar_t* path = ON_RDK_UD_ROOT ON_XML_SLASH ON_RDK_DECALS; @@ -1232,13 +1317,13 @@ void ON_DecalCollection::Populate(void) const if (nullptr != decals_node) { // Iterate over the decals under the decals node adding a new decal for each one. - ON_ASSERT(m_decals.Count() == 0); + ON_ASSERT(m_decals.size() == 0); auto it = decals_node->GetChildIterator(); ON_XMLNode* child_node = nullptr; while (nullptr != (child_node = it.GetNextChild())) { - auto* decal = new ON_Decal(*const_cast(this), *child_node); - m_decals.Append(decal); + auto decal = std::make_shared(*const_cast(this), *child_node); + m_decals.push_back(decal); } } } diff --git a/opennurbs_decals.h b/opennurbs_decals.h index 997af74a..3812751d 100644 --- a/opennurbs_decals.h +++ b/opennurbs_decals.h @@ -252,8 +252,6 @@ public: bool SetCustomXML(const ON_UUID& renderEngineId, const ON_XMLNode& custom_param_node); public: // For internal use only. - static ON_DECAL_CRC ComputeDecalCRC(ON__UINT32, const ON_XMLNode&); - void GetEntireCustomXML(ON_XMLNode&) const; void AppendCustomXML(const ON_XMLNode&); private: diff --git a/opennurbs_dimensionstyle.cpp b/opennurbs_dimensionstyle.cpp index f1e25d35..a0d90998 100644 --- a/opennurbs_dimensionstyle.cpp +++ b/opennurbs_dimensionstyle.cpp @@ -3130,17 +3130,29 @@ bool ON_DimStyle::Read( rc = true; break; } - // Dale Lear April 8, 2016 - // working on http://mcneel.myjetbrains.com/youtrack/issue/RH-31796 - // bSupressPartiallyReadChunkWarning suppresses a partially read chunk warning - // of skipping 16 bytes. - const bool bSupressPartiallyReadChunkWarning - = 60 == file.Archive3dmVersion() - && file.ArchiveOpenNURBSVersion() <= 2348833437 - && 1 == major_version - && 0 == minor_version + // Fix below for RH-86880 covers this case + ////// Dale Lear April 8, 2016 + ////// working on http://mcneel.myjetbrains.com/youtrack/issue/RH-31796 + ////// bSupressPartiallyReadChunkWarning suppresses a partially read chunk warning + ////// of skipping 16 bytes. + ////const bool bSupressPartiallyReadChunkWarning + //// = 60 == file.Archive3dmVersion() + //// && file.ArchiveOpenNURBSVersion() <= 2348833437 + //// && 1 == major_version + //// && 0 == minor_version + //// ; + + // Dale Lear April 3, 2025 - fix for reading the v8 file in RH-86880 + // Somebody added UseKerning() (minor_version = 10) and LineSpaceScale() (minor_version = 11) + // to the v9 dimstyle (see 9.x repo) while v9 was writing v8 files. + // When v8 opennurbs reads this files the chunk is partially read + // This fix also fixes the bug I fixed on April 8, 2016 RH-31796 + const bool bSupressPartiallyReadChunkWarning + = (file.Archive3dmVersion() < 80) + || (80 == file.Archive3dmVersion() && 1 == major_version && minor_version > 9) ; + if (!file.EndRead3dmChunk(bSupressPartiallyReadChunkWarning)) rc = false; diff --git a/opennurbs_extensions.cpp b/opennurbs_extensions.cpp index 0c92f842..0dcd012e 100644 --- a/opennurbs_extensions.cpp +++ b/opennurbs_extensions.cpp @@ -332,6 +332,8 @@ ON_InternalXMLImpl::~ON_InternalXMLImpl() ON_XMLNode& ON_InternalXMLImpl::Node(void) const { + std::lock_guard lg(_mutex); + // If the model node pointer is set, return that. This is a pointer to a node owned by the ON_3dmRenderSettings // which contains the entire RDK document XML. This is used by objects (Ground Plane, etc.) that are owned by the // ON_3dmRenderSettings. In the case of Ground Plane etc, it's a pointer into the ON_3dmRenderSettings XML. @@ -354,6 +356,8 @@ void ON_InternalXMLImpl::SetModelNode(ON_XMLNode& node) { ON_ASSERT(_model_node == nullptr); + std::lock_guard lg(_mutex); + if (nullptr != _local_node) { delete _local_node; @@ -2212,14 +2216,25 @@ void ONX_Model::DumpComponentLists( ON_TextLog& dump ) const ON_SHA1_Hash ONX_Model::ContentHash( ) const +{ + return ContentHash(ON_TextHash::Null); +} + +ON_SHA1_Hash ONX_Model::ContentHash( + ON_TextLog& hashed_text +) const { const bool bRemapIds = true; ON_TextHash hash_log; hash_log.SetIdRemap(bRemapIds); + if (false == hashed_text.IsNull()) + hash_log.SetOutputTextLog(&hashed_text); Dump(hash_log); return hash_log.Hash(); + } + class ON__CIndexPair { public: @@ -4177,7 +4192,7 @@ unsigned int ONX_ModelTest::Source3dmFileVersion() const bool ONX_ModelTest::SkipCompare(unsigned int source_3dm_file_version) { const bool bSkipCompare - = (source_3dm_file_version >= 1 && source_3dm_file_version < 50); + = (source_3dm_file_version >= 1 && source_3dm_file_version < 70); return bSkipCompare; } @@ -4363,8 +4378,9 @@ static void InternalDumpResultAndErrorCount( text_log.Print("%s", ONX_ModelTest::ResultToString(result)); if (false == InternalCleanPass(result,error_counter)) { - text_log.Print(": "); + text_log.Print(" ("); error_counter.Dump(text_log); + text_log.Print(")"); } text_log.PrintNewLine(); } @@ -4413,7 +4429,8 @@ std::shared_ptr ONX_ModelTest::ReadWriteReadModel() const static const ON_wString Internal_DumpModelfileName( const ON_wString source_3dm_file_path, - bool bSourceModel + bool bSourceModel, + bool bIsHashLog ) { ON_wString file_name_stem = ON_FileSystemPath::FileNameFromPath(source_3dm_file_path,false); @@ -4422,34 +4439,43 @@ static const ON_wString Internal_DumpModelfileName( ON_wString text_file_path = ON_FileSystemPath::VolumeAndDirectoryFromPath(source_3dm_file_path); text_file_path += file_name_stem; text_file_path += L"_ONX_ModelTest_"; + if (bIsHashLog) + text_file_path += L"HASH_LOG_"; if (bSourceModel) text_file_path += L"original"; else text_file_path += L"copy"; + if (false == bIsHashLog) + { + // The C-runtime loat/double formatting + // can change with change with OS and relase/debug. + // This information is appended so the person reading + // the text file knows the context where it was created. #if defined(ON_RUNTIME_WIN) #if defined(ON_64BIT_RUNTIME) - text_file_path += L"_Win64"; + text_file_path += L"_Win64"; #elif defined(ON_32BIT_RUNTIME) - text_file_path += L"_Win32"; + text_file_path += L"_Win32"; #else - text_file_path += L"_Win"; + text_file_path += L"_Win"; #endif #elif defined(ON_RUNTIME_APPLE_MACOS) - text_file_path += L"_MacOS"; + text_file_path += L"_MacOS"; #elif defined(ON_RUNTIME_APPLE_IOS) - text_file_path += L"_iOS"; + text_file_path += L"_iOS"; #elif defined(ON_RUNTIME_APPLE) - text_file_path += L"_AppleOS"; + text_file_path += L"_AppleOS"; #elif defined(ON_RUNTIME_ANDROID) - text_file_path += L"_AndroidOS"; + text_file_path += L"_AndroidOS"; #endif #if defined(ON_DEBUG) - text_file_path += L"Debug"; + text_file_path += L"Debug"; #else - text_file_path += L"Release"; + text_file_path += L"Release"; #endif + } text_file_path += L".txt"; return text_file_path; @@ -4457,7 +4483,7 @@ static const ON_wString Internal_DumpModelfileName( bool ONX_ModelTest::DumpSourceModel() const { - const ON_wString text_file_path = Internal_DumpModelfileName(m_source_3dm_file_path,true); + const ON_wString text_file_path = Internal_DumpModelfileName(m_source_3dm_file_path, true, false); return DumpSourceModel(text_file_path); } @@ -4493,7 +4519,7 @@ bool ONX_ModelTest::DumpSourceModel(ON_TextLog& text_log) const bool ONX_ModelTest::DumpReadWriteReadModel() const { - const ON_wString text_file_path = Internal_DumpModelfileName(m_source_3dm_file_path,false); + const ON_wString text_file_path = Internal_DumpModelfileName(m_source_3dm_file_path,false, false); return DumpReadWriteReadModel(text_file_path); } @@ -4529,6 +4555,60 @@ bool ONX_ModelTest::DumpReadWriteReadModel(ON_TextLog& text_log) const return ONX_ModelTest::DumpModel(ReadWriteReadModel().get(), text_log); } +bool ONX_ModelTest::DumpHashLogs( + ON_wString& source_model_hash_log_filename, + ON_SHA1_Hash& source_model_hash, + ON_wString& copy_model_hash_log_filename, + ON_SHA1_Hash& copy_model_hash +) const +{ + source_model_hash_log_filename = ON_wString::EmptyString; + source_model_hash = ON_SHA1_Hash::EmptyContentHash; + copy_model_hash_log_filename = ON_wString::EmptyString; + copy_model_hash = ON_SHA1_Hash::EmptyContentHash; + + for (;;) + { + const auto source_model_sp = SourceModel(); + const ONX_Model* source_model = source_model_sp.get(); + if (nullptr == source_model) + break; + if (source_model->Manifest().ActiveComponentCount(ON_ModelComponent::Type::Unset) <= 0) + break; + + const auto copy_model_sp = ReadWriteReadModel(); + const ONX_Model* copy_model = copy_model_sp.get(); + if (nullptr == copy_model) + break; + if (copy_model->Manifest().ActiveComponentCount(ON_ModelComponent::Type::Unset) <= 0) + break; + + source_model_hash_log_filename = Internal_DumpModelfileName(m_source_3dm_file_path, true, true); + if (source_model_hash_log_filename.IsEmpty()) + break; + FILE* fp = ON_FileStream::Open(static_cast(source_model_hash_log_filename), L"w"); + if (nullptr == fp) + break; + ON_TextLog source_hash_log(fp); + source_model_hash = source_model->ContentHash(source_hash_log); + ON_FileStream::Close(fp); + + copy_model_hash_log_filename = Internal_DumpModelfileName(m_source_3dm_file_path, false, true); + if (copy_model_hash_log_filename.IsEmpty()) + break; + fp = ON_FileStream::Open(static_cast(copy_model_hash_log_filename), L"w"); + if (nullptr == fp) + break; + ON_TextLog copy_hash_log(fp); + source_model_hash = copy_model->ContentHash(copy_hash_log); + ON_FileStream::Close(fp); + + return true; + } + return false; +} + + void ONX_ModelTest::Dump(ON_TextLog& text_log) const { const ONX_ModelTest::Type test_type = TestType(); @@ -4545,12 +4625,19 @@ void ONX_ModelTest::Dump(ON_TextLog& text_log) const //const int i_rwrcompare = static_cast(ONX_ModelTest::Type::ReadWriteReadCompare); const bool bSkipCompare - = ONX_ModelTest::SkipCompare(Source3dmFileVersion()) - && ONX_ModelTest::Type::ReadWriteReadCompare == test_type; + = ONX_ModelTest::Type::ReadWriteReadCompare == test_type + && InternalCleanPass(m_test_results[0], m_error_counts[0]) // init passed cleanly + && InternalCleanPass(m_test_results[1], m_error_counts[1]) // read source passed cleanly + && InternalCleanPass(m_test_results[2], m_error_counts[2]) // write temporary passed cleanly + && InternalCleanPass(m_test_results[2], m_error_counts[3]) // read temporary passed cleanly + && (ONX_ModelTest::SkipCompare(Source3dmFileVersion()) || ONX_ModelTest::Result::Skip == m_test_results[4]); + ; const unsigned int imax = bSkipCompare ? static_cast(ONX_ModelTest::Type::ReadWriteRead) : static_cast(test_type); + + // bSkipDetails = true if all tests passed and there are no errors, warnings, failures of any sort. bool bSkipDetails = InternalCleanPass(m_test_result, m_error_count); for (unsigned int i = 0; i <= imax && bSkipDetails; i++) { @@ -4561,6 +4648,7 @@ void ONX_ModelTest::Dump(ON_TextLog& text_log) const { if (bSkipCompare) { + // Let user know we skipped the compare text_log.PushIndent(); text_log.Print("Compare test skipped because source file version is too old.\n"); text_log.PopIndent(); @@ -4584,13 +4672,13 @@ void ONX_ModelTest::Dump(ON_TextLog& text_log) const break; i++; - text_log.Print("Write temporary files: "); + text_log.Print("Write temporary file: "); InternalDumpResultAndErrorCount(m_test_results[i], m_error_counts[i], text_log); if (i >= imax) break; i++; - text_log.Print("Read temporary files: "); + text_log.Print("Read temporary file: "); InternalDumpResultAndErrorCount(m_test_results[i], m_error_counts[i], text_log); if (i >= imax) break; diff --git a/opennurbs_extensions.h b/opennurbs_extensions.h index e6d2c23c..cdfb7543 100644 --- a/opennurbs_extensions.h +++ b/opennurbs_extensions.h @@ -1577,6 +1577,22 @@ public: */ ON_SHA1_Hash ContentHash() const; + /// + /// Get a SHA-1 has of the model's content and the text that was hashed. + /// + /// + /// The text that was hashed is returned here. Typically this is used when + /// a human wants to look at text diffences to determine why two models + /// that were supposed to be the same had different values of ContentHash(). + /// + /// + /// A SHA-1 hash of the model's content. If two models have identical content, + /// then the have equal ContentHash() values. + /// + ON_SHA1_Hash ContentHash( + ON_TextLog& hashed_text + ) const; + private: void Internal_DumpSummary( ON_TextLog& dump, @@ -1995,6 +2011,42 @@ public: */ bool DumpReadWriteReadModel(ON_TextLog& text_log) const; + /// + /// The read-write-read-compare test + /// 1.) Reads the source model and calculates a SHA-1 hash of its contents. + /// 2.) Writes the source model to a temporary archive. + /// 3.) Reads the temporary archive and calculates a SHA-1 hash of its contents. + /// 4.) Compares the SHA-1 hashes from step 1 and step 3. + /// If those hashes are equal, then the read-write-read-compare test passes. + /// If those hashes are different, the test fails. + /// When the test fails, use this function to save the information used to + /// compute the hashes in human readable text files that can be compared + /// using a text compare tool. + /// The differences between the text files will tell you what caused + /// the hashes to be different. + /// + /// + /// The name of the text file containing the hashed text description + /// of the source model is returned here. + /// + /// + /// The hash of the source model is returned here. + /// + /// + /// The name of the text file containing the hashed text description + /// of the read-write-read model is returned here. + /// + /// + /// The hash of the read-write-read model is returned here. + /// + /// + bool DumpHashLogs( + ON_wString& source_model_hash_log_filename, + ON_SHA1_Hash& source_model_hash, + ON_wString& copy_model_hash_log_filename, + ON_SHA1_Hash& copy_model_hash + ) const; + private: void Internal_BeginTest(); diff --git a/opennurbs_ground_plane.cpp b/opennurbs_ground_plane.cpp index 5668d88b..8c47db7c 100644 --- a/opennurbs_ground_plane.cpp +++ b/opennurbs_ground_plane.cpp @@ -246,6 +246,7 @@ bool ON_GroundPlane::PopulateMaterial(ON_Material& mat) const void ON_GroundPlane::SetXMLNode(ON_XMLNode& node) const { + std::lock_guard lg(_impl->_mutex); _impl->SetModelNode(node); } diff --git a/opennurbs_internal_V5_annotation.cpp b/opennurbs_internal_V5_annotation.cpp index 1ea8bfdb..77b558a6 100644 --- a/opennurbs_internal_V5_annotation.cpp +++ b/opennurbs_internal_V5_annotation.cpp @@ -749,33 +749,6 @@ ON_3dPoint ON_OBSOLETE_V2_AnnotationArrow::Tail() const } -static bool ON_Internal_UseSubDMeshProxy( - const ON_BinaryArchive& archive -) -{ - if (archive.Archive3dmVersion() != 60) - { - return false; // subd mesh proxy only applies when reading or writing a V6 files. - } - - // In a WIP, used subd objects. - const unsigned int min_subd_version = -#if defined(RHINO_COMMERCIAL_BUILD) - // Sep 5 2017 Dale Lear RH-41113 - // Rhino 6 commercial will have SubD objects. - // Nope. -> V6 commercial builds do not have subd objects - use proxy mesh when version < 7. - // 7 - 6 -#else - // V6 WIP builds and all V7 and later builds have subd objects. - // Use proxy mesh when version < 6. - 6 -#endif - ; - return (ON::VersionMajor() < min_subd_version); -} - - /* Description: In rare cases one object must be converted into another. @@ -888,38 +861,19 @@ ON_Object* ON_BinaryArchive::Internal_ConvertObject( if (ON::object_type::mesh_object == archive_object->ObjectType()) { - const ON_Mesh* mesh = ON_Mesh::Cast(archive_object); - if (nullptr != mesh ) + if (m_3dm_version <= 60) { - if ( false == ON_Internal_UseSubDMeshProxy(*this) ) + const ON_Mesh* mesh = ON_Mesh::Cast(archive_object); + if (nullptr != mesh) { // If mesh is a subd mesh proxy, return the original subd. ON_SubD* subd = ON_SubDMeshProxyUserData::SubDFromMeshProxy(mesh); - if ( nullptr != subd ) + if (nullptr != subd) return subd; } } } - else if (ON::object_type::subd_object == archive_object->ObjectType()) - { - const ON_SubD* subd = ON_SubD::Cast(archive_object); - if (nullptr != subd) - { - ON_Mesh* mesh = nullptr; - if ( Archive3dmVersion() < 60 ) - { - mesh = subd->GetControlNetMesh(nullptr, ON_SubDGetControlNetMeshPriority::Geometry); - } - else if ( ON_Internal_UseSubDMeshProxy(*this) ) - { - // Use a subd mesh proxy for V6 commercial builds. - mesh = ON_SubDMeshProxyUserData::MeshProxyFromSubD(subd); - } - if (nullptr != mesh) - return mesh; - } - } - + // no conversion required. return nullptr; } diff --git a/opennurbs_internal_defines.h b/opennurbs_internal_defines.h index 60130a4d..e16dea50 100644 --- a/opennurbs_internal_defines.h +++ b/opennurbs_internal_defines.h @@ -113,11 +113,11 @@ public: const ON_DecalCollection& operator = (const ON_DecalCollection& dc); - ON_Decal* AddDecal(void); + std::shared_ptr AddDecal(void); bool RemoveDecal(const ON_Decal&); void RemoveAllDecals(void); void ClearDecalArray(void); - const ON_SimpleArray& GetDecalArray(void) const; + const std::vector>& GetDecalArray(void) const; void SetChanged(void); @@ -128,9 +128,10 @@ private: int FindDecalIndex(const ON_UUID& id) const; private: + mutable std::recursive_mutex _mutex; ON_3dmObjectAttributes* m_attr; mutable ON_XMLRootNode m_root_node; - mutable ON_SimpleArray m_decals; + mutable std::vector> m_decals; mutable bool m_populated = false; mutable bool m_changed = false; }; diff --git a/opennurbs_linear_workflow.cpp b/opennurbs_linear_workflow.cpp index 3c05f637..79afeb0f 100644 --- a/opennurbs_linear_workflow.cpp +++ b/opennurbs_linear_workflow.cpp @@ -200,6 +200,7 @@ ON__UINT32 ON_LinearWorkflow::DataCRC(ON__UINT32 crc) const void ON_LinearWorkflow::SetXMLNode(ON_XMLNode& node) const { + std::lock_guard lg(_impl->_mutex); _impl->SetModelNode(node); } diff --git a/opennurbs_mesh_modifiers.cpp b/opennurbs_mesh_modifiers.cpp index cb1206d0..d3532c24 100644 --- a/opennurbs_mesh_modifiers.cpp +++ b/opennurbs_mesh_modifiers.cpp @@ -37,7 +37,10 @@ class ON_MeshModifier::CImpl : public ON_InternalXMLImpl { public: CImpl() { } - CImpl(const ON_XMLNode& n) { Node() = n; } + CImpl(const ON_XMLNode& n) + { + Node() = n; + } }; ON_MeshModifier::ON_MeshModifier() @@ -61,6 +64,7 @@ ON_XMLNode* ON_MeshModifier::AddChildXML(ON_XMLRootNode& root) const ON_XMLNode* mm_node = root.AttachChildNode(new ON_XMLNode(L"")); if (nullptr != mm_node) { + std::lock_guard lg(m_impl->_mutex); *mm_node = m_impl->Node(); } @@ -225,6 +229,7 @@ ON_Displacement::ON_Displacement(const ON_XMLNode& dsp_node) } // Copy the new displacement node to our node. It only contains displacement XML with no sub-item nodes. + std::lock_guard lg(m_impl->_mutex); m_impl->Node() = new_dsp_node; } @@ -627,6 +632,7 @@ void ON_Displacement::SubItem::SetWhitePoint(double w) void ON_Displacement::SubItem::ToXML(ON_XMLNode& node) const { + std::lock_guard lg(m_impl->_mutex); node = m_impl->Node(); } @@ -1406,6 +1412,7 @@ ON_ShutLining::ON_ShutLining(const ON_XMLNode& sl_node) } // Copy the new shut-lining node to our node. It only contains shut-lining XML with no curve nodes. + std::lock_guard lg(m_impl->_mutex); m_impl->Node() = new_sl_node; } @@ -1536,6 +1543,7 @@ ON_ShutLining::CurveIterator ON_ShutLining::GetCurveIterator(void) const ON_ShutLining::Curve& ON_ShutLining::AddCurve(void) { + std::lock_guard lg(m_impl->_mutex); ON_XMLNode* curve_node = m_impl->Node().AttachChildNode(new ON_XMLNode(ON_SHUTLINING_CURVE)); Curve* curve = new Curve(*curve_node); m_impl_sl->m_curves.Append(curve); diff --git a/opennurbs_model_component.cpp b/opennurbs_model_component.cpp index a7379179..0505d0b6 100644 --- a/opennurbs_model_component.cpp +++ b/opennurbs_model_component.cpp @@ -3888,6 +3888,10 @@ ON_ModelComponentReference ON_ModelComponentReference::CreateConstantSystemCompo if ( system_model_component.IsSystemComponent()) return CreateForExperts(const_cast(&system_model_component),false); + + // If you get this error, first double check and make sure you + // are calling ON::Begin() before you make any other calls + // into opennurbs. ON_ERROR("Invalid system_model_component parameter."); return ON_ModelComponentReference::Empty; } diff --git a/opennurbs_model_geometry.cpp b/opennurbs_model_geometry.cpp index d15acef8..1986123d 100644 --- a/opennurbs_model_geometry.cpp +++ b/opennurbs_model_geometry.cpp @@ -318,16 +318,7 @@ void ON_ModelGeometryComponent::Dump( ON_TextLog& text_log ) const else { attributes->Dump(text_log); - // attributes user data - const ON_UserData* ud = attributes->FirstUserData(); - while (0 != ud) - { - text_log.Print("Attributes user data:\n"); - text_log.PushIndent(); - ud->Dump(text_log); - text_log.PopIndent(); - ud = ud->Next(); - } + attributes->DumpUserData(L"Attributes user data:", text_log); } text_log.PopIndent(); @@ -339,16 +330,7 @@ void ON_ModelGeometryComponent::Dump( ON_TextLog& text_log ) const else { geometry->Dump(text_log); - // geometry user data - const ON_UserData* ud = geometry->FirstUserData(); - while (0 != ud) - { - text_log.Print("Geometry user data:\n"); - text_log.PushIndent(); - ud->Dump(text_log); - text_log.PopIndent(); - ud = ud->Next(); - } + geometry->DumpUserData(L"Geometry user data:", text_log); } text_log.PopIndent(); } diff --git a/opennurbs_object.cpp b/opennurbs_object.cpp index 6cf39001..fe28d939 100644 --- a/opennurbs_object.cpp +++ b/opennurbs_object.cpp @@ -1861,6 +1861,32 @@ void ON_Object::Dump( ON_TextLog& dump ) const } } +void ON_Object::DumpUserData(const wchar_t* description, ON_TextLog& text_log) const +{ + if (text_log.IsTextHash()) + { + // Dale Lear April 7, 2025 - Fix RH-86913 + // User data is not hashed because the output depends on + // the order the user data is attached, which plug-ins happen + // to be loaded, copy counts (which get incremented in a read-write-read test) + // and other factors that lead to variable and unpredictable results. + return; + } + + const ON_UserData* ud = this->FirstUserData(); + while (0 != ud) + { + if (nullptr != description) + text_log.Print(L"%ls\n", description); + text_log.PushIndent(); + ud->Dump(text_log); + text_log.PopIndent(); + ud = ud->Next(); + } + + return; +} + bool ON_Object::Write( ON_BinaryArchive& ) const diff --git a/opennurbs_object.h b/opennurbs_object.h index 2ea69e5b..5ae47d31 100644 --- a/opennurbs_object.h +++ b/opennurbs_object.h @@ -583,6 +583,18 @@ public: virtual void Dump( ON_TextLog& ) const; + /// + /// Go through the linked list of ON_UserData attached to this and + /// call the virtual Dump(text_log) function on each instance. + /// + /// + /// General context description to print before each instance. + /// + /// + /// Destination text log. + /// + void DumpUserData(const wchar_t* description, ON_TextLog& text_log) const; + /* Returns: An estimate of the amount of memory the class uses in bytes. diff --git a/opennurbs_post_effects.cpp b/opennurbs_post_effects.cpp index 4d1399f9..b46f6ed0 100644 --- a/opennurbs_post_effects.cpp +++ b/opennurbs_post_effects.cpp @@ -464,6 +464,7 @@ ON_PostEffects::CImpl::~CImpl() ON_XMLNode& ON_PostEffects::CImpl::PostEffectsNode(void) const { + std::lock_guard lg(_mutex); ON_XMLNode* post_effects_node = Node().CreateNodeAtPath(XMLPathPeps()); if (nullptr != post_effects_node) return *post_effects_node; diff --git a/opennurbs_public_version.h b/opennurbs_public_version.h index 73d3bdc9..68e2701c 100644 --- a/opennurbs_public_version.h +++ b/opennurbs_public_version.h @@ -6,7 +6,7 @@ // To update version numbers, edit ..\build\build_dates.msbuild #define RMA_VERSION_MAJOR 8 -#define RMA_VERSION_MINOR 18 +#define RMA_VERSION_MINOR 19 //////////////////////////////////////////////////////////////// // @@ -14,9 +14,9 @@ // first step in each build. // #define RMA_VERSION_YEAR 2025 -#define RMA_VERSION_MONTH 4 -#define RMA_VERSION_DATE 8 -#define RMA_VERSION_HOUR 11 +#define RMA_VERSION_MONTH 5 +#define RMA_VERSION_DATE 12 +#define RMA_VERSION_HOUR 1 #define RMA_VERSION_MINUTE 0 //////////////////////////////////////////////////////////////// @@ -35,8 +35,8 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 8,18,25098,11000 -#define VERSION_WITH_PERIODS 8.18.25098.11000 +#define VERSION_WITH_COMMAS 8,19,25132,1000 +#define VERSION_WITH_PERIODS 8.19.25132.01000 #define COPYRIGHT "Copyright (C) 1993-2025, Robert McNeel & Associates. All Rights Reserved." #define SPECIAL_BUILD_DESCRIPTION "Public OpenNURBS C++ 3dm file IO library." @@ -44,11 +44,11 @@ #define RMA_VERSION_NUMBER_MAJOR_WSTRING L"8" #define RMA_PREVIOUS_VERSION_NUMBER_MAJOR_WSTRING L"7" -#define RMA_VERSION_NUMBER_SR_STRING "SR18" -#define RMA_VERSION_NUMBER_SR_WSTRING L"SR18" +#define RMA_VERSION_NUMBER_SR_STRING "SR19" +#define RMA_VERSION_NUMBER_SR_WSTRING L"SR19" -#define RMA_VERSION_WITH_PERIODS_STRING "8.18.25098.11000" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.18.25098.11000" +#define RMA_VERSION_WITH_PERIODS_STRING "8.19.25132.01000" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.19.25132.01000" diff --git a/opennurbs_subd.h b/opennurbs_subd.h index e898af58..f329e63d 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -22235,7 +22235,7 @@ public: #if defined(ON_COMPILING_OPENNURBS) /* The ON_SubDAsUserData class is used to attach a subd to it proxy mesh -when writing V6 files in commercial rhino. +when writing prior than V6 files in commercial rhino. */ class ON_SubDMeshProxyUserData : public ON_UserData { diff --git a/opennurbs_subd_archive.cpp b/opennurbs_subd_archive.cpp index 5595509a..4388947e 100644 --- a/opennurbs_subd_archive.cpp +++ b/opennurbs_subd_archive.cpp @@ -2408,7 +2408,7 @@ bool ON_SubDMeshProxyUserData::WriteToArchive( { for (;;) { - if (archive.Archive3dmVersion() < 60) + if (archive.Archive3dmVersion() >= 60) break; if (false == IsValid()) return false; diff --git a/opennurbs_sun.cpp b/opennurbs_sun.cpp index e95164e4..496b03e9 100644 --- a/opennurbs_sun.cpp +++ b/opennurbs_sun.cpp @@ -1519,6 +1519,7 @@ void ON_Sun::SetAzimuthAndAltitudeFromVector(const ON_3dVector& v) void ON_Sun::SetXMLNode(ON_XMLNode& node) const { + std::lock_guard lg(_impl->_mutex); _impl->SetModelNode(node); } diff --git a/opennurbs_symmetry.cpp b/opennurbs_symmetry.cpp index c7acfe93..55dffae8 100644 --- a/opennurbs_symmetry.cpp +++ b/opennurbs_symmetry.cpp @@ -1360,6 +1360,7 @@ int ON_Symmetry::CompareSymmetryTransformation(const ON_Symmetry* lhs, const ON_ default: break; } + break; } return ON_Symmetry::Compare(lhs, rhs); diff --git a/opennurbs_textlog.cpp b/opennurbs_textlog.cpp index 842352ae..6d0eaf12 100644 --- a/opennurbs_textlog.cpp +++ b/opennurbs_textlog.cpp @@ -1237,7 +1237,30 @@ void ON_TextHash::SetOutputTextLog( ON_TextLog* output_text_log ) { - m_output_text_log = output_text_log; + if (nullptr == output_text_log) + { + m_output_text_log = nullptr; + } + else if (this == output_text_log) + { + // protect a confused user from an infinite recursion crash. + m_output_text_log = nullptr; + ON_ERROR("The output_text_log parameter must be an ordinary text log."); + } + else if (output_text_log->IsTextHash()) + { + // protect a confused user from nested hashing. + m_output_text_log = nullptr; + ON_ERROR("The output_text_log parameter must be an ordinary text log."); + } + else if (output_text_log->IsNull()) + { + m_output_text_log = nullptr; + } + else + { + m_output_text_log = output_text_log; + } } ON_TextLog* ON_TextHash::OutputTextLog() const @@ -1264,6 +1287,8 @@ void ON_TextHash::AppendText(const char* s) { // no id remapping - just accumulate m_sha1.AccumulateString(s, -1, m_string_map_ordinal_type); + if (nullptr != m_output_text_log) + m_output_text_log->AppendText(s); return; } diff --git a/opennurbs_version_number.cpp b/opennurbs_version_number.cpp index 280a66c1..ac7dbf80 100644 --- a/opennurbs_version_number.cpp +++ b/opennurbs_version_number.cpp @@ -316,6 +316,73 @@ bool ON_VersionNumberParse( return rc; } +int ON_VersionNumberCompare( + unsigned int lhs_version_number, + unsigned int rhs_version_number, + int invalid_input_result +) +{ + // Ignore the minor version. The major version and date determine + // what code is newer/older when it comes to file contents. + // The "branch" is meaningless. + unsigned int lhs_major_version = 0; + unsigned int lhs_year = 0; + unsigned int lhs_month = 0; + unsigned int lhs_date = 0; + const bool bLHSIsValid = ON_VersionNumberParse( + lhs_version_number, + &lhs_major_version, + nullptr, + &lhs_year, + &lhs_month, + &lhs_date, + nullptr + ); + if (false == bLHSIsValid) + return invalid_input_result; + + // Ignore the minor version. The major version and date determine + // what code is newer/older when it comes to file contents. + // The "branch" is meaningless. + unsigned int rhs_major_version = 0; + unsigned int rhs_year = 0; + unsigned int rhs_month = 0; + unsigned int rhs_date = 0; + const bool bRHSIsValid = ON_VersionNumberParse( + rhs_version_number, + &rhs_major_version, + nullptr, + &rhs_year, + &rhs_month, + &rhs_date, + nullptr + ); + if (false == bRHSIsValid) + return invalid_input_result; + + if (lhs_major_version < rhs_major_version) + return -1; + + if (lhs_major_version > rhs_major_version) + return 1; + + // The minor version number does not tell us which version of opennurbs is older/newer. + // The date is the only way to determine this. + + const unsigned int lhs_ymd = ((lhs_year * 100 + lhs_month) * 100 + lhs_date); + const unsigned int rhs_ymd = ((rhs_year * 100 + rhs_month) * 100 + rhs_date); + + if (lhs_ymd < rhs_ymd) + return -1; + + if (lhs_ymd > rhs_ymd) + return 1; + + // The branch is meaningless. + + return 0; +} + const ON_String ON_VersionNumberToString( unsigned int version_number, bool bUnsignedFormat, diff --git a/opennurbs_version_number.h b/opennurbs_version_number.h index a0639745..ff60139f 100644 --- a/opennurbs_version_number.h +++ b/opennurbs_version_number.h @@ -181,6 +181,31 @@ bool ON_VersionNumberParse( unsigned int* version_branch ); + +/// +/// Compare two opennurbs version numbers +/// +/// +/// +/// +/// +/// +/// Value to return if one or both of lhs_version_number and rhs_version_number is not a valid +/// version number. +/// +/// +/// invalid_input_result: One or both of the version numbers is not valid. +/// -1: lhs_version_number is older than rhs_version_number. +/// +1: lhs_version_number is newer than rhs_version_number. +/// 0: lhs_version_number and rhs_version_number identify the same version of opennurbs +/// +ON_DECL +int ON_VersionNumberCompare( + unsigned int lhs_version_number, + unsigned int rhs_version_number, + int invalid_input_result +); + /* Description: A tool to validate version information and to test opennurbs version number encoding and parsing. diff --git a/opennurbs_xml.cpp b/opennurbs_xml.cpp index da9c343b..ab67b714 100644 --- a/opennurbs_xml.cpp +++ b/opennurbs_xml.cpp @@ -3788,7 +3788,7 @@ void* ON_XMLNode::PropertyIterator::EVF(const wchar_t*, void*) // TODO: Somehow I managed to port the non-rc version of the root node. // TODO: We really need the rc version. -static const ON_wString sXMLRootNodeName(L"xml"); +static const wchar_t* xml_root_node_name(L"xml"); //class ON_XMLRootNodePrivate final // For future use. //{ @@ -3796,14 +3796,14 @@ static const ON_wString sXMLRootNodeName(L"xml"); ON_XMLRootNode::ON_XMLRootNode() : - ON_XMLNode(sXMLRootNodeName) + ON_XMLNode(xml_root_node_name) { _private = nullptr; //new ON_XMLRootNodePrivate; } ON_XMLRootNode::ON_XMLRootNode(const ON_XMLNode& src) : - ON_XMLNode(sXMLRootNodeName) + ON_XMLNode(xml_root_node_name) { _private = nullptr; //new ON_XMLRootNodePrivate; *this = src; @@ -3811,7 +3811,7 @@ ON_XMLRootNode::ON_XMLRootNode(const ON_XMLNode& src) ON_XMLRootNode::ON_XMLRootNode(const ON_XMLRootNode& src) : - ON_XMLNode(sXMLRootNodeName) + ON_XMLNode(xml_root_node_name) { _private = nullptr; //new ON_XMLRootNodePrivate; *this = src; @@ -3913,7 +3913,7 @@ void ON_XMLRootNode::Clear(void) { ON_XMLNode::Clear(); - SetTagName(sXMLRootNodeName); + SetTagName(xml_root_node_name); } // ON_XMLUserData -- Specializes ON_UserData for XML use.