// // Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// #include "opennurbs.h" #include "opennurbs_internal_defines.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif const ONX_ErrorCounter ONX_ErrorCounter::operator+ ( const ONX_ErrorCounter& rhs ) { ONX_ErrorCounter sum; sum.m_failure_count = m_failure_count + rhs.m_failure_count; sum.m_error_count = m_error_count + rhs.m_error_count; sum.m_warning_count = m_warning_count + rhs.m_warning_count; sum.m_state_bit_field = m_state_bit_field | rhs.m_state_bit_field; sum.m_opennurbs_library_error_count = (m_opennurbs_library_error_count < rhs.m_opennurbs_library_error_count) ? rhs.m_opennurbs_library_error_count : m_opennurbs_library_error_count; sum.m_opennurbs_library_warning_count = (m_opennurbs_library_warning_count < rhs.m_opennurbs_library_warning_count) ? rhs.m_opennurbs_library_warning_count : m_opennurbs_library_warning_count; return sum; } const ONX_ErrorCounter ONX_ErrorCounter::operator+= (const ONX_ErrorCounter& rhs) { m_failure_count += rhs.m_failure_count; m_error_count += rhs.m_error_count; m_warning_count += rhs.m_warning_count; m_state_bit_field |= rhs.m_state_bit_field; if (m_opennurbs_library_error_count < rhs.m_opennurbs_library_error_count) m_opennurbs_library_error_count = rhs.m_opennurbs_library_error_count; if (m_opennurbs_library_warning_count < rhs.m_opennurbs_library_warning_count) m_opennurbs_library_warning_count = rhs.m_opennurbs_library_warning_count; return *this; } unsigned int ONX_ErrorCounter::FailureCount() const { return this->m_failure_count; } unsigned int ONX_ErrorCounter::ErrorCount() const { return this->m_error_count; } unsigned int ONX_ErrorCounter::WarningCount() const { return this->m_warning_count; } unsigned int ONX_ErrorCounter::TotalCount() const { return FailureCount() + ErrorCount() + WarningCount(); } unsigned int ONX_ErrorCounter::IncrementFailureCount() { return ++m_failure_count; // <- Good location for a debugger breakpoint. } unsigned int ONX_ErrorCounter::IncrementErrorCount() { return ++m_error_count; // <- Good location for a debugger breakpoint. } unsigned int ONX_ErrorCounter::IncrementWarningCount() { return ++m_warning_count; // <- Good location for a debugger breakpoint. } void ONX_ErrorCounter::ClearLibraryErrors() { m_opennurbs_library_error_count = ON_GetErrorCount(); m_state_bit_field |= 1; } unsigned int ONX_ErrorCounter::AddLibraryErrors() { const bool bActive = (0 != (1 & m_state_bit_field)); const unsigned int count0 = m_opennurbs_library_error_count; ClearLibraryErrors(); const unsigned int count = bActive ? (m_opennurbs_library_error_count - count0) : 0U; if (bActive && count > 0) m_error_count += count; // <- Good location for a debugger breakpoint. return count; } void ONX_ErrorCounter::ClearLibraryWarnings() { m_opennurbs_library_warning_count = ON_GetWarningCount(); m_state_bit_field |= 2; } unsigned int ONX_ErrorCounter::AddLibraryWarnings() { const bool bActive = (0 != (2 & m_state_bit_field)); const unsigned int count0 = m_opennurbs_library_warning_count; ClearLibraryWarnings(); const unsigned int count = bActive ? (m_opennurbs_library_warning_count - count0) : 0U; if (bActive && count>0) m_warning_count += count; // <- Good location for a debugger breakpoint. return count; } void ONX_ErrorCounter::ClearLibraryErrorsAndWarnings() { ClearLibraryErrors(); ClearLibraryWarnings(); } unsigned int ONX_ErrorCounter::AddLibraryErrorsAndWarnings() { return AddLibraryErrors() + AddLibraryWarnings(); } void ONX_ErrorCounter::Dump(ON_TextLog& text_log) const { text_log.Print( "%u failures, %u errors, %u warnings", m_failure_count, m_error_count, m_warning_count ); } #if defined(OPENNURBS_EXPORTS) //////////////////////////////////////////////////////////////////////////////// // // When openNURBS is used as a Microsoft Windows DLL, it is possible // for new/delete to allocate memory in one executable and delete // it in another. Because Microsoft Windows has incompatible memory // managers in its plethora of C libraries and the choice of which // C library actually gets used depends on the code generation // options you choose, we get lots of support questions asking // about hard to trace crashes. // // If you are using openNURBS as a Windows DLL, you are sure you know // what you are doing, and you promise never to ask for support, then // feel free to delete these overrides. // // #if defined(ON_COMPILER_MSC) #pragma message( " --- OpenNURBS overriding ONX_Model new and delete" ) #endif // ONX_Model_UserData new/delete void* ONX_Model_UserData::operator new(size_t sz) { // ONX_Model_UserData new return onmalloc(sz); } void ONX_Model_UserData::operator delete(void* p) { // ONX_Model_UserData delete onfree(p); } void* ONX_Model_UserData::operator new[] (size_t sz) { // ONX_Model_UserData array new return onmalloc(sz); } void ONX_Model_UserData::operator delete[] (void* p) { // ONX_Model_UserData array delete onfree(p); } void* ONX_Model_UserData::operator new(size_t, void* p) { // ONX_Model_UserData placement new return p; } void ONX_Model_UserData::operator delete(void*, void*) { // ONX_Model_UserData placement delete return; } // ONX_Model new/delete void* ONX_Model::operator new(size_t sz) { // ONX_Model new return onmalloc(sz); } void ONX_Model::operator delete(void* p) { // ONX_Model delete onfree(p); } void* ONX_Model::operator new[] (size_t sz) { // ONX_Model array new return onmalloc(sz); } void ONX_Model::operator delete[] (void* p) { // ONX_Model array delete onfree(p); } void* ONX_Model::operator new(size_t, void* p) { // ONX_Model placement new return p; } void ONX_Model::operator delete(void*, void*) { // ONX_Model placement delete return; } #endif // // //////////////////////////////////////////////////////////////////////////////// class ONX_ModelComponentReferenceLink { public: ONX_ModelComponentReferenceLink() = default; ~ONX_ModelComponentReferenceLink() = default; ONX_ModelComponentReferenceLink(const ONX_ModelComponentReferenceLink&) = default; ONX_ModelComponentReferenceLink& operator=(const ONX_ModelComponentReferenceLink&) = default; public: // m_mcr in this class is the "first" std::shared_ptr that manages // the referenced ON_ModelComponent and is what insures ON_ModelComponent alive inside // and ONX_Model for the duration of the model's lifetime. // // m_sn = ON_ModelComponent.RuntimeSerialNumber() so sn lookup can be safely used to find runtime pointer. // When m_mcr.ModelComponent() is not nullptr, then m_sn = m_mcr.ModelComponent()->RuntimeSerialNumber(). ON_ModelComponentReference m_mcr; ON__UINT64 m_sn = 0; ONX_ModelComponentReferenceLink* m_next = nullptr; ONX_ModelComponentReferenceLink* m_prev = nullptr; }; enum class RenderContentKinds { Material, Environment, Texture }; ON_3dmObjectAttributes* GetComponentAttributes(const ON_ModelComponent& component) { // To have attributes, the component must be an ON_ModelGeometryComponent. const auto* mgc = ON_ModelGeometryComponent::Cast(&component); if (nullptr == mgc) return nullptr; // Wrong type of component. return mgc->ExclusiveAttributes(); } class ONX_ModelPrivate final { public: ONX_ModelPrivate(ONX_Model& m); ~ONX_ModelPrivate(); using EmbeddedFileMap = std::unordered_map; bool GetRDKDocumentXML(ON_wString& xml, bool embedded_files, int archive_3dm_version) const; ONX_Model_UserData* GetRDKDocumentUserData(int archive_3dm_version) const; void PopulateDefaultRDKDocumentXML(ON_XMLRootNode& root) const; bool PopulateRDKComponents(int archive_3dm_version); bool UpdateRDKUserData(int archive_3dm_version); bool CreateRenderContentFromXML(class ON_XMLNode& model_node, RenderContentKinds kind); bool CreateXMLFromRenderContent(ON_XMLNode& model_node, RenderContentKinds kind) const; bool SetRDKDocumentInformation(const wchar_t* xml, ONX_Model_UserData& docud, int archive_3dm_version) const; ON_XMLNode* GetRenderContentSectionNode(ON_XMLNode& model_node, RenderContentKinds kind) const; ON_XMLNode* GetPostEffectSectionNode(ON_XMLNode& model_node, ON_PostEffect::Types type) const; static void RemoveAllEmbeddedFiles(ONX_Model& model); static bool GetEntireRDKDocument(const ONX_Model_UserData& docud, ON_wString& xml, ONX_Model* model); public: ONX_Model& m_model; ON__UINT64 m_model_content_version_number = 0; ON_ClassArray m_mcr_lists; }; ON_InternalXMLImpl::~ON_InternalXMLImpl() { if (nullptr != _local_node) { delete _local_node; _local_node = nullptr; } } 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. // In the case of decals, it's a pointer into the decal collection's XML. if (nullptr != _model_node) return *_model_node; // Since the model node is not set, we need a local node to hold the XML. If one has not been created yet, // create it. The local node is owned by this object. This case occurs for free-floating copies of model // objects and also for free-floating copies of decals and mesh modifiers. This node only contains the XML // data that's relevant to the object it's for, not the entire XML, but the object's node is still a child // node under the same node hierarchy as if it were the entire XML. if (nullptr == _local_node) _local_node = new ON_XMLNode(NameOfRootNode()); return *_local_node; } void ON_InternalXMLImpl::SetModelNode(ON_XMLNode& node) { ON_ASSERT(_model_node == nullptr); std::lock_guard lg(_mutex); if (nullptr != _local_node) { delete _local_node; _local_node = nullptr; } _model_node = &node; } ON_wString ON_InternalXMLImpl::NameOfRootNode(void) const { return ON_XMLRootNode().TagName(); } ON_XMLVariant ON_InternalXMLImpl::GetParameter(const wchar_t* path_to_node, const wchar_t* param_name, const ON_XMLVariant& def) const { return InternalGetParameter(path_to_node, param_name, L"", def); } ON_XMLVariant ON_InternalXMLImpl::GetParameter_NoType(const wchar_t* path_to_node, const wchar_t* param_name, const wchar_t* default_type, const ON_XMLVariant& def) const { return InternalGetParameter(path_to_node, param_name, default_type, def); } ON_XMLVariant ON_InternalXMLImpl::InternalGetParameter(const wchar_t* path_to_node, const wchar_t* param_name, const wchar_t* default_type, const ON_XMLVariant& def) const { std::lock_guard lg(_mutex); const ON_XMLNode* node_read = Node().GetNodeAtPath(path_to_node); if (nullptr == node_read) return def; ON_XMLVariant value; ON_XMLParameters p(*node_read); p.SetDefaultReadType(default_type); if (!p.GetParam(param_name, value)) return def; return value; } bool ON_InternalXMLImpl::SetParameter(const wchar_t* path_to_node, const wchar_t* param_name, const ON_XMLVariant& value) { return InternalSetParameter(path_to_node, param_name, true, value); } bool ON_InternalXMLImpl::SetParameter_NoType(const wchar_t* path_to_node, const wchar_t* param_name, const ON_XMLVariant& value) { return InternalSetParameter(path_to_node, param_name, false, value); } bool ON_InternalXMLImpl::InternalSetParameter(const wchar_t* path_to_node, const wchar_t* param_name, bool write_type, const ON_XMLVariant& value) { std::lock_guard lg(_mutex); bool success = false; ON_XMLNode* node_write = Node().CreateNodeAtPath(path_to_node); if (nullptr != node_write) { ON_XMLParameters p(*node_write); p.SetWriteTypeProperty(write_type); if (nullptr != p.SetParam(param_name, value)) success = true; } return success; } bool ON_InternalXMLImpl::RemoveParameter(const wchar_t* path_to_node, const wchar_t* param_name) { std::lock_guard lg(_mutex); bool success = false; ON_XMLNode* node = Node().GetNodeAtPath(path_to_node); if (nullptr != node) { ON_XMLNode* child = node->GetNamedChild(param_name); success = node->RemoveChild(child); } return success; } // ONX_Model ONX_Model::ONX_Model() { m_private = new ONX_ModelPrivate(*this); // Tell the sun in our render settings to use the earth anchor point in our settings. m_settings.m_RenderSettings.Sun().UseEarthAnchorPoint(m_settings.m_earth_anchor_point); } ONX_Model::~ONX_Model() { Reset(); delete m_private; m_private = nullptr; } void ONX_Model::Reset() { m_3dm_file_version = 0; m_3dm_opennurbs_version = 0; m_sStartSectionComments = ON_String::EmptyString; m_properties = ON_3dmProperties::Empty; m_settings = ON_3dmSettings::Default; for (unsigned int i = 0; i < m_userdata_table.UnsignedCount(); i++) { delete m_userdata_table[i]; } m_userdata_table.Destroy(); for (int i = 0; i < m_private->m_mcr_lists.Count(); i++) { ONX_ModelComponentList& list = m_private->m_mcr_lists[i]; ONX_ModelComponentReferenceLink* mcr_link = list.m_first_mcr_link; while (nullptr != mcr_link) { mcr_link->m_mcr = ON_ModelComponentReference::Empty; mcr_link = mcr_link->m_next; } list.m_first_mcr_link = nullptr; list.m_last_mcr_link = nullptr; } m_mcr_sn_map.EmptyList(); m_mcr_link_fsp.ReturnAll(); m_default_line_pattern = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_Linetype::Continuous); m_default_layer = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_Layer::Default); m_default_text_style = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_TextStyle::Default); m_default_dimension_style = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_DimStyle::Default); m_manifest.Reset(); m_original_to_manifest_map = ON_ManifestMap::Empty; m_manifest_to_original_map = ON_ManifestMap::Empty; m_model_geometry_bbox = ON_BoundingBox::UnsetBoundingBox; m_render_light_bbox = ON_BoundingBox::UnsetBoundingBox; if (nullptr != m_model_user_string_list) { delete m_model_user_string_list; m_model_user_string_list = nullptr; } } void ONX_Model::Internal_ComponentTypeBoundingBox( const ON_ModelComponent::Type component_type, ON_BoundingBox& bbox ) const { if (false == bbox.IsValid()) { ON_BoundingBox local_bbox; for ( const ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(component_type).m_first_mcr_link; nullptr != link; link = link->m_next ) { const ON_ModelComponent* model_component = link->m_mcr.ModelComponent(); if (nullptr == model_component) continue; if (component_type != model_component->ComponentType()) continue; const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr == model_geometry) continue; const ON_3dmObjectAttributes* attributes = model_geometry->Attributes(nullptr); if (nullptr != attributes && attributes->IsInstanceDefinitionObject()) continue; const ON_Geometry* geometry = model_geometry->Geometry(nullptr); if (nullptr == geometry) continue; local_bbox.Union(geometry->BoundingBox()); } bbox = local_bbox; } return; } ON_BoundingBox ONX_Model::ModelGeometryBoundingBox() const { Internal_ComponentTypeBoundingBox(ON_ModelComponent::Type::ModelGeometry,m_model_geometry_bbox); return m_model_geometry_bbox; } ON_BoundingBox ONX_Model::RenderLightBoundingBox() const { Internal_ComponentTypeBoundingBox(ON_ModelComponent::Type::RenderLight,m_render_light_bbox); return m_render_light_bbox; } const ON_ComponentManifest& ONX_Model::Manifest() const { return m_manifest; } const ON_ManifestMap& ONX_Model::OriginalToModelMap() const { return m_original_to_manifest_map; } const ON_ManifestMap& ONX_Model::ModelToOriginalMap() const { return m_manifest_to_original_map; } bool ONX_Model::ValdateComponentIdAndName( ON_ModelComponent::Type component_type, const ON_UUID& candidate_id, const ON_UUID& component_parent_id, const wchar_t* candidate_name, bool bResolveIdConflict, bool bResolveNameConflict, ON_UUID& model_id, ON_wString& model_name ) const { for (;;) { if (false == ON_ModelComponent::ComponentTypeIsValid(component_type)) { ON_ERROR("Invalid component_type parameter."); break; } const bool bIndexRequired = ON_ModelComponent::IndexRequired(component_type); const unsigned int count = m_manifest.ComponentIndexLimit(component_type); if (bIndexRequired && count >= 0x7FFFFFFFU) { ON_ERROR("Unable to create model component index."); break; } const bool bIdAvailable = m_manifest.IdIsAvailable(candidate_id); const bool bCreateId = ON_UuidIsNil(candidate_id) || (false == bIdAvailable && bResolveIdConflict); if (false == bIdAvailable && false == bCreateId) { break; } ON_wString name(candidate_name); name.TrimLeftAndRight(); const bool bUniqueNameReqired = ON_ModelComponent::UniqueNameRequired(component_type); if ( bUniqueNameReqired ) { const ON_UUID name_parent_id = ON_ModelComponent::UniqueNameIncludesParent(component_type) ? component_parent_id : ON_nil_uuid; ON_NameHash name_hash = ON_NameHash::Create(name_parent_id, name); if (name_hash.IsInvalidNameHash()) { if (false == bResolveNameConflict) { ON_ERROR("Invalid candidate_name parameter."); break; } name = ON_wString::EmptyString; name_hash = ON_NameHash::Create(name_parent_id, name); } const bool bNameIsValid = name.IsNotEmpty() && m_manifest.NameIsAvailable(component_type, name_hash); if (false == bNameIsValid ) { // we need to create a unique and non-empty name if (false == bResolveNameConflict) { // not allowed to modify name break; } name = m_manifest.UnusedName(component_type, component_parent_id, nullptr, name, nullptr, ON_UNSET_UINT_INDEX, nullptr); if (name.IsEmpty()) { ON_ERROR("Unable to create component name."); break; } } } model_id = bCreateId ? ON_CreateId() : candidate_id; model_name = name; return true; } model_id = ON_nil_uuid; model_name = ON_wString::EmptyString; return false; } unsigned int ONX_Model::ComponentIndexLimit( ON_ModelComponent::Type component_type ) const { return m_manifest.ComponentIndexLimit(component_type); } unsigned int ONX_Model::ActiveAndDeletedComponentCount( ON_ModelComponent::Type component_type ) const { return m_manifest.ActiveAndDeletedComponentCount(component_type); } unsigned int ONX_Model::ActiveComponentCount( ON_ModelComponent::Type component_type ) const { return m_manifest.ActiveComponentCount(component_type); } unsigned int ONX_Model::DeletedComponentCount( ON_ModelComponent::Type component_type ) const { return m_manifest.DeletedComponentCount(component_type); } ON_ModelComponentReference ONX_Model::ComponentFromIndex( ON_ModelComponent::Type component_type, int component_model_index ) const { if (component_model_index >= 0) { return ComponentFromUnsignedIndex(component_type,(unsigned int)component_model_index); } return ON_ModelComponentReference::Empty; } ON_ModelComponentReference ONX_Model::ComponentFromUnsignedIndex( ON_ModelComponent::Type component_type, unsigned int component_model_index ) const { ON__UINT64 sn = m_manifest.ItemFromIndex(component_type,component_model_index).ComponentRuntimeSerialNumber(); ONX_ModelComponentReferenceLink* link = Internal_ModelComponentLinkFromSerialNumber(sn); return (nullptr != link) ? link->m_mcr : ON_ModelComponentReference::Empty; } ON_ModelComponentReference ONX_Model::ComponentFromId( ON_ModelComponent::Type component_type, ON_UUID component_model_id ) const { ON__UINT64 sn = m_manifest.ItemFromId(component_type, component_model_id).ComponentRuntimeSerialNumber(); ONX_ModelComponentReferenceLink* link = Internal_ModelComponentLinkFromSerialNumber(sn); return (nullptr != link) ? link->m_mcr : ON_ModelComponentReference::Empty; } const ON_ModelComponentReference& ONX_Model::ComponentFromRuntimeSerialNumber( ON__UINT64 runtime_serial_number ) const { ONX_ModelComponentReferenceLink* link = Internal_ModelComponentLinkFromSerialNumber(runtime_serial_number); return (nullptr != link) ? link->m_mcr : ON_ModelComponentReference::Empty; } ON_ModelComponentReference ONX_Model::ModelGeometryFromId( ON_UUID model_object_id ) const { return ComponentFromId(ON_ModelComponent::Type::ModelGeometry, model_object_id); } const ON_ModelGeometryComponent& ONX_Model::ModelGeometryComponentFromId( ON_UUID model_object_id ) const { const ON_ModelGeometryComponent* p = ON_ModelGeometryComponent::Cast(ModelGeometryFromId(model_object_id).ModelComponent()); return (nullptr != p) ? *p : ON_ModelGeometryComponent::Unset; } ON_ModelComponentReference ONX_Model::ComponentFromName( ON_ModelComponent::Type component_type, ON_UUID component_parent_id, const wchar_t* component_model_name ) const { const ON_UUID name_parent_id = ON_ModelComponent::UniqueNameIncludesParent(component_type) ? component_parent_id : ON_nil_uuid; const bool bIgnoreCase = ON_ModelComponent::UniqueNameIgnoresCase(component_type); const ON_NameHash component_model_name_hash = ON_NameHash::Create(name_parent_id,component_model_name,bIgnoreCase); return ComponentFromNameHash(component_type,component_model_name_hash); } ON_ModelComponentReference ONX_Model::ComponentFromNameHash( ON_ModelComponent::Type component_type, const ON_NameHash& component_model_name_hash ) const { ON__UINT64 sn = m_manifest.ItemFromNameHash(component_type, component_model_name_hash).ComponentRuntimeSerialNumber(); ONX_ModelComponentReferenceLink* link = Internal_ModelComponentLinkFromSerialNumber(sn); return (nullptr != link) ? link->m_mcr : ON_ModelComponentReference::Empty; } ON_ModelComponentReference ONX_Model::ImageFromIndex( int image_model_index ) const { ON_ModelComponentReference cr = ComponentFromIndex(ON_ModelComponent::Type::Image, image_model_index); return cr; } ON_ModelComponentReference ONX_Model::ImageFromId( ON_UUID image_id ) const { ON_ModelComponentReference cr = ComponentFromId(ON_ModelComponent::Type::Image, image_id); return cr; } ON_ModelComponentReference ONX_Model::ImageFromFileFullPath( const wchar_t* image_file_full_path_name ) const { ON_FileReference file_reference; file_reference.SetFullPath(image_file_full_path_name,false); return ImageFromFileReference(file_reference); } ON_ModelComponentReference ONX_Model::ImageFromFileContent( const ON_ContentHash& image_file_content_hash ) const { ON_FileReference file_reference; file_reference.SetContentHash(image_file_content_hash); return ImageFromFileReference(file_reference); } ON_ModelComponentReference ONX_Model::ImageFromFileReference( const ON_FileReference& file_reference ) const { const ON_wString full_path_name(file_reference.FullPath()); const bool bCheckFullPath = full_path_name.IsNotEmpty(); const ON_ContentHash file_content_hash = file_reference.ContentHash(); const bool bCheckContentHash = file_content_hash.IsSet(); if (false == bCheckFullPath && false == bCheckContentHash) return ON_ModelComponentReference::Empty; ONX_ModelComponentIterator it(*this,ON_ModelComponent::Type::Image); for ( ON_ModelComponentReference cr = it.FirstComponentReference(); false == cr.IsEmpty(); cr = it.NextComponentReference()) { const ON_Bitmap* image = ON_Bitmap::Cast(cr.ModelComponent()); if (nullptr == image) continue; const ON_FileReference& image_file_reference = image->FileReference(); if (bCheckFullPath) { if (false == full_path_name.EqualPath(image_file_reference.FullPath())) continue; } if (bCheckContentHash) { if (0 != ON_ContentHash::CompareContent(file_content_hash, image_file_reference.ContentHash())) continue; } return cr; } return ON_ModelComponentReference::Empty; } ON_ModelComponentReference ONX_Model::LinePatternFromIndex( int layer_model_index ) const { ON_ModelComponentReference cr = ComponentFromIndex(ON_ModelComponent::Type::LinePattern,layer_model_index); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LinePatternFromId( ON_UUID layer_model_id ) const { ON_ModelComponentReference cr = ComponentFromId(ON_ModelComponent::Type::LinePattern,layer_model_id); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LinePatternFromName( const wchar_t* line_pattern_name ) const { ON_ModelComponentReference cr = ComponentFromName(ON_ModelComponent::Type::LinePattern,ON_nil_uuid,line_pattern_name); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LinePatternFromNameHash( ON_NameHash line_pattern_model_name_hash ) const { ON_ModelComponentReference cr = ComponentFromNameHash(ON_ModelComponent::Type::LinePattern,line_pattern_model_name_hash); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LinePatternFromAttributes( const ON_3dmObjectAttributes& attributes ) const { int line_pattern_index = ON_Linetype::Continuous.Index(); switch ( attributes.LinetypeSource() ) { case ON::linetype_from_layer: if (attributes.m_layer_index >= 0) { const ON_Layer* layer = ON_Layer::Cast(LayerFromIndex(attributes.m_layer_index).ModelComponent()); if ( nullptr != layer ) line_pattern_index = layer->LinetypeIndex(); } break; case ON::linetype_from_object: line_pattern_index = attributes.m_linetype_index; break; case ON::linetype_from_parent: line_pattern_index = attributes.m_linetype_index; // TODO: if object is an instance definition, get linetype // from instance references. break; } return LinePatternFromIndex(line_pattern_index); } ON_ModelComponentReference ONX_Model::LinePatternFromLayerIndex( int layer_index ) const { ON_ModelComponentReference layer_component = LayerFromIndex(layer_index); const ON_Layer* layer = ON_Layer::Cast(layer_component.ModelComponent()); if (nullptr == layer) layer = &ON_Layer::Default; return LinePatternFromIndex(layer->m_linetype_index); } ON_ModelComponentReference ONX_Model::LayerFromIndex( int layer_model_index ) const { ON_ModelComponentReference cr = ComponentFromIndex(ON_ModelComponent::Type::Layer,layer_model_index); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LayerFromId( ON_UUID layer_model_id ) const { ON_ModelComponentReference cr = ComponentFromId(ON_ModelComponent::Type::Layer,layer_model_id); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LayerFromName( ON_UUID layer_parent_id, const wchar_t* layer_model_name ) const { ON_ModelComponentReference cr = ComponentFromName(ON_ModelComponent::Type::Layer,layer_parent_id,layer_model_name); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LayerFromNameHash( const ON_NameHash& layer_model_name_hash ) const { ON_ModelComponentReference cr = ComponentFromNameHash(ON_ModelComponent::Type::Layer,layer_model_name_hash); return cr.IsEmpty() ? m_default_layer : cr; } ON_ModelComponentReference ONX_Model::LayerFromAttributes( const ON_3dmObjectAttributes& attributes ) const { return LayerFromIndex(attributes.m_layer_index); } ON_ModelComponentReference ONX_Model::DimensionStyleFromIndex( int dimension_style_model_index ) const { ON_ModelComponentReference cr = ComponentFromIndex(ON_ModelComponent::Type::DimStyle,dimension_style_model_index); return cr.IsEmpty() ? DefaultDimensionStyle() : cr; } ON_ModelComponentReference ONX_Model::DimensionStyleFromId( ON_UUID dimension_style_model_id ) const { ON_ModelComponentReference cr = ComponentFromId(ON_ModelComponent::Type::DimStyle,dimension_style_model_id); return cr.IsEmpty() ? DefaultDimensionStyle() : cr; } ON_ModelComponentReference ONX_Model::DimensionStyleFromName( const wchar_t* dimension_style_model_name ) const { ON_ModelComponentReference cr = ComponentFromName(ON_ModelComponent::Type::DimStyle,ON_nil_uuid,dimension_style_model_name); return cr.IsEmpty() ? DefaultDimensionStyle() : cr; } ON_ModelComponentReference ONX_Model::DimensionStyleFromNameHash( ON_NameHash dimension_style_model_name_hash ) const { ON_ModelComponentReference cr = ComponentFromNameHash(ON_ModelComponent::Type::DimStyle,dimension_style_model_name_hash); return cr.IsEmpty() ? DefaultDimensionStyle() : cr; } ON_UUID ONX_Model::CurrentDimensionStyleId() const { const ON_UUID id = m_settings.CurrentDimensionStyleId(); if (ON_nil_uuid == id) return id; if (id == ON_DimStyle::SystemDimstyleFromId(id).Id()) return id; const ON_ModelComponentReference cr = ComponentFromId(ON_ModelComponent::Type::DimStyle,id); if (false == cr.IsEmpty()) return id; return ON_nil_uuid; } ON_ModelComponentReference ONX_Model::CurrentDimensionStyle() const { return DimensionStyleFromId(CurrentDimensionStyleId()); } bool ONX_Model::SetCurrentDimensionStyleId( ON_UUID dimension_style_id ) { for (;;) { if (ON_nil_uuid == dimension_style_id) break; if (dimension_style_id == ON_DimStyle::SystemDimstyleFromId(dimension_style_id).Id()) break; const ON_ModelComponentReference cr = ComponentFromId(ON_ModelComponent::Type::DimStyle,dimension_style_id); if ( nullptr != ON_DimStyle::Cast(cr.ModelComponent()) ) break; ON_ERROR("Invalid dimension_style_id parameter."); return false; } m_settings.SetCurrentDimensionStyleId(dimension_style_id); return true; } ON_ModelComponentReference ONX_Model::DefaultDimensionStyle() const { return m_default_dimension_style; } static bool Internal_DimStyleHasFont( const ON_ModelComponentReference& mcr, unsigned int managed_font_sn, double model_space_text_scale, bool bIgnoreSystemDimStyles ) { for (;;) { if (0 == managed_font_sn) break; const ON_DimStyle* dim_style_component = ON_DimStyle::Cast(mcr.ModelComponent()); if (nullptr == dim_style_component) break; if (managed_font_sn != dim_style_component->Font().ManagedFontSerialNumber()) break; // wrong font if (bIgnoreSystemDimStyles && dim_style_component->IsSystemComponent()) break; // no system components if (dim_style_component->ParentIdIsNotNil()) break; // no overrides if (model_space_text_scale > 0.0 && !(model_space_text_scale == dim_style_component->DimScale())) break; return true; } return false; } ON_ModelComponentReference ONX_Model::FirstDimensionStyleFromManagedFontSerialNumber( unsigned int managed_font_serial_number, double model_space_text_scale, bool bIgnoreSystemDimStyles ) const { if (Internal_DimStyleHasFont(m_default_dimension_style, managed_font_serial_number, model_space_text_scale, bIgnoreSystemDimStyles)) return m_default_dimension_style; for ( ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::DimStyle).m_first_mcr_link; nullptr != link; link = link->m_next ) { if (Internal_DimStyleHasFont(link->m_mcr, managed_font_serial_number, model_space_text_scale, bIgnoreSystemDimStyles)) return link->m_mcr; } return ON_ModelComponentReference::Empty; } ON_ModelComponentReference ONX_Model::FirstDimensionStyleFromFont( const ON_Font* font, double model_space_text_scale, bool bIgnoreSystemDimStyles ) const { const ON_Font* managed_font = (nullptr != font) ? font->ManagedFont() : nullptr; const unsigned int managed_font_sn = (nullptr != managed_font) ? managed_font->ManagedFontSerialNumber() : 0; if ( 0 == managed_font_sn ) { ON_ERROR("Invalid font parameter"); return ON_ModelComponentReference::Empty; } return FirstDimensionStyleFromManagedFontSerialNumber(managed_font_sn, model_space_text_scale, bIgnoreSystemDimStyles); } ON_ModelComponentReference ONX_Model::DimensionStyleWithFontCharacteristics( const ON_Font& font_characteristics, double model_space_text_scale ) { // search for existing dimension style const bool bIgnoreSystemComponents = true; ON_ModelComponentReference existing_mcr = this->FirstDimensionStyleFromFont(&font_characteristics, model_space_text_scale, bIgnoreSystemComponents); const ON_DimStyle* dim_style = ON_DimStyle::Cast(existing_mcr.ModelComponent()); if (nullptr != dim_style) return existing_mcr; // create new dimension style const ON_DimStyle* default_dim_style = ON_DimStyle::Cast(this->DimensionStyleFromId(this->m_settings.CurrentDimensionStyleId()).ModelComponent()); ON_DimStyle* new_dim_style = ON_DimStyle::CreateFromFont(&font_characteristics, model_space_text_scale, default_dim_style, &this->Manifest(), nullptr); if (nullptr != new_dim_style) { const bool bResolveIdAndNameConflicts = true; return this->AddManagedModelComponent(new_dim_style, bResolveIdAndNameConflicts); } return ON_ModelComponentReference::Empty; } ON_ModelComponentReference ONX_Model::RemoveModelComponent( ON_ModelComponent::Type component_type, ON_UUID component_id ) { const ON_ComponentManifestItem item = m_manifest.ItemFromId(component_id); if (item.IsUnset()) { ON_ERROR("Invalid component_id parameter."); return ON_ModelComponentReference::Empty; } if (ON_ModelComponent::Type::Unset != component_type && component_type != item.ComponentType()) { ON_ERROR("Invalid model_component type."); return ON_ModelComponentReference::Empty; } if (!m_manifest.RemoveComponent(item.Id())) { ON_ERROR("Unable to remove component from manifest."); return ON_ModelComponentReference::Empty; } ONX_ModelComponentReferenceLink* mcr_link = Internal_ModelComponentLinkFromSerialNumber(item.ComponentRuntimeSerialNumber()); if (nullptr == mcr_link) { ON_ERROR("component not in model."); return ON_ModelComponentReference::Empty; } ON_ModelComponentReference mcr = mcr_link->m_mcr; Internal_RemoveModelComponentReferenceLink(mcr_link); return mcr; } ON_ModelComponentReference ONX_Model::AddManagedModelComponent( class ON_ModelComponent* managed_model_component ) { return AddManagedModelComponent(managed_model_component,true); } int ONX_Model::AddLayer( const wchar_t* layer_name, ON_Color layer_color ) { ON_Layer layer; const ON_wString unused_name = m_manifest.UnusedName(layer.ComponentType(), layer.ParentId(), layer_name, nullptr, nullptr, 0, nullptr); layer.SetName(unused_name); if ( layer_color != ON_Color::UnsetColor) layer.SetColor(layer_color); const ON_ModelComponentReference mr = AddModelComponent(layer, true); const ON_Layer* managed_layer = ON_Layer::FromModelComponentRef(mr, nullptr); int layer_index = (nullptr != managed_layer) ? managed_layer->Index() : ON_UNSET_INT_INDEX; if ( layer_index < 0 ) { ON_ERROR("failed to add layer."); } return layer_index; } int ONX_Model::AddDefaultLayer( const wchar_t* layer_name, ON_Color layer_color ) { ON_UUID default_layer_id = m_settings.CurrentLayerId(); int default_layer_index = m_settings.CurrentLayerIndex(); for ( int pass = 0; pass < 2; pass++ ) { if (0 == pass) { if (ON_nil_uuid == default_layer_id) continue; } else { if (ON_UNSET_INT_INDEX == default_layer_index) continue; } ON_ModelComponentReference mcr = (0 == pass) ? LayerFromId(default_layer_id) : LayerFromIndex(default_layer_index); const ON_Layer* layer = ON_Layer::FromModelComponentRef(mcr, nullptr); if (nullptr == layer) continue; if (false == layer->IsSystemComponent() && layer->Index() >= 0 && layer->ParentIdIsNil() && layer->IsVisible() && false == layer->IsLocked() ) { m_settings.SetCurrentLayerId(layer->Id()); return layer->Index(); } ON_Layer default_layer(*layer); default_layer.ClearId(); default_layer.ClearIndex(); default_layer.ClearParentId(); default_layer.SetVisible(true); default_layer.SetLocked(false); if (nullptr == layer_name || 0 == layer_name[0]) layer_name = layer->NameAsPointer(); default_layer.SetName(m_manifest.UnusedName(default_layer.ComponentType(), ON_nil_uuid, layer_name, nullptr, nullptr, 0, nullptr) ); if (ON_Color::UnsetColor != layer_color) default_layer.SetColor(layer_color); const ON_ModelComponentReference mr = AddModelComponent(default_layer, true); const ON_Layer* managed_layer = ON_Layer::FromModelComponentRef(mr, nullptr); if (nullptr != managed_layer && managed_layer->Index() >= 0 && false == managed_layer->IsSystemComponent()) { m_settings.SetCurrentLayerId(managed_layer->Id()); return managed_layer->Index(); } } int layer_index = AddLayer(layer_name, layer_color); if (layer_index >= 0) { const ON_ModelComponentReference mr = LayerFromIndex(layer_index); const ON_Layer* managed_layer = ON_Layer::FromModelComponentRef(mr, nullptr); if (nullptr != managed_layer && managed_layer->Index() >= 0 && false == managed_layer->IsSystemComponent()) { m_settings.SetCurrentLayerId(managed_layer->Id()); return managed_layer->Index(); } } ON_ERROR("Failed to add default layer."); return ON_UNSET_INT_INDEX; } int ONX_Model::AddDefaultDimensionStyle( const wchar_t* dimension_style_name, ON::LengthUnitSystem length_unit_system, double model_tolerance ) { const ON_DimStyle* source_dimstyle = nullptr; ON_UUID dimstyle_id = m_settings.CurrentDimensionStyleId(); if (ON_nil_uuid != dimstyle_id) { const ON_DimStyle* dimstyle = ON_DimStyle::FromModelComponentRef(DimensionStyleFromId(dimstyle_id),nullptr); if ( nullptr != dimstyle && dimstyle->ParentIdIsNil() ) { if ( dimstyle->Index() < 0 || dimstyle->IsSystemComponent() ) source_dimstyle = dimstyle; else return dimstyle->Index(); } else { source_dimstyle = &ON_DimStyle::SystemDimstyleFromId(dimstyle_id); if (dimstyle_id != source_dimstyle->Id()) source_dimstyle = nullptr; } } if (nullptr == source_dimstyle) { bool bIsMetricLengthUnit = false; bool bIsUnitedStatesLengthUnit = false; for (int pass = 0; pass < 3; pass++) { if (1 == pass) length_unit_system = m_settings.m_ModelUnitsAndTolerances.m_unit_system.UnitSystem(); else if (pass > 1) length_unit_system = ON_3dmSettings::Default.m_ModelUnitsAndTolerances.m_unit_system.UnitSystem(); bIsMetricLengthUnit = ON::IsMetricLengthUnit(length_unit_system); bIsUnitedStatesLengthUnit = bIsMetricLengthUnit ? false : ON::IsUnitedStatesCustomaryLengthUnit(length_unit_system); if (bIsMetricLengthUnit || bIsUnitedStatesLengthUnit) break; } for (int pass = 0; pass < 2; pass++) { if ( model_tolerance > 0.0 ) break; if (1 == pass) model_tolerance = m_settings.m_ModelUnitsAndTolerances.m_absolute_tolerance; else if (pass > 1) model_tolerance = ON_3dmSettings::Default.m_ModelUnitsAndTolerances.m_absolute_tolerance; } const ON_DimStyle* system_dimstyle = nullptr; if (bIsMetricLengthUnit) { double meters_per_unit = ON::UnitScale(length_unit_system, ON::LengthUnitSystem::Meters); double tolerance_mm = model_tolerance > 0.0 ? model_tolerance*ON::UnitScale(length_unit_system, ON::LengthUnitSystem::Millimeters) : 0.0; system_dimstyle = (tolerance_mm <= 1.0 || meters_per_unit < 1.0) ? &ON_DimStyle::DefaultMillimeterSmall : &ON_DimStyle::DefaultMillimeterLarge; } else if (bIsUnitedStatesLengthUnit) { system_dimstyle = (ON::LengthUnitSystem::Feet == length_unit_system) ? &ON_DimStyle::DefaultFootInchArchitecture : &ON_DimStyle::DefaultInchDecimal; } else { system_dimstyle = &ON_DimStyle::Default; } source_dimstyle = system_dimstyle; } ON_DimStyle* default_dimstyle = new ON_DimStyle(*source_dimstyle); default_dimstyle->ClearIndex(); default_dimstyle->ClearParentId(); default_dimstyle->SetId(); default_dimstyle->SetName(m_manifest.UnusedName(*default_dimstyle)); ON_ModelComponentReference mcr = AddManagedModelComponent(default_dimstyle, true); const ON_DimStyle* model_dimstyle = ON_DimStyle::FromModelComponentRef(mcr,nullptr); if (nullptr == model_dimstyle) { ON_ERROR("Failed to add default dimstyle."); return ON_UNSET_INT_INDEX; } m_settings.SetCurrentDimensionStyleId(model_dimstyle->Id()); return model_dimstyle->Index(); } ON_ModelComponentReference ONX_Model::AddManagedModelComponent( class ON_ModelComponent* managed_model_component, bool bResolveIdAndNameConflicts ) { const bool bManagedComponent = true; const bool bUpdateComponentIdentification = true; return AddModelComponentForExperts(managed_model_component, bManagedComponent, bResolveIdAndNameConflicts, bUpdateComponentIdentification); } ON_ModelComponentReference ONX_Model::AddModelComponent( const class ON_ModelComponent& model_component ) { return AddModelComponent(model_component, true); } ON_ModelComponentReference ONX_Model::AddModelComponent( const class ON_ModelComponent& model_component, bool bResolveIdAndNameConflicts ) { const ON_ModelComponent::Type component_type = model_component.ComponentType(); if (!ON_ModelComponent::ComponentTypeIsValidAndNotMixed(component_type)) { ON_ERROR("Invalid model_component parameter."); return ON_ModelComponentReference::Empty; } ON_UUID id; ON_wString name; if (false == ValdateComponentIdAndName(component_type, model_component.Id(), model_component.ParentId(), model_component.Name(), bResolveIdAndNameConflicts, bResolveIdAndNameConflicts, id, name)) { ON_ERROR("Invalid model_component id or name."); return ON_ModelComponentReference::Empty; } ON_ModelComponent* candidate_model_component = nullptr; if (ON_ModelComponent::Type::RenderLight == component_type || ON_ModelComponent::Type::ModelGeometry == component_type) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(&model_component); if (nullptr != model_geometry) { const ON_Geometry* geometry = model_geometry->Geometry(nullptr); candidate_model_component = ON_ModelGeometryComponent::Create(*geometry, model_geometry->Attributes(nullptr), nullptr); } } else { // Something simple like ON_Layer, ON_DimStyle, ON_RenderContent, etc candidate_model_component = model_component.Duplicate(); } bool bManagedComponent = true; bool bUpdateComponentIdentification = true; return Internal_AddModelComponent(candidate_model_component, id, model_component.ParentId(), name, bManagedComponent, bUpdateComponentIdentification); } ON_ModelComponentReference ONX_Model::AddModelComponentForExperts( ON_ModelComponent* model_component, bool bManagedComponent, bool bResolveIdAndNameConflicts, bool bUpdateComponentIdentification ) { if (nullptr == model_component) { ON_ERROR("model_component parameter is nullptr."); return ON_ModelComponentReference::Empty; } const ON_ModelComponent::Type component_type = model_component->ComponentType(); if (!ON_ModelComponent::ComponentTypeIsValidAndNotMixed(component_type)) { ON_ERROR("Invalid model_component->ComponentType() value."); return ON_ModelComponentReference::Empty; } const ON_UUID parent_id = model_component->ParentId(); ON_UUID id = ON_nil_uuid; ON_wString name; if (false == ValdateComponentIdAndName(component_type, model_component->Id(), parent_id, model_component->Name(), bResolveIdAndNameConflicts, bResolveIdAndNameConflicts && bUpdateComponentIdentification, id, name)) { ON_ERROR("Invalid model_component id or name."); return ON_ModelComponentReference::Empty; } return Internal_AddModelComponent(model_component, id, parent_id, name, bManagedComponent, bUpdateComponentIdentification); } ON_ModelComponentReference ONX_Model::AddModelGeometryComponentForExperts( bool bManageGeometry, class ON_Object* geometry_object, bool bManageAttributes, class ON_3dmObjectAttributes* attributes, bool bResolveIdAndNameConflicts ) { if ( nullptr == ON_Geometry::Cast(geometry_object) ) { ON_ERROR("Invalid geometry_object parameter."); return ON_ModelComponentReference::Empty; } if (nullptr == attributes) { bManageAttributes = true; bResolveIdAndNameConflicts = true; ON_Light* light = ON_Light::Cast(geometry_object); if (nullptr != light && ON_nil_uuid != light->m_light_id && false == m_manifest.IdIsAvailable(light->m_light_id) ) { light->m_light_id = ON_nil_uuid; } const ON_Layer* default_layer = (ON_nil_uuid == this->m_settings.CurrentLayerId()) ? nullptr : ON_Layer::FromModelComponentRef(LayerFromId(m_settings.CurrentLayerId()),nullptr); if (nullptr == default_layer) { ONX_ModelComponentIterator layer_it(*this, ON_ModelComponent::Type::Layer); for (const ON_Layer* layer = ON_Layer::Cast(layer_it.FirstComponent()); nullptr != layer; layer = ON_Layer::Cast(layer_it.NextComponent()) ) { if (layer->IsVisible() && false == layer->IsLocked() && layer->ParentIdIsNil()) { default_layer = layer; break; } } } attributes = new ON_3dmObjectAttributes(); if (nullptr != light) { attributes->m_uuid = light->m_light_id; attributes->m_name = light->m_light_name; } attributes->m_layer_index = (nullptr == default_layer) ? ON_Layer::Default.Index() : default_layer->Index(); } else if ( ON_nil_uuid != attributes->m_uuid && false == m_manifest.IdIsAvailable(attributes->m_uuid)) { if (bResolveIdAndNameConflicts) attributes->m_uuid = ON_nil_uuid; else { ON_ERROR("attributes->m_uuid is not valid or is in use."); return ON_ModelComponentReference::Empty; } } ON_ModelGeometryComponent* model_geometry_component = ON_ModelGeometryComponent::CreateForExperts( bManageGeometry, geometry_object, bManageAttributes, attributes, nullptr ); if ( nullptr == model_geometry_component ) return ON_ModelComponentReference::Empty; ON_ModelComponentReference model_component_reference = AddManagedModelComponent(model_geometry_component, bResolveIdAndNameConflicts); if (model_component_reference.IsEmpty()) delete model_geometry_component; return model_component_reference; } ON_ModelComponentReference ONX_Model::AddModelGeometryComponent( const class ON_Object* geometry_object, const class ON_3dmObjectAttributes* attributes ) { return AddModelGeometryComponent(geometry_object, attributes, true); } ON_ModelComponentReference ONX_Model::AddModelGeometryComponent( const class ON_Object* geometry_object, const class ON_3dmObjectAttributes* attributes, bool bResolveIdAndNameConflicts ) { if ( nullptr == ON_Geometry::Cast(geometry_object) ) { ON_ERROR("Invalid geometry_object parameter."); return ON_ModelComponentReference::Empty; } ON_UUID id = ON_nil_uuid; if (nullptr != attributes && ON_nil_uuid != attributes->m_uuid ) { if (m_manifest.IdIsAvailable(attributes->m_uuid)) id = attributes->m_uuid; else if (false == bResolveIdAndNameConflicts) { ON_ERROR("attributes->m_uuid is invalid or in use in this model."); return ON_ModelComponentReference::Empty; } } ON_3dmObjectAttributes* managed_attributes = nullptr; ON_Object* managed_geometry_object = geometry_object->Duplicate(); if (nullptr != attributes) { managed_attributes = new ON_3dmObjectAttributes(*attributes); managed_attributes->m_uuid = id; } const bool bManageGeometry = true; const bool bManageAttributes = true; return AddModelGeometryComponentForExperts( bManageGeometry, managed_geometry_object, bManageAttributes, managed_attributes, bResolveIdAndNameConflicts ); } ON_ModelComponentReference ONX_Model::AddManagedModelGeometryComponent( class ON_Object* managed_geometry_object, class ON_3dmObjectAttributes* managed_attributes ) { return AddManagedModelGeometryComponent(managed_geometry_object, managed_attributes, true); } ON_ModelComponentReference ONX_Model::AddManagedModelGeometryComponent( class ON_Object* managed_geometry_object, class ON_3dmObjectAttributes* managed_attributes, bool bResolveIdAndNameConflicts ) { if ( nullptr == ON_Geometry::Cast(managed_geometry_object) ) { ON_ERROR("Invalid managed_geometry_object parameter."); return ON_ModelComponentReference::Empty; } const bool bManageGeometry = true; const bool bManageAttributes = true; return AddModelGeometryComponentForExperts( bManageGeometry, managed_geometry_object, bManageAttributes, managed_attributes, bResolveIdAndNameConflicts ); } ONX_ModelComponentReferenceLink* ONX_Model::Internal_ModelComponentLinkFromSerialNumber( ON__UINT64 model_component_runtime_serial_number ) const { const struct ON_SerialNumberMap::SN_ELEMENT* e = m_mcr_sn_map.FindSerialNumber(model_component_runtime_serial_number); return (ONX_ModelComponentReferenceLink*)((nullptr != e) ? e->m_value.m_u.ptr : nullptr); } ONX_ModelComponentReferenceLink* ONX_Model::Internal_AddModelComponentReference( ON_ModelComponentReference mcr ) { const ON_ModelComponent* model_component = mcr.ModelComponent(); if (nullptr == model_component) { ON_ERROR("Invalid mcr parameter - mcr.ModelComponent() is nullptr."); return nullptr; } const ON_ModelComponent::Type component_type = model_component->ComponentType(); if (ON_ModelComponent::Type::Unset == component_type || ON_ModelComponent::Type::Mixed == component_type) { ON_ERROR("Invalid component type"); return nullptr; } ONX_ModelComponentReferenceLink* mcr_link = Internal_ModelComponentLinkFromSerialNumber(model_component->RuntimeSerialNumber()); if (nullptr != mcr_link) { // This component was already added. return mcr_link; } struct ON_SerialNumberMap::SN_ELEMENT* e = m_mcr_sn_map.AddSerialNumber(model_component->RuntimeSerialNumber()); if (nullptr == e) { ON_ERROR("m_mcr_sn_map.AddSerialNumber(model_component->RuntimeSerialNumber()) failed."); return nullptr; } if ( 0 == m_mcr_link_fsp.SizeofElement()) m_mcr_link_fsp.Create(sizeof(*mcr_link),0,0); e->m_value.m_u.ptr = m_mcr_link_fsp.AllocateDirtyElement(); mcr_link = new(e->m_value.m_u.ptr) ONX_ModelComponentReferenceLink(); mcr_link->m_mcr = mcr; ONX_Model::ONX_ModelComponentList& list = Internal_ComponentList( component_type ); if (component_type != list.m_component_type) { ON_ERROR("Internal_ComponentList(component_type) failed"); } else { if (nullptr == list.m_first_mcr_link) { list.m_first_mcr_link = mcr_link; mcr_link->m_prev = nullptr; } else { mcr_link->m_prev = list.m_last_mcr_link; list.m_last_mcr_link->m_next = mcr_link; } mcr_link->m_next = nullptr; list.m_last_mcr_link = mcr_link; list.m_count++; } return mcr_link; } void ONX_Model::Internal_RemoveModelComponentReferenceLink( class ONX_ModelComponentReferenceLink* mcr_link ) { if (nullptr == mcr_link) return; const ON_ModelComponent* model_component = mcr_link->m_mcr.ModelComponent(); if ( nullptr == model_component ) return; m_mcr_sn_map.RemoveSerialNumberAndId(model_component->ReferenceModelSerialNumber()); mcr_link->m_mcr = ON_ModelComponentReference::Empty; ONX_Model::ONX_ModelComponentList& list = Internal_ComponentList(model_component->ComponentType()); if (list.m_count > 0) { if (mcr_link->m_prev) mcr_link->m_prev->m_next = mcr_link->m_next; else list.m_first_mcr_link = mcr_link->m_next; if (mcr_link->m_next) mcr_link->m_next->m_prev = mcr_link->m_prev; else list.m_last_mcr_link = mcr_link->m_prev; list.m_count--; } mcr_link->m_prev = nullptr; mcr_link->m_next = nullptr; m_mcr_link_fsp.ReturnElement(mcr_link); } ONX_Model::ONX_ModelComponentList& ONX_Model::Internal_ComponentList( ON_ModelComponent::Type component_type ) { const int i = static_cast(component_type); return (i < ONX_Model::m_private->m_mcr_lists.Count()) ? m_private->m_mcr_lists[i] : m_private->m_mcr_lists[0]; } const ONX_Model::ONX_ModelComponentList& ONX_Model::Internal_ComponentListConst( ON_ModelComponent::Type component_type ) const { const int i = static_cast(component_type); return (i < ONX_Model::m_private->m_mcr_lists.Count()) ? m_private->m_mcr_lists[i] : m_private->m_mcr_lists[0]; } ON_ModelComponentReference ONX_Model::Internal_AddModelComponent( ON_ModelComponent* model_component, ON_UUID id, ON_UUID name_parent_id, const ON_wString& name, bool bManagedComponent, bool bUpdateComponentIdentification ) { for (;;) { if (nullptr == model_component) { ON_ERROR("Invalid model_component parameter."); break; } if (model_component->IsSystemComponent()) { ON_ERROR("Invalid model_component parameter."); break; } const ON_ModelComponent::Type component_type = model_component->ComponentType(); if (!ON_ModelComponent::ComponentTypeIsValidAndNotMixed(component_type)) { ON_ERROR("Invalid model_component parameter."); break; } const int original_index = model_component->Index(); const ON_UUID original_id = model_component->Id(); const bool bIsEmbeddedFileReference = (ON_ModelComponent::Type::Image == component_type); const ON_Bitmap* embedded_file = bIsEmbeddedFileReference ? ON_Bitmap::Cast(model_component) : nullptr; const ON_NameHash original_name_hash = bIsEmbeddedFileReference ? (nullptr == embedded_file ? ON_NameHash::UnsetNameHash : ON_NameHash::CreateFilePathHash(embedded_file->FileReference()) ) : model_component->NameHash(); const ON_NameHash name_hash = bIsEmbeddedFileReference ? original_name_hash : ON_NameHash::Create(name_parent_id,name,ON_ModelComponent::UniqueNameIgnoresCase(component_type)); int manifest_item_index = ON_UNSET_INT_INDEX; const bool bIndexRequired = ON_ModelComponent::IndexRequired(component_type); if (bIndexRequired) { manifest_item_index = m_manifest.ComponentIndexLimit(component_type); if (manifest_item_index >= 0x7FFFFFFF && bUpdateComponentIdentification) { ON_ERROR("Unable to set model_component_index."); break; } } const bool bUpdateIndex = (bUpdateComponentIdentification && bIndexRequired && original_index != (int)manifest_item_index); const bool bUpdateId = (bUpdateComponentIdentification && !(original_id == id)); const bool bUpdateName = (false == bIsEmbeddedFileReference && bUpdateComponentIdentification && original_name_hash != name_hash); if (bUpdateIndex && model_component->IndexIsLocked()) { ON_ERROR("Unable to set component index."); break; } if (bUpdateId && model_component->IdIsLocked()) { ON_ERROR("Unable to set component id."); break; } if (bUpdateName && model_component->NameIsLocked()) { ON_ERROR("Unable to set component name."); break; } if (bUpdateIndex && false == model_component->SetIndex((int)manifest_item_index)) { ON_ERROR("model_component->SetIndex(...) failed."); break; } if(bUpdateId && false == model_component->SetId(id) ) { ON_ERROR("model_component->SetId(...) failed."); break; } if(bUpdateName && false == model_component->SetName(name) ) { ON_ERROR("model_component->SetName(...) failed."); break; } if (bUpdateComponentIdentification) { ON_ModelGeometryComponent* geometry_component = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != geometry_component) { const ON_Light* light = ON_Light::Cast(geometry_component->Geometry(nullptr)); if (nullptr != light) { if (id != light->m_light_id) const_cast(light)->m_light_id = id; if (name != light->m_light_name) const_cast(light)->m_light_name = name; } const ON_3dmObjectAttributes* attributes = geometry_component->Attributes(nullptr); if (nullptr != attributes) { if (id != attributes->m_uuid) const_cast(attributes)->m_uuid = id; if (name != attributes->m_name) const_cast(attributes)->m_name = name; } } } Internal_IncrementModelContentVersionNumber(); const class ON_ComponentManifestItem& manifest_item = m_manifest.AddComponentToManifest( model_component->ComponentType(), model_component->RuntimeSerialNumber(), id, name_hash ); if (manifest_item.IsUnset()) { ON_ERROR("Unable to update model manifest."); break; } ON_ManifestMapItem original_to_manifest; if ( original_to_manifest.SetSourceIdentification(model_component) && original_to_manifest.SetDestinationIdentification(&manifest_item)) { m_original_to_manifest_map.AddMapItem(original_to_manifest); } ON_ManifestMapItem manifest_to_original; if ( manifest_to_original.SetSourceIdentification(&manifest_item) && manifest_to_original.SetDestinationIdentification(model_component) ) { m_manifest_to_original_map.AddMapItem(manifest_to_original); } if (bIndexRequired) { if (model_component->Index() != manifest_item.Index()) { ON_ERROR("Unexpected manifest_item_index value."); if (bUpdateIndex && false == model_component->SetIndex((int)(manifest_item_index))) { ON_ERROR("model_component->SetIndex(...) failed."); break; } } } else { if (ON_ComponentManifest::UnsetComponentIndex != model_component->Index() ) { ON_ERROR("Unexpected model_component->Index() value."); if (bUpdateIndex && false == model_component->SetIndex(ON_ComponentManifest::UnsetComponentIndex)) { ON_ERROR("model_component->SetIndex(...) failed."); break; } } if ( ON_UNSET_INT_INDEX != manifest_item.Index() ) { ON_ERROR("Unexpected manifest_item_index value."); } } if (ON_ModelComponent::Type::ModelGeometry == model_component->ComponentType()) { ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != model_geometry) { // TEMPORARY - until the id and name can be removed from ON_3dmObjectAttributes, // they need to be synchronized with the ones on ON_ModelGeometryComponent. ON_3dmObjectAttributes* attributes = const_cast< ON_3dmObjectAttributes* >(model_geometry->Attributes(nullptr)); if (nullptr != attributes) { if ( bUpdateId ) attributes->m_uuid = model_geometry->Id(); if (bUpdateName) attributes->m_name = model_geometry->Name(); } } } if (bUpdateComponentIdentification) { // continue even when UpdateReferencedComponents() returns false model_component->UpdateReferencedComponents(ON_ComponentManifest::Empty,m_manifest,m_original_to_manifest_map); } ON_ModelComponentReference model_component_reference = ON_ModelComponentReference::CreateForExperts(model_component,bManagedComponent); Internal_AddModelComponentReference(model_component_reference); return model_component_reference; } return ON_ModelComponentReference::Empty; } /* ON_DEPRECATED */ ON_ModelComponentReference ONX_Model::RenderMaterialFromAttributes(const ON_3dmObjectAttributes& attr) const { return MaterialFromAttributes(attr); } /* ON_DEPRECATED */ ON_ModelComponentReference ONX_Model::RenderMaterialFromLayerIndex(int index) const { return MaterialFromLayerIndex(index); } /* ON_DEPRECATED */ ON_ModelComponentReference ONX_Model::RenderMaterialFromIndex(int index) const { return MaterialFromIndex(index); } /* ON_DEPRECATED */ ON_ModelComponentReference ONX_Model::RenderMaterialFromId(ON_UUID id) const { return MaterialFromId(id); } ON_ModelComponentReference ONX_Model::MaterialFromAttributes( const ON_3dmObjectAttributes& attributes ) const { switch ( attributes.MaterialSource() ) { case ON::material_from_layer: return MaterialFromLayerIndex( attributes.m_layer_index ); break; case ON::material_from_object: return MaterialFromIndex( attributes.m_material_index ); break; case ON::material_from_parent: // TODO: If object is an idef, get material from iref attributes. return MaterialFromIndex( attributes.m_material_index ); break; } return m_default_render_material; } ON_ModelComponentReference ONX_Model::MaterialFromLayerIndex( int layer_index ) const { int material_index = ON_Layer::FromModelComponentRef( LayerFromIndex(layer_index), &ON_Layer::Default )->RenderMaterialIndex(); return MaterialFromIndex(material_index); } ON_ModelComponentReference ONX_Model::MaterialFromIndex( int material_index ) const { ON_ModelComponentReference cr = ComponentFromIndex(ON_ModelComponent::Type::Material, material_index); return cr.IsEmpty() ? m_default_render_material : cr; } ON_ModelComponentReference ONX_Model::MaterialFromId( ON_UUID material_id ) const { ON_ModelComponentReference cr = ComponentFromId(ON_ModelComponent::Type::Material, material_id); return cr.IsEmpty() ? m_default_render_material : cr; } ON_Color ONX_Model::WireframeColorFromAttributes( const ON_3dmObjectAttributes& attributes ) const { ON_Color color = ON_UNSET_COLOR; switch ( attributes.ColorSource() ) { case ON::color_from_layer: color = ON_Layer::FromModelComponentRef( LayerFromIndex(attributes.m_layer_index), &ON_Layer::Default )->Color(); break; case ON::color_from_object: color = attributes.m_color; break; case ON::color_from_material: color = ON_Material::FromModelComponentRef( MaterialFromAttributes(attributes), &ON_Material::Default)->Diffuse(); break; case ON::color_from_parent: color = attributes.m_color; // TODO: if object is an instance definition, get color // from instance references. break; } if ( color == ON_UNSET_COLOR ) color.SetRGB(128,128,128); return color; } void ONX_Model::DumpSummary( ON_TextLog& dump ) const { dump.Print("File version: %u\n",m_3dm_file_version); if (false == dump.IsTextHash()) { dump.Print("File openNURBS version: %u\n", m_3dm_opennurbs_version); if (m_3dm_file_byte_count > 0) dump.Print("File length: %llu bytes\n", m_3dm_file_byte_count); if (m_sStartSectionComments.Length() > 0) { dump.Print("Start section comments:\n"); dump.PushIndent(); dump.PrintWrappedText(static_cast(m_sStartSectionComments)); dump.PopIndent(); dump.Print("\n"); } m_properties.Dump(dump); dump.Print("\n"); } m_settings.Dump(dump); dump.Print("\n"); dump.Print("Contents:\n"); dump.PushIndent(); dump.Print("%u embedded images\n",Internal_ComponentListConst(ON_ModelComponent::Type::Image).m_count); dump.Print("%u materials\n",Internal_ComponentListConst(ON_ModelComponent::Type::RenderMaterial).m_count); dump.Print("%u line patterns\n",Internal_ComponentListConst(ON_ModelComponent::Type::LinePattern).m_count); dump.Print("%u text styles\n",Internal_ComponentListConst(ON_ModelComponent::Type::TextStyle).m_count); dump.Print("%u annotation styles\n",Internal_ComponentListConst(ON_ModelComponent::Type::DimStyle).m_count); dump.Print("%u hatch patterns\n",Internal_ComponentListConst(ON_ModelComponent::Type::HatchPattern).m_count); dump.Print("%u layers\n",Internal_ComponentListConst(ON_ModelComponent::Type::Layer).m_count); dump.Print("%u groups\n",Internal_ComponentListConst(ON_ModelComponent::Type::Group).m_count); dump.Print("%u lights\n",Internal_ComponentListConst(ON_ModelComponent::Type::RenderLight).m_count); dump.Print("%u model geometry objects\n",Internal_ComponentListConst(ON_ModelComponent::Type::ModelGeometry).m_count); if (false == dump.IsTextHash()) { dump.Print("%u user data objects\n", m_userdata_table.UnsignedCount()); } dump.PopIndent(); } void ONX_Model::DumpComponentList( ON_ModelComponent::Type component_type, ON_TextLog& text_log ) const { const ON_wString type_name_string = ON_ModelComponent::ComponentTypeToString(component_type); const wchar_t* type_name = static_cast(type_name_string); unsigned int i = 0; for ( const ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(component_type).m_first_mcr_link; nullptr != link; link = link->m_next ) { text_log.Print(L"%ls %d:\n",type_name,i); const ON_ModelComponent* model_component = link->m_mcr.ModelComponent(); text_log.PushIndent(); if ( nullptr == model_component ) text_log.Print(L"nullptr\n"); else model_component->Dump(text_log); link->m_mcr.ModelComponent(); text_log.PopIndent(); i++; } } void ONX_Model::DumpUserDataTable( ON_TextLog& dump) const { int i; for ( i = 0; i < m_userdata_table.Count(); i++ ) { const ONX_Model_UserData* ud = m_userdata_table[i]; if ( nullptr == ud) continue; dump.Print("User Data Table %d:\n",i); dump.PushIndent(); dump.Print("uuid = "); dump.Print(ud->m_uuid); dump.Print("\n"); ud->m_goo.Dump(dump); dump.PopIndent(); } } void ONX_Model::Dump(ON_TextLog& dump) const { dump.Print("Model summary:\n"); dump.PushIndent(); DumpSummary(dump); dump.PopIndent(); dump.PrintNewLine(); DumpComponentLists(dump); if ( false == dump.IsTextHash() ) { dump.Print("User data table:\n"); dump.PushIndent(); DumpUserDataTable(dump); dump.PopIndent(); dump.PrintNewLine(); } } void ONX_Model::DumpComponentLists( ON_TextLog& dump ) const { const ON_ModelComponent::Type table_types[] = { ON_ModelComponent::Type::Image, ON_ModelComponent::Type::TextureMapping, ON_ModelComponent::Type::Material, ON_ModelComponent::Type::LinePattern, ON_ModelComponent::Type::Layer, ON_ModelComponent::Type::Group, ON_ModelComponent::Type::TextStyle, ON_ModelComponent::Type::DimStyle, ON_ModelComponent::Type::RenderLight, ON_ModelComponent::Type::HatchPattern, ON_ModelComponent::Type::ModelGeometry, ON_ModelComponent::Type::HistoryRecord, ON_ModelComponent::Type::RenderContent, ON_ModelComponent::Type::EmbeddedFile, //ON_ModelComponent::Type::ObsoleteValue, ON_ModelComponent::Type::SectionStyle, ON_ModelComponent::Type::Unset // list terminator }; for (unsigned i = 0; ON_ModelComponent::Type::Unset != table_types[i]; i++) { ON_wString type_name = ON_ModelComponent::ComponentTypeToString(table_types[i]); dump.Print(L"%ls table:\n",static_cast(type_name)); dump.PushIndent(); DumpComponentList(table_types[i],dump); dump.PopIndent(); dump.Print("\n"); } } 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: static int CompareOldIndex( const ON__CIndexPair* a, const ON__CIndexPair* b ); static int CompareOldAndNewIndex( const ON__CIndexPair* a, const ON__CIndexPair* b ); int m_old_index; // value in model.m_..._table[m_table_index].m_..._index; (Read from file) int m_new_index; // index in model.m_..._table[] array }; int ON__CIndexPair::CompareOldIndex( const ON__CIndexPair* a, const ON__CIndexPair* b ) { return (a->m_old_index - b->m_old_index); } int ON__CIndexPair::CompareOldAndNewIndex( const ON__CIndexPair* a, const ON__CIndexPair* b ) { int i; if ( 0 == (i = a->m_old_index - b->m_old_index) ) i = a->m_new_index - b->m_new_index; return i; } bool ONX_Model::Read(const char* filename, ON_TextLog* error_log) { bool rc = false; if (nullptr != filename) { FILE* fp = ON::OpenFile(filename, "rb"); if ( 0 != fp ) { ON_BinaryFile file(ON::archive_mode::read3dm,fp); rc = Read(file, error_log); ON::CloseFile(fp); } } return rc; } bool ONX_Model::Read(const wchar_t* filename, ON_TextLog* error_log) { bool rc = false; if (nullptr != filename) { FILE* fp = ON::OpenFile(filename, L"rb"); if ( 0 != fp ) { ON_BinaryFile file(ON::archive_mode::read3dm,fp); rc = Read(file, error_log); ON::CloseFile(fp); } } return rc; } bool ONX_Model::IncrementalReadBegin( ON_BinaryArchive& archive, bool bManageComponents, unsigned int table_filter, ON_TextLog* error_log ) { Reset(); const bool bResolveIdAndNameConflicts = true; const bool bUpdateComponentIdentification = true; int rc; if ( 0 == table_filter ) table_filter = 0xFFFFFFFF; // read everything // STEP 1: REQUIRED - Read start section if ( !archive.Read3dmStartSection( &m_3dm_file_version, m_sStartSectionComments ) ) { return false; } // STEP 2: REQUIRED - Read properties section if ( !archive.Read3dmProperties( m_properties ) ) { return false; } // version of opennurbs used to write the file. m_3dm_opennurbs_version = archive.ArchiveOpenNURBSVersion(); // STEP 3: REQUIRED - Read setting section if ( !archive.Read3dmSettings( m_settings ) ) { return false; } // STEP 4: REQUIRED - Read embedded bitmap table if ( archive.BeginRead3dmBitmapTable() ) { if ( 0 != (static_cast(ON_3dmArchiveTableType::bitmap_table) & table_filter) ) { for(;;) { ON_Bitmap* bitmap = nullptr; rc = archive.Read3dmBitmap(&bitmap); if ( rc==0 ) break; // end of bitmap table if (rc < 0) break; if ( AddModelComponentForExperts(bitmap,bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty() ) delete bitmap; } } // If BeginRead3dmBitmapTable() returns true, // then you MUST call EndRead3dmBitmapTable(). if ( !archive.EndRead3dmBitmapTable() ) { return false; } } // STEP 5: REQUIRED - Read texture mapping table if ( archive.BeginRead3dmTextureMappingTable() ) { if ( 0 != (static_cast(ON_3dmArchiveTableType::texture_mapping_table) & table_filter) ) { for(;;) { ON_TextureMapping* texture_mapping = nullptr; rc = archive.Read3dmTextureMapping(&texture_mapping); if ( rc==0 ) break; // end of texture_mapping table if ( rc < 0 ) break; if (AddModelComponentForExperts(texture_mapping, bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty()) delete texture_mapping; } } // If BeginRead3dmTextureMappingTable() returns true, // then you MUST call EndRead3dmTextureMappingTable(). if ( !archive.EndRead3dmTextureMappingTable() ) { return false; } } // STEP 6: REQUIRED - Read render material table if ( archive.BeginRead3dmMaterialTable() ) { const ON_UUID settings_current_id = m_settings.CurrentMaterialId(); const int settings_current_index = m_settings.CurrentMaterialIndex(); bool bSetCurrentById = !(ON_nil_uuid == settings_current_id); bool bSetCurrentByIndex = false == bSetCurrentById && settings_current_index >= 0; m_settings.SetCurrentMaterialId(ON_nil_uuid); if ( 0 != (static_cast(ON_3dmArchiveTableType::material_table) & table_filter) ) { for(;;) { ON_Material* material = nullptr; rc = archive.Read3dmMaterial(&material); if ( rc==0 ) break; // end of material table if (rc < 0) break; // index or id might be modified by AddModelComponentForExperts() const bool bSetAsCurrent = (bSetCurrentById && settings_current_id == material->Id()) || (bSetCurrentByIndex && settings_current_index == material->Index()); if ( AddModelComponentForExperts(material,bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty() ) { delete material; } else if (bSetAsCurrent) { m_settings.SetCurrentMaterialId(material->Id()); bSetCurrentById = false; bSetCurrentByIndex = false; } } } // If BeginRead3dmMaterialTable() returns true, // then you MUST call EndRead3dmMaterialTable(). if ( !archive.EndRead3dmMaterialTable() ) { return false; } } // STEP 7: REQUIRED - Read line type table if ( archive.BeginRead3dmLinetypeTable() ) { const ON_UUID settings_current_id = m_settings.CurrentLinePatternId(); const int settings_current_index = m_settings.CurrentLinePatternIndex(); bool bSetCurrentById = !(ON_nil_uuid == settings_current_id); bool bSetCurrentByIndex = false == bSetCurrentById && settings_current_index >= 0; m_settings.SetCurrentLinePatternId(ON_nil_uuid); if ( 0 != (static_cast(ON_3dmArchiveTableType::linetype_table) & table_filter) ) { for(;;) { ON_Linetype* line_pattern = nullptr; rc = archive.Read3dmLinetype(&line_pattern); if ( rc==0 ) break; // end of linetype table if (rc < 0) break; // index or id might be modified by AddModelComponentForExperts() const bool bSetAsCurrent = (bSetCurrentById && settings_current_id == line_pattern->Id()) || (bSetCurrentByIndex && settings_current_index == line_pattern->Index()); if ( AddModelComponentForExperts(line_pattern,bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty() ) { delete line_pattern; } else if (bSetAsCurrent) { m_settings.SetCurrentLinePatternId(line_pattern->Id()); bSetCurrentById = false; bSetCurrentByIndex = false; } } } // If BeginRead3dmLinetypeTable() returns true, // then you MUST call EndRead3dmLinetypeTable(). if ( !archive.EndRead3dmLinetypeTable() ) { return false; } } // STEP 8: REQUIRED - Read layer table if ( archive.BeginRead3dmLayerTable() ) { const ON_UUID settings_current_id = m_settings.CurrentLayerId(); const int settings_current_index = m_settings.CurrentLayerIndex(); bool bSetCurrentById = !(ON_nil_uuid == settings_current_id); bool bSetCurrentByIndex = false == bSetCurrentById && settings_current_index >= 0; m_settings.SetCurrentLayerId(ON_nil_uuid); if ( 0 != (static_cast(ON_3dmArchiveTableType::layer_table) & table_filter) ) { for(;;) { ON_Layer* layer = nullptr; rc = archive.Read3dmLayer(&layer); if ( rc==0 ) break; // end of layer table if (rc < 0) break; // index or id might be modified by AddModelComponentForExperts() const bool bSetAsCurrent = (bSetCurrentById && settings_current_id == layer->Id()) || (bSetCurrentByIndex && settings_current_index == layer->Index()); if (AddModelComponentForExperts(layer, bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty()) { delete layer; } else if (bSetAsCurrent) { m_settings.SetCurrentLayerId(layer->Id()); bSetCurrentById = false; bSetCurrentByIndex = false; } } } // If BeginRead3dmLayerTable() returns true, // then you MUST call EndRead3dmLayerTable(). if ( !archive.EndRead3dmLayerTable() ) { return false; } ON_UUID current_layer_id = m_settings.CurrentLayerId(); const ON_Layer* current_layer = (ON_nil_uuid == current_layer_id) ? nullptr : ON_Layer::FromModelComponentRef(LayerFromId(current_layer_id), nullptr); if (nullptr == current_layer) { current_layer_id = ON_nil_uuid; int layer_count = 0; ONX_ModelComponentIterator layer_it(*this, ON_ModelComponent::Type::Layer); for (const ON_Layer* layer = ON_Layer::Cast(layer_it.FirstComponent()); nullptr != layer; layer = ON_Layer::Cast(layer_it.NextComponent())) { layer_count++; if (layer->IsVisible() && false == layer->IsLocked()) { current_layer = layer; if ( layer->ParentIdIsNil() ) break; } } if (nullptr == current_layer && layer_count > 0) { current_layer = ON_Layer::Cast(layer_it.FirstComponent()); } if (nullptr != current_layer) m_settings.SetCurrentLayerId(current_layer->Id()); else AddDefaultLayer(nullptr, ON_Color::UnsetColor); } } // STEP 9: REQUIRED - Read group table if ( archive.BeginRead3dmGroupTable() ) { if ( 0 != (static_cast(ON_3dmArchiveTableType::group_table) & table_filter) ) { for(;;) { ON_Group* group = nullptr; rc = archive.Read3dmGroup(&group); if ( rc==0 ) break; // end of group table if (rc < 0) break; if (AddModelComponentForExperts(group, bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty()) delete group; } } // If BeginRead3dmGroupTable() returns true, // then you MUST call EndRead3dmGroupTable(). if ( !archive.EndRead3dmGroupTable() ) { return false; } } // STEP 11: REQUIRED - Read dimstyle table if ( archive.BeginRead3dmDimStyleTable() ) { const ON_UUID settings_current_id = m_settings.CurrentDimensionStyleId(); const int settings_current_index = m_settings.CurrentDimensionStyleIndex(); bool bSetCurrentById = !(ON_nil_uuid == settings_current_id); bool bSetCurrentByIndex = false == bSetCurrentById && settings_current_index >= 0; m_settings.SetCurrentDimensionStyleId(ON_nil_uuid); if ( 0 != (static_cast(ON_3dmArchiveTableType::dimension_style_table) & table_filter) ) { for(;;) { ON_DimStyle* dimension_style = nullptr; rc = archive.Read3dmDimStyle(&dimension_style); if ( rc==0 ) break; // end of dimstyle table if ( rc < 0 ) break; // index or id might be modified by AddModelComponentForExperts() const bool bSetAsCurrent = (bSetCurrentById && settings_current_id == dimension_style->Id()) || (bSetCurrentByIndex && settings_current_index == dimension_style->Index()); if (AddModelComponentForExperts(dimension_style, bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty()) { delete dimension_style; } else if (bSetAsCurrent) { m_settings.SetCurrentDimensionStyleId(dimension_style->Id()); bSetCurrentById = false; bSetCurrentByIndex = false; } } } // If BeginRead3dmDimStyleTable() returns true, // then you MUST call EndRead3dmDimStyleTable(). if ( !archive.EndRead3dmDimStyleTable() ) { return false; } } // STEP 12: REQUIRED - Read render lights table if ( archive.BeginRead3dmLightTable() ) { if ( 0 != (static_cast(ON_3dmArchiveTableType::light_table) & table_filter) ) { for(;;) { ON_ModelGeometryComponent* model_light = nullptr; rc = archive.Read3dmModelLight(&model_light); if (rc == 0) break; // end of light table if ( rc < 0 ) break; if (AddModelComponentForExperts(model_light, bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty()) delete model_light; } } // If BeginRead3dmLightTable() returns true, // then you MUST call EndRead3dmLightTable(). if ( !archive.EndRead3dmLightTable() ) { return false; } } // STEP 13 - read hatch pattern table if ( archive.BeginRead3dmHatchPatternTable() ) { const ON_UUID settings_current_id = m_settings.CurrentHatchPatternId(); const int settings_current_index = ON_UNSET_INT_INDEX; bool bSetCurrentById = !(ON_nil_uuid == settings_current_id); bool bSetCurrentByIndex = false; m_settings.SetCurrentHatchPatternId(ON_nil_uuid); if ( 0 != (static_cast(ON_3dmArchiveTableType::hatchpattern_table) & table_filter) ) { for(;;) { ON_HatchPattern* hatch_pattern = nullptr; rc = archive.Read3dmHatchPattern(&hatch_pattern); if ( rc==0 ) break; // end of hatchpattern table if ( rc < 0 ) break; // index or id might be modified by AddModelComponentForExperts() const bool bSetAsCurrent = (bSetCurrentById && settings_current_id == hatch_pattern->Id()) || (bSetCurrentByIndex && settings_current_index == hatch_pattern->Index()); if (AddModelComponentForExperts(hatch_pattern, bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty()) { delete hatch_pattern; } else if (bSetAsCurrent) { m_settings.SetCurrentHatchPatternId(hatch_pattern->Id()); bSetCurrentById = false; bSetCurrentByIndex = false; } } } // If BeginRead3dmHatchPatternTable() returns true, // then you MUST call EndRead3dmHatchPatternTable(). if ( !archive.EndRead3dmHatchPatternTable() ) { return false; } } // STEP 14: REQUIRED - Read instance definition table if ( archive.BeginRead3dmInstanceDefinitionTable() ) { if ( 0 != (static_cast(ON_3dmArchiveTableType::instance_definition_table) & table_filter) ) { for(;;) { ON_InstanceDefinition* instance_definition = nullptr; rc = archive.Read3dmInstanceDefinition(&instance_definition); if ( rc==0 ) break; // end of instance definition table if ( rc < 0 ) break; if (AddModelComponentForExperts(instance_definition, bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty()) delete instance_definition; } } // If BeginRead3dmInstanceDefinitionTable() returns true, // then you MUST call EndRead3dmInstanceDefinitionTable(). if ( !archive.EndRead3dmInstanceDefinitionTable() ) { return false; } } return (0 == archive.CriticalErrorCount()); } bool ONX_Model::IncrementalReadModelGeometry( ON_BinaryArchive& archive, bool bManageModelGeometryComponent, bool bManageGeometry, bool bManageAttributes, unsigned int object_filter, ON_ModelComponentReference& model_component_reference ) { model_component_reference = ON_ModelComponentReference::Empty; ON_3dmArchiveTableType active_table = archive.Active3dmTable(); if (ON_3dmArchiveTableType::Unset == active_table) { ON_3dmArchiveTableType previous_table = archive.Previous3dmTable(); if (ON_3dmArchiveTableType::Unset == previous_table) { // Yokel probably forgot to call IncrementalReadBegin() ON_ERROR("IncrementalReadBegin() must be called before IncrementalReadModelGeometry()."); return false; } if (static_cast(previous_table) >= static_cast(ON_3dmArchiveTableType::object_table)) { // Yokel either read or skipped reading the geometry table. ON_ERROR("Too late to read the geometry table."); return false; } if (false == archive.BeginRead3dmObjectTable()) { ON_ERROR("Geometry table cannot be read from archive."); return false; } active_table = archive.Active3dmTable(); if (active_table != ON_3dmArchiveTableType::object_table) { ON_ERROR("Catastrophic geometry table reading error."); return false; } } else if (active_table != ON_3dmArchiveTableType::object_table) { // Yokel is calling IncrementalReadModelGeometry() at the wrong time. ON_ERROR("IncrementalReadModelGeometry() cannot be called while reading another part of the 3dm archive."); return false; } ON_3dmArchiveTableStatus object_table_status = archive.Archive3dmTableStatus(ON_3dmArchiveTableType::object_table); if (ON_3dmArchiveTableType::object_table != object_table_status.m_table_type) { ON_ERROR("Catastrophic geometry table reading error."); return false; } switch(object_table_status.m_state) { case ON_3dmArchiveTableStatus::TableState::Started: case ON_3dmArchiveTableStatus::TableState::InProgress: break; case ON_3dmArchiveTableStatus::TableState::Finished: { ON_ERROR("Geometry table has already been read from archive."); return false; } default: { ON_ERROR("Geometry table reading error."); return false; } } for(;;) { ON_ModelGeometryComponent* model_geometry = nullptr; int rc = archive.Read3dmModelGeometryForExperts(bManageGeometry,bManageAttributes,&model_geometry,object_filter); if ( rc <= 0 ) { // end of object table or error reading // If BeginRead3dmObjectTable() returns true, // then you MUST call EndRead3dmObjectTable(). archive.EndRead3dmObjectTable(); return (0==rc); } if (2 == rc && 0 != object_filter) { if ( nullptr != model_geometry) delete model_geometry; continue; // item was intentionally skipped. } model_component_reference = AddModelComponentForExperts(model_geometry,bManageModelGeometryComponent,true,true); if (model_component_reference.IsEmpty()) continue; // return the read object. break; } return true; } bool ONX_Model::IncrementalReadFinish( ON_BinaryArchive& archive, bool bManageComponents, unsigned int table_filter, ON_TextLog* error_log ) { int rc; const bool bResolveIdAndNameConflicts = true; const bool bUpdateComponentIdentification = true; if ( 0 == table_filter ) table_filter = 0xFFFFFFFF; // read everything // STEP 16: Read history table if ( archive.BeginRead3dmHistoryRecordTable() ) { if ( 0 != (static_cast(ON_3dmArchiveTableType::historyrecord_table) & table_filter) ) { for(;;) { ON_HistoryRecord* pHistoryRecord = nullptr; rc = archive.Read3dmHistoryRecord(pHistoryRecord); if ( rc == 0 ) break; // end of history record table if ( rc < 0 ) break; if ( AddModelComponentForExperts(pHistoryRecord,bManageComponents,bResolveIdAndNameConflicts,bUpdateComponentIdentification).IsEmpty() ) { delete pHistoryRecord; continue; } } } // If BeginRead3dmHistoryRecordTable() returns true, // then you MUST call EndRead3dmHistoryRecordTable(). if ( !archive.EndRead3dmHistoryRecordTable() ) { return false; } } if (0 != archive.CriticalErrorCount()) return false; // STEP 17: OPTIONAL - Read user tables as anonymous goo // If you develop a plug-ins or application that uses OpenNURBS files, // you can store anything you want in a user table. for(;;) { if ( archive.Archive3dmVersion() <= 1 ) { // no user tables in version 1 archives. break; } { ON__UINT32 tcode = 0; ON__INT64 big_value = 0; if ( !archive.PeekAt3dmBigChunkType(&tcode,&big_value) ) break; if ( TCODE_USER_TABLE != tcode ) break; } ON_UUID plugin_id = ON_nil_uuid; bool bGoo = false; int usertable_3dm_version = 0; unsigned int usertable_opennurbs_version = 0; if ( !archive.BeginRead3dmUserTable( plugin_id, &bGoo, &usertable_3dm_version, &usertable_opennurbs_version ) ) { // attempt to skip bogus user table const ON__UINT64 pos0 = archive.CurrentPosition(); ON__UINT32 tcode = 0; ON__INT64 big_value = 0; if ( !archive.BeginRead3dmBigChunk(&tcode,&big_value) ) break; if ( !archive.EndRead3dmChunk() ) break; const ON__UINT64 pos1 = archive.CurrentPosition(); if (pos1 <= pos0) break; if ( TCODE_USER_TABLE != tcode ) break; continue; // skip this bogus user table } if ( nullptr == m_model_user_string_list && plugin_id == ON_CLASS_ID(ON_DocumentUserStringList) ) { // Read the document user strings (key-value pairs) as // a user table with plug-in id = ON_CLASS_ID(ON_DocumentUserStringList) ON_Object* p = 0; archive.ReadObject(&p); m_model_user_string_list = ON_DocumentUserStringList::Cast(p); if ( 0 == m_model_user_string_list ) { ON_ERROR("The document user string information in the file is damaged."); if ( 0 != p ) delete p; p = 0; } } else if ( 0 != (static_cast(ON_3dmArchiveTableType::user_table) & table_filter) ) { // read user data tables as anonymous goo ONX_Model_UserData* model_ud = new ONX_Model_UserData(); model_ud->m_uuid = plugin_id; model_ud->m_usertable_3dm_version = usertable_3dm_version; model_ud->m_usertable_opennurbs_version = usertable_opennurbs_version; if ( !archive.Read3dmAnonymousUserTable( usertable_3dm_version, usertable_opennurbs_version, model_ud->m_goo ) ) { delete model_ud; break; } m_userdata_table.Append(model_ud); } // If BeginRead3dmObjectTable() returns true, // then you MUST call EndRead3dmUserTable(). if ( !archive.EndRead3dmUserTable() ) { break; } } if (0 != archive.CriticalErrorCount()) return false; // STEP 18: OPTIONAL - check for end mark size_t file_length = 0; if (!archive.Read3dmEndMark(&file_length)) { if (archive.Archive3dmVersion() != 1) { // some v1 files are missing end-of-archive markers } } else { m_3dm_file_byte_count = file_length; } return (0 == archive.CriticalErrorCount()); } bool ONX_Model::Read( const char* filename, unsigned int table_filter, unsigned int model_object_type_filter, ON_TextLog* error_log ) { const ON_wString wfilename_buffer(filename); const wchar_t* wfilename = static_cast< const wchar_t* >(wfilename_buffer); return Read(wfilename,table_filter,model_object_type_filter,error_log); } bool ONX_Model::Read( const wchar_t* filename, unsigned int table_filter, unsigned int model_object_type_filter, ON_TextLog* error_log ) { bool bCallReset = true; bool rc = false; if ( 0 != filename ) { FILE* fp = ON::OpenFile(filename,L"rb"); if ( 0 != fp ) { bCallReset = false; ON_BinaryFile file(ON::archive_mode::read3dm,fp); rc = Read(file, table_filter, model_object_type_filter, error_log); ON::CloseFile(fp); } } if ( bCallReset ) Reset(); return rc; } bool ONX_Model::Read(ON_BinaryArchive& archive, unsigned int table_filter, unsigned int model_object_type_filter, ON_TextLog* error_log) { // STEPS 1 to 14: REQUIRED. const bool bManageComponents = true; IncrementalReadBegin(archive, bManageComponents, table_filter, error_log); if (0 != archive.CriticalErrorCount()) return false; // STEP 15: REQUIRED - Read object (geometry and annotation) table. if (0 == (static_cast(ON_3dmArchiveTableType::object_table) & table_filter)) { const bool bManageGeometry = true; const bool bManageAttributes = true; for (;;) { ON_ModelComponentReference model_geometry_reference; if (!IncrementalReadModelGeometry(archive, bManageComponents, bManageGeometry, bManageAttributes, model_object_type_filter, model_geometry_reference)) { // Catastrophic error. break; } if (model_geometry_reference.IsEmpty()) break; // No more geometry. } if (0 != archive.CriticalErrorCount()) return false; } IncrementalReadFinish(archive, bManageComponents, table_filter, error_log); if (0 != archive.CriticalErrorCount()) return false; if (0 != archive.BadCRCCount()) return false; // Having read the model data, populate the RDK components. const int archive_3dm_version = archive.Archive3dmVersion(); m_private->PopulateRDKComponents(archive_3dm_version); return true; } bool ONX_Model::Read(ON_BinaryArchive& archive, ON_TextLog* error_log) { unsigned int table_filter = 0; // read every table unsigned int model_object_type_filter = 0; // read every type of object return Read(archive, table_filter, model_object_type_filter, error_log); } bool ONX_Model::Write(const char* filename, int version, ON_TextLog* error_log) const { bool rc = false; if (nullptr != filename && 0 != filename[0]) { FILE* fp = ON::OpenFile(filename, "wb"); if (nullptr != fp) { ON_BinaryFile file(ON::archive_mode::write3dm, fp); const ON_wString wFileName(filename); file.SetArchiveFullPath(static_cast(wFileName)); rc = Write(file, version, error_log); ON::CloseFile(fp); } } return rc; } bool ONX_Model::Write(const wchar_t* filename, int version, ON_TextLog* error_log) const { bool rc = false; if (nullptr != filename && 0 != filename[0]) { FILE* fp = ON::OpenFile(filename, L"wb"); if (nullptr != fp) { ON_BinaryFile file(ON::archive_mode::write3dm, fp); file.SetArchiveFullPath(filename); rc = Write(file, version, error_log); ON::CloseFile(fp); } } return rc; } bool ONX_Model::Write(ON_BinaryArchive& archive, int version, ON_TextLog* error_log) const { m_private->UpdateRDKUserData(version); if ( 0 != version ) { if ( version < 2 || version > ON_BinaryArchive::CurrentArchiveVersion() || (version < 50 && version > ON_BinaryArchive::CurrentArchiveVersion()/10) || (version >= 50 && 0 != (version % 10)) ) { version = 0; if ( error_log) error_log->Print("ONX_Model::Write version parameter = %d; it must be 0, or >= 2 and <= %d, or a multiple of 10 >= 50 and <= %d.\n", version,ON_BinaryArchive::CurrentArchiveVersion()/10,ON_BinaryArchive::CurrentArchiveVersion()); } } if ( !archive.WriteMode() ) { // You passed in a bogus archive. You must pass ON::archive_mode::write3dm to the archive constructor. if ( error_log) error_log->Print("ONX_Model::Write archive.Mode() is not ON::archive_mode::write3dm.\n" "See ONX_Model::Write example in the header file.\n"); return false; } bool ok; // START SECTION ok = archive.Write3dmStartSection( version, static_cast< const char* >(m_sStartSectionComments) ); if ( !ok ) { // make sure your archive was created with ON::archive_mode::write3dm mode. if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmStartSection() failed.\n" "Your archive is not properly initialized\n" "(make sure you passed ON::archive_mode::write3dm to the constructor),\n" "a file is locked, a disk is locked, or something along those lines.\n"); return false; } // PROPERTIES SECTION if ( m_properties.m_RevisionHistory.m_revision_count == 0 ) const_cast(this)->m_properties.m_RevisionHistory.NewRevision(); ok = archive.Write3dmProperties( m_properties ); if ( !ok ) { // make sure m_properties is valid if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmProperties() failed.\n" "Your m_properties information is not valid or basic file writing failed.\n" ); return false; } // SETTINGS SECTION ok = archive.Write3dmSettings( m_settings ); if ( !ok ) { // make sure m_settings is valid if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmSettings() failed.\n" "Your m_settings information is not valid or basic file writing failed.\n"); return false; } // BITMAP TABLE ok = archive.BeginWrite3dmBitmapTable(); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmBitmapTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::Image).m_first_mcr_link; nullptr != link && ok; link = link->m_next ) { ok = archive.Write3dmImageComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmImageComponent() failed.\n"); } } if ( !archive.EndWrite3dmBitmapTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmBitmapTable() failed.\n"); return false; } if (!ok) return false; // TEXTURE MAPPING TABLE if ( archive.Archive3dmVersion() >= 4 ) { ok = archive.BeginWrite3dmTextureMappingTable(); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmTextureMappingTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::TextureMapping).m_first_mcr_link; nullptr != link && ok; link = link->m_next ) { ok = archive.Write3dmTextureMappingComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.TextureMapping() failed.\n"); } } if ( !archive.EndWrite3dmTextureMappingTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmTextureMappingTable() failed.\n"); return false; } if (!ok) return false; } // MATERIAL TABLE ok = archive.BeginWrite3dmMaterialTable(); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmMaterialTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::RenderMaterial).m_first_mcr_link; nullptr != link && ok; link = link->m_next ) { ok = archive.Write3dmMaterialComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmMaterialComponent() failed.\n"); } } if ( !archive.EndWrite3dmMaterialTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmMaterialTable() failed.\n"); return false; } if (!ok) return false; // LINETYPE TABLE if ( archive.Archive3dmVersion() >= 4 ) { ok = archive.BeginWrite3dmLinetypeTable(); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmLinetypeTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::LinePattern).m_first_mcr_link; nullptr != link && ok; link = link->m_next ) { ok = archive.Write3dmLinePatternComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmLinePatternComponent() failed.\n"); } } if ( !archive.EndWrite3dmLinetypeTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmLinetypeTable() failed.\n"); return false; } if (!ok) return false; } // LAYER TABLE ok = archive.BeginWrite3dmLayerTable(); if ( !ok ) { // make sure m_settings is valid if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmLayerTable() failed.\n"); return false; } unsigned int layer_count = 0; for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::Layer).m_first_mcr_link; nullptr != link && ok; link = link->m_next ) { ok = archive.Write3dmLayerComponent(link->m_mcr); if (!ok) { if (error_log) error_log->Print("ONX_Model::Write archive.Write3dmLayerComponent() failed.\n"); } else layer_count++; } if (0 == layer_count && ok) { ON_Layer layer(ON_Layer::Default); layer.SetId(); layer.SetIndex(0); ok = archive.Write3dmLayer(layer); if (!ok) { if (error_log) error_log->Print("ONX_Model::Write archive.Write3dmLayer() failed.\n"); } } if ( !archive.EndWrite3dmLayerTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmLayerTable() failed.\n"); return false; } if (!ok) return false; // GROUP TABLE ok = archive.BeginWrite3dmGroupTable(); if ( !ok ) { // make sure m_settings is valid if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmGroupTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::Group).m_first_mcr_link; nullptr != link && ok; link = link->m_next ) { ok = archive.Write3dmGroupComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmGroupComponent() failed.\n"); } } if ( !archive.EndWrite3dmGroupTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmGroupTable() failed.\n"); return false; } if (!ok) return false; // DIMSTYLE TABLE if ( archive.Archive3dmVersion() >= 3 ) { ok = archive.BeginWrite3dmDimStyleTable(); if ( !ok ) { // make sure m_settings is valid if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmDimStyleTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::DimStyle).m_first_mcr_link; nullptr != link; link = link->m_next ) { ok = archive.Write3dmDimStyleComponent(link->m_mcr); if (!ok) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmDimStyleComponent() failed.\n"); } } if ( !archive.EndWrite3dmDimStyleTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmDimStyleTable() failed.\n"); return false; } if (!ok) return false; } // LIGHT TABLE ok = archive.BeginWrite3dmLightTable(); if ( !ok ) { // make sure m_settings is valid if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmLightTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::RenderLight).m_first_mcr_link; nullptr != link; link = link->m_next ) { ok = archive.Write3dmModelLightComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmModelLightComponent() failed.\n"); } } if ( !archive.EndWrite3dmLightTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmLightTable() failed.\n"); return false; } if (!ok) return false; // HATCH PATTERN TABLE if ( archive.Archive3dmVersion() >= 4 ) { ok = archive.BeginWrite3dmHatchPatternTable(); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmHatchPatternTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::HatchPattern).m_first_mcr_link; nullptr != link; link = link->m_next ) { ok = archive.Write3dmHatchPatternComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmHatchPatternComponent() failed.\n"); } } if ( !archive.EndWrite3dmHatchPatternTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmHatchPatternTable() failed.\n"); return false; } if (!ok) return false; } // INSTANCE DEFINITION TABLE if ( archive.Archive3dmVersion() >= 3 ) { ok = archive.BeginWrite3dmInstanceDefinitionTable(); if ( !ok ) { // make sure m_settings is valid if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmInstanceDefinitionTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::InstanceDefinition).m_first_mcr_link; nullptr != link; link = link->m_next ) { ok = archive.Write3dmInstanceDefinitionComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmInstanceDefinitionComponent() failed.\n"); } } if ( !archive.EndWrite3dmInstanceDefinitionTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmInstanceDefinitionTable() failed.\n"); return false; } if (!ok) return false; } // OBJECT TABLE ok = archive.BeginWrite3dmObjectTable(); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmObjectTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::ModelGeometry).m_first_mcr_link; nullptr != link; link = link->m_next ) { ok = archive.Write3dmModelGeometryComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmModelGeometryComponent() failed.\n"); } } if ( !archive.EndWrite3dmObjectTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmObjectTable() failed.\n"); return false; } if (!ok) return false; // HISTORY RECORD TABLE if ( archive.Archive3dmVersion() >= 4 ) { ok = archive.BeginWrite3dmHistoryRecordTable(); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.BeginWrite3dmHistoryRecordTable() failed.\n"); return false; } for( class ONX_ModelComponentReferenceLink* link = Internal_ComponentListConst(ON_ModelComponent::Type::HistoryRecord).m_first_mcr_link; nullptr != link; link = link->m_next ) { ok = archive.Write3dmHistoryRecordComponent(link->m_mcr); if ( !ok ) { if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmistoryRecordComponent() failed.\n"); } } if( !archive.EndWrite3dmHistoryRecordTable() ) { if ( error_log) error_log->Print("ONX_Model::Write archive.EndWrite3dmHistoryTable() failed.\n"); return false; } if (!ok) return false; } // STEP 17: - write user tables (plug-in info, etc.) if (archive.ArchiveContains3dmTable(ON_3dmArchiveTableType::user_table)) { if (nullptr != m_model_user_string_list && m_model_user_string_list->UserStringCount() > 0) { // Write the document user strings (key-value pairs) as // a user table with plug-in id = ON_UUID model_user_string_plugin_id = ON_CLASS_ID(ON_DocumentUserStringList); if (archive.BeginWrite3dmUserTable(model_user_string_plugin_id, false, 0, 0)) { archive.WriteObject(m_model_user_string_list); archive.EndWrite3dmUserTable(); } } // USER DATA TABLE for (int i = 0; ok && i < m_userdata_table.Count(); i++) { const ONX_Model_UserData* model_ud = m_userdata_table[i]; if (nullptr == model_ud) continue; if (ON_UuidIsNotNil(model_ud->m_uuid)) { if (!archive.Write3dmAnonymousUserTableRecord( model_ud->m_uuid, model_ud->m_usertable_3dm_version, model_ud->m_usertable_opennurbs_version, model_ud->m_goo) ) { continue; } } } } if ( !archive.Write3dmEndMark() ) { ok = false; if ( error_log) error_log->Print("ONX_Model::Write archive.Write3dmEndMark() failed.\n"); } return ok; } int ONX_Model::UsesIDef( const ON_InstanceRef& iref, ON_UUID idef_uuid ) const { // get id of idef we are looking for if ( ON_UuidIsNil(idef_uuid) ) return 0; // id of idef that defines iref ON_UUID iref_idef_uuid = iref.m_instance_definition_uuid; if ( 0 == ON_UuidCompare( idef_uuid, iref_idef_uuid ) ) return 1; ON_ModelComponentReference idef_reference = ComponentFromId(ON_ModelComponent::Type::InstanceDefinition,iref_idef_uuid); const ON_InstanceDefinition* iref_idef = ON_InstanceDefinition::FromModelComponentRef(idef_reference,nullptr); if ( nullptr == iref_idef ) return -1; // invalid id. // set iref_list[] = list of all nested instance references in iref_idef. ON_SimpleArray iref_list(256); const ON_SimpleArray& iref_idef_object_uuid = iref_idef->InstanceGeometryIdList(); for ( unsigned j = 0; j < iref_idef_object_uuid.UnsignedCount(); j++ ) { ON_ModelComponentReference component_reference = ComponentFromId(ON_ModelComponent::Type::InstanceDefinition,iref_idef_object_uuid[j]); const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(component_reference.ModelComponent()); if ( nullptr == model_geometry ) continue; const ON_Geometry* geometry = model_geometry->Geometry(nullptr); if ( nullptr == geometry ) continue; if ( geometry->ObjectType() != ON::instance_reference ) continue; const ON_InstanceRef* pNestedIRef = ON_InstanceRef::Cast(geometry); if ( nullptr == pNestedIRef ) continue; if ( 0 == ON_UuidCompare( idef_uuid, pNestedIRef->m_instance_definition_uuid ) ) return 2; iref_list.Append(pNestedIRef); } // test the nested instance references to see if they use idef_index. unsigned int i1 = 0; int depth = 3; for ( depth=3; i1 < iref_list.UnsignedCount(); depth++ ) { const unsigned int i0 = i1; i1 = iref_list.UnsignedCount(); for ( unsigned int i = i0; i < i1; i++ ) { const ON_InstanceRef* pNestedIRef = iref_list[i]; if ( nullptr == pNestedIRef ) continue; ON_ModelComponentReference nested_idef_reference = ComponentFromId(ON_ModelComponent::Type::InstanceDefinition,pNestedIRef->m_instance_definition_uuid); const ON_InstanceDefinition* nested_idef = ON_InstanceDefinition::FromModelComponentRef(nested_idef_reference,nullptr); if ( nullptr == nested_idef ) continue; const ON_SimpleArray& nested_idef_object_uuid = nested_idef->InstanceGeometryIdList(); for ( unsigned int j = 0; j < nested_idef_object_uuid.UnsignedCount(); j++ ) { ON_ModelComponentReference component_reference = ComponentFromId(ON_ModelComponent::Type::InstanceDefinition,nested_idef_object_uuid[j]); const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(component_reference.ModelComponent()); if ( nullptr == model_geometry ) continue; const ON_Geometry* geometry = model_geometry->Geometry(nullptr); if ( nullptr == geometry ) continue; if ( geometry->ObjectType() != ON::instance_reference ) continue; const ON_InstanceRef* pNestedIRef_local = ON_InstanceRef::Cast(geometry); if ( nullptr == pNestedIRef_local ) continue; if ( 0 == ON_UuidCompare( idef_uuid, pNestedIRef_local->m_instance_definition_uuid ) ) return depth; iref_list.Append(pNestedIRef_local); } } if ( i1 > 10000 && i1 < iref_list.UnsignedCount() ) return -2; // probably have a circular reference } return 0; } ON__UINT64 ONX_Model::ModelContentVersionNumber() const { return m_private->m_model_content_version_number; } void ONX_Model::Internal_IncrementModelContentVersionNumber() { m_private->m_model_content_version_number++; } bool ONX_Model::SetDocumentUserString( const wchar_t* key, const wchar_t* string_value ) { if (nullptr == key || 0 == key[0] ) return false; if ( nullptr == m_model_user_string_list ) m_model_user_string_list = new ON_DocumentUserStringList(); return m_model_user_string_list->SetUserString(key,string_value); } bool ONX_Model::GetDocumentUserString( const wchar_t* key, ON_wString& string_value ) const { if ( nullptr != m_model_user_string_list ) return m_model_user_string_list->GetUserString(key,string_value); string_value = ON_wString::EmptyString; return false; } int ONX_Model::GetDocumentUserStrings( ON_ClassArray& user_strings ) const { if ( nullptr != m_model_user_string_list ) return m_model_user_string_list->GetUserStrings(user_strings); return 0; } ONX_ModelComponentIterator::ONX_ModelComponentIterator( const ONX_Model& model, ON_ModelComponent::Type component_type ) : m_component_type(component_type) , m_model(&model) {} const ONX_Model* ONX_ModelComponentIterator::Model() const { return m_model; } const class ONX_Model::ONX_ModelComponentList* ONX_ModelComponentIterator::Internal_List() const { if (nullptr != m_list) return m_list; if ( nullptr == m_model ) return nullptr; m_list = &m_model->Internal_ComponentListConst(m_component_type); return m_list; } void ONX_ModelComponentIterator::Internal_SetLink( const class ONX_ModelComponentReferenceLink* link ) const { // m_model is never nullptr when this function is called m_model_content_version = m_model->ModelContentVersionNumber(); m_link = link; if (nullptr == m_link) { m_current_component_sn = 0; m_next_component_sn = 0; m_prev_component_sn = 0; m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; m_model_content_version = 0; } else { m_current_component_sn = link->m_sn; m_next_component_sn = (nullptr != link->m_next) ? link->m_next->m_sn : 0; m_prev_component_sn = (nullptr != link->m_prev) ? link->m_prev->m_sn : 0; m_current_component_weak_ref = link->m_mcr; } } void ONX_ModelComponentIterator::Internal_SetLink( ON__UINT64 model_component_sn ) const { } ON_ModelComponentReference ONX_ModelComponentIterator::FirstComponentReference() { return ON_ModelComponentReference(FirstComponentWeakReference()); } ON_ModelComponentReference ONX_ModelComponentIterator::LastComponentReference() { return ON_ModelComponentReference(LastComponentWeakReference()); } ON_ModelComponentReference ONX_ModelComponentIterator::CurrentComponentReference() const { return ON_ModelComponentReference(CurrentComponentWeakReference()); } ON_ModelComponentReference ONX_ModelComponentIterator::NextComponentReference() { return ON_ModelComponentReference(NextComponentWeakReference()); } ON_ModelComponentReference ONX_ModelComponentIterator::PreviousComponentReference() { return ON_ModelComponentReference(PreviousComponentWeakReference()); } ON_ModelComponentWeakReference ONX_ModelComponentIterator::FirstComponentWeakReference() { const ONX_Model::ONX_ModelComponentList* list = Internal_List(); Internal_SetLink((nullptr != list) ? list->m_first_mcr_link : nullptr); return m_current_component_weak_ref; } ON_ModelComponentWeakReference ONX_ModelComponentIterator::LastComponentWeakReference() { const ONX_Model::ONX_ModelComponentList* list = Internal_List(); Internal_SetLink((nullptr != list) ? list->m_last_mcr_link : nullptr); return m_current_component_weak_ref; } ON_ModelComponentWeakReference ONX_ModelComponentIterator::CurrentComponentWeakReference() const { // unchanged ModelContentVersionNumber() means that m_link is safe to dereference. // Otherwise use sn for safe reset. if (m_model_content_version != m_model->ModelContentVersionNumber() ) Internal_SetLink(m_model->Internal_ModelComponentLinkFromSerialNumber(m_current_component_sn)); return m_current_component_weak_ref; } ON_ModelComponentWeakReference ONX_ModelComponentIterator::NextComponentWeakReference() { if ( nullptr == m_list ) return FirstComponentReference(); if ( nullptr == m_link ) return ON_ModelComponentReference::Empty; if (m_model_content_version == m_model->ModelContentVersionNumber() && nullptr != m_link ) { // unchanged ModelContentVersionNumber() means that m_link is safe to dereference. m_link = m_link->m_next; if (nullptr == m_link) { m_prev_component_sn = m_current_component_sn; m_current_component_sn = 0; m_next_component_sn = 0; m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; } else { m_current_component_sn = m_link->m_sn; m_next_component_sn = (nullptr != m_link->m_next) ? m_link->m_next->m_sn : 0; m_prev_component_sn = (nullptr != m_link->m_prev) ? m_link->m_prev->m_sn : 0; m_current_component_weak_ref = m_link->m_mcr; } } else if ( 0 != m_next_component_sn ) { // Otherwise m_link is not safe to dereference. // Use slower serial number lookup. Internal_SetLink(m_model->Internal_ModelComponentLinkFromSerialNumber(m_next_component_sn)); } else { m_link = nullptr; m_current_component_sn = 0; m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; } return m_current_component_weak_ref; } ON_ModelComponentWeakReference ONX_ModelComponentIterator::PreviousComponentWeakReference() { if ( nullptr == m_list ) return LastComponentReference(); if ( nullptr == m_link ) return ON_ModelComponentReference::Empty; if (m_model_content_version == m_model->ModelContentVersionNumber() && nullptr != m_link ) { m_link = m_link->m_prev; if (nullptr == m_link) { m_next_component_sn = m_current_component_sn; m_current_component_sn = 0; m_prev_component_sn = 0; m_current_component_weak_ref = ON_ModelComponentWeakReference::Empty; } else { m_current_component_sn = m_link->m_sn; m_next_component_sn = (nullptr != m_link->m_next) ? m_link->m_next->m_sn : 0; m_prev_component_sn = (nullptr != m_link->m_prev) ? m_link->m_prev->m_sn : 0; m_current_component_weak_ref = m_link->m_mcr; } } else if ( 0 != m_prev_component_sn ) { Internal_SetLink(m_model->Internal_ModelComponentLinkFromSerialNumber(m_prev_component_sn)); } else { m_link = nullptr; m_current_component_sn = 0; m_current_component_weak_ref = ON_ModelComponentReference::Empty; } return m_current_component_weak_ref; } const ON_ModelComponent* ONX_ModelComponentIterator::FirstComponent() { return FirstComponentReference().ModelComponent(); } const ON_ModelComponent* ONX_ModelComponentIterator::LastComponent() { return LastComponentReference().ModelComponent(); } const ON_ModelComponent* ONX_ModelComponentIterator::CurrentComponent() const { return CurrentComponentReference().ModelComponent(); } const ON_ModelComponent* ONX_ModelComponentIterator::NextComponent() { return NextComponentReference().ModelComponent(); } const ON_ModelComponent* ONX_ModelComponentIterator::PreviousComponent() { return PreviousComponentReference().ModelComponent(); } unsigned int ONX_ModelComponentIterator::ActiveComponentCount() const { return (nullptr != m_list) ? m_list->m_count : 0; } class ON_TextLogNull : public ON_TextLog { public: ON_TextLogNull() = default; ~ON_TextLogNull() = default; void AppendText(const char*) override {} void AppendText(const wchar_t*) override {} }; const char* ONX_ModelTest::TestTypeToString(ONX_ModelTest::Type test_type) { switch (test_type) { case ONX_ModelTest::Type::Unset: return "Unset"; break; case ONX_ModelTest::Type::Read: return "Read"; break; case ONX_ModelTest::Type::ReadWrite: return "ReadWrite"; break; case ONX_ModelTest::Type::ReadWriteRead: return "ReadWriteRead"; break; case ONX_ModelTest::Type::ReadWriteReadCompare: return "ReadWriteReadCompare"; break; default: break; } ON_ERROR("Invalid test_type parameter."); return "Invalid test_type parameter"; } const wchar_t* ONX_ModelTest::TestTypeToWideString(ONX_ModelTest::Type test_type) { switch (test_type) { case ONX_ModelTest::Type::Unset: return L"Unset"; break; case ONX_ModelTest::Type::Read: return L"Read"; break; case ONX_ModelTest::Type::ReadWrite: return L"ReadWrite"; break; case ONX_ModelTest::Type::ReadWriteRead: return L"ReadWriteRead"; break; case ONX_ModelTest::Type::ReadWriteReadCompare: return L"ReadWriteReadCompare"; break; default: break; } ON_ERROR("Invalid test_type parameter."); return L"Invalid test_type parameter"; } const char* ONX_ModelTest::ResultToString(ONX_ModelTest::Result result) { switch (result) { case ONX_ModelTest::Result::Unset: return "Unset"; break; case ONX_ModelTest::Result::Fail: return "Fail"; break; case ONX_ModelTest::Result::Errors: return "Errors"; break; case ONX_ModelTest::Result::Warnings: return "Warnings"; break; case ONX_ModelTest::Result::Pass: return "Pass"; break; case ONX_ModelTest::Result::Skip: return "Skip"; break; default: break; } ON_ERROR("Invalid result parameter."); return "Invalid result parameter"; } const wchar_t* ONX_ModelTest::ResultToWideString(ONX_ModelTest::Result result) { switch (result) { case ONX_ModelTest::Result::Unset: return L"Unset"; break; case ONX_ModelTest::Result::Fail: return L"Fail"; break; case ONX_ModelTest::Result::Errors: return L"Errors"; break; case ONX_ModelTest::Result::Warnings: return L"Warnings"; break; case ONX_ModelTest::Result::Pass: return L"Pass"; break; case ONX_ModelTest::Result::Skip: return L"Skip"; break; default: break; } ON_ERROR("Invalid result parameter."); return L"Invalid result parameter"; } ONX_ModelTest::Result ONX_ModelTest::WorstResult( ONX_ModelTest::Result a, ONX_ModelTest::Result b ) { if (ONX_ModelTest::Result::Unset == a && ONX_ModelTest::Result::Unset != b) return b; if (ONX_ModelTest::Result::Unset != a && ONX_ModelTest::Result::Unset == b) return a; return (static_cast(a) < static_cast(b)) ? a : b; } ONX_ModelTest::Result ONX_ModelTest::ResultFromErrorCounter( ONX_ErrorCounter error_count, ONX_ModelTest::Result no_errors_result ) { if (error_count.FailureCount() > 0) return ONX_ModelTest::Result::Fail; if (error_count.ErrorCount() > 0) return ONX_ModelTest::Result::Errors; if (error_count.WarningCount() > 0) return ONX_ModelTest::Result::Warnings; return no_errors_result; } void ONX_ModelTest::Internal_BeginTest() { *this = ONX_ModelTest::Unset; m_test_result = ONX_ModelTest::Result::Unset; const size_t count = sizeof(m_test_results) / sizeof(m_test_results[0]); for (size_t i = 0; i < count; i++) { m_test_results[i] = ONX_ModelTest::Result::Unset; } m_error_count = ONX_ErrorCounter::Zero; for (size_t i = 0; i < count; i++) { m_error_counts[i] = ONX_ErrorCounter::Zero; } m_error_counts[0].ClearLibraryErrorsAndWarnings(); m_error_count.ClearLibraryErrorsAndWarnings(); m_current_test_index = 0; for (int i = 0; i < 3; i++) { m_model_3dm_file_version[i] = 0; m_model_hash[i] = ON_SHA1_Hash::ZeroDigest; } } void ONX_ModelTest::Internal_EndCurrentTest() { if (m_current_test_index > 0 && ONX_ModelTest::Result::Unset == m_test_results[m_current_test_index]) { m_error_counts[m_current_test_index].AddLibraryErrorsAndWarnings(); m_test_results[m_current_test_index] = ONX_ModelTest::WorstResult(m_test_results[m_current_test_index], ONX_ModelTest::ResultFromErrorCounter(m_error_counts[m_current_test_index], ONX_ModelTest::Result::Pass)); m_error_counts[0].ClearLibraryErrorsAndWarnings(); } } void ONX_ModelTest::Internal_BeginNextTest( ONX_ModelTest::Type test_type ) { m_error_counts[0].AddLibraryErrorsAndWarnings(); //const size_t count = sizeof(m_test_results) / sizeof(m_test_results[0]); const unsigned int test_index = static_cast(test_type); if ( test_index > m_current_test_index ) { Internal_EndCurrentTest(); m_current_test_index = test_index; m_test_results[m_current_test_index] = ONX_ModelTest::Result::Unset; m_error_counts[m_current_test_index] = ONX_ErrorCounter::Zero; m_error_counts[m_current_test_index].ClearLibraryErrorsAndWarnings(); } } bool ONX_ModelTest::Internal_TallyTestResults() { const size_t count = sizeof(m_test_results) / sizeof(m_test_results[0]); m_test_results[0] = ONX_ModelTest::WorstResult(m_test_results[0], ONX_ModelTest::Result::Pass); for (size_t i = 0; i < count; i++) { m_test_results[i] = ONX_ModelTest::WorstResult(m_test_results[i], ONX_ModelTest::ResultFromErrorCounter(m_error_counts[i], ONX_ModelTest::Result::Unset)); m_test_result = ONX_ModelTest::WorstResult(m_test_result, m_test_results[i]); m_error_count += m_error_counts[i]; } m_error_count.ClearLibraryErrorsAndWarnings(); m_test_result = ONX_ModelTest::WorstResult(m_test_result, ONX_ModelTest::ResultFromErrorCounter(m_error_count, ONX_ModelTest::Result::Unset)); return (ONX_ModelTest::Result::Pass == m_test_result); } ONX_ModelTest::Type ONX_ModelTest::TestType() const { return m_test_type; } const ON_wString ONX_ModelTest::Source3dmFilePath() const { return m_source_3dm_file_path; } const ON_wString ONX_ModelTest::TextLogSource3dmFilePath() const { return m_text_log_3dm_file_path.IsNotEmpty() ? m_text_log_3dm_file_path : Source3dmFilePath(); } unsigned int ONX_ModelTest::Source3dmFileVersion() const { return m_model_3dm_file_version[0]; } bool ONX_ModelTest::SkipCompare(unsigned int source_3dm_file_version) { const bool bSkipCompare = (source_3dm_file_version >= 1 && source_3dm_file_version < 70); return bSkipCompare; } ONX_ModelTest::Result ONX_ModelTest::TestResult() const { return m_test_result; } ONX_ModelTest::Result ONX_ModelTest::TestResult( ONX_ModelTest::Type test_type ) { const unsigned int i = static_cast(m_test_type); return m_test_results[i]; } ONX_ErrorCounter ONX_ModelTest::ErrorCounter() const { return m_error_count; } ONX_ErrorCounter ONX_ModelTest::ErrorCounter( ONX_ModelTest::Type test_type ) const { const unsigned int i = static_cast(m_test_type); return m_error_counts[i]; } bool ONX_ModelTest::ReadTest( const char* file_path, ONX_ModelTest::Type test_type, bool bKeepModels, const char* text_log_file_path, ON_TextLog* text_log ) { Internal_BeginTest(); m_test_type = test_type; ON_TextLogNull devnull; if (nullptr == text_log) text_log = &ON_TextLog::Null; FILE* fp = nullptr; for (;;) { if (nullptr == file_path || 0 == file_path[0]) { m_error_counts[0].IncrementFailureCount(); text_log->Print("file_path was the empty string."); break; } fp = ON_FileStream::Open3dmToRead(file_path); if (nullptr == fp) { m_error_counts[0].IncrementFailureCount(); text_log->Print("ON_FileStream::Open(%s, \"rb\") failed.",file_path); break; } ON_BinaryFile archive(ON::archive_mode::read3dm, fp); archive.SetArchiveFullPath(ON_wString(file_path)); ON_wString wide_text_log_file_path(text_log_file_path); Internal_ReadTest(archive, test_type, bKeepModels, wide_text_log_file_path, text_log); break; } if (nullptr != fp) { if (0 != ON_FileStream::Close(fp)) { text_log->Print("ON_FileStream::Close(%s) failed.", file_path); m_error_counts[0].IncrementErrorCount(); } } return Internal_TallyTestResults(); } bool ONX_ModelTest::ReadTest( const wchar_t* file_path, ONX_ModelTest::Type test_type, bool bKeepModels, const wchar_t* text_log_file_path, ON_TextLog* text_log ) { Internal_BeginTest(); ON_TextLogNull devnull; if (nullptr == text_log) text_log = &ON_TextLog::Null; FILE* fp = nullptr; for (;;) { if (nullptr == file_path || 0 == file_path[0]) { m_error_counts[0].IncrementFailureCount(); text_log->Print("file_path was the empty string."); break; } fp = ON_FileStream::Open3dmToRead(file_path); if (nullptr == fp) { m_error_counts[0].IncrementFailureCount(); text_log->Print(L"ON_FileStream::Open(%ls, L\"rb\") failed.",file_path); break; } ON_BinaryFile archive(ON::archive_mode::read3dm, fp); archive.SetArchiveFullPath(file_path); Internal_ReadTest(archive, test_type, bKeepModels, text_log_file_path, text_log); break; } if (nullptr != fp) { if (0 != ON_FileStream::Close(fp)) { text_log->Print(L"ON_FileStream::Close(%ls) failed.",file_path); m_error_counts[0].IncrementErrorCount(); } } return Internal_TallyTestResults(); } bool ONX_ModelTest::ReadTest( FILE* fp, ONX_ModelTest::Type test_type, bool bKeepModels, const wchar_t* text_log_file_path, ON_TextLog* text_log ) { Internal_BeginTest(); ON_TextLogNull devnull; if (nullptr == text_log) text_log = &ON_TextLog::Null; for (;;) { if (nullptr == fp) { m_error_counts[0].IncrementFailureCount(); text_log->Print("fp is nullptr."); break; } ON_BinaryFile archive(ON::archive_mode::read3dm, fp); Internal_ReadTest(archive, test_type, bKeepModels, text_log_file_path, text_log); break; } return Internal_TallyTestResults(); } static bool InternalCleanPass( ONX_ModelTest::Result result, ONX_ErrorCounter error_counter ) { return (ONX_ModelTest::Result::Pass == result && 0 == error_counter.TotalCount()); } static void InternalDumpResultAndErrorCount( ONX_ModelTest::Result result, ONX_ErrorCounter error_counter, ON_TextLog& text_log ) { text_log.Print("%s", ONX_ModelTest::ResultToString(result)); if (false == InternalCleanPass(result,error_counter)) { text_log.Print(" ("); error_counter.Dump(text_log); text_log.Print(")"); } text_log.PrintNewLine(); } bool ONX_ModelTest::DumpModel(const ONX_Model* model, ON_TextLog& text_log) { if (nullptr == model || model->Manifest().ActiveComponentCount(ON_ModelComponent::Type::Unset) <= 0) return false; ON_TextHash hash_log; hash_log.SetIdRemap(true); hash_log.SetOutputTextLog(&text_log); model->Dump(hash_log); const ON_SHA1_Hash dump_hash = hash_log.Hash(); text_log.PrintNewLine(); text_log.Print("Model Hash: "); dump_hash.Dump(text_log); text_log.PrintNewLine(); return (false == dump_hash.IsZeroDigestOrEmptyContentHash()); } std::shared_ptr ONX_ModelTest::SourceModel() const { return m_model[0]; } std::shared_ptr ONX_ModelTest::ReadWriteReadModel() const { const auto source_model = SourceModel(); if (nullptr != source_model) { for (int i = 1; i < 3; i++) { const ONX_Model* copy_model = m_model[i].get(); if (nullptr == copy_model) continue; if (copy_model->m_3dm_file_version != source_model->m_3dm_file_version) continue; return m_model[i]; } } std::shared_ptr nullsp; return nullsp; } static const ON_wString Internal_DumpModelfileName( const ON_wString source_3dm_file_path, bool bSourceModel, bool bIsHashLog ) { ON_wString file_name_stem = ON_FileSystemPath::FileNameFromPath(source_3dm_file_path,false); if (file_name_stem.IsEmpty()) return ON_wString::EmptyString; 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"; #elif defined(ON_32BIT_RUNTIME) text_file_path += L"_Win32"; #else text_file_path += L"_Win"; #endif #elif defined(ON_RUNTIME_APPLE_MACOS) text_file_path += L"_MacOS"; #elif defined(ON_RUNTIME_APPLE_IOS) text_file_path += L"_iOS"; #elif defined(ON_RUNTIME_APPLE) text_file_path += L"_AppleOS"; #elif defined(ON_RUNTIME_ANDROID) text_file_path += L"_AndroidOS"; #endif #if defined(ON_DEBUG) text_file_path += L"Debug"; #else text_file_path += L"Release"; #endif } text_file_path += L".txt"; return text_file_path; } bool ONX_ModelTest::DumpSourceModel() const { const ON_wString text_file_path = Internal_DumpModelfileName(m_source_3dm_file_path, true, false); return DumpSourceModel(text_file_path); } bool ONX_ModelTest::DumpSourceModel(const wchar_t* text_file_full_path) const { bool rc = false; FILE* fp = nullptr; for (;;) { if (nullptr == text_file_full_path || 0 == text_file_full_path[0]) break; fp = ON_FileStream::Open(text_file_full_path, L"w"); if (nullptr == fp) break; const auto model = SourceModel(); if (nullptr == model) break; if (model->Manifest().ActiveComponentCount(ON_ModelComponent::Type::Unset) <= 0) break; ON_TextLog text_log(fp); rc = DumpSourceModel(text_log); break; } if (nullptr != fp) ON_FileStream::Close(fp); return rc; } bool ONX_ModelTest::DumpSourceModel(ON_TextLog& text_log) const { return ONX_ModelTest::DumpModel(SourceModel().get(), text_log); } bool ONX_ModelTest::DumpReadWriteReadModel() const { const ON_wString text_file_path = Internal_DumpModelfileName(m_source_3dm_file_path,false, false); return DumpReadWriteReadModel(text_file_path); } bool ONX_ModelTest::DumpReadWriteReadModel(const wchar_t* text_file_full_path) const { bool rc = false; FILE* fp = nullptr; for (;;) { if (nullptr == text_file_full_path || 0 == text_file_full_path[0]) break; fp = ON_FileStream::Open(text_file_full_path, L"w"); if (nullptr == fp) break; const auto model = ReadWriteReadModel(); if (nullptr == model) break; if (model->Manifest().ActiveComponentCount(ON_ModelComponent::Type::Unset) <= 0) break; ON_TextLog text_log(fp); rc = DumpReadWriteReadModel(text_log); break; } if (nullptr != fp) ON_FileStream::Close(fp); return rc; } 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(); text_log.Print("Test type: %s\n", ONX_ModelTest::TestTypeToString(test_type)); //const ON_wString source_archive = Source3dmFilePath(); const ON_wString test_log_source_archive = TextLogSource3dmFilePath(); text_log.Print(L"Source 3dm file path: %ls\n", static_cast(test_log_source_archive)); text_log.Print(L"Source 3dm file version: %u\n", Source3dmFileVersion()); text_log.Print("Result: "); InternalDumpResultAndErrorCount(m_test_result, m_error_count, text_log); //const int i_rwrcompare = static_cast(ONX_ModelTest::Type::ReadWriteReadCompare); const bool bSkipCompare = 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++) { bSkipDetails = InternalCleanPass(m_test_results[i], m_error_counts[i]); } if (bSkipDetails) { 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(); } return; } text_log.PushIndent(); for(;;) { unsigned int i = 0; text_log.Print("Initialization: "); InternalDumpResultAndErrorCount(m_test_results[i], m_error_counts[i], text_log); if (i >= imax) break; i++; text_log.Print("Read source file: "); InternalDumpResultAndErrorCount(m_test_results[i], m_error_counts[i], text_log); if (i >= imax) break; i++; 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 file: "); InternalDumpResultAndErrorCount(m_test_results[i], m_error_counts[i], text_log); if (i >= imax) break; i++; text_log.Print("Compare models from source and temporary file: "); InternalDumpResultAndErrorCount(m_test_results[i], m_error_counts[i], text_log); if (i >= imax) break; break; } text_log.PopIndent(); } bool ONX_ModelTest::ReadTest( ON_BinaryArchive& archive, ONX_ModelTest::Type test_type, bool bKeepModels, const wchar_t* text_log_file_path, ON_TextLog* text_log ) { Internal_BeginTest(); Internal_ReadTest(archive, test_type, bKeepModels, text_log_file_path, text_log); return Internal_TallyTestResults(); } void ONX_ModelTest::Internal_ReadTest( ON_BinaryArchive& archive, ONX_ModelTest::Type test_type, bool bKeepModels, const wchar_t* text_log_file_path, ON_TextLog* text_log ) { m_test_type = test_type; m_source_3dm_file_path = archive.ArchiveFullPath(); m_text_log_3dm_file_path = text_log_file_path; const unsigned int current_3dm_file_version = (unsigned int)ON_BinaryArchive::CurrentArchiveVersion(); ON_TextLogNull devnull; if (nullptr == text_log) text_log = &ON_TextLog::Null; for (;;) { Internal_BeginNextTest(ONX_ModelTest::Type::Read); if ( ON::archive_mode::read3dm != archive.Mode() ) { m_error_counts[0].IncrementFailureCount(); text_log->Print("archive.Mode() must be ON::archive_mode::read3dm."); break; } ONX_Model* model0 = new ONX_Model(); std::shared_ptr model0_sp = std::shared_ptr(model0); if (bKeepModels) this->m_model[0] = model0_sp; ON_String text_log_3dm_archive_name = TextLogSource3dmFilePath(); if (text_log_3dm_archive_name.IsEmpty()) { text_log_3dm_archive_name = "archive"; } const ON_String read0_description = ON_String::FormatToString("ONX_Model.Read(%s,...)", static_cast(text_log_3dm_archive_name)); // read the original file text_log->Print("Calling %s ...\n", static_cast(read0_description)); text_log->PushIndent(); ONX_ErrorCounter read0_error_counter; read0_error_counter.ClearLibraryErrorsAndWarnings(); const bool bRead0 = model0->Read(archive, text_log); read0_error_counter.AddLibraryErrorsAndWarnings(); m_model_3dm_file_version[0] = model0->m_3dm_file_version; text_log->PopIndent(); if (false == bRead0) { m_error_counts[m_current_test_index].IncrementFailureCount(); text_log->Print("%s failed.\n", static_cast(read0_description)); break; } else { text_log->Print("... %s ", static_cast(read0_description)); if ( 0 == read0_error_counter.TotalCount() ) text_log->Print("succeeded."); else { text_log->Print("finished. "); read0_error_counter.Dump(*text_log); } text_log->PrintNewLine(); } text_log->PushIndent(); text_log->Print("Source model 3dm file version: %d", model0->m_3dm_file_version); text_log->PrintNewLine(); m_model_hash[0] = model0->ContentHash(); text_log->Print("Source model hash: "); m_model_hash[0].Dump(*text_log); text_log->PrintNewLine(); text_log->PopIndent(); if ( ONX_ModelTest::Type::Read == test_type) break; Internal_EndCurrentTest(); Internal_BeginNextTest(ONX_ModelTest::Type::ReadWrite); //const unsigned int original_model_3dm_file_version = (unsigned int)(model0->m_3dm_file_version); // Write original_model to a termporary archive using "buffer" for storage. ON_Buffer temporary_buffer[2]; const unsigned int temporary_buffer_3dm_version[2] = { current_3dm_file_version - 10, current_3dm_file_version }; for (int buffer_index = 0; buffer_index < 2; buffer_index++) { ON_BinaryArchiveBuffer temporary_archive(ON::archive_mode::write3dm, &temporary_buffer[buffer_index]); const ON_String write1_description = ON_String::FormatToString( "ONX_Model.Write( temporary_archive version %d, ...)", temporary_buffer_3dm_version[buffer_index]); text_log->Print("Calling %s ...\n", static_cast(write1_description)); text_log->PushIndent(); ONX_ErrorCounter write1_error_counter; write1_error_counter.ClearLibraryErrorsAndWarnings(); bool bWrite1 = model0->Write(temporary_archive, temporary_buffer_3dm_version[buffer_index], text_log); write1_error_counter.AddLibraryErrorsAndWarnings(); text_log->PopIndent(); if (false == bWrite1) { m_error_counts[m_current_test_index].IncrementFailureCount(); text_log->Print("%s failed.\n", static_cast(write1_description)); break; } else { text_log->Print("... %s ", static_cast(write1_description)); if ( 0 == write1_error_counter.TotalCount() ) text_log->Print("succeeded."); else { text_log->Print("finished. "); write1_error_counter.Dump(*text_log); } text_log->PrintNewLine(); } } // no longer need model0 model0 = nullptr; if ( ONX_ModelTest::Type::ReadWrite == test_type) break; Internal_EndCurrentTest(); Internal_BeginNextTest(ONX_ModelTest::Type::ReadWriteRead); // read models from the temporary archives for (int buffer_index = 0; buffer_index < 2; buffer_index++) { ON_BinaryArchiveBuffer temporary_archive(ON::archive_mode::read3dm, &temporary_buffer[buffer_index]); const ON_String read1_description = ON_String::FormatToString( "ONX_Model.Read( temporary_archive version %d, ...)", temporary_buffer_3dm_version[buffer_index]); text_log->Print("Calling %s ...\n", static_cast(read1_description)); text_log->PushIndent(); ONX_Model* model1 = new ONX_Model(); std::shared_ptr model1_sp = std::shared_ptr(model1); if (bKeepModels) this->m_model[buffer_index+1] = model1_sp; ONX_ErrorCounter read1_error_counter; read1_error_counter.ClearLibraryErrorsAndWarnings(); const bool bRead1 = model1->Read(temporary_archive, text_log); read1_error_counter.AddLibraryErrorsAndWarnings(); m_model_3dm_file_version[buffer_index + 1] = model1->m_3dm_file_version; text_log->PopIndent(); if (false == bRead1) { m_error_counts[m_current_test_index].IncrementFailureCount(); text_log->Print("%s failed.\n", static_cast(read1_description)); break; } else { text_log->Print("... %s ", static_cast(read1_description)); if ( 0 == read1_error_counter.TotalCount()) text_log->Print("succeeded."); else { text_log->Print("finished. "); read1_error_counter.Dump(*text_log); } text_log->PrintNewLine(); } text_log->PushIndent(); text_log->Print("Temporary model %d 3dm file version: %d",buffer_index+1,model1->m_3dm_file_version); text_log->PrintNewLine(); m_model_hash[buffer_index+1] = model1->ContentHash(); text_log->Print("Temporary model %d hash: ",buffer_index+1); m_model_hash[buffer_index+1].Dump(*text_log); text_log->PrintNewLine(); text_log->PopIndent(); } if ( ONX_ModelTest::Type::ReadWrite == test_type) break; Internal_EndCurrentTest(); Internal_BeginNextTest(ONX_ModelTest::Type::ReadWriteReadCompare); bool bSkippedCompare = true; for (int buffer_index = 0; buffer_index < 2; buffer_index++) { if (m_model_3dm_file_version[0] != m_model_3dm_file_version[buffer_index+1]) continue; if (m_model_3dm_file_version[0] != temporary_buffer_3dm_version[buffer_index]) continue; bSkippedCompare = false; if (m_model_hash[0] != m_model_hash[buffer_index+1]) { m_error_counts[m_current_test_index].IncrementFailureCount(); text_log->Print("The source model and temporary model %d are different.\n",buffer_index+1); break; } else { text_log->Print("The source model and temporary model %d are identical.\n",buffer_index+1); } } unsigned int compare_test_index = m_current_test_index; if (bSkippedCompare) this->m_test_results[compare_test_index] = ONX_ModelTest::Result::Skip; break; } Internal_EndCurrentTest(); } /* +----------------------------------------------------------------------------+ | | | RDK version 4 document archive format | | | +================+==========================+================================+ | Type | Usage | ON_BinaryArchive function | +================+==========================+================================+ | ON__INT32 | RDK document version | ReadInt() / WriteInt() | +----------------+--------------------------+--------------------------------+ | ON__INT32 | Length of UTF8 XML block | ReadInt() / WriteInt() | +----------------+--------------------------+--------------------------------+ | UTF8 chars | Entire RDK document XML | ReadChar() / WriteChar() | +----------------+--------------------------+--------------------------------+ | ON__INT32 | Number of embedded files | ReadInt() / WriteInt() | +----------------+--------------------------+--------------------------------+ If the number of embedded files is not zero, then for each embedded file: +----------------+--------------------------+--------------------------------+ | String | Full path to file | ReadString() / WriteString() | +----------------+--------------------------+--------------------------------+ | Bytes | Compressed embedded file | Read / WriteCompressedBuffer() | +----------------+--------------------------+--------------------------------+ */ static const wchar_t* RenderContentKindString(RenderContentKinds kind) { switch (kind) { case RenderContentKinds::Material: return ON_KIND_MATERIAL; case RenderContentKinds::Environment: return ON_KIND_ENVIRONMENT; case RenderContentKinds::Texture: return ON_KIND_TEXTURE; default: ON_ASSERT(false); return L""; } } extern int ON_ComponentManifestImpl_TableCount(void); ON_XMLNode& ON_GetRdkDocNode(const ON_3dmRenderSettings& rs); ONX_ModelPrivate::ONX_ModelPrivate(ONX_Model& m) : m_model(m) { // The TableCount enum in opennurbs_archive_manifest.cpp should always be // equal to ON_ModelComponent::Type::NumOf. ON_ASSERT(int(ON_ModelComponent::Type::NumOf) == ON_ComponentManifestImpl_TableCount()); for (unsigned int i = 0; i < int(ON_ModelComponent::Type::NumOf); i++) { ONX_Model::ONX_ModelComponentList& list = m_mcr_lists.AppendNew(); list.m_component_type = ON_ModelComponent::ComponentTypeFromUnsigned(i); } } ONX_ModelPrivate::~ONX_ModelPrivate() { } ONX_Model_UserData* ONX_ModelPrivate::GetRDKDocumentUserData(int archive_3dm_version) const { // Try to find existing RDK document user data. for (int i = 0; i < m_model.m_userdata_table.Count(); i++) { auto* pUserData = m_model.m_userdata_table[i]; if (nullptr != pUserData) { if (::IsRDKDocumentInformation(*pUserData)) return pUserData; // Found it. } } // Not found, so create it. auto* ud = new ONX_Model_UserData; ud->m_goo.m_typecode = TCODE_USER_RECORD; ud->m_uuid = RdkPlugInId(); ud->m_usertable_3dm_version = archive_3dm_version; ud->m_usertable_opennurbs_version = ON::Version(); ON_XMLRootNode root; PopulateDefaultRDKDocumentXML(root); SetRDKDocumentInformation(root.String(), *ud, archive_3dm_version); m_model.m_userdata_table.Append(ud); return ud; } void ONX_ModelPrivate::PopulateDefaultRDKDocumentXML(ON_XMLRootNode& root) const { // Populate default render content kinds. GetRenderContentSectionNode(root, RenderContentKinds::Material); GetRenderContentSectionNode(root, RenderContentKinds::Environment); GetRenderContentSectionNode(root, RenderContentKinds::Texture); } bool ONX_ModelPrivate::GetRDKDocumentXML(ON_wString& xml, bool embedded_files, int archive_3dm_version) const { // Gets the entire RDK document XML as a string in 'xml'. If 'embedded_files' is true, // ON_EmbeddedFile objects are created for each embedded file. const ONX_Model_UserData* pUserData = GetRDKDocumentUserData(archive_3dm_version); if (nullptr != pUserData) { ONX_Model* model = embedded_files ? &m_model : nullptr; if (GetEntireRDKDocument(*pUserData, xml, model)) return true; } return false; } static bool ContentIsKind(const ON_RenderContent* pContent, RenderContentKinds kind) { switch (kind) { case RenderContentKinds::Material: return nullptr != ON_RenderMaterial ::Cast(pContent); case RenderContentKinds::Environment: return nullptr != ON_RenderEnvironment::Cast(pContent); case RenderContentKinds::Texture: return nullptr != ON_RenderTexture ::Cast(pContent); } return false; } ON_XMLNode* ONX_ModelPrivate::GetPostEffectSectionNode(ON_XMLNode& docNode, ON_PostEffect::Types type) const { ON_wString s = ON_RDK_DOCUMENT ON_XML_SLASH ON_RDK_SETTINGS ON_XML_SLASH ON_RDK_POST_EFFECTS ON_XML_SLASH; s += ON_PostEffectTypeString(type); return docNode.CreateNodeAtPath(s); } ON_XMLNode* ONX_ModelPrivate::GetRenderContentSectionNode(ON_XMLNode& docNode, RenderContentKinds kind) const { ON_wString s = ON_RDK_DOCUMENT ON_XML_SLASH; s += RenderContentKindString(kind); s += ON_RDK_POSTFIX_SECTION; return docNode.CreateNodeAtPath(s); } bool ONX_ModelPrivate::CreateRenderContentFromXML(ON_XMLNode& model_node, RenderContentKinds kind) { const ON_XMLNode* rc_section_node = GetRenderContentSectionNode(model_node, kind); if (nullptr == rc_section_node) return false; auto it = rc_section_node->GetChildIterator(); const ON_XMLNode* rc_node = it.GetNextChild(); while (nullptr != rc_node) { ON_RenderContent* rc = NewRenderContentFromNode(*rc_node); if (nullptr != rc) { // The name currently in the render content came from XML. It's been corrected if it was invalid, // but it still might be a duplicate of another one in the model, so we have to turn it into an // unused name if necessary. const ON_wString name = m_model.m_manifest.UnusedName(rc->ComponentType(), ON_nil_uuid, rc->Name(), nullptr, nullptr, 0, nullptr); rc->SetName(name); const auto ref = m_model.AddModelComponent(*rc); auto* model_rc = ON_RenderContent::Cast(ref.ModelComponent()); if (nullptr != model_rc) { SetModel(*model_rc, m_model); } delete rc; } rc_node = it.GetNextChild(); } return true; } bool ONX_ModelPrivate::CreateXMLFromRenderContent(ON_XMLNode& model_node, RenderContentKinds kind) const { ON_XMLNode* rc_section_node = GetRenderContentSectionNode(model_node, kind); if (nullptr == rc_section_node) return false; rc_section_node->RemoveAllChildren(); ONX_ModelComponentIterator it(m_model, ON_ModelComponent::Type::RenderContent); const ON_ModelComponent* component = it.FirstComponent(); while (nullptr != component) { const auto* rc = ON_RenderContent::Cast(component); if (nullptr != rc) { if (ContentIsKind(rc, kind)) { SetRenderContentNodeRecursive(*rc, *rc_section_node); } } component = it.NextComponent(); } return true; } bool ONX_ModelPrivate::PopulateRDKComponents(int archive_3dm_version) { // Get the entire RDK document XML. This includes not only render contents // but also Sun, GroundPlane and other RDK document data. Ignore embedded files. ON_wString xml; if (!GetRDKDocumentXML(xml, true, archive_3dm_version)) return false; // Read the entire XML into the document node. ON_XMLNode& doc_node = ON_GetRdkDocNode(m_model.m_settings.m_RenderSettings); const auto read = doc_node.ReadFromStream(xml, false, true); if (ON_XMLNode::ReadError == read) return false; // Create the render contents from the relevant nodes. CreateRenderContentFromXML(doc_node, RenderContentKinds::Material); CreateRenderContentFromXML(doc_node, RenderContentKinds::Environment); CreateRenderContentFromXML(doc_node, RenderContentKinds::Texture); // Create the mesh modifiers. CreateMeshModifiersFromXML(m_model, archive_3dm_version); return true; } bool ONX_ModelPrivate::UpdateRDKUserData(int archive_3dm_version) { if (0 == archive_3dm_version) archive_3dm_version = ON_BinaryArchive::CurrentArchiveVersion(); ON_XMLNode& doc_node = ON_GetRdkDocNode(m_model.m_settings.m_RenderSettings); // For each kind, convert the render content hierarchy to fresh XML. CreateXMLFromRenderContent(doc_node, RenderContentKinds::Material); CreateXMLFromRenderContent(doc_node, RenderContentKinds::Environment); CreateXMLFromRenderContent(doc_node, RenderContentKinds::Texture); // Convert the mesh modifier collection to fresh XML. CreateXMLFromMeshModifiers(m_model, archive_3dm_version); // Get the RDK document user data. ONX_Model_UserData* pUserData = GetRDKDocumentUserData(archive_3dm_version); if (nullptr == pUserData) return false; // Shouldn't happen because we were able to get the XML earlier. // Get the entire document XML as a string and set it to the user data. ON_wString xml = doc_node.String(); pUserData->m_usertable_3dm_version = archive_3dm_version; SetRDKDocumentInformation(xml, *pUserData, archive_3dm_version); return true; } bool IsRDKDocumentInformation(const ONX_Model_UserData& docud) { return (0 == ON_UuidCompare(RdkPlugInId(), docud.m_uuid)) && (docud.m_goo.m_value >= 4) && (nullptr != docud.m_goo.m_goo); } bool ONX_Model::IsRDKDocumentInformation(const ONX_Model_UserData& docud) { return ::IsRDKDocumentInformation(docud); } static size_t ArchiveLengthUpToEmbeddedFiles(size_t utf8_length) { // version utf8_length utf8_buf const size_t length = sizeof(ON__INT32) + sizeof(ON__INT32) + utf8_length; ON_ASSERT(length <= INT_MAX); return length; } bool ONX_Model::GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers) { ON_SimpleArray dummy; return ::GetRDKEmbeddedFiles(docud, paths, embedded_files_as_buffers, dummy); } bool ONX_Model::GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers, ON_SimpleArray& buffer_sizes) // Static. { return ::GetRDKEmbeddedFiles(docud, paths, embedded_files_as_buffers, buffer_sizes); } bool ONX_Model::GetRDKDocumentInformation(const ONX_Model_UserData& docud, ON_wString& xml) // Static. { return ONX_ModelPrivate::GetEntireRDKDocument(docud, xml, nullptr); } static int SeekArchiveToEmbeddedFiles(ON_Read3dmBufferArchive& archive, int goo_length) { // Skips over the bulk of the archive to arrive at the embedded files. // Then reads the number of embedded files and returns it. The archive // is then ready to read the first embedded file. if (!archive.ReadMode()) return 0; // Read the version number. Must be 4. int version = 0; if (!archive.ReadInt(&version) || (4 != version)) return 0; // Read the UTF8 data length. int utf8_length = 0; if (!archive.ReadInt(&utf8_length)) return 0; // Validate the length. if (utf8_length <= 0) return 0; const auto length_so_far = ArchiveLengthUpToEmbeddedFiles(utf8_length); if (length_so_far > size_t(goo_length)) return 0; // Sanity check. // Seek past the UTF8 data. if (!archive.SeekForward(utf8_length)) return 0; // Read the number of embedded files. int num_embedded_files = 0; if (!archive.ReadInt(&num_embedded_files)) return 0; return num_embedded_files; } static bool SeekArchivePastCompressedBuffer(ON_BinaryArchive& archive) { // WARNING: This function has intimate knowledge of how ON_BinaryArchive::WriteCompressedBuffer() works. // It is my opinion that this function should really be a method on ON_BinaryArchive since only that // class should know how its own implementation works. JohnC, 2022AD. if (!archive.ReadMode()) return false; bool rc = false; unsigned int buffer_crc0 = 0; char method = 0; size_t sizeof__outbuffer; if (!archive.ReadCompressedBufferSize(&sizeof__outbuffer)) return false; if (0 == sizeof__outbuffer) return true; if (!archive.ReadInt(&buffer_crc0)) // 32 bit crc of uncompressed buffer return false; if (!archive.ReadChar(&method)) return false; if (method != 0 && method != 1) return false; switch (method) { case 0: // uncompressed rc = archive.SeekForward(sizeof__outbuffer); break; case 1: // compressed { ON__UINT32 tcode = 0; ON__INT64 big_value = 0; rc = archive.BeginRead3dmBigChunk(&tcode, &big_value); if (rc) rc = archive.EndRead3dmChunk(); } break; } return rc; } static bool ReadEmbeddedFilePathsFromArchive(ON_Read3dmBufferArchive& archive, int count, ON_ClassArray& paths) { // Reads the embedded file paths and skips over the data for 'count' embedded files. Must be called with // the archive position at the start of the first embedded file, right after the count of embedded files. if (0 == count) return false; for (int i = 0; i < count; i++) { ON_wString sPath; if (!archive.ReadString(sPath)) return false; paths.Append(sPath); SeekArchivePastCompressedBuffer(archive); } return paths.Count() > 0; } bool ONX_Model::GetRDKEmbeddedFilePaths(const ONX_Model_UserData& docud, ON_ClassArray& paths) { if (!::IsRDKDocumentInformation(docud)) return false; ON_Read3dmBufferArchive archive(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); const int count = SeekArchiveToEmbeddedFiles(archive, docud.m_goo.m_value); if (!ReadEmbeddedFilePathsFromArchive(archive, count, paths)) return false; return true; } bool GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray& paths, ON_SimpleArray& embedded_files_as_buffers, ON_SimpleArray& buffer_sizes) { if (!::IsRDKDocumentInformation(docud)) return false; ON_Read3dmBufferArchive a(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); const int count = SeekArchiveToEmbeddedFiles(a, docud.m_goo.m_value); if (0 == count) return false; int unpacked = 0; for (int i = 0; i < count; i++) { ON_wString sPath; if (!a.ReadString(sPath)) return false; size_t size; if (!a.ReadCompressedBufferSize(&size)) return false; auto* buffer = new unsigned char[size]; bool bFailedCRC = false; if (a.ReadCompressedBuffer(size, buffer, &bFailedCRC)) { if (!bFailedCRC) { embedded_files_as_buffers.Append(buffer); paths.Append(sPath); buffer_sizes.Append(size); unpacked++; } else { delete[] buffer; } } } return unpacked > 0; } bool ONX_Model::GetRDKEmbeddedFile(const ONX_Model_UserData& docud, const wchar_t* path, ON_SimpleArray& bytes) { if (!::IsRDKDocumentInformation(docud)) return false; ON_Read3dmBufferArchive archive(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); const auto count = SeekArchiveToEmbeddedFiles(archive, docud.m_goo.m_value); if (0 == count) return false; bool found = false; for (int i = 0; i < count; i++) { ON_wString sPath; if (!archive.ReadString(sPath)) break; if (0 == sPath.ComparePath(path)) { size_t size; if (!archive.ReadCompressedBufferSize(&size)) break; bytes.Destroy(); bytes.Reserve(size); bool bFailedCRC = false; bool bRet = archive.ReadCompressedBuffer(size, bytes.Array(), &bFailedCRC); if (!bRet || bFailedCRC) break; bytes.SetCount((int)size); found = true; break; } else { SeekArchivePastCompressedBuffer(archive); } } return found; } void ONX_ModelPrivate::RemoveAllEmbeddedFiles(ONX_Model& model) { ON_SimpleArray a; const auto type = ON_ModelComponent::Type::EmbeddedFile; ONX_ModelComponentIterator it(model, type); const ON_ModelComponent* pComponent = it.FirstComponent(); while (nullptr != pComponent) { a.Append(pComponent->Id()); pComponent = it.NextComponent(); } for (int i = 0; i < a.Count(); i++) { model.RemoveModelComponent(type, a[i]); } } bool ONX_ModelPrivate::GetEntireRDKDocument(const ONX_Model_UserData& docud, ON_wString& xml, ONX_Model* model) // Static. { if (!::IsRDKDocumentInformation(docud)) return false; ON_Read3dmBufferArchive archive(docud.m_goo.m_value, docud.m_goo.m_goo, false, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); int version = 0; if (!archive.ReadInt(&version)) return false; if (1 == version) { // Version 1 was a UTF-16 string. if (!archive.ReadString(xml)) return false; } else if ((3 == version) || (4 == version)) { // Version 4 files are exactly the same as version 3, but with the addition of embedded files. // Version 3 and 4 is a UTF-8 string. int utf8_length = 0; if (!archive.ReadInt(&utf8_length)) return false; if (utf8_length <= 0) return false; const size_t length_so_far = ArchiveLengthUpToEmbeddedFiles(utf8_length); if (length_so_far > size_t(docud.m_goo.m_value)) return false; // Sanity check. ON_String s; s.SetLength(utf8_length); if (!archive.ReadChar(utf8_length, s.Array())) return false; if (s.IsNotEmpty()) { const char* sArray = s.Array(); unsigned int error_status = 0; auto wideLength = ON_ConvertUTF8ToWideChar(false, sArray, -1, 0, 0, &error_status, 0, 0, 0); if ((wideLength > 0) && (0 == error_status)) { xml.SetLength(wideLength); ON_ConvertUTF8ToWideChar(false, sArray, -1, xml.Array(), wideLength+1, &error_status, 0, 0, 0); } if (0 != error_status) { ON_ERROR("RDK xml document settings is not a valid UTF-8 string."); } } if (nullptr != model) { RemoveAllEmbeddedFiles(*model); if (4 == version) { // Read the number of embedded files. int num_embedded_files = 0; if (!archive.ReadInt(&num_embedded_files)) return false; // Create an ON_EmbeddedFile object for each embedded file. for (int i = 0; i < num_embedded_files; i++) { // We keep the embedded file object even if it fails to load; then it will have an error flag set. // See ON_EmbeddedFile::Error(). ON_EmbeddedFile ef; ef.Read(archive); model->AddModelComponent(ef); } } } } return xml.Length() > 0; } bool ONX_ModelPrivate::SetRDKDocumentInformation(const wchar_t* xml, ONX_Model_UserData& docud, int archive_3dm_version) const { ON_Write3dmBufferArchive archive(0, 0, docud.m_usertable_3dm_version, docud.m_usertable_opennurbs_version); // Write the version. int version = 4; // Where do I get this from? if (!archive.WriteInt(version)) return false; // Convert the XML to UTF8 and write it to the archive. unsigned int error_status = 0; const int utf8_length = ON_ConvertWideCharToUTF8(false, xml, -1, 0, 0, &error_status, 0, 0, 0); { // BEGIN UTF8 auto utf8 = std::unique_ptr(new char[utf8_length]); char* utf8_buf = utf8.get(); ON_ConvertWideCharToUTF8(false, xml, -1, utf8_buf, utf8_length, &error_status, 0, 0, 0); // Write the length of the UTF8 data. if (!archive.WriteInt(utf8_length)) return false; // Write the UTF8 data. if (!archive.WriteChar(size_t(utf8_length), utf8_buf)) return false; } // END UTF8 const auto length_so_far = ArchiveLengthUpToEmbeddedFiles(utf8_length); ON_ASSERT(archive.SizeOfArchive() == length_so_far); // Sanity check. // Write the number of embedded files. const auto num_embedded_files = int(m_model.ActiveComponentCount(ON_ModelComponent::Type::EmbeddedFile)); if (!archive.WriteInt(num_embedded_files)) return false; // Write the embedded files to the archive. ONX_ModelComponentIterator it(m_model, ON_ModelComponent::Type::EmbeddedFile); const ON_ModelComponent* pComponent = it.FirstComponent(); while (nullptr != pComponent) { const auto* embedded_file = ON_EmbeddedFile::Cast(pComponent); if (nullptr != embedded_file) { embedded_file->Write(archive); } pComponent = it.NextComponent(); } // Delete the old goo. if (nullptr != docud.m_goo.m_goo) onfree(docud.m_goo.m_goo); // Allocate the new goo and copy the archive to it. const auto length_goo = archive.SizeOfArchive(); docud.m_goo.m_goo = (unsigned char*)onmalloc(length_goo); docud.m_goo.m_value = int(length_goo); memcpy(docud.m_goo.m_goo, archive.Buffer(), length_goo); return true; } // Object user data static ON_UserData* RDKObjectUserDataHelper(const ON_UserData* objectud) { if (nullptr == objectud) return nullptr; if (0 == ON_UuidCompare(objectud->m_application_uuid, RdkPlugInId())) { if (0 != ON_UuidCompare(objectud->m_userdata_uuid, ON_RdkUserData::Uuid())) return nullptr; // Not RDK user data. return const_cast(objectud); // RDK user data -- used for decals. } return nullptr; } static bool IsMeshModifierObjectUserData(ON_UserData& objectud) { if (0 == ON_UuidCompare(objectud.m_application_uuid, ON_MeshModifier::PlugInId())) return true; // User data from Displacement plug-in. return false; } bool ONX_Model::IsRDKObjectInformation(const ON_UserData& objectud) // Static. { return nullptr != RDKObjectUserDataHelper(&objectud); } static bool CreateArchiveBufferFromXML(const ON_wString& xml, ON_Buffer& buf, int archive_3dm_version) { const auto archive_opennurbs_version_number = ON::Version(); // I don't know if this is correct. ON_Write3dmBufferArchive archive(0, 0, archive_3dm_version, archive_opennurbs_version_number); int version = 2; // Not sure if this is correct. if (!archive.WriteInt(version)) return false; const wchar_t* wsz = static_cast(xml); unsigned int error_status = 0; const int num_chars = ON_ConvertWideCharToUTF8(false, wsz, -1, nullptr, 0, &error_status, 0, 0, nullptr); auto p = std::unique_ptr(new char[size_t(num_chars) + 1]); char* pBuffer = p.get(); ON_ConvertWideCharToUTF8(false, wsz, -1, pBuffer, num_chars + 1, &error_status, 0, 0, nullptr); if (0 != error_status) { ON_ERROR("XML is not a valid UTF-8 string."); return false; } int len = num_chars * sizeof(char); if (!archive.WriteInt(len)) return false; if (!archive.WriteChar(len, pBuffer)) return false; buf.Write(archive.SizeOfArchive(), archive.Buffer()); buf.SeekFromStart(0); return true; } bool SetXMLToUserData(const ON_wString& xml, ON_UserData& ud, int archive_3dm_version) { ON_Buffer buf; if (!CreateArchiveBufferFromXML(xml, buf, archive_3dm_version)) return false; ON_BinaryArchiveBuffer arc(ON::archive_mode::read, &buf); ud.Read(arc); return true; } bool SetRDKObjectInformation(ON_Object& object, const ON_wString& xml, int archive_3dm_version) { // Create a buffer from the XML. ON_Buffer buf; if (!CreateArchiveBufferFromXML(xml, buf, archive_3dm_version)) return false; const auto archive_opennurbs_version = ON::Version(); // I don't know if this is correct. // Create an archive from the buffer. ON_BinaryArchiveBuffer archive(ON::archive_mode::read, &buf); archive.SetArchive3dmVersion(archive_3dm_version); ON_SetBinaryArchiveOpenNURBSVersion(archive, archive_opennurbs_version); // Try to find existing user data. ON_UserData* rdk_ud = nullptr; for (ON_UserData* ud = object.FirstUserData(); (nullptr != ud) && (nullptr == rdk_ud); ud = ud->Next()) { rdk_ud = RDKObjectUserDataHelper(ud); } if (nullptr != rdk_ud) { // Found it, so read the archive into it. rdk_ud->Read(archive); } else { // No user data found; create a new one and read the archive into it. auto* ud = new ON_RdkUserData; ud->Read(archive); if (!object.AttachUserData(ud)) { delete ud; return false; } } return true; } static bool GetRDKObjectInformation(const ON_Object& object, ON_wString& xml, int archive_3dm_version) { if (0 == archive_3dm_version) archive_3dm_version = ON_BinaryArchive::CurrentArchiveVersion(); xml.SetLength(0); const ON_UserData* rdk_ud = nullptr; const auto* ud = ON_UserData::Cast(&object); if (nullptr != ud) { rdk_ud = RDKObjectUserDataHelper(ud); } else { for (ud = object.FirstUserData(); (nullptr != ud) && (nullptr == rdk_ud); ud = ud->Next()) { rdk_ud = RDKObjectUserDataHelper(ud); } } if (nullptr == rdk_ud) return false; ON_Buffer buf; ON_BinaryArchiveBuffer arc(ON::archive_mode::write, &buf); rdk_ud->Write(arc); const auto sizeof_buffer = buf.Size(); auto p = std::unique_ptr(new ON__UINT8[size_t(sizeof_buffer)]); ON__UINT8* buffer = p.get(); buf.SeekFromStart(0); buf.Read(sizeof_buffer, buffer); const unsigned int archive_opennurbs_version_number = ON::Version(); ON_Read3dmBufferArchive archive(size_t(sizeof_buffer), buffer, false, archive_3dm_version, archive_opennurbs_version_number); int version = 0; if (!archive.ReadInt(&version)) return false; if (1 == version) { // Version 1 was a UTF-16 string. if (!archive.ReadString(xml)) return false; } else if (2 == version) { // Version 2 is a UTF-8 string. int len = 0; if (!archive.ReadInt(&len)) return false; if (len <= 0) return false; if (size_t(len + 4) > sizeof_buffer) // JohnC asks: What does the 4 signify? return false; ON_SimpleArray s; s.Reserve(size_t(len) + 1); s.SetCount(len + 1); s[len] = 0; char* sArray = s.Array(); if (nullptr == sArray) return false; if (!archive.ReadChar(len, sArray)) return false; if (0 == sArray[0]) return false; unsigned int error_status = 0; const int num_chars = ON_ConvertUTF8ToWideChar(false, sArray, -1, 0, 0, &error_status, 0, 0, 0); if ((num_chars > 0) && (0 == error_status)) { xml.SetLength(size_t(num_chars) + 2); ON_ConvertUTF8ToWideChar(false, sArray, -1, xml.Array(), num_chars + 1, &error_status, 0, 0, 0); xml.SetLength(num_chars); } else { xml.SetLength(0); ON_ERROR("RDK xml object information is not a valid UTF-8 string."); } } return xml.Length() > 0; } bool GetEntireDecalXML(const ON_3dmObjectAttributes& attr, ON_XMLRootNode& xmlOut) { // Get the entire XML off of the attributes user data. At the moment (V8) this can only contain decals. ON_wString xml; if (!GetRDKObjectInformation(attr, xml, 0)) return false; // No XML on attributes. // Read the XML into a root node. if (ON_XMLNode::ReadError == xmlOut.ReadFromStream(xml)) return false; // Failed to read XML. return true; } static bool GetMeshModifierUserDataXML(ON_UserData& ud, ON_wString& xml, int archive_3dm_version) { ON_Buffer buf; ON_BinaryArchiveBuffer arc(ON::archive_mode::write, &buf); ud.Write(arc); const ON__UINT64 sizeof_buffer = buf.Size(); auto p = std::unique_ptr(new ON__UINT8[size_t(sizeof_buffer)]); ON__UINT8* buffer = p.get(); buf.SeekFromStart(0); buf.Read(sizeof_buffer, buffer); const auto archive_opennurbs_version_number = ON::Version(); ON_Read3dmBufferArchive archive(size_t(sizeof_buffer), buffer, false, archive_3dm_version, archive_opennurbs_version_number); int version = 0; if (!archive.ReadInt(&version)) return false; if (1 == version) { // Version 1 was a UTF-16 string. if (!archive.ReadString(xml)) return false; } else if (2 == version) { // Version 2 is a UTF-8 string. int len = 0; if (!archive.ReadInt(&len)) return false; if (len <= 0) return false; if (size_t(len + 4) > sizeof_buffer) // JohnC asks: What does the 4 signify? return false; ON_SimpleArray s; s.Reserve(size_t(len) + 1); s.SetCount(len + 1); s[len] = 0; char* sArray = s.Array(); if (nullptr == sArray) return false; if (!archive.ReadChar(len, sArray)) return false; if (0 == sArray[0]) return false; unsigned int error_status = 0; const int num_chars = ON_ConvertUTF8ToWideChar(false, sArray, -1, 0, 0, &error_status, 0, 0, 0); if ((num_chars > 0) && (0 == error_status)) { xml.SetLength(size_t(num_chars) + 2); ON_ConvertUTF8ToWideChar(false, sArray, -1, xml.Array(), num_chars + 1, &error_status, 0, 0, 0); xml.SetLength(num_chars); } else { xml.SetLength(0); ON_ERROR("Mesh modifier xml object information is not a valid UTF-8 string."); } } return true; } bool ONX_Model::GetRDKObjectInformation(const ON_Object& object, ON_wString& xml) // Static. { // Deprecated; only for backward compatibility. return ::GetRDKObjectInformation(object, xml, ON_BinaryArchive::CurrentArchiveVersion()); } bool GetMeshModifierObjectInformation(const ON_Object& object, ON_wString& xml, int archive_3dm_version) { xml = L""; // The mesh modifiers are stored in separate user data items. We must get each one's // XML and combine it into a single XML node containing the entire information for all // mesh modifiers on this object. ON_XMLRootNode entire; ON_UserData* ud = object.FirstUserData(); while (nullptr != ud) { if (IsMeshModifierObjectUserData(*ud)) { ON_wString ud_xml; GetMeshModifierUserDataXML(*ud, ud_xml, archive_3dm_version); ON_XMLRootNode root; root.ReadFromStream(ud_xml); ON_XMLNode* mm_node = root.FirstChild(); if (nullptr != mm_node) { root.DetachChild(*mm_node); entire.AttachChildNode(mm_node); } } ud = ud->Next(); } if (entire.FirstChild() == nullptr) return false; // Return the entire XML for all mesh modifiers on this object. xml = entire.String(); return true; } static ON_UserData* GetMeshModifierUserData(ON_Object& object, const ON_UUID& uuid_mm) { ON_UserData* existing_ud = object.FirstUserData(); while (nullptr != existing_ud) { if (existing_ud->m_userdata_uuid == uuid_mm) return existing_ud; existing_ud = existing_ud->Next(); } // Not found so create it. ON_XMLUserData* new_ud = nullptr; if (uuid_mm == ON_DisplacementUserData::Uuid()) new_ud = new ON_DisplacementUserData; else if (uuid_mm == ON_EdgeSofteningUserData::Uuid()) new_ud = new ON_EdgeSofteningUserData; else if (uuid_mm == ON_ThickeningUserData::Uuid()) new_ud = new ON_ThickeningUserData; else if (uuid_mm == ON_CurvePipingUserData::Uuid()) new_ud = new ON_CurvePipingUserData; else if (uuid_mm == ON_ShutLiningUserData::Uuid()) new_ud = new ON_ShutLiningUserData; else ON_ASSERT(false); if (nullptr != new_ud) { new_ud->SetToDefaults(); // This doesn't work because the XML gets overwritten by the cached XML. // In fact, having cached XML in this and decals is the reason why the systems are wrong. // NO! LIGHT BULB MOMENT says that's not why it's wrong. Something else is wrong here. // I'm about to fix the decals to not have cached XML and directly use the user data. NO! After // that I'll do the same for mesh modifiers. NO! Then, this should work because there is no longer // an XML cache to overwrite the new defaults in the user data's XML. NO! if (!object.AttachUserData(new_ud)) { delete new_ud; new_ud = nullptr; } } return new_ud; } void SetMeshModifierObjectInformation(ON_Object& object, const ON_MeshModifier* mm, int archive_3dm_version) { if (nullptr == mm) return; // Can't create user data for non-existent mesh modifiers. ON_XMLRootNode root; mm->AddChildXML(root); ON_UserData* ud = GetMeshModifierUserData(object, mm->Uuid()); if (nullptr != ud) { SetXMLToUserData(root.String(), *ud, archive_3dm_version); } }