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_archive.h b/opennurbs_archive.h index 197117aa..cb71fa25 100644 --- a/opennurbs_archive.h +++ b/opennurbs_archive.h @@ -5332,7 +5332,7 @@ private: // m_archive_dim_style_table_status values: // READING: // 0 = not started - // 1 = BeginWrite3dmDimStyle() has been called, + // 1 = BeginRead3dmDimStyle() has been called, // m_archive_text_style_table[] is valid, // and Read3dmDimStyle() can be called. // 2 = All entries of m_archive_text_style_table[] have been read by Read3dmDimStyle(). diff --git a/opennurbs_array.cpp b/opennurbs_array.cpp index 8c870636..7264e9aa 100644 --- a/opennurbs_array.cpp +++ b/opennurbs_array.cpp @@ -1189,10 +1189,6 @@ ON_UuidPtr* ON_UuidPtrList::SearchHelper(const ON_UUID* uuid) const return p; } -// static_assert(sizeof(ON_UuidPairList) == sizeof(ON_UuidPairList2), "ON_UuidPairList and ON_UuidPairList2 are not the same size"); -// static_assert(sizeof(ON_UuidPtrList) == sizeof(ON_UuidPtrList2), "ON_UuidPtrList and ON_UuidPtrList2 are not the same size"); -// static_assert(sizeof(ON_UuidIndexList) == sizeof(ON_UuidIndexList2), "ON_UuidIndexList and ON_UuidIndexList2 are not the same size"); - template struct ON_UuidList2_Private { diff --git a/opennurbs_array.h b/opennurbs_array.h index 46053ece..d5fef2f9 100644 --- a/opennurbs_array.h +++ b/opennurbs_array.h @@ -152,7 +152,7 @@ public: // Decrements count by removed items. Does // not change capacity - void RemoveIf(bool predicate(const T& key)); + void RemoveIf(bool (*predicate)(const T& key)); // Removes elements for which predicate // returns true. Decrements count // by removed items. @@ -1282,104 +1282,7 @@ private: }; -class ON_CLASS ON_UuidPtrList2 -{ -public: - ON_UuidPtrList2(); - ~ON_UuidPtrList2(); - ON_UuidPtrList2(const ON_UuidPtrList2& src); - ON_UuidPtrList2& operator=(const ON_UuidPtrList2& src); - unsigned int Count() const; - void RemoveAll(); - void Reserve(size_t capacity); - bool AddUuidPtr(const ON_UUID& uuid, ON__UINT_PTR ptr); - bool RemoveUuid(const ON_UUID& uuid); - bool FindUuid(const ON_UUID& uuid) const; - bool FindUuid(const ON_UUID& uuid, ON__UINT_PTR* ptr) const; - bool FindUuidPtr(const ON_UUID& uuid, ON__UINT_PTR index) const; - unsigned int GetUuids(ON_SimpleArray& uuid_list) const; - void ImproveSearchSpeed(); - -private: -#pragma warning (push) -#pragma warning (disable : 4251) - std::unique_ptr m_private; - unsigned int padding[2]; -#if defined (ON_RUNTIME_ANDROID) //Android has 32-bit pointers? - unsigned char padding_array[12]; -#else - unsigned char padding_array[16]; -#endif -#pragma warning (pop) -}; - -class ON_CLASS ON_UuidPairList2 -{ -public: - ON_UuidPairList2(); - ~ON_UuidPairList2(); - ON_UuidPairList2(const ON_UuidPairList2& src); - ON_UuidPairList2& operator=(const ON_UuidPairList2& src); - - static const ON_UuidPairList2 EmptyList; - - unsigned int Count() const; - void Empty(); - void Reserve(size_t capacity); - bool AddPair(const ON_UUID& id1, const ON_UUID& id2); - bool RemovePair(const ON_UUID& id1); - bool RemovePair(const ON_UUID& id1, const ON_UUID& id2); - bool FindId1(const ON_UUID& id1, ON_UUID* id2 = 0) const; - bool FindPair(const ON_UUID& id1, const ON_UUID& id2) const; - int GetId1s(ON_SimpleArray& uuid_list) const; - - void ImproveSearchSpeed(); - -private: -#pragma warning (push) -#pragma warning (disable : 4251) - std::unique_ptr m_private; - unsigned int padding[2]; -#if defined (ON_RUNTIME_ANDROID) //Android has 32-bit pointers? - unsigned char padding_array[12]; -#else - unsigned char padding_array[16]; -#endif -#pragma warning (pop) -}; - -class ON_CLASS ON_UuidIndexList2 -{ -public: - ON_UuidIndexList2(); - ~ON_UuidIndexList2(); - ON_UuidIndexList2(const ON_UuidIndexList2& src); - ON_UuidIndexList2& operator=(const ON_UuidIndexList2& src); - - unsigned int Count() const; - void RemoveAll(); - void Reserve(size_t capacity); - bool AddUuidIndex(const ON_UUID& uuid, int index); - bool RemoveUuid(const ON_UUID& uuid); - bool FindUuid(const ON_UUID& uuid) const; - bool FindUuid(const ON_UUID& uuid, int* index) const; - bool FindUuidIndex(const ON_UUID& uuid, int index) const; - unsigned int GetUuids(ON_SimpleArray& uuid_list) const; - void ImproveSearchSpeed(); - -private: -#pragma warning (push) -#pragma warning (disable : 4251) - std::unique_ptr m_private; - unsigned int padding[2]; -#if defined (ON_RUNTIME_ANDROID) //Android has 32-bit pointers? - unsigned char padding_array[12]; -#else - unsigned char padding_array[16]; -#endif -#pragma warning (pop) -}; @@ -1536,6 +1439,92 @@ private: +class ON_CLASS ON_UuidPtrList2 +{ +public: + ON_UuidPtrList2(); + ~ON_UuidPtrList2(); + ON_UuidPtrList2(const ON_UuidPtrList2& src); + ON_UuidPtrList2& operator=(const ON_UuidPtrList2& src); + + unsigned int Count() const; + void RemoveAll(); + void Reserve(size_t capacity); + bool AddUuidPtr(const ON_UUID& uuid, ON__UINT_PTR ptr); + bool RemoveUuid(const ON_UUID& uuid); + bool FindUuid(const ON_UUID& uuid) const; + bool FindUuid(const ON_UUID& uuid, ON__UINT_PTR* ptr) const; + bool FindUuidPtr(const ON_UUID& uuid, ON__UINT_PTR index) const; + unsigned int GetUuids(ON_SimpleArray& uuid_list) const; + void ImproveSearchSpeed(); + +private: +#pragma warning (push) +#pragma warning (disable : 4251) + std::unique_ptr m_private; + unsigned char padding[sizeof(ON_UuidPtrList) - sizeof(m_private)]; +#pragma warning (pop) +}; + +class ON_CLASS ON_UuidPairList2 +{ +public: + ON_UuidPairList2(); + ~ON_UuidPairList2(); + ON_UuidPairList2(const ON_UuidPairList2& src); + ON_UuidPairList2& operator=(const ON_UuidPairList2& src); + + static const ON_UuidPairList2 EmptyList; + + unsigned int Count() const; + void Empty(); + void Reserve(size_t capacity); + bool AddPair(const ON_UUID& id1, const ON_UUID& id2); + bool RemovePair(const ON_UUID& id1); + bool RemovePair(const ON_UUID& id1, const ON_UUID& id2); + bool FindId1(const ON_UUID& id1, ON_UUID* id2 = 0) const; + bool FindPair(const ON_UUID& id1, const ON_UUID& id2) const; + int GetId1s(ON_SimpleArray& uuid_list) const; + + void ImproveSearchSpeed(); + +private: +#pragma warning (push) +#pragma warning (disable : 4251) + std::unique_ptr m_private; + unsigned char padding[sizeof(ON_UuidPairList) - sizeof(m_private)]; +#pragma warning (pop) +}; + +class ON_CLASS ON_UuidIndexList2 +{ +public: + ON_UuidIndexList2(); + ~ON_UuidIndexList2(); + ON_UuidIndexList2(const ON_UuidIndexList2& src); + ON_UuidIndexList2& operator=(const ON_UuidIndexList2& src); + + unsigned int Count() const; + void RemoveAll(); + void Reserve(size_t capacity); + bool AddUuidIndex(const ON_UUID& uuid, int index); + bool RemoveUuid(const ON_UUID& uuid); + bool FindUuid(const ON_UUID& uuid) const; + bool FindUuid(const ON_UUID& uuid, int* index) const; + bool FindUuidIndex(const ON_UUID& uuid, int index) const; + unsigned int GetUuids(ON_SimpleArray& uuid_list) const; + void ImproveSearchSpeed(); + +private: +#pragma warning (push) +#pragma warning (disable : 4251) + std::unique_ptr m_private; + unsigned char padding[sizeof(ON_UuidIndexList) - sizeof(m_private)]; +#pragma warning (pop) +}; + + + class ON_CLASS ON_2dexMap : private ON_SimpleArray { diff --git a/opennurbs_array_defs.h b/opennurbs_array_defs.h index 96d8de92..65547405 100644 --- a/opennurbs_array_defs.h +++ b/opennurbs_array_defs.h @@ -623,7 +623,7 @@ void ON_SimpleArray::RemoveValue(const T& key) } template -void ON_SimpleArray::RemoveIf(bool predicate(const T& key)) +void ON_SimpleArray::RemoveIf(bool (*predicate)(const T& key)) { int t = 0; for (int i = 0; i < m_count; i++) diff --git a/opennurbs_decals.cpp b/opennurbs_decals.cpp index 4ad1dbd2..8b2497f8 100644 --- a/opennurbs_decals.cpp +++ b/opennurbs_decals.cpp @@ -26,16 +26,12 @@ static ON_4dPoint UNSET_4D_POINT = ON_4dPoint(ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE, ON_UNSET_VALUE); -ON__UINT32 ON_DecalCRCFromNode(const ON_XMLNode& node) -{ - return ON_Decal::ComputeDecalCRC(0, node); -} - class ON_Decal::CImpl : public ON_InternalXMLImpl { public: - CImpl() { ON_CreateUuid(_decal_id); } - CImpl(ON_DecalCollection& dc, ON_XMLNode& node) : _collection(&dc), ON_InternalXMLImpl(&node) { ON_CreateUuid(_decal_id); } + CImpl(); + CImpl(ON_DecalCollection* dc, ON_XMLNode& node); + CImpl(ON_DecalCollection* dc, const ON_XMLNode& node); ON_UUID TextureInstanceId(void) const; void SetTextureInstanceId(const ON_UUID& id); @@ -110,6 +106,27 @@ static ON_Decal::Mappings MappingFromString(const ON_wString& s) return ON_Decal::Mappings::None; } +ON_Decal::CImpl::CImpl() +{ + ON_CreateUuid(_decal_id); +} + +ON_Decal::CImpl::CImpl(ON_DecalCollection* dc, ON_XMLNode& node) + : + _collection(dc), + ON_InternalXMLImpl(&node) +{ + ON_CreateUuid(_decal_id); +} + +ON_Decal::CImpl::CImpl(ON_DecalCollection* dc, const ON_XMLNode& node) + : + _collection(dc), + ON_InternalXMLImpl(&const_cast(node)) +{ + ON_CreateUuid(_decal_id); +} + ON_XMLVariant ON_Decal::CImpl::GetParameter(const wchar_t* param_name, const ON_XMLVariant& def) const { return ON_InternalXMLImpl::GetParameter(L"", param_name, def); @@ -125,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(); @@ -136,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; @@ -146,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(); @@ -157,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; @@ -178,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(); @@ -193,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; @@ -213,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; @@ -223,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) { @@ -233,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(); @@ -243,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; @@ -252,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(); @@ -262,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; @@ -271,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(); @@ -281,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; @@ -290,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(); @@ -300,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; @@ -309,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(); @@ -319,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; @@ -328,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(); @@ -338,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; @@ -347,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(); @@ -357,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) { @@ -367,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(); @@ -379,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) { @@ -390,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(); @@ -402,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) { @@ -413,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(); @@ -429,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) { @@ -442,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())) @@ -462,11 +537,25 @@ ON_Decal::ON_Decal() _impl = new CImpl; } +ON_Decal::ON_Decal(ON_XMLNode& node) +{ + ON_ASSERT(node.TagName() == ON_RDK_DECAL); + + _impl = new CImpl(nullptr, node); +} + +ON_Decal::ON_Decal(const ON_XMLNode& node) +{ + ON_ASSERT(node.TagName() == ON_RDK_DECAL); + + _impl = new CImpl(nullptr, node); +} + ON_Decal::ON_Decal(ON_DecalCollection& dc, ON_XMLNode& node) { ON_ASSERT(node.TagName() == ON_RDK_DECAL); - _impl = new CImpl(dc, node); + _impl = new CImpl(&dc, node); } ON_Decal::ON_Decal(const ON_Decal& d) @@ -485,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(); } @@ -495,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; @@ -679,18 +774,10 @@ ON_UUID ON_Decal::Id(void) const return _impl->Id(); } -ON__UINT32 ON_Decal::DecalCRC(void) const -{ - return DataCRC(0); -} - -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); @@ -724,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); @@ -734,6 +826,23 @@ bool ON_Decal::SetCustomXML(const ON_UUID& renderEngineId, const ON_XMLNode& cus return true; } +void ON_Decal::AppendCustomXML(const ON_XMLNode& custom_node) +{ + // This function only exists to support the RDK. + + ON_ASSERT(custom_node.TagName() == L"entire-custom-xml"); + + ON_XMLNode* child = custom_node.FirstChild(); + while (nullptr != child) + { + std::lock_guard lg(_impl->_mutex); + + _impl->Node().AttachChildNode(new ON_XMLNode(*child)); + + child = child->NextSibling(); + } +} + // Copied from IRhRdkDecal::GetTextureMapping -- TODO: Refactor. [JOHN-DECAL-FIX] bool ON_Decal::GetTextureMapping(ON_TextureMapping& mappingOut) const { @@ -810,24 +919,24 @@ bool ON_Decal::GetTextureMapping(ON_TextureMapping& mappingOut) const This object encapsulates the reading of all decal properties from XML nodes. It is used by the decal CRC calculation in ComputeDecalCRC(). - TODO: It could also be used by the ON_Decal XML node access. + TODO: It could also be used by the ON_Decal XML node access (for Rhino 9). */ class ON_DecalNodeReader { public: - ON_DecalNodeReader(const ON_XMLNode* p) : m_pNode(p) { } + ON_DecalNodeReader(const ON_XMLNode* decal_node); ON_XMLVariant Mapping(void) const { return Value(ON_RDK_DECAL_MAPPING, ON_RDK_DECAL_MAPPING_NONE); } ON_XMLVariant Projection(void) const { return Value(ON_RDK_DECAL_PROJECTION, ON_RDK_DECAL_PROJECTION_NONE); } - ON_XMLVariant MapToInside(void) const { return Value(ON_RDK_DECAL_MAP_TO_INSIDE_ON, m_def.MapToInside()); } - ON_XMLVariant Transparency(void) const { return Value(ON_RDK_DECAL_TRANSPARENCY , m_def.Transparency()); } - ON_XMLVariant TextureInstanceId(void) const { return Value(ON_RDK_DECAL_TEXTURE_INSTANCE, m_def.TextureInstanceId()); } - ON_XMLVariant Height(void) const { return Value(ON_RDK_DECAL_HEIGHT , m_def.Height()); } - ON_XMLVariant Radius(void) const { return Value(ON_RDK_DECAL_RADIUS , m_def.Radius()); } - ON_XMLVariant Origin(void) const { return Value(ON_RDK_DECAL_ORIGIN , m_def.Origin()); } - ON_XMLVariant VectorUp(void) const { return Value(ON_RDK_DECAL_VECTOR_UP , ON_3dPoint(m_def.VectorUp())); } - ON_XMLVariant VectorAcross(void) const { return Value(ON_RDK_DECAL_VECTOR_ACROSS , ON_3dPoint(m_def.VectorAcross())); } + ON_XMLVariant MapToInside(void) const { return Value(ON_RDK_DECAL_MAP_TO_INSIDE_ON, _def.MapToInside()); } + ON_XMLVariant Transparency(void) const { return Value(ON_RDK_DECAL_TRANSPARENCY , _def.Transparency()); } + ON_XMLVariant TextureInstanceId(void) const { return Value(ON_RDK_DECAL_TEXTURE_INSTANCE, _def.TextureInstanceId()); } + ON_XMLVariant Height(void) const { return Value(ON_RDK_DECAL_HEIGHT , _def.Height()); } + ON_XMLVariant Radius(void) const { return Value(ON_RDK_DECAL_RADIUS , _def.Radius()); } + ON_XMLVariant Origin(void) const { return Value(ON_RDK_DECAL_ORIGIN , _def.Origin()); } + ON_XMLVariant VectorUp(void) const { return Value(ON_RDK_DECAL_VECTOR_UP , ON_3dPoint(_def.VectorUp())); } + ON_XMLVariant VectorAcross(void) const { return Value(ON_RDK_DECAL_VECTOR_ACROSS , ON_3dPoint(_def.VectorAcross())); } ON_XMLVariant HorzSweepSta(void) const { return Value(ON_RDK_DECAL_HORZ_SWEEP_STA , DefaultHorzSweepSta()); } ON_XMLVariant HorzSweepEnd(void) const { return Value(ON_RDK_DECAL_HORZ_SWEEP_END , DefaultHorzSweepEnd()); } ON_XMLVariant VertSweepSta(void) const { return Value(ON_RDK_DECAL_VERT_SWEEP_STA , DefaultVertSweepSta()); } @@ -837,109 +946,79 @@ public: ON_XMLVariant MaxU(void) const { return Value(ON_RDK_DECAL_MAX_U , DefaultMaxU()); } ON_XMLVariant MaxV(void) const { return Value(ON_RDK_DECAL_MAX_V , DefaultMaxV()); } ON_XMLVariant IsTemporary(void) const { return Value(ON_RDK_DECAL_IS_TEMPORARY , false); } - ON_XMLVariant IsVisible(void) const { return Value(ON_RDK_DECAL_IS_VISIBLE , m_def.IsVisible()); } - ON_XMLVariant InstanceId(void) const { return Value(ON_RDK_DECAL_INSTANCE_ID , m_def.Id()); } + ON_XMLVariant IsVisible(void) const { return Value(ON_RDK_DECAL_IS_VISIBLE , _def.IsVisible()); } + ON_XMLVariant InstanceId(void) const { return Value(ON_RDK_DECAL_INSTANCE_ID , _def.Id()); } private: ON_XMLVariant Value(const wchar_t* wszName, const ON_XMLVariant& vDefault) const; - double DefaultHorzSweepSta(void) const { double a, b; m_def.GetHorzSweep(a, b); return a; } - double DefaultHorzSweepEnd(void) const { double a, b; m_def.GetHorzSweep(a, b); return b; } - double DefaultVertSweepSta(void) const { double a, b; m_def.GetVertSweep(a, b); return a; } - double DefaultVertSweepEnd(void) const { double a, b; m_def.GetVertSweep(a, b); return b; } + double DefaultHorzSweepSta(void) const { double a, b; _def.GetHorzSweep(a, b); return a; } + double DefaultHorzSweepEnd(void) const { double a, b; _def.GetHorzSweep(a, b); return b; } + double DefaultVertSweepSta(void) const { double a, b; _def.GetVertSweep(a, b); return a; } + double DefaultVertSweepEnd(void) const { double a, b; _def.GetVertSweep(a, b); return b; } - double DefaultMinU(void) const { double a, b, c, d; m_def.GetUVBounds(a, b, c, d); return a; } - double DefaultMinV(void) const { double a, b, c, d; m_def.GetUVBounds(a, b, c, d); return b; } - double DefaultMaxU(void) const { double a, b, c, d; m_def.GetUVBounds(a, b, c, d); return c; } - double DefaultMaxV(void) const { double a, b, c, d; m_def.GetUVBounds(a, b, c, d); return d; } + double DefaultMinU(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return a; } + double DefaultMinV(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return b; } + double DefaultMaxU(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return c; } + double DefaultMaxV(void) const { double a, b, c, d; _def.GetUVBounds(a, b, c, d); return d; } private: - const ON_XMLNode* m_pNode; - const ON_Decal m_def; + const ON_XMLNode* _decal_node; + const ON_Decal _def; }; +ON_DecalNodeReader::ON_DecalNodeReader(const ON_XMLNode* decal_node) + : + _decal_node(decal_node) +{ + ON_ASSERT(_decal_node && (_decal_node->TagName() == ON_RDK_DECAL)); +} + ON_XMLVariant ON_DecalNodeReader::Value(const wchar_t* wszName, const ON_XMLVariant& vDefault) const { ON_XMLVariant vValue = vDefault; - if (nullptr != m_pNode) + if (nullptr != _decal_node) { - const ON_XMLParameters p(*m_pNode); + const ON_XMLParameters p(*_decal_node); p.GetParam(wszName, vValue); } return vValue; } -static void DecalUpdateCRC(ON__UINT32& crc, const ON_XMLVariant value) +#if (defined _DEBUG) && (defined HUMAN_READABLE_DECAL_CRC) +#define ON_DECAL_PROP_NAME(s) , s +static void DecalUpdateCRC(ON_DECAL_CRC& crc, const ON_XMLVariant value, const wchar_t* name) +{ + crc = value.DataCRC(crc); + crc._info1 += ON_wString(name) + L"=" + value.AsString() + ON_wString(L" "); + crc._info2 += ON_wString(name) + L"=" + value.AsString() + ON_wString(L"\n"); +} +#else +#define ON_DECAL_PROP_NAME(s) +static void DecalUpdateCRC(ON_DECAL_CRC& crc, const ON_XMLVariant& value) { crc = value.DataCRC(crc); } +#endif -ON__UINT32 ON_Decal::ComputeDecalCRC(ON__UINT32 crc, const ON_XMLNode& node) // Static. +static void DecalUpdateCRC_Custom(const ON_XMLNode& decal_node, ON_DECAL_CRC& crc) { - const ON_DecalNodeReader d(&node); - - const ON_wString s = d.Mapping().AsString(); - const auto mapping = MappingFromString(s); - - DecalUpdateCRC(crc, d.Mapping()); - DecalUpdateCRC(crc, d.IsVisible()); - DecalUpdateCRC(crc, d.IsTemporary()); - DecalUpdateCRC(crc, d.Transparency()); - DecalUpdateCRC(crc, d.TextureInstanceId()); - - if (Mappings::Planar == mapping) - { - DecalUpdateCRC(crc, d.MinU()); - DecalUpdateCRC(crc, d.MinV()); - DecalUpdateCRC(crc, d.MaxU()); - DecalUpdateCRC(crc, d.MaxV()); - } - else - { - DecalUpdateCRC(crc, d.Origin()); - DecalUpdateCRC(crc, d.VectorUp()); - DecalUpdateCRC(crc, d.VectorAcross()); - DecalUpdateCRC(crc, d.Projection()); - - if ((Mappings::Cylindrical == mapping) || (Mappings::Spherical == mapping)) - { - DecalUpdateCRC(crc, d.Radius()); - DecalUpdateCRC(crc, d.MapToInside()); - DecalUpdateCRC(crc, d.HorzSweepSta()); - DecalUpdateCRC(crc, d.HorzSweepEnd()); - - if (Mappings::Cylindrical == mapping) - { - DecalUpdateCRC(crc, d.Height()); - } - else - if (Mappings::Spherical == mapping) - { - DecalUpdateCRC(crc, d.VertSweepSta()); - DecalUpdateCRC(crc, d.VertSweepEnd()); - } - } - } - // Look for custom data nodes and for each one, find the parameter node and then iterate over its // children and CRC the properties. For now, we will have to rely on the raw XML. A better solution // would be to have the plug-in that created this XML calculate the CRC itself. - auto it = node.GetChildIterator(); + + const ON_wString custom = L"[CUSTOM] "; + + auto it = decal_node.GetChildIterator(); ON_XMLNode* pChildNode = nullptr; while (nullptr != (pChildNode = it.GetNextChild())) { if (pChildNode->TagName() != ON_RDK_DECAL_CUSTOM) continue; // Not a custom data node. - ON_XMLProperty* prop = pChildNode->GetNamedProperty(ON_RDK_DECAL_CUSTOM_RENDERER); - if (nullptr != prop) - { - // Include the render engine id. - const ON_UUID uuid = prop->GetValue().AsUuid(); - crc = ON_CRC32(crc, sizeof(uuid), &uuid); - } + const ON__UINT32 crc_before_custom_params = crc; // Find the custom parameter node. const ON_XMLNode* pParamNode = pChildNode->GetNamedChild(ON_RDK_DECAL_CUSTOM_PARAMS); @@ -947,26 +1026,126 @@ ON__UINT32 ON_Decal::ComputeDecalCRC(ON__UINT32 crc, const ON_XMLNode& node) // { // Iterate over the nodes inside the custom parameter node. const ON_XMLParameters p(*pParamNode); - auto* pIterator = p.NewIterator(); - - ON_wString sParamName; - ON_XMLVariant vParamValue; - while (pIterator->Next(sParamName, vParamValue)) + auto* iterator = p.NewIterator(); + if (nullptr != iterator) { - DecalUpdateCRC(crc, vParamValue); + ON_wString sParamName; + ON_XMLVariant vParamValue; + while (iterator->Next(sParamName, vParamValue)) + { + DecalUpdateCRC(crc, vParamValue ON_DECAL_PROP_NAME(custom + sParamName)); + } + + delete iterator; } - delete pIterator; + if (crc != crc_before_custom_params) + { + // 20th January 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-71351 + // Since the crc has changed, there must be some custom params, so only now do we include the render + // engine id. Prior to this, the render engine id was getting included even when there were no custom + // params. This caused the UI to overlook decals which were programatically created by clients. + const ON_XMLProperty* prop = pChildNode->GetNamedProperty(ON_RDK_DECAL_CUSTOM_RENDERER); + if (nullptr != prop) + { + // Include the render engine id. + const ON_UUID uuid = prop->GetValue().AsUuid(); + DecalUpdateCRC(crc, uuid ON_DECAL_PROP_NAME(custom + L"render_engine_id")); + } + } } } +} - // Make sure it's not zero which would mean 'nil'. - if (0 == crc) - crc--; +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 + // or not a decal is temporary has nothing to do with what it looks like. We do however, include the 'IsVisible' + // property because a decal being visible or invisible actually affects its appearance. Also, the RDK change + // queue relies on the CRC to update the viewport so including the visibility is critical. Furthermore, the + // CRC only includes the properties that are relevant for the decal's mapping type. + + ON_DECAL_CRC crc = current_remainder; + + if (decal_node.TagName() == ON_RDK_DECAL) + { + const ON_DecalNodeReader d(&decal_node); + + DecalUpdateCRC(crc, d.Mapping() ON_DECAL_PROP_NAME(L"mapping")); + DecalUpdateCRC(crc, d.IsVisible() ON_DECAL_PROP_NAME(L"visible")); + DecalUpdateCRC(crc, d.Transparency() ON_DECAL_PROP_NAME(L"transparency")); + DecalUpdateCRC(crc, d.TextureInstanceId() ON_DECAL_PROP_NAME(L"texture_id")); + + const ON_Decal::Mappings mapping = MappingFromString(d.Mapping().AsString()); + + 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")); + DecalUpdateCRC(crc, d.MaxU() ON_DECAL_PROP_NAME(L"max_u")); + DecalUpdateCRC(crc, d.MaxV() ON_DECAL_PROP_NAME(L"max_v")); + } + else + { + DecalUpdateCRC(crc, d.Origin() ON_DECAL_PROP_NAME(L"origin")); + DecalUpdateCRC(crc, d.VectorUp() ON_DECAL_PROP_NAME(L"up")); + DecalUpdateCRC(crc, d.VectorAcross() ON_DECAL_PROP_NAME(L"across")); + + 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 (ON_Decal::Mappings::Cylindrical == mapping) + { + DecalUpdateCRC(crc, d.Height() ON_DECAL_PROP_NAME(L"height")); + } + else + 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 (ON_Decal::Mappings::Planar == mapping) + { + DecalUpdateCRC(crc, d.Projection() ON_DECAL_PROP_NAME(L"projection")); + } + } + + DecalUpdateCRC_Custom(decal_node, crc); + + // Make sure it's not nil. + if (crc == ON_NIL_DECAL_CRC) + crc = 0xFFFFFFFF; + } 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() @@ -976,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; @@ -985,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) @@ -1001,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(); } @@ -1041,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(); } @@ -1059,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(); } @@ -1077,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)); } } @@ -1093,10 +1280,21 @@ 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 { - if (!m_populated) + // 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. + + std::lock_guard lg(_mutex); + +//if (!m_populated) // Always repopulate the array. { + m_decals.clear(); + Populate(); m_populated = true; @@ -1110,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; @@ -1117,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 f148cd3e..3812751d 100644 --- a/opennurbs_decals.h +++ b/opennurbs_decals.h @@ -14,12 +14,97 @@ #if !defined(ON_DECALS_INC_) #define ON_DECALS_INC_ +// Comment in this line to enable human-readable decal CRCs. One of the most difficult things about +// diagnosing decal bugs is not being able to get useful information from their CRCs. By enabling +// this feature, the decal CRC will change from being an integer to being a class with debug information +// in it. THIS BREAKS THE SDK SO ONLY USE IT FOR LOCAL DEBUGGING. NEVER CHECK THIS LINE IN WITH IT ENABLED. +//#define HUMAN_READABLE_DECAL_CRC + +#ifndef _DEBUG +#ifdef HUMAN_READABLE_DECAL_CRC +#error "HUMAN_READABLE_DECAL_CRC is defined in release build" +#endif +#endif + +#if (defined _DEBUG) && (defined HUMAN_READABLE_DECAL_CRC) +class ON_HumanReadableDecalCRC +{ +public: + ON_HumanReadableDecalCRC(ON__UINT32 v=0) : _value(v) { } + + void operator = (ON__UINT32 v) { _value = v; } + bool operator == (ON__UINT32 v) const { return _value == v; } + bool operator != (ON__UINT32 v) const { return _value != v; } + + operator ON__UINT32(void) const { return _value; } + + operator const wchar_t*(void) const { return _info1; } + +public: + ON__UINT32 _value = 0; + ON_wString _info1; + ON_wString _info2; +}; +#define ON_DECAL_CRC ON_HumanReadableDecalCRC +#else +#define ON_DECAL_CRC ON__UINT32 +#endif + +#define ON_NIL_DECAL_CRC ON_DECAL_CRC(0) // If a decal CRC is nil, it means 'no decal'. + +// ON_Decal encapsulates a rendering decal which is an image that sticks to the surface of an object +// like a real-life decal does. Decals are identified by their CRC which is a value generated from the +// decal's current state. When a property changes, the CRC will also change. +// +// Decals have a choice of mappings: +// +// - UV: Maps a texture to (u,v) coordinates on an object. +// - Planar: Maps a texture to a rectangle like an ordinary sticker. +// - Spherical: Maps a texture to a spherical object like a label on a ball. +// - Cylindrical: Maps a texture to a cylinder like a label on a can. +// +// They also have a choice of projections: +// +// - Forward: Projects the decal forward onto the object. +// - Backward: Projects the decal backward onto the object. +// - Both: Projects the decal forward and backward onto the object. +// +// Some of the properties are only used by certain mappings: +// +// - UV bounds: UV mapping only. +// - Transparency: All mappings. +// - Origin: All mappings except UV. +// - Up vector: All mappings except UV. +// - Across vector: All mappings except UV. +// - Height: Cylindrical mapping only. +// - Map to inside: Cylindrical and spherical mappings only. +// - Radius: Cylindrical and spherical mappings only. +// - Horizontal sweep: Cylindrical and spherical mappings only. +// - Vertical sweep: Spherical mapping only. +// +// The class stores its data as XML but it is optimized with a built-in cache. As each property is +// requested, it is read from the XML and cached. Thereafter, the cached value is returned and the XML +// for that property is never read again. On writing to a property, the cache is updated and by default +// the XML is also updated. However, it is possible to inhibit the XML update for increased performance. +// See SetCacheOnly() below. + class ON_CLASS ON_Decal { public: + // Construct a decal with all properties set to defaults. ON_Decal(); - ON_Decal(class ON_DecalCollection& coll, ON_XMLNode& node); - ON_Decal(const ON_Decal& d); + + // Construct a decal passing the XML node to be used for its storage. + ON_Decal(ON_XMLNode& node); + + // Construct a decal passing the const XML node to be used for its storage. This decal is read only. + ON_Decal(const ON_XMLNode& node); + + ON_Decal(class ON_DecalCollection& coll, ON_XMLNode& node); // For internal use only. + + // Construct this decal as a copy of another decal. + ON_Decal(const ON_Decal& other); + virtual ~ON_Decal(); virtual const ON_Decal& operator = (const ON_Decal& d); @@ -136,7 +221,7 @@ public: bool GetTextureMapping(ON_TextureMapping& tm) const; // Returns the Decal CRC of the decal. - ON__UINT32 DecalCRC(void) const; + ON_DECAL_CRC DecalCRC(void) const; // Returns the Data CRC of the decal. This is not necessarily the same as the decal CRC // because it allows a starting current remainder. @@ -167,7 +252,7 @@ public: bool SetCustomXML(const ON_UUID& renderEngineId, const ON_XMLNode& custom_param_node); public: // For internal use only. - static ON__UINT32 ComputeDecalCRC(ON__UINT32, const ON_XMLNode&); + void AppendCustomXML(const ON_XMLNode&); private: class CImpl; @@ -175,6 +260,6 @@ private: }; // For internal use only. -ON__UINT32 ON_DECL ON_DecalCRCFromNode(const ON_XMLNode& node); +ON_DECAL_CRC ON_DECL ON_DecalCRCFromNode(const ON_XMLNode& node); #endif 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_font.cpp b/opennurbs_font.cpp index c5175449..e4944e7e 100644 --- a/opennurbs_font.cpp +++ b/opennurbs_font.cpp @@ -10752,7 +10752,16 @@ bool ON_Font::Read( || (file.PeekAt3dmBigChunkType(&typecode,&big_value) && 1 == typecode) ) { - ON_WARNING("Should probably be reading an ON_TextStyle"); + // Dale Lear 2025 May 8 - RH-87126 + // Some older version files are triggering this warning when + // override dimstyles are read. It is true V5 files had a text style table, + // but we didn't expect to encounter override dimstyles in these old + // files. It's not clear to me how these files come into existence, + // but this warning seems to be doing more harm than good. In the xase in the bug, + // the older version files read correctly. When V8 and V9 files are saved, they appear + // to be getting saved correctly. + // + // ON_WARNING("Should probably be reading an ON_TextStyle"); int font_index = -1; ON_UUID font_id = ON_nil_uuid; return ReadV5( 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_input_libsdir.h b/opennurbs_input_libsdir.h index 3936cc77..b3ce1579 100644 --- a/opennurbs_input_libsdir.h +++ b/opennurbs_input_libsdir.h @@ -16,6 +16,12 @@ #if !defined(OPENNURBS_INPUT_LIBSDIR_INC_) #define OPENNURBS_INPUT_LIBSDIR_INC_ + + +// RH-86025, RH3DM-179, 2025-02-14, Pierre: +// ON_CMAKE_BUILD should be renamed "ON_NOT_USING_MSVC_LINK_LIB_PRAGMA" +// This whole thing should be reworked so MSVC builds also use build properties, +// instead of the non-portable #pragma comment(lib, "path"). #if defined(ON_COMPILER_MSC) && !defined(OPENNURBS_INPUT_LIBS_DIR) && !defined(ON_CMAKE_BUILD) // This header file insures OPENNURBS_INPUT_LIBS_DIR is defined to be diff --git a/opennurbs_instance.cpp b/opennurbs_instance.cpp index 7030c91a..22666c51 100644 --- a/opennurbs_instance.cpp +++ b/opennurbs_instance.cpp @@ -743,6 +743,15 @@ bool ON_ReferencedComponentSettingsImpl::Internal_UpdateLayer( // Add more settings here without breaking anything + // 25-Apr-2025 Dale Fugier, https://mcneel.myjetbrains.com/youtrack/issue/RH-68718 + const bool visible_in_new_details = ON_ReferencedComponentSettingsImpl::Internal_UpdateBool( + previous_referenced_file_layer.PerViewportIsVisibleInNewDetails(), + reference_file_layer.PerViewportIsVisibleInNewDetails(), + previous_model_layer.PerViewportIsVisibleInNewDetails() + ); + model_layer.SetPerViewportIsVisibleInNewDetails(visible_in_new_details); + + // Dale Lear August 2017 - RH-39457 // Saved PerViewport settings need to be applied to model_layer // 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.cpp b/opennurbs_mesh.cpp index d4f5e941..16f3147c 100644 --- a/opennurbs_mesh.cpp +++ b/opennurbs_mesh.cpp @@ -15901,21 +15901,21 @@ ON_Mesh* ON_Mesh::CopyComponents( return CopyComponents(ci_list.Array(), ci_list.UnsignedCount(), destination_mesh); } -bool ON_Mesh::SetSurfaceParamtersFromTextureCoodinates() +bool ON_Mesh::SetSurfaceParametersFromTextureCoodinates(const ON_SimpleArray& TCs) { unsigned int i; const unsigned int vcount = m_V.UnsignedCount(); bool rc; ON_Interval dom; - if (vcount == m_T.UnsignedCount()) + if (vcount == TCs.UnsignedCount()) { dom.Set(0.0, 1.0); m_S.SetCount(0); m_S.Reserve(vcount); for (i = 0; i < vcount; i++) { - ON_2dPoint S = m_T[i]; + ON_2dPoint S = TCs[i]; m_S.Append(S); } rc = true; @@ -16041,6 +16041,12 @@ bool ON_Mesh::SetSurfaceParamtersFromTextureCoodinates() //return true; } +bool ON_Mesh::SetSurfaceParamtersFromTextureCoodinates() +{ + return SetSurfaceParametersFromTextureCoodinates(m_T); +} + + ////////////////////////////////////////////////////////////////////////// // // ON_MeshCache diff --git a/opennurbs_mesh.h b/opennurbs_mesh.h index 0ba4b75a..a749b36f 100644 --- a/opennurbs_mesh.h +++ b/opennurbs_mesh.h @@ -6135,6 +6135,8 @@ The map is an array of length m_F.Count(), ngon_map[] */ bool SetSurfaceParamtersFromTextureCoodinates(); + bool SetSurfaceParametersFromTextureCoodinates(const ON_SimpleArray& TCs); + ///////////////////////////////////////////////////////////////// // Implementation - curvature 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_nurbssurface.cpp b/opennurbs_nurbssurface.cpp index a7aadbb5..306a4c7c 100644 --- a/opennurbs_nurbssurface.cpp +++ b/opennurbs_nurbssurface.cpp @@ -778,60 +778,127 @@ bool ON_NurbsSurface::Read( // NOTE - check legacy I/O code if changed int major_version = 0; int minor_version = 0; - bool rc = file.Read3dmChunkVersion(&major_version,&minor_version); - if (rc && major_version==1) { + while (file.Read3dmChunkVersion(&major_version, &minor_version)) + { + if (1 != major_version) + break; + // common to all 1.x versions - int dim = 0, is_rat = 0, order0 = 0, order1 = 0, cv_count0 = 0, cv_count1 = 0; - int reserved1 = 0, reserved2 = 0; - if (rc) rc = file.ReadInt( &dim ); - if (rc) rc = file.ReadInt( &is_rat ); - if (rc) rc = file.ReadInt( &order0 ); - if (rc) rc = file.ReadInt( &order1 ); - if (rc) rc = file.ReadInt( &cv_count0 ); - if (rc) rc = file.ReadInt( &cv_count1 ); + int dim = 0; + if (false == file.ReadInt(&dim)) + break; + if (dim < 1) + break; + int is_rat = 0; + if (false == file.ReadInt(&is_rat)) + break; - if (rc) rc = file.ReadInt(&reserved1); - if (rc) rc = file.ReadInt(&reserved2); + int order0 = 0; + if (false == file.ReadInt( &order0 )) + break; + if (order0 < 2) + break; + int order1 = 0; + if (false == file.ReadInt( &order1 )) + break; + if (order1 < 2) + break; - if (rc) { - ON_BoundingBox bbox; // read bounding box - may be used in future - rc = file.ReadBoundingBox(bbox); - } + int cv_count0 = 0; + if (false == file.ReadInt( &cv_count0 )) + break; + if (cv_count0 < order0) + break; + int cv_count1 = 0; + if (false == file.ReadInt( &cv_count1 )) + break; + if (cv_count1 < order1) + break; + + // These are sanity checks for overflow. + // Safe fix for RH-83540 + // They prevent attempt to allocate large numbers of knots when + // one of the orders or cv_counts red above is from a corrupt file. + const int expected_knot_count0 = ON_KnotCount(order0, cv_count0); + if (expected_knot_count0 < 2) + break; + const int expected_knot_count1 = ON_KnotCount(order1, cv_count1); + if (expected_knot_count1 < 2) + break; + const int expected_cv_count = cv_count0 * cv_count1; + if (expected_cv_count < 4) + break; + const int expected_cv_capacity = (is_rat != 0 ? (dim + 1) : dim) * expected_cv_count; + if (expected_cv_capacity < 4) + break; + + + int reserved1 = 0; + if (false == file.ReadInt(&reserved1)) + break; + int reserved2 = 0; + if (false == file.ReadInt(&reserved2)) + break; + + ON_BoundingBox ignored_bbox; // read bounding box place holder - may be used in future + if (false == file.ReadBoundingBox(ignored_bbox)) + break; - Create( dim, is_rat, order0, order1, cv_count0, cv_count1 ); - - int count = 0; - if (rc) rc = file.ReadInt(&count); - if (rc && count < 0) - rc = false; - if (rc ) rc = ReserveKnotCapacity(0,count); - if (rc) rc = file.ReadDouble( count, m_knot[0] ); - - count = 0; - if (rc) rc = file.ReadInt(&count); - if (rc && count < 0) - rc = false; - if (rc ) rc = ReserveKnotCapacity(1,count); - if (rc) rc = file.ReadDouble( count, m_knot[1] ); - - count = 0; - if (rc) rc = file.ReadInt(&count); - if (rc && count < 0) - rc = false; + if (false == Create(dim, is_rat, order0, order1, cv_count0, cv_count1)) + break; const int cv_size = CVSize(); - if (rc) rc = ReserveCVCapacity( count*cv_size ); - if (count > 0 && cv_size > 0 && rc ) { - int i, j; - for ( i = 0; i < m_cv_count[0] && rc; i++ ) { - for ( j = 0; j < m_cv_count[1] && rc; j++ ) { - rc = file.ReadDouble( cv_size, CV(i,j) ); - } + if (cv_size < dim) + break; + + int knot_count0 = 0; + if (false == file.ReadInt(&knot_count0)) + break; + if (knot_count0 != expected_knot_count0) + break; + if (false == ReserveKnotCapacity(0, knot_count0)) + break; + if (false == file.ReadDouble(knot_count0, m_knot[0])) + break; + + int knot_count1 = 0; + if (false == file.ReadInt(&knot_count1)) + break; + if (knot_count1 != expected_knot_count1) + break; + if (false == ReserveKnotCapacity(1, knot_count1)) + break; + if (false == file.ReadDouble(knot_count1, m_knot[1])) + break; + + int cv_count = 0; + if (false == file.ReadInt(&cv_count)) + break; + if (cv_count != expected_cv_count) + break; + if (false == ReserveCVCapacity(cv_count * cv_size)) + break; + int i = 0; + for ( i = 0; i < m_cv_count[0]; i++ ) + { + int j = 0; + for ( j = 0; j < m_cv_count[1]; j++ ) + { + if (false == file.ReadDouble(cv_size, CV(i, j))) + break; } + if (j != m_cv_count[1]) + break; } + if (i != m_cv_count[0]) + break; + + // successful read + return true; } - if ( !rc ) - Destroy(); - return rc; + + // Corrupt file + Destroy(); + return false; } ON_Interval ON_NurbsSurface::Domain( int dir ) const 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 71a657bf..6f2545d5 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 17 +#define RMA_VERSION_MINOR 20 //////////////////////////////////////////////////////////////// // @@ -14,10 +14,10 @@ // first step in each build. // #define RMA_VERSION_YEAR 2025 -#define RMA_VERSION_MONTH 3 -#define RMA_VERSION_DATE 7 -#define RMA_VERSION_HOUR 7 -#define RMA_VERSION_MINUTE 0 +#define RMA_VERSION_MONTH 5 +#define RMA_VERSION_DATE 13 +#define RMA_VERSION_HOUR 3 +#define RMA_VERSION_MINUTE 38 //////////////////////////////////////////////////////////////// // @@ -35,8 +35,8 @@ // 3 = build system release build #define RMA_VERSION_BRANCH 0 -#define VERSION_WITH_COMMAS 8,17,25066,7000 -#define VERSION_WITH_PERIODS 8.17.25066.07000 +#define VERSION_WITH_COMMAS 8,20,25133,3380 +#define VERSION_WITH_PERIODS 8.20.25133.03380 #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 "SR17" -#define RMA_VERSION_NUMBER_SR_WSTRING L"SR17" +#define RMA_VERSION_NUMBER_SR_STRING "SR20" +#define RMA_VERSION_NUMBER_SR_WSTRING L"SR20" -#define RMA_VERSION_WITH_PERIODS_STRING "8.17.25066.07000" -#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.17.25066.07000" +#define RMA_VERSION_WITH_PERIODS_STRING "8.20.25133.03380" +#define RMA_VERSION_WITH_PERIODS_WSTRING L"8.20.25133.03380" diff --git a/opennurbs_subd.h b/opennurbs_subd.h index b8cda2d2..f329e63d 100644 --- a/opennurbs_subd.h +++ b/opennurbs_subd.h @@ -3131,7 +3131,7 @@ public: /// Postfix operator ++ sets this to ON_SubDComponentPtr::NextComponent() /// and returns the previous value of this. /// - /// ON_SubDComponentPtr::NextComponent() + /// *this, before increment const ON_SubDComponentPtr operator++(int); @@ -17694,13 +17694,34 @@ public: /* Description: - Increment the iterator. + Prefix increment the iterator. + Returns: + Next vertex. + Remarks: + operator++(void) and NextVertex() behave the same. + In OpenNURBS 8.17 and earlier, this function was incorrectly implemented as + a postfix increment (operator++(int)). This has been corrected in OpenNURBS 8.18. + If you have a plugin compiled without inlining optimizations (e.g. in Debug mode), + operator++(void) will be calling the version in opennurbs.dll distributed + with Rhino that is used to run the plugin. + If you have a plugin compiled with inlining optimizations (e.g. in Release mode), + operator++(void) will behave like the version in opennurbs_subd.h distributed + with the Rhino SDK that was used to compiled the plugin. + */ + const class ON_SubDVertex* operator++() + { + return NextVertex(); + } + + /* + Description: + Postfix increment the iterator. Returns: Current vertex. Remarks: - operator++ and NextVertex() behave differently. + operator++(int) and NextVertex() behave differently. */ - const class ON_SubDVertex* operator++() + const class ON_SubDVertex* operator++(int) { const class ON_SubDVertex* v = m_v_current; NextVertex(); @@ -17739,11 +17760,12 @@ public: /* Description: - Increment the iterator. + Pre-increment the iterator and return the new current vertex. Returns: Next vertex. Remarks: - operator++ and NextVertex() behave differently. + operator++(void) and NextVertex() behave the same. + operator++(int) and NextVertex() behave differently. */ const class ON_SubDVertex* NextVertex() { @@ -17801,6 +17823,41 @@ public: return (m_v_current = m_v_last); } + /* + Description: + Get the iterator's base component in which we are iterating, if it exists. + Returns: + m_component_ptr if it exists, or ON_SubDComponentPtr::Null. + */ + ON_SubDComponentPtr BaseComponentPtr() const + { + return m_component_ptr.m_ptr == 0 ? ON_SubDComponentPtr::Null : m_component_ptr; + } + + /* + Description: + Get the iterator's base edge in which we are iterating, if it exists. + Returns: + m_component_ptr.Edge() if it exists, or nullptr. + */ + ON_SubDEdge* BaseEdge() const + { + if (m_component_ptr.m_ptr == 0) return nullptr; + return m_component_ptr.IsEdge() ? m_component_ptr.Edge() : nullptr; + } + + /* + Description: + Get the iterator's base edge in which we are iterating, if it exists. + Returns: + m_component_ptr.Face() if it exists, or nullptr. + */ + ON_SubDFace* BaseFace() const + { + if (m_component_ptr.m_ptr == 0) return nullptr; + return m_component_ptr.IsFace() ? m_component_ptr.Face() : nullptr; + } + private: void Internal_Init( const ON_SubDRef& subd_ref, @@ -17967,13 +18024,34 @@ public: /* Description: - Increment the iterator. + Prefix increment the iterator. + Returns: + Next edge. + Remarks: + operator++(void) and NextEdge() behave the same. + In OpenNURBS 8.17 and earlier, this function was incorrectly implemented as + a postfix increment (operator++(int)). This has been corrected in OpenNURBS 8.18. + If you have a plugin compiled without inlining optimizations (e.g. in Debug mode), + operator++(void) will be calling the version in opennurbs.dll distributed + with Rhino that is used to run the plugin. + If you have a plugin compiled with inlining optimizations (e.g. in Release mode), + operator++(void) will behave like the version in opennurbs_subd.h distributed + with the Rhino SDK that was used to compiled the plugin. + */ + const class ON_SubDEdge* operator++() + { + return NextEdge(); + } + + /* + Description: + Postfix increment the iterator. Returns: Current edge. Remarks: - operator++ and NextEdge() behave differently. + operator++(int) and NextEdge() behave differently. */ - const class ON_SubDEdge* operator++() + const class ON_SubDEdge* operator++(int) { const class ON_SubDEdge* e = m_e_current; NextEdge(); @@ -18012,11 +18090,12 @@ public: /* Description: - Increment the iterator. + Pre-increment the iterator and return the new current edge. Returns: Next edge. Remarks: - operator++ and NextEdge() behave differently. + operator++(void) and NextEdge() behave the same. + operator++(int) and NextEdge() behave differently. */ const class ON_SubDEdge* NextEdge() { @@ -18074,6 +18153,41 @@ public: return m_e_current = m_e_last; } + /* + Description: + Get the iterator's base component in which we are iterating, if it exists. + Returns: + m_component_ptr if it exists, or ON_SubDComponentPtr::Null. + */ + ON_SubDComponentPtr BaseComponentPtr() const + { + return m_component_ptr.m_ptr == 0 ? ON_SubDComponentPtr::Null : m_component_ptr; + } + + /* + Description: + Get the iterator's base vertex in which we are iterating, if it exists. + Returns: + m_component_ptr.Vertex() if it exists, or nullptr. + */ + ON_SubDVertex* BaseVertex() const + { + if (m_component_ptr.m_ptr == 0) return nullptr; + return m_component_ptr.IsVertex() ? m_component_ptr.Vertex() : nullptr; + } + + /* + Description: + Get the iterator's base edge in which we are iterating, if it exists. + Returns: + m_component_ptr.Face() if it exists, or nullptr. + */ + ON_SubDFace* BaseFace() const + { + if (m_component_ptr.m_ptr == 0) return nullptr; + return m_component_ptr.IsFace() ? m_component_ptr.Face() : nullptr; + } + private: void Internal_Init( const ON_SubDRef& subd_ref, @@ -18240,13 +18354,34 @@ public: /* Description: - Returns the current face and increment the iterator. + Prefix increment the iterator. + Returns: + Next face. + Remarks: + operator++(void) and NextFace() behave the same. + In OpenNURBS 8.17 and earlier, this function was incorrectly implemented as + a postfix increment (operator++(int)). This has been corrected in OpenNURBS 8.18. + If you have a plugin compiled without inlining optimizations (e.g. in Debug mode), + operator++(void) will be calling the version in opennurbs.dll distributed + with Rhino that is used to run the plugin. + If you have a plugin compiled with inlining optimizations (e.g. in Release mode), + operator++(void) will behave like the version in opennurbs_subd.h distributed + with the Rhino SDK that was used to compiled the plugin. + */ + const class ON_SubDFace* operator++() + { + return NextFace(); + } + + /* + Description: + Postfix increment the iterator. Returns: Current face. Remarks: - operator++ and NextFace() behave differently. + operator++(int) and NextFace() behave differently. */ - const class ON_SubDFace* operator++() + const class ON_SubDFace* operator++(int) { const class ON_SubDFace* f = m_face_current; NextFace(); @@ -18286,11 +18421,12 @@ public: /* Description: - Returns the next face and increments the iterator. + Pre-increment the iterator and return the new current face. Returns: Next face. Remarks: - operator++ and NextFace() behave differently. + operator++(void) and NextFace() behave the same. + operator++(int) and NextFace() behave differently. */ const class ON_SubDFace* NextFace() { @@ -18348,6 +18484,41 @@ public: return (m_face_current = m_face_last); } + /* + Description: + Get the iterator's base component in which we are iterating, if it exists. + Returns: + m_component_ptr if it exists, or ON_SubDComponentPtr::Null. + */ + ON_SubDComponentPtr BaseComponentPtr() const + { + return m_component_ptr.m_ptr == 0 ? ON_SubDComponentPtr::Null : m_component_ptr; + } + + /* + Description: + Get the iterator's base vertex in which we are iterating, if it exists. + Returns: + m_component_ptr.Vertex() if it exists, or nullptr. + */ + ON_SubDVertex* BaseVertex() const + { + if (m_component_ptr.m_ptr == 0) return nullptr; + return m_component_ptr.IsVertex() ? m_component_ptr.Vertex() : nullptr; + } + + /* + Description: + Get the iterator's base edge in which we are iterating, if it exists. + Returns: + m_component_ptr.Edge() if it exists, or nullptr. + */ + ON_SubDEdge* BaseEdge() const + { + if (m_component_ptr.m_ptr == 0) return nullptr; + return m_component_ptr.IsEdge() ? m_component_ptr.Edge() : nullptr; + } + private: void Internal_Init( @@ -22064,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 84fb8c19..496b03e9 100644 --- a/opennurbs_sun.cpp +++ b/opennurbs_sun.cpp @@ -892,6 +892,11 @@ double ON_Sun::CImpl::North(void) const void ON_Sun::CImpl::SetNorth(double north) { + // 28th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-81036 + // Only set the north if it actually changes. + if (north == North()) + return; + if (nullptr != _earth_anchor_point) { // Store the north in the earth anchor point. This is more complicated than just setting one value. @@ -938,6 +943,11 @@ double ON_Sun::CImpl::Latitude(void) const void ON_Sun::CImpl::SetLatitude(double lat) { + // 28th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-81036 + // Only set the latitude if it actually changes. + if (lat == Latitude()) + return; + if (nullptr != _earth_anchor_point) { // Store the latitude in the earth anchor point. @@ -971,6 +981,11 @@ double ON_Sun::CImpl::Longitude(void) const void ON_Sun::CImpl::SetLongitude(double lon) { + // 28th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-81036 + // Only set the longitude if it actually changes. + if (lon == Longitude()) + return; + if (nullptr != _earth_anchor_point) { // Store the longitude in the earth anchor point. @@ -1504,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); } @@ -1538,6 +1554,12 @@ void ON_Sun::OnInternalXmlChanged(const ON_Sun* sun) SetLatitude(sun->Latitude()); SetLongitude(sun->Longitude()); } + + // 18th March 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-86536 + // Since the XML has been bulk-overwritten, we need to make sure the sun calculation is done next time + // Azimuth() or Altitude() is called. This bug was introduced by the fix for RH-81036 because that fix + // prevented code which had the side-effect of setting _calc_dirty from being executed. + _impl->_calc_dirty = true; } static const int SunVersion = 1; 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 20934e66..ab67b714 100644 --- a/opennurbs_xml.cpp +++ b/opennurbs_xml.cpp @@ -2174,6 +2174,13 @@ void ON_XMLNodePrivate::MoveBefore(ON_XMLNode& other) { pBeforeOther->_private->m_next_sibling = &m_node; } + + // 13th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-86050 + if (m_parent->_private->m_last_child == &m_node) + { + // 'this' was the tail; redirect the parent's last child. + m_parent->_private->m_last_child = pPrev; + } } void ON_XMLNodePrivate::MoveAfter(ON_XMLNode& other) @@ -2197,6 +2204,13 @@ void ON_XMLNodePrivate::MoveAfter(ON_XMLNode& other) m_parent->_private->m_first_child = m_next_sibling; } + // 13th February 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-86050 + if (m_parent->_private->m_last_child == &m_node) + { + // 'this' was the tail; redirect the parent's last child. + m_parent->_private->m_last_child = pPrev; + } + m_next_sibling = other._private->m_next_sibling; other._private->m_next_sibling = &m_node; @@ -3774,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. //{ @@ -3782,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; @@ -3797,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; @@ -3899,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. @@ -3950,8 +3964,20 @@ const ON_XMLRootNode& ON_XMLUserData::XMLRootForRead(void) const return _private->m_XMLRoot.NodeForRead(); } -ON_XMLRootNode& ON_XMLUserData::XMLRootForWrite(void) const +ON_XMLRootNode& ON_XMLUserData::XMLRootForWrite(void) const // const is a mistake. [SDK_UNFREEZE] { + // 22nd January 2025 John Croudy, https://mcneel.myjetbrains.com/youtrack/issue/RH-67878 + // Per conversation with Dale Lear, this is bad because we are actually going to change the user data while + // it's already attached to the attributes. We're actually expected to delete the old user data and create + // new user data with the changes in it, because otherwise Rhino can't know that it was changed. However, + // Dale suggested the easiest way to fix this is to just bump the copy count, because the optimization in + // CRhinoObject::ModifyAttributes() involving the copy count is really a hack anyway. + + if (m_userdata_copycount > 0) // Zero means we are not even copying user data. + { + const_cast(this)->m_userdata_copycount++; + } + return _private->m_XMLRoot.NodeForWrite(); } diff --git a/opennurbs_xml.h b/opennurbs_xml.h index 58a668b7..f5bd4d76 100644 --- a/opennurbs_xml.h +++ b/opennurbs_xml.h @@ -139,6 +139,8 @@ typedef bool (*ON_XMLRecurseChildrenCallback)(class ON_XMLNode*, void*); // Decals (stored in object attributes user data). +#define ON_RDK_USER_DATA_ROOT L"render-content-manager-data" + #define ON_RDK_DECALS L"decals" #define ON_RDK_DECAL L"decal" diff --git a/opennurbs_zlib.cpp b/opennurbs_zlib.cpp index 2ca0d40a..cbe93598 100644 --- a/opennurbs_zlib.cpp +++ b/opennurbs_zlib.cpp @@ -23,6 +23,13 @@ #include "opennurbs_zlib.h" +// RH-86025, RH3DM-179, 2025-02-14, Pierre: +// We only really need the #pragma comment(lib, "path") to work when we are using +// MSVC and the Microsoft linker, but not defining include dirs in build properties. +// Other compilers do not recognize this pragma and instead use build properties to link to libs. +// ON_CMAKE_BUILD should be renamed "ON_NOT_USING_MSVC_LINK_LIB_PRAGMA" +// This whole thing should be reworked so MSVC builds also use build properties, +// instead of this non-portable pragma. #if defined(ON_COMPILER_MSC) && !defined(ON_CMAKE_BUILD) #if !defined(OPENNURBS_ZLIB_LIB_DIR) @@ -35,9 +42,8 @@ #else // Define OPENNURBS_ZLIB_LIB_DIR to be the directory containing zlib.lib #error You must define OPENNURBS_ZLIB_LIB_DIR -#endif +#endif // defined(OPENNURBS_INPUT_LIBS_DIR) -#endif #if defined(_LIB) && defined(_MT) && !defined(_DLL) @@ -48,9 +54,11 @@ // using Microsoft DLL C-runtime #pragma message ( "Linking with zlib.lib in " OPENNURBS_PP2STR(OPENNURBS_ZLIB_LIB_DIR) ) #pragma comment(lib, "\"" OPENNURBS_ZLIB_LIB_DIR "/" "zlib.lib" "\"") -#endif +#endif // defined(_LIB) && defined(_MT) && !defined(_DLL) -#endif +#endif // !defined(OPENNURBS_ZLIB_LIB_DIR) + +#endif // defined(ON_COMPILER_MSC) && !defined(ON_CMAKE_BUILD) // compressed buffer I/O uses zlib 1.1.3 inflate()/deflate() class ON_CompressorImplementation