#include "pch.h" #include "CreoManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _USE_MATH_DEFINES #include #include #include #include // Define PI constant for compatibility #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #include #include // Shell Analysis Algorithm Constants const int SHELL_ANALYSIS_NUM_DIRECTIONS = 96; const double SHELL_ANALYSIS_MIN_VISIBILITY_RATIO = 0.05; const int SHELL_ANALYSIS_MIN_VOTES = 2; const double SHELL_ANALYSIS_HIGH_VISIBILITY_THRESHOLD = 0.25; const double SHELL_ANALYSIS_MEDIUM_VISIBILITY_THRESHOLD = 0.08; #include #include #include #include #include #include #include #include #include #include // 防止Windows宏冲突 #ifdef max #undef max #endif #ifdef min #undef min #endif // 构造函数:简化实现 CreoManager::CreoManager() { // 配置设置可能需要通过config.pro文件或其他方式 // 暂时移除代码中的配置设置 } CreoManager& CreoManager::Instance() { static CreoManager instance; return instance; } CreoStatus CreoManager::GetCreoStatus() { CreoStatus status; SessionInfo sessionInfo = GetSessionInfo(); status.is_connected = sessionInfo.is_valid; status.version = sessionInfo.version; status.build = sessionInfo.build; if (sessionInfo.is_valid) { // 获取工作目录 try { xstring workdir = sessionInfo.session->GetCurrentDirectory(); status.working_directory = XStringToString(workdir); } catch (...) { status.working_directory = "Failed to get working directory"; } status.session_id = 1; } else { status.working_directory = "Failed to connect to Creo"; status.session_id = 0; } return status; } ModelStatus CreoManager::GetModelStatus() { ModelStatus status; SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { return status; } try { pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel(); if (current_model) { status.has_model = true; // 获取模型名称和文件名 try { xstring name_xstr = current_model->GetFileName(); status.name = XStringToString(name_xstr); status.filename = status.name; } catch (...) { status.name = "Failed to get model name"; status.filename = "Failed to get filename"; } // 获取模型类型 try { pfcModelType model_type = current_model->GetType(); switch (model_type) { case pfcMDL_PART: status.type = "Part"; status.is_assembly = false; break; case pfcMDL_ASSEMBLY: status.type = "Assembly"; status.is_assembly = true; break; case pfcMDL_DRAWING: status.type = "Drawing"; status.is_assembly = false; break; default: status.type = ""; status.is_assembly = false; break; } } catch (...) { status.type = "Failed to get model type"; status.is_assembly = false; } // 获取模型文件大小(装配体统计所有零件和子装配体的总大小) try { if (status.is_assembly) { // 装配体:统计所有组件的文件大小 status.file_size = CalculateAssemblyTotalSize(current_model); } else { // 单个零件:直接获取文件大小 status.file_size = GetModelFileSize(current_model); } } catch (...) { status.file_size = "Exception getting file size"; } // 检查模型是否已修改 try { status.is_modified = current_model->GetIsModified(); } catch (...) { status.is_modified = false; // 默认值:未修改 } // 获取真实的零件数量和装配体层级 if (status.is_assembly) { try { wfcWAssembly_ptr wAssembly = wfcWAssembly::cast(current_model); if (wAssembly) { // 获取所有显示的组件 wfcWComponentPaths_ptr components = wAssembly->ListDisplayedComponents(); if (components) { status.total_parts = components->getarraysize(); } else { status.total_parts = 0; } status.assembly_levels = SafeCalculateAssemblyLevels(wAssembly); } else { status.total_parts = 0; status.assembly_levels = 0; } } catch (...) { status.total_parts = 0; status.assembly_levels = 0; } } else { // 非装配体(零件)的真实数据 status.total_parts = 1; status.assembly_levels = 1; } // 解析软件信息 std::string full_version = sessionInfo.version; size_t space_pos = full_version.find(" "); if (space_pos != std::string::npos) { status.software = full_version.substr(0, space_pos); status.version = full_version.substr(space_pos + 1); } else { status.software = "Failed to parse software name"; status.version = "Failed to parse version"; } // 设置其他信息 status.connection_time = GetCurrentTimeString(); status.open_time = status.connection_time; status.connection_status = "Connected"; } } catch (...) { status.has_model = false; status.name = "Failed to access model"; status.filename = "Failed to access model"; status.type = "Failed to access model"; status.is_assembly = false; status.total_parts = 0; status.assembly_levels = 0; status.software = "Failed to access model"; status.version = "Failed to access model"; status.connection_time = "Failed to get time"; status.open_time = "Failed to get time"; status.connection_status = "Failed to get status"; status.file_size = "Failed to access model"; } return status; } bool CreoManager::ShowMessage(const std::string& message) { SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { return false; } try { xstring msg_xstr = StringToXString(message); sessionInfo.wSession->UIShowMessageDialog(msg_xstr, NULL); return true; } catch (...) { return false; } } std::string CreoManager::XStringToString(const xstring& xstr) { try { // 安全检查:空xstring处理 if (xstr.IsNull()) { return ""; } std::wstring wstr(xstr); if (wstr.empty()) { return ""; } // 长度限制检查,防止过长字符串导致内存问题 if (wstr.length() > 32767) { // Windows API限制 return ""; } // 使用WideCharToMultiByte进行正确的UTF-8编码转换 int wstr_len = static_cast(wstr.length()); int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr_len, NULL, 0, NULL, NULL); if (size_needed <= 0 || size_needed > 65535) { // 安全边界检查 return ""; } std::string result(size_needed, 0); int convert_result = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr_len, &result[0], size_needed, NULL, NULL); if (convert_result != size_needed) { return ""; // 转换失败 } return result; } catch (const std::bad_alloc&) { return ""; // 内存分配失败 } catch (const std::length_error&) { return ""; // 字符串长度错误 } catch (...) { return ""; } } std::string CreoManager::BuildComponentFullPath(wfcWComponentPath_ptr componentPath, const std::string& assemblyName) { if (!componentPath) return ""; std::vector pathComponents; // 添加根装配体名称 if (!assemblyName.empty()) { pathComponents.push_back(assemblyName); } // 获取路径中的组件ID序列 xintsequence_ptr componentIds = componentPath->GetComponentIds(); pfcAssembly_ptr rootAsm = componentPath->GetRoot(); // 遍历每个组件ID,获取对应的名称 pfcAssembly_ptr currentAsm = rootAsm; for (int i = 0; i < componentIds->getarraysize(); i++) { int compId = componentIds->get(i); // 从当前装配体获取组件特征 pfcFeature_ptr feat = currentAsm->GetFeatureById(compId); pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feat); // 获取组件名称 pfcModelDescriptor_ptr modelDesc = compFeat->GetModelDescr(); xstring nameXStr = modelDesc->GetFileName(); std::string compName = XStringToString(nameXStr); pathComponents.push_back(compName); // 如果是子装配体,进入下一层 if (i < componentIds->getarraysize() - 1) { SessionInfo sessionInfo = GetSessionInfo(); pfcModel_ptr nextModel = sessionInfo.session->GetModelFromDescr(modelDesc); if (nextModel->GetType() == pfcMDL_ASSEMBLY) { currentAsm = pfcAssembly::cast(nextModel); } } } // 连接路径 std::string fullPath; for (size_t i = 0; i < pathComponents.size(); i++) { if (i > 0) fullPath += "/"; fullPath += pathComponents[i]; } return fullPath; } xstring CreoManager::StringToXString(const std::string& str) { try { if (str.empty()) { return xstring(); } // 长度限制检查,防止过长字符串导致内存问题 if (str.length() > 65535) { // 合理的长度限制 return xstring(); } // 验证输入字符串是否包含无效字符 for (char c : str) { if (c == '\0' && &c != &str.back()) { // 中间包含null字符 return xstring(); } } // 使用MultiByteToWideChar进行正确的UTF-8解码转换 int str_len = static_cast(str.length()); int size_needed = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.c_str(), str_len, NULL, 0); if (size_needed <= 0 || size_needed > 32767) { // 安全边界检查 return xstring(); } std::wstring wstr(size_needed, 0); int convert_result = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.c_str(), str_len, &wstr[0], size_needed); if (convert_result != size_needed) { return xstring(); // 转换失败 } return xstring(wstr.c_str()); } catch (const std::bad_alloc&) { return xstring(); // 内存分配失败 } catch (const std::length_error&) { return xstring(); // 字符串长度错误 } catch (...) { return xstring(); } } // 路径分离和验证函数 std::pair CreoManager::ParseFilePath(const std::string& file_path) { std::string dirname, filename; if (file_path.empty()) { return std::make_pair("", ""); } // 支持Windows和Unix路径分隔符 size_t pos = file_path.find_last_of("/\\"); if (pos != std::string::npos) { dirname = file_path.substr(0, pos); filename = file_path.substr(pos + 1); } else { dirname = ""; filename = file_path; } // 基本验证:文件名不能为空 if (filename.empty()) { return std::make_pair("", ""); } // 验证文件名不包含非法字符 const std::string invalid_chars = "<>:\"|?*"; if (filename.find_first_of(invalid_chars) != std::string::npos) { return std::make_pair("", ""); } return std::make_pair(dirname, filename); } std::string CreoManager::GetCurrentTimeString() { std::time_t now = std::time(nullptr); std::tm* local_tm = std::localtime(&now); std::ostringstream oss; oss << std::put_time(local_tm, "%Y-%m-%d %H:%M:%S"); return oss.str(); } std::string CreoManager::GetCurrentTimeStringISO() { std::time_t now = std::time(nullptr); std::tm* utc_tm = std::gmtime(&now); std::ostringstream oss; oss << std::put_time(utc_tm, "%Y-%m-%dT%H:%M:%S"); oss << ".000000Z"; // 添加微秒和UTC标识 return oss.str(); } CreoManager::SessionInfo CreoManager::GetSessionInfo() { SessionInfo info; info.is_valid = false; try { info.session = pfcGetCurrentSessionWithCompatibility(pfcC4Compatible); if (info.session) { info.wSession = wfcWSession::cast(info.session); if (info.wSession) { // 获取版本信息 int version_num = info.wSession->GetReleaseNumericVersion(); xstring date_code = info.wSession->GetDisplayDateCode(); // 尝试获取真实的软件名称 std::string software_name = "Creo"; // 基础名称,如果无法获取更详细的名称 std::ostringstream version_str; version_str << software_name << " " << version_num << ".0"; info.version = version_str.str(); info.build = XStringToString(date_code); info.is_valid = true; } else { info.version = "Failed to get version"; info.build = "Failed to get build"; } } else { info.version = "Failed to connect to Creo"; info.build = "Failed to connect to Creo"; } } catch (...) { info.version = "Failed to get version"; info.build = "Failed to get build"; } return info; } std::string CreoManager::GetFileSize(const std::string& filepath) { try { if (filepath.empty()) { return "Empty filepath"; } // 复用现有的字符串转换逻辑 xstring xpath = StringToXString(filepath); std::wstring wpath(xpath); WIN32_FILE_ATTRIBUTE_DATA fileInfo; if (GetFileAttributesExW(wpath.c_str(), GetFileExInfoStandard, &fileInfo)) { LARGE_INTEGER size; size.HighPart = fileInfo.nFileSizeHigh; size.LowPart = fileInfo.nFileSizeLow; double file_size_mb = static_cast(size.QuadPart) / (1024.0 * 1024.0); std::ostringstream oss; oss << std::fixed << std::setprecision(1) << file_size_mb << "MB"; return oss.str(); } else { DWORD error = GetLastError(); std::ostringstream oss; oss << "File access failed (Error: " << error << ") Path: " << filepath; return oss.str(); } } catch (...) { return "Exception in GetFileSize"; } } int CreoManager::SafeCalculateAssemblyLevels(wfcWAssembly_ptr assembly) { try { if (!assembly) { return 1; } // 使用ComponentPath分析装配体层级深度 wfcWComponentPaths_ptr components = assembly->ListDisplayedComponents(); if (!components) { return 1; } int component_count = components->getarraysize(); if (component_count == 0) { return 1; } int max_level = 1; // 移除组件数量限制,检查所有组件 for (int i = 0; i < component_count; i++) { try { wfcWComponentPath_ptr comp_path = components->get(i); if (comp_path) { // 使用GetComponentIds获取组件路径 xintsequence_ptr ids = comp_path->GetComponentIds(); if (ids) { // 路径深度就是装配体层级 int path_depth = ids->getarraysize(); if (path_depth > max_level) { max_level = path_depth; } } } } catch (...) { // 跳过有问题的组件 continue; } } // Add 1 to include root assembly level, consistent with hierarchy analysis return max_level + 1; } catch (...) { return 1; } } std::string CreoManager::GetModelFileSize(pfcModel_ptr model) { try { if (!model) { return "0.0MB"; } // 先检查模型是否可以安全调用GetDescr try { pfcModelDescriptor_ptr descr = model->GetDescr(); if (!descr) { return "0.0MB"; } } catch (...) { // 如果GetDescr失败,可能是轻量级模型,尝试其他方法 try { xstring origin = model->GetOrigin(); std::string origin_str = XStringToString(origin); if (!origin_str.empty()) { return GetFileSize(origin_str); } } catch (...) { // 所有方法都失败,返回默认值 return "0.0MB"; } return "0.0MB"; } // 使用origin路径获取文件大小 try { xstring origin = model->GetOrigin(); std::string origin_str = XStringToString(origin); if (!origin_str.empty()) { return GetFileSize(origin_str); } } catch (...) { return "0.0MB"; } return "0.0MB"; } catch (...) { return "0.0MB"; } } double CreoManager::ParseMBFromSizeString(const std::string& size_str) { try { if (size_str.find("MB") != std::string::npos) { size_t mb_pos = size_str.find("MB"); std::string size_num = size_str.substr(0, mb_pos); return std::stod(size_num); } return 0.0; } catch (...) { return 0.0; } } std::string CreoManager::CalculateAssemblyTotalSize(pfcModel_ptr model) { try { wfcWAssembly_ptr assembly = wfcWAssembly::cast(model); if (!assembly) { return "Not an assembly"; } double total_size_bytes = 0; int processed_count = 0; // 首先添加主装配体文件大小 std::string main_size = GetModelFileSize(model); double main_mb = ParseMBFromSizeString(main_size); if (main_mb > 0) { total_size_bytes += main_mb * 1024 * 1024; processed_count++; } // 使用ListDisplayedComponents获取组件(保持原有逻辑) wfcWComponentPaths_ptr components = assembly->ListDisplayedComponents(); if (components) { int component_count = components->getarraysize(); for (int i = 0; i < component_count; i++) { try { wfcWComponentPath_ptr comp_path = components->get(i); if (comp_path) { pfcSolid_ptr leaf_solid = comp_path->GetLeaf(); if (leaf_solid) { pfcModel_ptr comp_model = pfcModel::cast(leaf_solid); if (comp_model) { std::string comp_size = GetModelFileSize(comp_model); double comp_mb = ParseMBFromSizeString(comp_size); // Always count valid components, regardless of file size processed_count++; if (comp_mb > 0) { total_size_bytes += comp_mb * 1024 * 1024; } } } } } catch (...) { continue; } } } // 转换为MB并返回 double total_mb = total_size_bytes / (1024.0 * 1024.0); std::ostringstream oss; oss << std::fixed << std::setprecision(1) << total_mb << "MB (from " << processed_count << " files)"; return oss.str(); } catch (...) { return "Exception in CalculateAssemblyTotalSize"; } } ExportResult CreoManager::ExportModelToSTEP(const std::string& export_path, const std::string& geom_flags) { ExportResult result; SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { result.error_message = "Creo session not available"; return result; } try { pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel(); if (!current_model) { result.error_message = "No current model loaded"; return result; } // 检查导出路径是否有效 if (export_path.empty()) { result.error_message = "Invalid export path"; return result; } // 检查模型类型是否支持导出 pfcModelType model_type = current_model->GetType(); if (model_type != pfcMDL_PART && model_type != pfcMDL_ASSEMBLY) { result.error_message = "Model type not supported for export"; return result; } // 创建几何导出标志 pfcGeomExportFlags_ptr geometryFlags = pfcGeomExportFlags::Create(); // 创建STEP导出指令 pfcSTEPExportInstructions_ptr exportInstructions = pfcSTEPExportInstructions::Create(geometryFlags); // 执行导出 - 使用xrstring类型 xrstring export_path_xrstr = export_path.c_str(); current_model->Export(export_path_xrstr, pfcExportInstructions::cast(exportInstructions)); // 检查导出文件是否存在(使用Windows API) WIN32_FILE_ATTRIBUTE_DATA fileInfo; if (GetFileAttributesExA(export_path.c_str(), GetFileExInfoStandard, &fileInfo)) { result.success = true; result.export_path = export_path; result.file_size = GetFileSize(export_path); result.format = "step"; result.export_time = GetCurrentTimeStringISO(); result.software = "Creo Parametric"; // 获取原始文件信息 try { xstring name_xstr = current_model->GetFileName(); result.original_file = XStringToString(name_xstr); } catch (...) { result.original_file = "Unknown"; } // 解析目录和文件名 size_t last_slash = export_path.find_last_of("\\/"); if (last_slash != std::string::npos) { result.dirname = export_path.substr(0, last_slash); result.filename = export_path.substr(last_slash + 1); } else { result.dirname = ""; result.filename = export_path; } } else { result.error_message = "Export file not created"; } } catch (...) { result.error_message = "Export operation failed"; } return result; } // 保存模型功能实现 SaveResult CreoManager::SaveModel() { SaveResult result; SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { result.error_message = "Creo session not available"; return result; } try { pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel(); if (!current_model) { result.error_message = "No current model loaded"; return result; } // 获取文件信息 xstring original_name = current_model->GetFileName(); result.original_file = XStringToString(original_name); result.software = "Creo Parametric"; // 执行保存操作 current_model->Save(); // 设置成功结果 result.save_time = GetCurrentTimeStringISO(); result.file_size = GetModelFileSize(current_model); result.success = true; } catch (const pfcXToolkitBadInputs&) { result.error_message = "Bad input parameters"; } catch (const pfcXToolkitGeneralError&) { result.error_message = "Creo toolkit error"; } catch (const pfcXToolkitInvalidName&) { result.error_message = "Invalid file name"; } catch (const pfcXToolkitCantWrite&) { result.error_message = "Cannot write to file"; } catch (const pfcXToolkitCantOpen&) { result.error_message = "Cannot open file"; } catch (const std::exception& e) { result.error_message = "Standard error: " + std::string(e.what()); } catch (...) { result.error_message = "Unknown error during save operation"; } return result; } // 关闭模型功能实现 CloseResult CreoManager::CloseModel(bool force_close) { CloseResult result; SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { result.error_message = "Creo session not available"; return result; } try { pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel(); if (!current_model) { result.error_message = "No current model loaded"; return result; } // 获取模型信息 xstring model_name_xstr = current_model->GetFileName(); result.model_name = XStringToString(model_name_xstr); // 检查模型是否已修改 result.was_modified = current_model->GetIsModified(); // 如果模型已修改且不是强制关闭,则需要处理 if (result.was_modified && !force_close) { result.error_message = "Model has unsaved changes. Use force_close=true to close without saving."; return result; } // 执行关闭操作 current_model->Erase(); // 设置成功结果 result.close_time = GetCurrentTimeStringISO(); result.success = true; } catch (const pfcXToolkitBadInputs&) { result.error_message = "Bad input parameters"; } catch (const pfcXToolkitGeneralError&) { result.error_message = "Creo toolkit error"; } catch (const pfcXToolkitInvalidName&) { result.error_message = "Invalid model name"; } catch (const std::exception& e) { result.error_message = "Standard error: " + std::string(e.what()); } catch (...) { result.error_message = "Unknown error during close operation"; } return result; } // 打开模型功能 OpenResult CreoManager::OpenModel(const std::string& file_path, const std::string& open_mode) { OpenResult result; // 分离目录和文件名(内联实现,避免成员函数调用问题) std::string dirname, filename; size_t pos = file_path.find_last_of("/\\"); if (pos != std::string::npos) { dirname = file_path.substr(0, pos); filename = file_path.substr(pos + 1); } else { dirname = ""; filename = file_path; } SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { result.error_message = "Creo session not available"; return result; } try { // 验证路径解析结果 if (filename.empty()) { result.error_message = "Invalid file path: " + file_path; return result; } // 创建模型描述符用于检查(符合CREOSON标准) xstring filename_xstr = StringToXString(filename); pfcModelDescriptor_ptr checkDesc = pfcModelDescriptor::CreateFromFileName(filename_xstr); // 检查模型是否已在会话中打开(使用描述符,符合CREOSON标准模式) // 对应CREOSON中的: session.getModelFromDescr(descr) pfcModel_ptr opened_model = nullptr; try { opened_model = sessionInfo.session->GetModelFromDescr(checkDesc); } catch (...) { // 模型未在内存中,稍后需要从磁盘加载 opened_model = nullptr; } if (!dirname.empty()) { xstring workdir = StringToXString(dirname); sessionInfo.session->ChangeDirectory(workdir); } // 重用已创建的模型描述符(避免重复创建) // 使用RetrieveModel打开模型(对应CREOSON的session.retrieveModel(descr)) opened_model = sessionInfo.session->RetrieveModel(checkDesc); // 检查模型是否成功打开 if (!opened_model) { result.error_message = "Failed to open model '" + filename + "' from directory '" + dirname + "'"; return result; } // 设置返回结果 result.success = true; result.model_name = XStringToString(opened_model->GetFileName()); result.file_path = file_path; result.open_time = GetCurrentTimeStringISO(); // 确定模型类型 try { pfcModelType model_type = opened_model->GetType(); switch (model_type) { case pfcMDL_ASSEMBLY: result.model_type = "assembly"; result.is_assembly = true; try { pfcSolid_ptr solid = pfcSolid::cast(opened_model); if (solid) { pfcFeatures_ptr features = solid->ListFeaturesByType(false, pfcFEATTYPE_COMPONENT); result.total_parts = features ? features->getarraysize() : 0; } } catch (...) { result.total_parts = 0; } break; case pfcMDL_PART: result.model_type = "part"; result.is_assembly = false; result.total_parts = 0; break; case pfcMDL_DRAWING: result.model_type = "drawing"; result.is_assembly = false; result.total_parts = 0; break; default: result.model_type = "unknown"; result.is_assembly = false; result.total_parts = 0; break; } } catch (...) { result.model_type = "unknown"; result.is_assembly = false; result.total_parts = 0; } result.file_size = GetModelFileSize(opened_model); result.model_in_session = true; result.window_model_match = true; } catch (const xthrowable& e) { result.error_message = "OTK error opening model '" + filename + "': Creo API exception occurred"; } catch (const std::exception& e) { result.error_message = "Standard exception opening model '" + filename + "': " + std::string(e.what()); } catch (...) { result.error_message = "Unknown error opening model '" + filename + "' from directory '" + dirname + "'"; } return result; } // 层级分析主方法 HierarchyAnalysisResult CreoManager::AnalyzeModelHierarchy(const HierarchyAnalysisRequest& request) { HierarchyAnalysisResult result; SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { result.error_message = "Creo session not available"; return result; } try { pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel(); if (!current_model) { result.error_message = "No current model loaded"; return result; } // 检查是否为装配体 if (current_model->GetType() != pfcMDL_ASSEMBLY) { result.error_message = "Current model is not an assembly"; return result; } // 转换为装配体 wfcWAssembly_ptr assembly = wfcWAssembly::cast(current_model); if (!assembly) { result.error_message = "Failed to cast model to assembly"; return result; } // 初始化结果(SOTA算法) result.project_name = request.project_name.empty() ? XStringToString(current_model->GetFileName()) : request.project_name; result.total_levels = 0; result.total_components = 0; result.hierarchy.clear(); // 创建根装配体组件信息 ComponentInfo root_component; // 获取根装配体文件名 try { xstring filename_xstr = current_model->GetFileName(); root_component.id = XStringToString(filename_xstr); } catch (...) { try { xstring origin = current_model->GetOrigin(); std::string origin_str = XStringToString(origin); size_t pos = origin_str.find_last_of("/\\"); if (pos != std::string::npos) { root_component.id = origin_str.substr(pos + 1); } else { root_component.id = origin_str; } } catch (...) { root_component.id = "root_assembly.asm"; } } // 设置根装配体属性 root_component.name = root_component.id; root_component.type = "assembly"; root_component.level = 0; root_component.path = root_component.id; root_component.full_path = root_component.id; root_component.file_size = GetModelFileSize(current_model); root_component.deletion_safety = "forbidden"; root_component.is_visible = true; root_component.model_type = "MDL_ASSEMBLY"; root_component.children_count = 0; // 将在递归后计算 // 初始化层级0并添加根装配体(只在target_level为-1或0时添加) if (request.target_level == -1 || request.target_level == 0) { result.hierarchy.push_back(std::vector()); result.hierarchy[0].push_back(root_component); } // 使用新的SOTA递归算法分析子组件(从层级1开始) AnalyzeAssemblyNode(assembly, 1, root_component.name, root_component.path, result, request.target_level); // 计算根装配体的children_count(只在根装配体被包含时) if ((request.target_level == -1 || request.target_level == 0) && result.hierarchy.size() > 0 && result.hierarchy[0].size() > 0) { // 安全地计算第一层的实际组件数 try { pfcFeatures_ptr features = assembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT); if (features) { result.hierarchy[0][0].children_count = features->getarraysize(); } } catch (...) { result.hierarchy[0][0].children_count = 0; } } // 计算最终统计 // total_levels已经在递归过程中正确计算,保持装配体的实际总层级数 // 不需要基于hierarchy.size()重新计算,因为当指定target_level时hierarchy可能只包含部分层级 // 计算总组件数(从所有层级统计) result.total_components = 0; for (const auto& level : result.hierarchy) { result.total_components += level.size(); } // 设置成功状态 result.success = true; result.message = "Hierarchy analysis completed"; return result; } catch (const std::exception& e) { result.error_message = "Exception during hierarchy analysis: " + std::string(e.what()); } catch (...) { result.error_message = "Unknown exception during hierarchy analysis"; } return result; } // 评估删除安全性 std::string CreoManager::EvaluateDeletionSafety(const ComponentInfo& component) { if (component.level == 0) { return "forbidden"; // 主装配体不能删除 } if (component.type == "assembly") { return "risky"; // 子装配体删除有风险 } return "moderate"; // 零件删除相对安全 } // 获取组件文件大小 (使用更安全的方法) std::string CreoManager::GetComponentFileSize(wfcWComponentPath_ptr component_path) { try { pfcSolid_ptr leaf_model = component_path->GetLeaf(); if (leaf_model) { // 使用更安全的转换方法 pfcModel_ptr model = pfcModel::cast(leaf_model); if (model) { return GetModelFileSize(model); } } } catch (...) { // 捕获所有异常,返回默认值 } return "0.0MB"; } // 获取模型类型字符串 std::string CreoManager::GetModelTypeString(pfcModelType model_type) { switch (model_type) { case pfcMDL_ASSEMBLY: return "MDL_ASSEMBLY"; case pfcMDL_PART: return "MDL_PART"; case pfcMDL_DRAWING: return "MDL_DRAWING"; default: return "MDL_UNKNOWN"; } } // 计算子组件数量 int CreoManager::CountChildComponents(wfcWComponentPath_ptr component_path) { try { pfcSolid_ptr leaf_model = component_path->GetLeaf(); if (leaf_model && leaf_model->GetType() == pfcMDL_ASSEMBLY) { wfcWAssembly_ptr sub_assembly = wfcWAssembly::cast(leaf_model); if (sub_assembly) { wfcWComponentPaths_ptr sub_components = sub_assembly->ListDisplayedComponents(); if (sub_components) { return sub_components->getarraysize(); } } } } catch (...) { // 忽略错误 } return 0; } // =============== SOTA层级分析算法 =============== void CreoManager::AnalyzeAssemblyNode(wfcWAssembly_ptr assembly, int level, const std::string& parentName, const std::string& currentPath, HierarchyAnalysisResult& result, int target_level) { if (!assembly) return; try { // 更新最大层级深度(始终统计) if (level + 1 > result.total_levels) { result.total_levels = level + 1; } // 确保层级容器足够大(只在需要时创建) if (target_level == -1 || level == target_level) { // 确保hierarchy有足够的空间到指定层级 // 即使前面的层级是空的,也要保证索引正确 while (result.hierarchy.size() <= level) { result.hierarchy.push_back(std::vector()); } } // 使用ListFeaturesByType获取所有组件特征(包括隐藏的) pfcFeatures_ptr features = assembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT); if (!features) return; int features_count = features->getarraysize(); if (features_count <= 0) return; // 遍历所有组件特征 for (int i = 0; i < features_count; i++) { try { pfcFeature_ptr feature = features->get(i); if (!feature) continue; // 转换为组件特征 pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature); if (!compFeat) continue; // 加载模型一次(避免重复调用) pfcModel_ptr childModel = LoadComponentModel(compFeat); // 创建组件信息,传递已加载的模型 ComponentInfo component = CreateComponentFromFeature(compFeat, level, parentName, currentPath, childModel); // 只在指定层级或返回所有层级时添加到结果 if (target_level == -1 || level == target_level) { result.hierarchy[level].push_back(component); } // 递归处理子装配体 if (component.type == "assembly" && childModel) { try { if (childModel->GetType() == pfcMDL_ASSEMBLY) { wfcWAssembly_ptr childAssembly = wfcWAssembly::cast(childModel); if (childAssembly) { // 始终递归处理子装配体(需要统计total_components等) AnalyzeAssemblyNode(childAssembly, level + 1, component.name, component.path, result, target_level); } } } catch (...) { // 处理无法加载的子装配体 } } } catch (...) { continue; // 忽略单个组件错误 } } // children_count在CreateComponentFromFeature中已经设置 } catch (...) { // 处理整体错误 } } ComponentInfo CreoManager::CreateComponentFromFeature(pfcComponentFeat_ptr compFeat, int level, const std::string& parentName, const std::string& currentPath, pfcModel_ptr preloadedModel) { ComponentInfo component; component.level = level; component.children_count = 0; component.is_visible = true; component.file_size = "0.0MB"; component.deletion_safety = "moderate"; try { // 获取组件模型描述符 auto modelDescr = compFeat->GetModelDescr(); if (modelDescr) { // 获取文件名作为ID xstring filename_xstr = modelDescr->GetFileName(); component.id = XStringToString(filename_xstr); // 生成显示名称(去掉扩展名并格式化) component.name = component.id; size_t ext_pos = component.name.find_last_of("."); if (ext_pos != std::string::npos) { component.name = component.name.substr(0, ext_pos); } // 格式化显示名称 if (!component.name.empty()) { component.name[0] = std::toupper(component.name[0]); for (size_t i = 1; i < component.name.size(); i++) { if (component.name[i-1] == '_' || component.name[i-1] == ' ') { component.name[i] = std::toupper(component.name[i]); } else { component.name[i] = std::tolower(component.name[i]); } } std::replace(component.name.begin(), component.name.end(), '_', ' '); } // 设置组件类型 if (modelDescr->GetType() == pfcMDL_ASSEMBLY) { component.type = "assembly"; component.model_type = "MDL_ASSEMBLY"; } else { component.type = "part"; component.model_type = "MDL_PART"; } // 构建完整路径 if (currentPath.empty()) { component.path = component.id; } else { component.path = currentPath + "/" + component.id; } component.full_path = component.path; // 使用预加载的模型获取文件大小 try { if (preloadedModel) { component.file_size = GetModelFileSize(preloadedModel); // 对于装配体,直接计算子组件数量(避免重复API调用) if (component.type == "assembly" && preloadedModel->GetType() == pfcMDL_ASSEMBLY) { wfcWAssembly_ptr assembly = wfcWAssembly::cast(preloadedModel); if (assembly) { try { pfcFeatures_ptr childFeatures = assembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT); if (childFeatures) { component.children_count = childFeatures->getarraysize(); } } catch (...) { component.children_count = 0; } } } } else { component.file_size = "0.0MB"; } } catch (...) { component.file_size = "0.0MB"; } } } catch (...) { // 使用默认值 component.id = "unknown_component_" + std::to_string(level); component.name = "Unknown Component"; component.type = "part"; component.path = currentPath + "/" + component.id; component.full_path = component.path; } return component; } pfcModel_ptr CreoManager::LoadComponentModel(pfcComponentFeat_ptr compFeat) { try { auto modelDescr = compFeat->GetModelDescr(); if (!modelDescr) return nullptr; SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) return nullptr; // 尝试从会话中获取已加载的模型 try { return sessionInfo.session->GetModelFromDescr(modelDescr); } catch (pfcXToolkitError&) { // 模型未加载,返回nullptr return nullptr; } } catch (...) { return nullptr; } } // 层级删除功能实现 CreoManager::HierarchyDeleteResult CreoManager::DeleteHierarchyComponents(const std::string& project_name, int target_level) { HierarchyDeleteResult result; result.target_level = target_level; SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { result.error_message = "Creo session not available"; return result; } try { pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel(); if (!current_model) { result.error_message = "No current model loaded"; return result; } // 检查是否为装配体 if (current_model->GetType() != pfcMDL_ASSEMBLY) { result.error_message = "Current model is not an assembly"; return result; } // 转换为装配体 wfcWAssembly_ptr assembly = wfcWAssembly::cast(current_model); if (!assembly) { result.error_message = "Failed to cast model to assembly"; return result; } // target_level=2表示保留2层,删除第3层的组件 // 层级映射:target_level=2 -> 删除level_3 -> currentLevel=2 int deleteLevel = target_level - 1; // 收集删除统计信息 std::map> componentsToDeleteByLevel; int total_deleted = 0; int successful_count = 0; int failed_count = 0; // 递归遍历到指定层级直接删除 std::function deleteAtLevel = [&](wfcWAssembly_ptr currentAssembly, int currentLevel) { if (!currentAssembly || currentLevel > deleteLevel) return; try { pfcFeatures_ptr features = currentAssembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT); // 正常执行,无需调试输出 if (features) { if (currentLevel == deleteLevel) { // 在目标层级:获取所有组件并删除 xintsequence_ptr featIds = xintsequence::create(); std::vector levelComponents; for (int i = 0; i < features->getarraysize(); i++) { pfcFeature_ptr feature = features->get(i); pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature); if (compFeat) { // 记录组件信息用于响应 try { auto modelDescr = compFeat->GetModelDescr(); if (modelDescr) { xstring filename_xstr = modelDescr->GetFileName(); std::string filename = XStringToString(filename_xstr); levelComponents.push_back(filename); total_deleted++; } } catch (...) { // 忽略获取文件名失败的组件 } // 添加到删除列表 int featId = feature->GetId(); featIds->append(featId); } } // 执行删除 if (featIds->getarraysize() > 0) { try { wfcWSolid_ptr wsolid = wfcWSolid::cast(currentAssembly); if (wsolid) { // 使用SuppressFeatures方法,更安全地"删除"组件 wfcFeatSuppressOrDeleteOptions_ptr options = wfcFeatSuppressOrDeleteOptions::create(); options->append(wfcFEAT_SUPP_OR_DEL_NO_OPTS); // 创建重生成指令,允许失败 wfcWRegenInstructions_ptr regenInstr = wfcWRegenInstructions::Create(); // 执行抑制操作(更安全,不会破坏引用关系) wsolid->SuppressFeatures(featIds, options, regenInstr); // 手动重生成模型 try { currentAssembly->Regenerate(nullptr); } catch (...) { // 重生成失败不影响抑制操作 } successful_count += featIds->getarraysize(); // 记录成功抑制的组件 - 累积而不是覆盖 if (componentsToDeleteByLevel.find(deleteLevel + 1) == componentsToDeleteByLevel.end()) { componentsToDeleteByLevel[deleteLevel + 1] = std::vector(); } componentsToDeleteByLevel[deleteLevel + 1].insert( componentsToDeleteByLevel[deleteLevel + 1].end(), levelComponents.begin(), levelComponents.end() ); } else { failed_count += featIds->getarraysize(); } } catch (...) { failed_count += featIds->getarraysize(); } } } else if (currentLevel < deleteLevel) { // 还没到目标层级:只对装配体组件继续递归 for (int i = 0; i < features->getarraysize(); i++) { pfcFeature_ptr feature = features->get(i); pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature); if (compFeat) { auto modelDescr = compFeat->GetModelDescr(); if (modelDescr && modelDescr->GetType() == pfcMDL_ASSEMBLY) { pfcModel_ptr childModel = LoadComponentModel(compFeat); if (childModel && childModel->GetType() == pfcMDL_ASSEMBLY) { wfcWAssembly_ptr childAssembly = wfcWAssembly::cast(childModel); if (childAssembly) { deleteAtLevel(childAssembly, currentLevel + 1); } } } } } } } } catch (...) { // 忽略单个装配体的错误 } }; // 从根装配体开始删除(层级0) deleteAtLevel(assembly, 0); result.deleted_components = componentsToDeleteByLevel; result.total_deleted = total_deleted; result.original_levels = 0; // 临时设置,避免异常值 // 删除完成后重新生成模型 if (successful_count > 0) { try { assembly->Regenerate(nullptr); } catch (...) { // 重新生成失败不影响删除结果 } } result.successful = successful_count; result.failed = failed_count; result.final_levels = target_level + 1; // 删除后的层级数 if (failed_count == 0) { result.success = true; result.message = "All components suppressed successfully (safer than deletion)"; } else { result.success = (successful_count > 0); result.message = "Suppression completed with " + std::to_string(failed_count) + " failures"; } } catch (const std::exception& e) { result.error_message = "Exception during suppression: " + std::string(e.what()); } catch (...) { result.error_message = "Unknown error during suppression"; } return result; } // Multi-directional extreme value projection shell analysis implementation CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeaturesEnhanced(const ShellAnalysisRequest& request) { ShellAnalysisResult result; try { // Get session SessionInfo sessionInfo = GetSessionInfo(); if (!sessionInfo.is_valid) { result.error_message = "Cannot connect to Creo session"; return result; } // Get current model pfcModel_ptr currentModel = sessionInfo.session->GetCurrentModel(); if (!currentModel) { result.error_message = "No model is open in Creo"; return result; } // Model type detection pfcModelType modelType = currentModel->GetType(); bool is_assembly = (modelType == pfcModelType::pfcMDL_ASSEMBLY); // Set analysis parameters result.analysis_parameters.preserve_external_surfaces = request.preserve_external_surfaces; result.analysis_parameters.min_wall_thickness = request.min_wall_thickness; result.analysis_parameters.confidence_threshold = request.confidence_threshold; result.analysis_parameters.assembly_analysis = is_assembly; if (!is_assembly) { result.error_message = "Multi-directional projection analysis is designed for assembly models only"; return result; } // Cast to assembly pfcAssembly_ptr assembly = pfcAssembly::cast(currentModel); if (!assembly) { result.error_message = "Cannot cast model to assembly"; return result; } // Execute multi-directional extreme value projection algorithm (now returns complete analysis data) ProjectionAnalysisData analysisData = PerformMultiDirectionalProjectionAnalysis(assembly); const std::unordered_set& outerComponentIds = analysisData.outerComponentIds; const std::unordered_map& visibilityVotes = analysisData.visibilityVotes; const std::vector& allComponents = analysisData.components; const AABB& globalAABB = analysisData.globalAABB; const int numDirections = SHELL_ANALYSIS_NUM_DIRECTIONS; // Build component AABB mapping for quick lookup std::unordered_map componentAABBs; std::unordered_map componentNames; // Map feature ID to name for (const ComponentItem& comp : allComponents) { componentAABBs[comp.featureId] = comp.worldAABB; componentNames[comp.featureId] = comp.name; } // Get all components using the same method as CollectAllComponents for ID consistency wfcWAssembly_ptr wAssembly = wfcWAssembly::cast(assembly); if (!wAssembly) { result.error_message = "Cannot cast assembly to wfcWAssembly"; return result; } wfcWComponentPaths_ptr componentPaths = wAssembly->ListDisplayedComponents(); if (!componentPaths) { result.error_message = "Cannot get component paths from assembly"; return result; } int total_components = componentPaths->getarraysize(); result.total_features_analyzed = total_components; // No need for feature ID map - we'll use component IDs directly from paths // Build analysis result from projection analysis for (int i = 0; i < total_components; i++) { try { wfcWComponentPath_ptr wPath = componentPaths->get(i); if (!wPath) continue; // Get component solid and model (same as in CollectAllComponents) pfcSolid_ptr compSolid = wPath->GetLeaf(); if (!compSolid) continue; pfcModel_ptr compModel = pfcModel::cast(compSolid); if (!compModel) continue; // Get component name std::string comp_name; try { xstring nameXStr = compModel->GetFileName(); comp_name = XStringToString(nameXStr); if (comp_name.empty()) { comp_name = "COMPONENT_" + std::to_string(i + 1); } } catch (...) { comp_name = "COMPONENT_" + std::to_string(i + 1); } // Get stable feature ID from component path - use the leaf component ID int stableFeatureId = -1; xintsequence_ptr componentIds = wPath->GetComponentIds(); if (componentIds && componentIds->getarraysize() > 0) { // Use the last ID in the path which represents the leaf component stableFeatureId = componentIds->get(componentIds->getarraysize() - 1); } else { // Should not happen, but use index as last resort stableFeatureId = i; } // Create analysis item ShellAnalysisItem item; item.name = comp_name; item.type = "COMPONENT"; item.feature_id = stableFeatureId; // Use stable feature ID // Build full path including assembly hierarchy try { pfcModel_ptr currentModel = sessionInfo.session->GetCurrentModel(); std::string assemblyName = ""; if (currentModel) { xstring assemblyXStr = currentModel->GetFileName(); assemblyName = XStringToString(assemblyXStr); } item.path = BuildComponentFullPath(wPath, assemblyName); if (item.path.empty()) { item.path = comp_name; // Fallback to component name } } catch (...) { item.path = comp_name; // Fallback on error } // Calculate visibility ratio for this component double visibilityRatio = 0.0; auto votesIter = visibilityVotes.find(item.feature_id); if (votesIter != visibilityVotes.end()) { visibilityRatio = (double)votesIter->second / numDirections; } // Determine confidence based on visibility ratio if (visibilityRatio >= SHELL_ANALYSIS_HIGH_VISIBILITY_THRESHOLD) { // Highly visible component - clearly on outer surface item.confidence = 0.1; // Very low deletion confidence item.recommendation = "KEEP"; item.reason = "Component highly visible from multiple directions (visibility: " + std::to_string((int)(visibilityRatio * 100)) + "%)"; result.shell_features_count++; } else if (visibilityRatio >= SHELL_ANALYSIS_MEDIUM_VISIBILITY_THRESHOLD) { // Partially visible component - likely on outer surface or important structure item.confidence = 0.4; // Medium deletion confidence item.recommendation = "REVIEW"; item.reason = "Component partially visible (visibility: " + std::to_string((int)(visibilityRatio * 100)) + "%)"; result.shell_features_count++; } else { // Internal component - mostly not visible item.confidence = 0.85; // High deletion confidence item.recommendation = "DELETE"; item.reason = "Internal component with minimal visibility (visibility: " + std::to_string((int)(visibilityRatio * 100)) + "%)"; result.internal_features_count++; } // Check if it was identified as outer component by the main algorithm bool is_outer_component = (outerComponentIds.find(item.feature_id) != outerComponentIds.end()); // Apply rule-based adjustments // Get component's AABB for geometric rules AABB compAABB; auto aabbIter = componentAABBs.find(item.feature_id); if (aabbIter != componentAABBs.end()) { compAABB = aabbIter->second; // Check geometric rules if (ComponentClassifier::IsLikelyInternal(compAABB, globalAABB)) { item.confidence = std::min(0.95, item.confidence + 0.2); // Increase deletion confidence item.reason += " [Geometry: Deep internal]"; } } // Apply naming pattern rules - use unified component name std::string unifiedName = comp_name; auto nameIter = componentNames.find(item.feature_id); if (nameIter != componentNames.end()) { unifiedName = nameIter->second; // Use name from ComponentItem for consistency } if (ComponentClassifier::IsFastener(unifiedName)) { item.confidence = std::min(0.95, item.confidence + 0.3); // Fasteners are typically deletable item.reason += " [Pattern: Fastener]"; if (item.recommendation == "KEEP") { item.recommendation = "REVIEW"; // Reconsider visible fasteners } } else if (ComponentClassifier::IsInternalStructure(unifiedName)) { item.confidence = std::min(0.90, item.confidence + 0.25); // Internal structures likely deletable item.reason += " [Pattern: Internal structure]"; } else if (ComponentClassifier::IsExternalShell(unifiedName)) { item.confidence = std::max(0.1, item.confidence - 0.5); // External shells should be preserved item.reason += " [Pattern: External shell]"; if (item.recommendation == "DELETE") { item.recommendation = "REVIEW"; // Reconsider deletion of shells } } // Apply user preferences if (request.preserve_external_surfaces && is_outer_component) { item.confidence = 0.0; // Force keep } // Check confidence threshold if (item.confidence >= request.confidence_threshold) { item.is_deletable = true; result.total_deletable++; } result.features.push_back(item); } catch (...) { // Skip problematic components continue; } } // Sort results by confidence (highest first) std::sort(result.features.begin(), result.features.end(), [](const ShellAnalysisItem& a, const ShellAnalysisItem& b) -> bool { return a.confidence > b.confidence; }); // Convert features to categorized deletion lists for (const auto& item : result.features) { FeatureDeletion deletion; deletion.id = item.feature_id; deletion.name = item.name; deletion.type = item.type; deletion.reason = item.reason; deletion.confidence = item.confidence; deletion.volume_reduction = 0.0; // Not calculated in projection analysis deletion.part_file = ""; // Component name is already in 'name' deletion.part_path = item.path; // Use full component path deletion.component_type = "COMPONENT"; // Set vote count from projection analysis auto votesIter = visibilityVotes.find(item.feature_id); deletion.vote_count = (votesIter != visibilityVotes.end()) ? votesIter->second : 0; if (item.confidence >= 0.8) { // Safe deletion - high confidence internal components result.safe_deletions.push_back(deletion); } else if (item.confidence >= 0.5) { // Suggested deletion - medium confidence components result.suggested_deletions.push_back(deletion); } else { // Preserve - low confidence or outer surface components result.preserve_list.push_back(deletion); } } // Update statistics fields result.analysis_parameters.total_features = result.total_features_analyzed; result.analysis_parameters.deletable_features = result.safe_deletions.size() + result.suggested_deletions.size(); result.analysis_parameters.preserved_features = result.preserve_list.size(); result.analysis_parameters.surface_count = result.total_features_analyzed; // All components analyzed result.analysis_parameters.shell_surfaces = result.shell_features_count; // Outer surface components result.analysis_parameters.internal_surfaces = result.internal_features_count; // Internal components // Calculate estimated reduction (simplified for projection analysis) if (result.total_features_analyzed > 0) { double safe_reduction_pct = (double)result.safe_deletions.size() / result.total_features_analyzed * 100.0; double suggested_reduction_pct = (double)result.suggested_deletions.size() / result.total_features_analyzed * 100.0; std::ostringstream volume_str, filesize_str, performance_str; volume_str << std::fixed << std::setprecision(1) << (safe_reduction_pct + suggested_reduction_pct * 0.5) << "%"; filesize_str << std::fixed << std::setprecision(1) << (safe_reduction_pct * 0.7 + suggested_reduction_pct * 0.3) << "%"; performance_str << std::fixed << std::setprecision(0) << (safe_reduction_pct * 1.5 + suggested_reduction_pct * 0.8) << "%"; result.estimated_reduction.volume_reduction = volume_str.str(); result.estimated_reduction.file_size_reduction = filesize_str.str(); result.estimated_reduction.performance_improvement = performance_str.str(); } else { result.estimated_reduction.volume_reduction = "0%"; result.estimated_reduction.file_size_reduction = "0%"; result.estimated_reduction.performance_improvement = "0%"; } // Calculate statistics result.deletion_percentage = (result.total_features_analyzed > 0) ? (double(result.total_deletable) / result.total_features_analyzed * 100.0) : 0.0; result.success = true; result.model_name = XStringToString(currentModel->GetFullName()); } catch (const std::exception& e) { result.error_message = "Exception: " + std::string(e.what()); } catch (...) { result.error_message = "Unknown error during multi-directional projection analysis"; } return result; } // Shell Analysis implementation using real OTK geometry APIs CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeatures(const ShellAnalysisRequest& request) { return AnalyzeShellFeaturesEnhanced(request); } // Geometry analysis: determine if feature touches model boundary bool CreoManager::AnalyzeFeatureGeometry(pfcFeature_ptr feature, pfcOutline3D_ptr globalOutline, double tolerance) { if (!feature || !globalOutline) { return false; // Cannot analyze, assume not touching boundary } try { // Get global bounding box pfcPoint3D_ptr globalMin = globalOutline->get(0); pfcPoint3D_ptr globalMax = globalOutline->get(1); // Since OTK lacks direct feature bbox API, use improved heuristics // This is enhanced semantic analysis based on feature types and relationships pfcFeatureType feat_type = feature->GetFeatType(); // === Improved feature boundary analysis === // 1. Features that build outer shape typically touch boundary if (feat_type == pfcFeatureType::pfcFEATTYPE_PROTRUSION || feat_type == pfcFeatureType::pfcFEATTYPE_SHELL || feat_type == pfcFeatureType::pfcFEATTYPE_DOME || feat_type == pfcFeatureType::pfcFEATTYPE_TORUS) { return true; // These features define model shape } // 2. Base feature typically on boundary if (feat_type == pfcFeatureType::pfcFEATTYPE_FIRST) { return true; // Base feature on boundary } // 3. Machining features - need careful analysis if (feat_type == pfcFeatureType::pfcFEATTYPE_CUT || feat_type == pfcFeatureType::pfcFEATTYPE_HOLE) { // CUT and HOLE features can be external or internal // Without geometry API, conservatively assume internal return false; // Typically internal features } // 4. ROUND and CHAMFER - can be anywhere if (feat_type == pfcFeatureType::pfcFEATTYPE_ROUND || feat_type == pfcFeatureType::pfcFEATTYPE_CHAMFER) { // Cannot assume all rounds/chamfers are on boundary // Many internal edges also have rounds/chamfers // Need parent feature analysis for accurate determination try { // Try to check parent features for context pfcFeatures_ptr parents = feature->ListParents(); if (parents && parents->getarraysize() > 0) { // If parent is internal, this is likely internal too for (int i = 0; i < parents->getarraysize(); i++) { pfcFeature_ptr parent = parents->get(i); if (parent) { pfcFeatureType parent_type = parent->GetFeatType(); if (parent_type == pfcFeatureType::pfcFEATTYPE_CUT || parent_type == pfcFeatureType::pfcFEATTYPE_HOLE) { return false; // Round/chamfer on internal feature } } } } } catch (...) { // Parent analysis failed, use conservative approach } // Without clear internal parent, conservatively assume boundary // to avoid deleting important external features return true; } // 5. RIB and DRAFT features if (feat_type == pfcFeatureType::pfcFEATTYPE_RIB || feat_type == pfcFeatureType::pfcFEATTYPE_DRAFT) { return false; // Typically internal strengthening features } // 6. Datum features do not affect geometry if (feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_PLANE || feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_AXIS || feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_POINT || feat_type == pfcFeatureType::pfcFEATTYPE_COORD_SYS) { return false; // Datum features don't affect boundary } // 7. Component features in assembly if (feat_type == pfcFeatureType::pfcFEATTYPE_COMPONENT) { // Component position analysis would require transform matrices // For now, use conservative approach // TODO: Implement component bbox vs assembly bbox comparison return false; // Assume internal by default, let hierarchy analysis decide } // 8. Pattern features if (feat_type == pfcFeatureType::pfcFEATTYPE_PATTERN) { // Pattern boundary depends on leader feature try { // Cast to pattern type - pfcFeaturePattern should be in pfcFeature.h pfcFeaturePattern_ptr pattern = pfcFeaturePattern::cast(feature); if (pattern) { pfcFeature_ptr leader = pattern->GetPatternLeader(); if (leader) { // Recursively check leader feature return AnalyzeFeatureGeometry(leader, globalOutline, tolerance); } } } catch (...) { // Pattern analysis failed } return false; // Conservative: assume internal } // 9. Default for unknown features // Use conservative approach to avoid deleting important features return false; // Changed from true to false for safety } catch (...) { // Analysis failed, use conservative strategy return true; // Assume touching boundary to avoid incorrect deletion } } // === Smart Boundary Detection Algorithm === // 1. Get optimal tolerance based on assembly size double CreoManager::GetOptimalTolerance(pfcOutline3D_ptr assembly_bbox) { try { if (!assembly_bbox) return 0.1; pfcPoint3D_ptr minPt = assembly_bbox->get(0); pfcPoint3D_ptr maxPt = assembly_bbox->get(1); // Calculate assembly diagonal length double diagonal = sqrt( pow(maxPt->get(0) - minPt->get(0), 2) + pow(maxPt->get(1) - minPt->get(1), 2) + pow(maxPt->get(2) - minPt->get(2), 2) ); // Tolerance is 1% of diagonal, with min/max limits double tolerance = diagonal * 0.01; return std::max(0.1, std::min(tolerance, 50.0)); } catch (...) { return 0.1; // Default fallback } } // 2. Calculate boundary overlap percentage double CreoManager::CalculateBoundaryOverlap(pfcOutline3D_ptr comp_bbox, pfcOutline3D_ptr assembly_bbox, double tolerance) { try { if (!comp_bbox || !assembly_bbox) return 0.0; pfcPoint3D_ptr comp_min = comp_bbox->get(0); pfcPoint3D_ptr comp_max = comp_bbox->get(1); pfcPoint3D_ptr asm_min = assembly_bbox->get(0); pfcPoint3D_ptr asm_max = assembly_bbox->get(1); double overlap_count = 0.0; double total_faces = 6.0; // 6 faces of bounding box // Check each face for boundary overlap // X-min face if (abs(comp_min->get(0) - asm_min->get(0)) <= tolerance) overlap_count += 1.0; // X-max face if (abs(comp_max->get(0) - asm_max->get(0)) <= tolerance) overlap_count += 1.0; // Y-min face if (abs(comp_min->get(1) - asm_min->get(1)) <= tolerance) overlap_count += 1.0; // Y-max face if (abs(comp_max->get(1) - asm_max->get(1)) <= tolerance) overlap_count += 1.0; // Z-min face if (abs(comp_min->get(2) - asm_min->get(2)) <= tolerance) overlap_count += 1.0; // Z-max face if (abs(comp_max->get(2) - asm_max->get(2)) <= tolerance) overlap_count += 1.0; return overlap_count / total_faces; // Return overlap percentage (0.0 to 1.0) } catch (...) { return 0.0; // Safe fallback } } // 3. Check if component is on assembly boundary bool CreoManager::IsOnAssemblyBoundary(pfcFeature_ptr component, pfcOutline3D_ptr assembly_bbox, double tolerance) { try { if (!component || !assembly_bbox) return true; // Conservative: assume on boundary pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(component); if (!compFeat) return true; // Get component model pfcModelDescriptor_ptr modelDesc = compFeat->GetModelDescr(); if (!modelDesc) return true; pfcSession_ptr session = pfcGetCurrentSession(); if (!session) return true; pfcModel_ptr componentModel = session->GetModelFromDescr(modelDesc); if (!componentModel || componentModel->GetType() != pfcModelType::pfcMDL_PART) { return true; // Assemblies are considered boundary components } pfcSolid_ptr componentSolid = pfcSolid::cast(componentModel); if (!componentSolid) return true; pfcOutline3D_ptr comp_bbox = componentSolid->EvalOutline(nullptr); if (!comp_bbox) return true; // Calculate boundary overlap double overlap = CalculateBoundaryOverlap(comp_bbox, assembly_bbox, tolerance); // Component is on boundary if it overlaps with any boundary face // Threshold: >= 16.7% (1 out of 6 faces) return overlap >= 0.167; } catch (...) { return true; // Conservative: assume on boundary to avoid deletion } } // Enhanced geometric boundary detection for part features bool CreoManager::AnalyzeFeatureGeometryEnhanced( pfcFeature_ptr feature, pfcSolid_ptr solid, pfcOutline3D_ptr globalOutline, double tolerance) { if (!feature || !solid || !globalOutline) { return false; // Conservative: assume internal } try { pfcFeatureType feat_type = feature->GetFeatType(); // Step 1: Quick type-based filtering for non-geometric features if (feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_PLANE || feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_AXIS || feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_POINT || feat_type == pfcFeatureType::pfcFEATTYPE_COORD_SYS) { return false; // Datum features don't affect geometry } // Step 2: Get surfaces affected by this feature std::vector affected_surfaces = GetFeatureAffectedSurfaces(feature, solid); if (affected_surfaces.empty()) { // No surfaces found - use fallback type-based analysis if (feat_type == pfcFeatureType::pfcFEATTYPE_PROTRUSION || feat_type == pfcFeatureType::pfcFEATTYPE_SHELL || feat_type == pfcFeatureType::pfcFEATTYPE_FIRST) { return true; // Shape-building features likely on boundary } return false; // Unknown features assumed internal } // Step 3: Check if any affected surface is on the model boundary int boundary_surface_count = 0; for (pfcSurface_ptr surface : affected_surfaces) { if (IsSurfaceOnBoundary(surface, solid, globalOutline, tolerance)) { boundary_surface_count++; } } // Step 4: Determine boundary status double boundary_ratio = (double)boundary_surface_count / affected_surfaces.size(); // Feature touches boundary if >30% of its surfaces are on boundary return boundary_ratio > 0.3; } catch (...) { // Analysis failed - use conservative fallback pfcFeatureType feat_type; try { feat_type = feature->GetFeatType(); if (feat_type == pfcFeatureType::pfcFEATTYPE_CUT || feat_type == pfcFeatureType::pfcFEATTYPE_HOLE) { return false; // Cuts/holes typically internal } } catch (...) { // Cannot get feature type } return true; // Conservative: assume boundary to avoid deletion } } // Get surfaces affected by a feature std::vector CreoManager::GetFeatureAffectedSurfaces(pfcFeature_ptr feature, pfcSolid_ptr solid) { std::vector surfaces; if (!feature || !solid) { return surfaces; } try { // Try to get feature surfaces using OTK API // Note: This is a simplified implementation // Full implementation would require feature-specific surface extraction pfcFeatureType feat_type = feature->GetFeatType(); // For certain feature types, we can get related surfaces if (feat_type == pfcFeatureType::pfcFEATTYPE_PROTRUSION || feat_type == pfcFeatureType::pfcFEATTYPE_CUT || feat_type == pfcFeatureType::pfcFEATTYPE_HOLE) { // Get surfaces affected by this feature using correct OTK API try { pfcModelItems_ptr model_items = solid->ListItems(pfcITEM_SURFACE); if (model_items) { int count = model_items->getarraysize(); // Feature affects surfaces based on creation order and type int start_idx = 0; int max_surfaces = 5; // Limit to avoid performance issues // For newer features, check last 20% of surfaces if (feat_type == pfcFeatureType::pfcFEATTYPE_CUT || feat_type == pfcFeatureType::pfcFEATTYPE_HOLE || feat_type == pfcFeatureType::pfcFEATTYPE_ROUND || feat_type == pfcFeatureType::pfcFEATTYPE_CHAMFER) { start_idx = std::max(0, (int)(count * 0.8)); } else if (feat_type == pfcFeatureType::pfcFEATTYPE_PROTRUSION) { // Protrusions affect newer surfaces start_idx = std::max(0, (int)(count * 0.6)); } else { // Other features, sample from middle start_idx = std::max(0, (int)(count * 0.4)); } for (int i = start_idx; i < count && i < start_idx + max_surfaces; i++) { try { pfcModelItem_ptr item = model_items->get(i); pfcSurface_ptr surface = pfcSurface::cast(item); if (surface) { surfaces.push_back(surface); } } catch (...) { continue; } } } } catch (...) { // Surface enumeration failed - return empty list for fallback } } } catch (...) { // Feature analysis failed } return surfaces; } // Check if a surface is on the model boundary using real geometry APIs bool CreoManager::IsSurfaceOnBoundary(pfcSurface_ptr surface, pfcSolid_ptr solid, pfcOutline3D_ptr globalOutline, double tolerance) { if (!surface) { return false; } try { // Step 1: Check if surface is visible (external surface indicator) if (!surface->GetIsVisible()) { return false; // Internal surfaces are not visible } // Step 2: Analyze surface contours to determine if it's truly external pfcContours_ptr contours = surface->ListContours(); if (!contours || contours->getarraysize() == 0) { // No contours available, use fallback bounding box check return CheckSurfaceBoundaryByBounds(surface, globalOutline, tolerance); } // Step 3: Check for external contours (non-internal traversal) bool has_external_contour = false; for (int i = 0; i < contours->getarraysize(); i++) { pfcContour_ptr contour = contours->get(i); if (contour && !contour->GetInternalTraversal()) { has_external_contour = true; break; // Found external contour, surface is on boundary } } // Step 4: Additional validation using boundary edges if contours exist if (has_external_contour) { // Verify by checking if any edges are boundary edges for (int i = 0; i < contours->getarraysize(); i++) { pfcContour_ptr contour = contours->get(i); if (contour) { pfcEdges_ptr edges = contour->ListElements(); if (edges) { for (int j = 0; j < edges->getarraysize(); j++) { pfcEdge_ptr edge = edges->get(j); if (edge && edge->GetSurface2() == nullptr) { return true; // Found boundary edge, confirmed external surface } } } } } } return has_external_contour; } catch (...) { // Real geometry analysis failed, use conservative fallback return CheckSurfaceBoundaryByBounds(surface, globalOutline, tolerance); } } // Fallback boundary check using bounding box analysis bool CreoManager::CheckSurfaceBoundaryByBounds(pfcSurface_ptr surface, pfcOutline3D_ptr globalOutline, double tolerance) { if (!surface || !globalOutline) { return false; } try { pfcOutline3D_ptr surf_outline = surface->GetXYZExtents(); if (!surf_outline) { return false; } pfcPoint3D_ptr global_min = globalOutline->get(0); pfcPoint3D_ptr global_max = globalOutline->get(1); pfcPoint3D_ptr surf_min = surf_outline->get(0); pfcPoint3D_ptr surf_max = surf_outline->get(1); // Check if surface touches any face of the global bounding box return (abs(surf_min->get(0) - global_min->get(0)) <= tolerance || abs(surf_max->get(0) - global_max->get(0)) <= tolerance || abs(surf_min->get(1) - global_min->get(1)) <= tolerance || abs(surf_max->get(1) - global_max->get(1)) <= tolerance || abs(surf_min->get(2) - global_min->get(2)) <= tolerance || abs(surf_max->get(2) - global_max->get(2)) <= tolerance); } catch (...) { return false; } } // Calculate distance from feature to nearest external surface double CreoManager::CalculateDistanceToExternalSurface(pfcFeature_ptr feature, pfcSolid_ptr solid) { if (!feature || !solid) { return -1.0; // Unknown distance } try { // This is a simplified implementation // Full implementation would require: // 1. Get feature geometry center // 2. Cast rays in multiple directions // 3. Find intersection with external surfaces // 4. Return minimum distance pfcFeatureType feat_type = feature->GetFeatType(); // Estimate based on feature type and model size pfcOutline3D_ptr outline = solid->EvalOutline(); if (outline) { pfcPoint3D_ptr min_pt = outline->get(0); pfcPoint3D_ptr max_pt = outline->get(1); double model_size = sqrt( pow(max_pt->get(0) - min_pt->get(0), 2) + pow(max_pt->get(1) - min_pt->get(1), 2) + pow(max_pt->get(2) - min_pt->get(2), 2) ); // Heuristic distance estimation if (feat_type == pfcFeatureType::pfcFEATTYPE_CUT || feat_type == pfcFeatureType::pfcFEATTYPE_HOLE) { return model_size * 0.1; // Assume cuts are somewhat internal } else if (feat_type == pfcFeatureType::pfcFEATTYPE_PROTRUSION) { return 0.0; // Protrusions typically on surface } else { return model_size * 0.05; // Default internal distance } } return 10.0; // Default fallback distance } catch (...) { return -1.0; // Analysis failed } } // Enhanced assembly component occlusion analysis bool CreoManager::IsComponentOccludedByOthers( pfcFeature_ptr component, pfcOutline3D_ptr assembly_bbox, const std::vector>& all_components, double tolerance) { if (!component || !assembly_bbox || all_components.empty()) { return false; // Cannot analyze } try { // Get the target component's bounding box pfcComponentFeat_ptr target_comp = pfcComponentFeat::cast(component); if (!target_comp) { return false; } pfcModelDescriptor_ptr target_desc = target_comp->GetModelDescr(); if (!target_desc) { return false; } pfcSession_ptr session = pfcGetCurrentSession(); if (!session) { return false; } pfcModel_ptr target_model = session->GetModelFromDescr(target_desc); if (!target_model || target_model->GetType() != pfcModelType::pfcMDL_PART) { return false; // Cannot analyze assemblies or invalid models } pfcSolid_ptr target_solid = pfcSolid::cast(target_model); if (!target_solid) { return false; } pfcOutline3D_ptr target_bbox = target_solid->EvalOutline(); if (!target_bbox) { return false; } // Check if this component is occluded by others int occluding_components = 0; int total_checked = 0; for (const auto& other_comp_pair : all_components) { pfcFeature_ptr other_comp = other_comp_pair.first; // Skip self if (other_comp == component) { continue; } total_checked++; // Calculate occlusion ratio double occlusion = CalculateOcclusionRatio(component, other_comp); if (occlusion > 0.3) { // 30% occlusion threshold occluding_components++; } } // Component is considered occluded if it's hidden by multiple other components // or significantly occluded by at least one large component if (total_checked > 0) { double occlusion_ratio = (double)occluding_components / total_checked; return occlusion_ratio > 0.25; // Occluded if 25% of other components hide it } return false; } catch (...) { return false; // Analysis failed, assume not occluded } } // Calculate how much one component occludes another double CreoManager::CalculateOcclusionRatio(pfcFeature_ptr target, pfcFeature_ptr occluder) { if (!target || !occluder) { return 0.0; } try { // Get both components as ComponentFeat pfcComponentFeat_ptr target_comp = pfcComponentFeat::cast(target); pfcComponentFeat_ptr occluder_comp = pfcComponentFeat::cast(occluder); if (!target_comp || !occluder_comp) { return 0.0; } // Get model descriptors pfcModelDescriptor_ptr target_desc = target_comp->GetModelDescr(); pfcModelDescriptor_ptr occluder_desc = occluder_comp->GetModelDescr(); if (!target_desc || !occluder_desc) { return 0.0; } pfcSession_ptr session = pfcGetCurrentSession(); if (!session) { return 0.0; } // Get models pfcModel_ptr target_model = session->GetModelFromDescr(target_desc); pfcModel_ptr occluder_model = session->GetModelFromDescr(occluder_desc); if (!target_model || !occluder_model || target_model->GetType() != pfcModelType::pfcMDL_PART || occluder_model->GetType() != pfcModelType::pfcMDL_PART) { return 0.0; } // Get solids pfcSolid_ptr target_solid = pfcSolid::cast(target_model); pfcSolid_ptr occluder_solid = pfcSolid::cast(occluder_model); if (!target_solid || !occluder_solid) { return 0.0; } // Get bounding boxes pfcOutline3D_ptr target_bbox = target_solid->EvalOutline(); pfcOutline3D_ptr occluder_bbox = occluder_solid->EvalOutline(); if (!target_bbox || !occluder_bbox) { return 0.0; } // Calculate bounding box overlap pfcPoint3D_ptr target_min = target_bbox->get(0); pfcPoint3D_ptr target_max = target_bbox->get(1); pfcPoint3D_ptr occluder_min = occluder_bbox->get(0); pfcPoint3D_ptr occluder_max = occluder_bbox->get(1); // Calculate overlap volume double overlap_x = std::max(0.0, std::min(target_max->get(0), occluder_max->get(0)) - std::max(target_min->get(0), occluder_min->get(0))); double overlap_y = std::max(0.0, std::min(target_max->get(1), occluder_max->get(1)) - std::max(target_min->get(1), occluder_min->get(1))); double overlap_z = std::max(0.0, std::min(target_max->get(2), occluder_max->get(2)) - std::max(target_min->get(2), occluder_min->get(2))); double overlap_volume = overlap_x * overlap_y * overlap_z; // Calculate target volume double target_volume = (target_max->get(0) - target_min->get(0)) * (target_max->get(1) - target_min->get(1)) * (target_max->get(2) - target_min->get(2)); if (target_volume <= 0) { return 0.0; } // Return occlusion ratio return overlap_volume / target_volume; } catch (...) { return 0.0; // Analysis failed } } // Check if two components have interference/overlap bool CreoManager::HasInterferenceWith(pfcFeature_ptr comp1, pfcFeature_ptr comp2) { if (!comp1 || !comp2) { return false; } try { // Simple implementation based on bounding box overlap double occlusion = CalculateOcclusionRatio(comp1, comp2); return occlusion > 0.1; // 10% overlap indicates interference } catch (...) { return false; // Analysis failed } } // ===================================================== // MULTI-DIRECTIONAL EXTREME VALUE PROJECTION ALGORITHM // ===================================================== // Convert pfcPoint3D to Vector3D CreoManager::Vector3D CreoManager::PfcPointToVector3D(pfcPoint3D_ptr point) { if (!point) return Vector3D(); return Vector3D(point->get(0), point->get(1), point->get(2)); } // Convert pfcOutline3D to AABB CreoManager::AABB CreoManager::PfcOutlineToAABB(pfcOutline3D_ptr outline) { if (!outline) return AABB(); Vector3D minPt = PfcPointToVector3D(outline->get(0)); Vector3D maxPt = PfcPointToVector3D(outline->get(1)); return AABB(minPt, maxPt); } // Get component world transformation matrix (updated for wfcWComponentPath) pfcTransform3D_ptr CreoManager::GetComponentWorldTransform(pfcComponentPath_ptr path) { // This function is no longer used in the new implementation // We now use wfcWComponentPath->GetTransform() directly return nullptr; } // Transform AABB from local to world coordinates CreoManager::AABB CreoManager::TransformAABB(const AABB& localAABB, pfcTransform3D_ptr transform) { if (!transform) return localAABB; try { // Transform all 8 corners of AABB std::vector corners = { Vector3D(localAABB.minPoint.x, localAABB.minPoint.y, localAABB.minPoint.z), Vector3D(localAABB.maxPoint.x, localAABB.minPoint.y, localAABB.minPoint.z), Vector3D(localAABB.minPoint.x, localAABB.maxPoint.y, localAABB.minPoint.z), Vector3D(localAABB.maxPoint.x, localAABB.maxPoint.y, localAABB.minPoint.z), Vector3D(localAABB.minPoint.x, localAABB.minPoint.y, localAABB.maxPoint.z), Vector3D(localAABB.maxPoint.x, localAABB.minPoint.y, localAABB.maxPoint.z), Vector3D(localAABB.minPoint.x, localAABB.maxPoint.y, localAABB.maxPoint.z), Vector3D(localAABB.maxPoint.x, localAABB.maxPoint.y, localAABB.maxPoint.z) }; AABB worldAABB; for (const Vector3D& corner : corners) { // Create pfcPoint3D pfcPoint3D_ptr localPoint = pfcPoint3D::create(); localPoint->set(0, corner.x); localPoint->set(1, corner.y); localPoint->set(2, corner.z); // Transform point pfcPoint3D_ptr worldPoint = transform->TransformPoint(localPoint); // Convert back to Vector3D and expand AABB Vector3D worldCorner = PfcPointToVector3D(worldPoint); worldAABB.expand(worldCorner); } return worldAABB; } catch (...) { return localAABB; // Fallback to local AABB if transform fails } } // Sample directions using Fibonacci sphere distribution std::vector CreoManager::SampleDirections(int count) { std::vector directions; directions.reserve(count); double phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio for (int i = 0; i < count; ++i) { double t = (double(i) + 0.5) / double(count); double z = 1.0 - 2.0 * t; // z from 1 to -1 (full sphere) // No clamping - use full sphere sampling double r = sqrt(std::max(0.0, 1.0 - z * z)); double azimuth = 2.0 * M_PI * i / phi; Vector3D dir(r * cos(azimuth), r * sin(azimuth), z); directions.push_back(dir.normalize()); } return directions; } // Calculate support point projection for AABB in given direction double CreoManager::CalculateProjectionSupport(const AABB& aabb, const Vector3D& direction) { // Support function for AABB: choose the corner that maximizes dot product with direction Vector3D supportPoint; supportPoint.x = (direction.x >= 0) ? aabb.maxPoint.x : aabb.minPoint.x; supportPoint.y = (direction.y >= 0) ? aabb.maxPoint.y : aabb.minPoint.y; supportPoint.z = (direction.z >= 0) ? aabb.maxPoint.z : aabb.minPoint.z; return supportPoint.dot(direction); } // Collect all components from assembly (no recursion needed) std::vector CreoManager::CollectAllComponents(pfcAssembly_ptr assembly) { std::vector components; if (!assembly) return components; try { // We don't need feature mapping - will use component IDs directly from paths // Step 2: Use wfcWAssembly to get component paths with transforms wfcWAssembly_ptr wAssembly = wfcWAssembly::cast(assembly); if (!wAssembly) return components; // Get component paths - this already returns full depth paths from root wfcWComponentPaths_ptr componentPaths = wAssembly->ListDisplayedComponents(); if (!componentPaths) return components; for (int i = 0; i < componentPaths->getarraysize(); ++i) { wfcWComponentPath_ptr wPath = componentPaths->get(i); if (!wPath) continue; try { // Get component solid from path pfcSolid_ptr compSolid = wPath->GetLeaf(); if (!compSolid) continue; pfcModel_ptr compModel = pfcModel::cast(compSolid); if (!compModel) continue; // Get component name std::string compName; try { xstring nameXStr = compModel->GetFileName(); compName = XStringToString(nameXStr); if (compName.empty()) { compName = "COMPONENT_" + std::to_string(i + 1); } } catch (...) { compName = "COMPONENT_" + std::to_string(i + 1); } // Get stable feature ID from component path - use the leaf component ID int stableFeatureId = -1; xintsequence_ptr componentIds = wPath->GetComponentIds(); if (componentIds && componentIds->getarraysize() > 0) { // Use the last ID in the path which represents the leaf component stableFeatureId = componentIds->get(componentIds->getarraysize() - 1); } else { // Should not happen, but use index as last resort stableFeatureId = i; } // Get local AABB pfcOutline3D_ptr localOutline = compSolid->EvalOutline(nullptr); if (!localOutline) continue; AABB localAABB = PfcOutlineToAABB(localOutline); // Get world transform from component path - relative to root assembly pfcTransform3D_ptr worldTransform = nullptr; try { worldTransform = wPath->GetTransform(xfalse); // From root assembly to component } catch (...) { // Transform failed, use identity } AABB worldAABB = TransformAABB(localAABB, worldTransform); // Create component item with complete information ComponentItem item; item.component = nullptr; // We don't have pfcComponentFeat directly item.solid = compSolid; item.path = nullptr; // We have wfcWComponentPath, different type item.worldAABB = worldAABB; // Fast rough filtering // Create preliminary item for OBB decision ComponentItem tempItem; tempItem.worldAABB = worldAABB; tempItem.name = compName; // Enhanced OBB computation: use unified decision logic if (ShouldUseOBB(tempItem)) { item.worldOBB = ComputePCABasedOBB(compSolid, worldTransform); } else { item.worldOBB = ExtractOBBFromTransform(localAABB, worldTransform); } item.featureId = stableFeatureId; // Use stable feature ID from component path item.name = compName; components.push_back(item); // No recursion needed - ListDisplayedComponents already returns full depth } catch (...) { // Skip this component if any operation fails continue; } } } catch (...) { // Collection failed, return what we have } return components; } std::pair CreoManager::CalculateAABBProjectionRange(const AABB& aabb, const Vector3D& direction) { Vector3D corners[8] = { {aabb.minPoint.x, aabb.minPoint.y, aabb.minPoint.z}, {aabb.maxPoint.x, aabb.minPoint.y, aabb.minPoint.z}, {aabb.minPoint.x, aabb.maxPoint.y, aabb.minPoint.z}, {aabb.maxPoint.x, aabb.maxPoint.y, aabb.minPoint.z}, {aabb.minPoint.x, aabb.minPoint.y, aabb.maxPoint.z}, {aabb.maxPoint.x, aabb.minPoint.y, aabb.maxPoint.z}, {aabb.minPoint.x, aabb.maxPoint.y, aabb.maxPoint.z}, {aabb.maxPoint.x, aabb.maxPoint.y, aabb.maxPoint.z} }; double minProj = corners[0].dot(direction); double maxProj = minProj; for (int i = 1; i < 8; i++) { double proj = corners[i].dot(direction); minProj = std::min(minProj, proj); maxProj = std::max(maxProj, proj); } return {minProj, maxProj}; } std::pair CreoManager::CalculateOBBProjectionRange(const OBB& obb, const Vector3D& direction) { std::vector corners = obb.getCorners(); double minProj = corners[0].dot(direction); double maxProj = minProj; for (size_t i = 1; i < corners.size(); i++) { double proj = corners[i].dot(direction); minProj = std::min(minProj, proj); maxProj = std::max(maxProj, proj); } return {minProj, maxProj}; } // Main multi-directional extreme value projection analysis with depth window and voting CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly) { ProjectionAnalysisData result; // Step 1: Collect all components result.components = CollectAllComponents(assembly); // Step 2: Calculate global assembly AABB for (const ComponentItem& comp : result.components) { result.globalAABB.expand(comp.worldAABB.minPoint); result.globalAABB.expand(comp.worldAABB.maxPoint); } // Step 3: Sample directions (good coverage) const int numDirections = SHELL_ANALYSIS_NUM_DIRECTIONS; std::vector directions = SampleDirections(numDirections); // Voting map: component ID -> number of directions where it's visible result.visibilityVotes.reserve(result.components.size()); // Step 4: For each direction, determine visible components using occlusion detection for (const Vector3D& direction : directions) { // Structure to hold projection data struct ProjectionData { double minProj; // Minimum projection value double maxProj; // Maximum projection value int featureId; }; std::vector projections; projections.reserve(result.components.size()); // Calculate projection ranges for all components for (const ComponentItem& comp : result.components) { bool useOBB = ShouldUseOBB(comp); double minProj, maxProj; if (useOBB) { auto range = CalculateOBBProjectionRange(comp.worldOBB, direction); minProj = range.first; maxProj = range.second; } else { auto range = CalculateAABBProjectionRange(comp.worldAABB, direction); minProj = range.first; maxProj = range.second; } projections.push_back({minProj, maxProj, comp.featureId}); } // Sort by minimum projection value (nearest first) std::sort(projections.begin(), projections.end(), [](const ProjectionData& a, const ProjectionData& b) { return a.minProj < b.minProj; }); // Occlusion detection: near components occlude far components double occlusionBoundary = -std::numeric_limits::max(); for (const auto& proj : projections) { // If component's nearest point is beyond occlusion boundary, it's visible if (proj.minProj > occlusionBoundary) { result.visibilityVotes[proj.featureId]++; // Update occlusion boundary to this component's farthest point occlusionBoundary = std::max(occlusionBoundary, proj.maxProj); } } } // Step 5: Determine outer components based on voting threshold double minVisibilityRatio = SHELL_ANALYSIS_MIN_VISIBILITY_RATIO; int minVotes = std::max(SHELL_ANALYSIS_MIN_VOTES, (int)(minVisibilityRatio * numDirections)); for (const auto& kvp : result.visibilityVotes) { if (kvp.second >= minVotes) { result.outerComponentIds.insert(kvp.first); } } return result; } // Get feature type name string std::string CreoManager::GetFeatureTypeName(pfcFeatureType feat_type) { switch (feat_type) { case pfcFeatureType::pfcFEATTYPE_PROTRUSION: return "PROTRUSION"; case pfcFeatureType::pfcFEATTYPE_CUT: return "CUT"; case pfcFeatureType::pfcFEATTYPE_HOLE: return "HOLE"; case pfcFeatureType::pfcFEATTYPE_ROUND: return "ROUND"; case pfcFeatureType::pfcFEATTYPE_CHAMFER: return "CHAMFER"; case pfcFeatureType::pfcFEATTYPE_SHELL: return "SHELL"; case pfcFeatureType::pfcFEATTYPE_RIB: return "RIB"; case pfcFeatureType::pfcFEATTYPE_DRAFT: return "DRAFT"; case pfcFeatureType::pfcFEATTYPE_PATTERN: return "PATTERN"; case pfcFeatureType::pfcFEATTYPE_COMPONENT: return "COMPONENT"; case pfcFeatureType::pfcFEATTYPE_DATUM_PLANE: return "DATUM_PLANE"; case pfcFeatureType::pfcFEATTYPE_DATUM_AXIS: return "DATUM_AXIS"; case pfcFeatureType::pfcFEATTYPE_DATUM_POINT: return "DATUM_POINT"; case pfcFeatureType::pfcFEATTYPE_COORD_SYS: return "COORD_SYS"; case pfcFeatureType::pfcFEATTYPE_FIRST: return "FIRST"; case pfcFeatureType::pfcFEATTYPE_DOME: return "DOME"; case pfcFeatureType::pfcFEATTYPE_TORUS: return "TORUS"; default: return "UNKNOWN"; } } // Component Classifier Implementation bool CreoManager::ComponentClassifier::MatchesPattern(const std::string& text, const std::vector& patterns) { // Convert to lowercase for case-insensitive matching std::string lowerText = text; std::transform(lowerText.begin(), lowerText.end(), lowerText.begin(), ::tolower); for (const std::string& pattern : patterns) { std::string lowerPattern = pattern; std::transform(lowerPattern.begin(), lowerPattern.end(), lowerPattern.begin(), ::tolower); if (lowerText.find(lowerPattern) != std::string::npos) { return true; } } return false; } bool CreoManager::ComponentClassifier::IsFastener(const std::string& name) { // Common fastener naming patterns static const std::vector patterns = { "bolt", "screw", "nut", "washer", "rivet", "pin", "stud", "spacer", "bushing", "fastener", "clamp", "m3", "m4", "m5", "m6", "m8", "m10", "m12", // Metric sizes "hex", "socket", "cap_screw", "set_screw" // Note: Chinese patterns removed to avoid encoding issues }; return MatchesPattern(name, patterns); } bool CreoManager::ComponentClassifier::IsInternalStructure(const std::string& name) { // Common internal structure naming patterns static const std::vector patterns = { "rib", "support", "internal", "bracket", "gusset", "reinforcement", "stiffener", "brace", "strut", "webbing", "boss", "pocket", "cavity" // Note: Chinese patterns removed to avoid encoding issues }; return MatchesPattern(name, patterns); } bool CreoManager::ComponentClassifier::IsExternalShell(const std::string& name) { // Common external shell naming patterns static const std::vector patterns = { "cover", "housing", "shell", "case", "enclosure", "panel", "shield", "shroud", "skin", "body", "exterior", "outer", "facade", "surface" // Note: Chinese patterns removed to avoid encoding issues }; return MatchesPattern(name, patterns); } bool CreoManager::ComponentClassifier::IsLikelyInternal(const AABB& compAABB, const AABB& globalAABB) { // Rule 1: Extremely small components (volume < 0.1% of total) Vector3D compSize = compAABB.diagonal(); Vector3D globalSize = globalAABB.diagonal(); double compVolume = compSize.x * compSize.y * compSize.z; double globalVolume = globalSize.x * globalSize.y * globalSize.z; if (globalVolume > 0) { double volumeRatio = compVolume / globalVolume; if (volumeRatio < 0.001) { // Less than 0.1% return true; // Likely a small internal feature } } // Rule 2: Component completely inside assembly (far from all boundaries) double minDistToBoundary = 1e9; // Calculate minimum distance to any boundary minDistToBoundary = std::min(minDistToBoundary, compAABB.minPoint.x - globalAABB.minPoint.x); minDistToBoundary = std::min(minDistToBoundary, globalAABB.maxPoint.x - compAABB.maxPoint.x); minDistToBoundary = std::min(minDistToBoundary, compAABB.minPoint.y - globalAABB.minPoint.y); minDistToBoundary = std::min(minDistToBoundary, globalAABB.maxPoint.y - compAABB.maxPoint.y); minDistToBoundary = std::min(minDistToBoundary, compAABB.minPoint.z - globalAABB.minPoint.z); minDistToBoundary = std::min(minDistToBoundary, globalAABB.maxPoint.z - compAABB.maxPoint.z); double globalDiagonal = globalAABB.getDiagonalLength(); if (globalDiagonal > 0 && minDistToBoundary > 0.2 * globalDiagonal) { return true; // Component is deep inside, far from boundaries } return false; } // ===================================================== // OBB (ORIENTED BOUNDING BOX) IMPLEMENTATION // ===================================================== // Extract OBB from local AABB and transform matrix CreoManager::OBB CreoManager::ExtractOBBFromTransform(const AABB& localAABB, pfcTransform3D_ptr transform) { OBB obb; // Calculate local center and half extents Vector3D localCenter = (localAABB.minPoint + localAABB.maxPoint) * 0.5; obb.halfExtents = (localAABB.maxPoint - localAABB.minPoint) * 0.5; if (!transform) { // No transform, degrade to AABB obb.center = localCenter; obb.axes[0] = Vector3D(1, 0, 0); obb.axes[1] = Vector3D(0, 1, 0); obb.axes[2] = Vector3D(0, 0, 1); return obb; } try { // Transform center point to world coordinates pfcPoint3D_ptr localPt = pfcPoint3D::create(); localPt->set(0, localCenter.x); localPt->set(1, localCenter.y); localPt->set(2, localCenter.z); pfcPoint3D_ptr worldPt = transform->TransformPoint(localPt); obb.center = PfcPointToVector3D(worldPt); // Extract rotation axes by transforming unit vectors for (int i = 0; i < 3; ++i) { pfcPoint3D_ptr origin = pfcPoint3D::create(); origin->set(0, 0); origin->set(1, 0); origin->set(2, 0); pfcPoint3D_ptr unitVec = pfcPoint3D::create(); unitVec->set(0, i == 0 ? 1.0 : 0.0); unitVec->set(1, i == 1 ? 1.0 : 0.0); unitVec->set(2, i == 2 ? 1.0 : 0.0); pfcPoint3D_ptr transformedOrigin = transform->TransformPoint(origin); pfcPoint3D_ptr transformedVec = transform->TransformPoint(unitVec); obb.axes[i].x = transformedVec->get(0) - transformedOrigin->get(0); obb.axes[i].y = transformedVec->get(1) - transformedOrigin->get(1); obb.axes[i].z = transformedVec->get(2) - transformedOrigin->get(2); obb.axes[i] = obb.axes[i].normalize(); } } catch (...) { // Transform failed, use AABB fallback obb.center = localCenter; obb.axes[0] = Vector3D(1, 0, 0); obb.axes[1] = Vector3D(0, 1, 0); obb.axes[2] = Vector3D(0, 0, 1); } return obb; } // Compute PCA-based OBB for enhanced precision CreoManager::OBB CreoManager::ComputePCABasedOBB(pfcSolid_ptr solid, pfcTransform3D_ptr transform) { OBB obb; if (!solid) return obb; try { // Extract vertices from solid geometry std::vector localVertices = ExtractSolidVertices(solid); if (localVertices.size() < 4) { // Fallback to AABB-based method for insufficient vertices pfcOutline3D_ptr localOutline = solid->EvalOutline(nullptr); if (localOutline) { AABB localAABB = PfcOutlineToAABB(localOutline); return ExtractOBBFromTransform(localAABB, transform); } return obb; } // Transform vertices to world coordinates if transform exists std::vector worldVertices; if (transform) { worldVertices.reserve(localVertices.size()); for (const Vector3D& localVertex : localVertices) { pfcPoint3D_ptr localPt = pfcPoint3D::create(); localPt->set(0, localVertex.x); localPt->set(1, localVertex.y); localPt->set(2, localVertex.z); pfcPoint3D_ptr worldPt = transform->TransformPoint(localPt); worldVertices.push_back(PfcPointToVector3D(worldPt)); } } else { worldVertices = localVertices; } // Compute OBB from transformed vertices using PCA obb = ComputeOBBFromVertices(worldVertices); } catch (...) { // Fallback to transform-based method pfcOutline3D_ptr localOutline = solid->EvalOutline(nullptr); if (localOutline) { AABB localAABB = PfcOutlineToAABB(localOutline); return ExtractOBBFromTransform(localAABB, transform); } } return obb; } // Extract vertices from solid geometry with sampling limit std::vector CreoManager::ExtractSolidVertices(pfcSolid_ptr solid, int maxVertices) { std::vector vertices; if (!solid) return vertices; try { // Direct approach: use EvalOutline to get AABB corners pfcOutline3D_ptr outline = solid->EvalOutline(nullptr); if (outline) { // Get min and max points from outline Vector3D minPt = PfcPointToVector3D(outline->get(0)); Vector3D maxPt = PfcPointToVector3D(outline->get(1)); // Generate 8 corner points of AABB vertices.push_back(Vector3D(minPt.x, minPt.y, minPt.z)); vertices.push_back(Vector3D(maxPt.x, minPt.y, minPt.z)); vertices.push_back(Vector3D(minPt.x, maxPt.y, minPt.z)); vertices.push_back(Vector3D(maxPt.x, maxPt.y, minPt.z)); vertices.push_back(Vector3D(minPt.x, minPt.y, maxPt.z)); vertices.push_back(Vector3D(maxPt.x, minPt.y, maxPt.z)); vertices.push_back(Vector3D(minPt.x, maxPt.y, maxPt.z)); vertices.push_back(Vector3D(maxPt.x, maxPt.y, maxPt.z)); // For enhanced precision, add mid-points on each face (optional enhancement) if (maxVertices > 8) { Vector3D center = (minPt + maxPt) * 0.5; // Add face centers for better PCA analysis vertices.push_back(Vector3D(minPt.x, center.y, center.z)); // Left face center vertices.push_back(Vector3D(maxPt.x, center.y, center.z)); // Right face center vertices.push_back(Vector3D(center.x, minPt.y, center.z)); // Front face center vertices.push_back(Vector3D(center.x, maxPt.y, center.z)); // Back face center vertices.push_back(Vector3D(center.x, center.y, minPt.z)); // Bottom face center vertices.push_back(Vector3D(center.x, center.y, maxPt.z)); // Top face center // Add edge midpoints if more precision needed if (maxVertices > 14) { vertices.push_back(Vector3D(center.x, minPt.y, minPt.z)); // Bottom front edge vertices.push_back(Vector3D(center.x, maxPt.y, minPt.z)); // Bottom back edge vertices.push_back(Vector3D(center.x, minPt.y, maxPt.z)); // Top front edge vertices.push_back(Vector3D(center.x, maxPt.y, maxPt.z)); // Top back edge vertices.push_back(Vector3D(minPt.x, center.y, minPt.z)); // Left bottom edge vertices.push_back(Vector3D(maxPt.x, center.y, minPt.z)); // Right bottom edge vertices.push_back(Vector3D(minPt.x, center.y, maxPt.z)); // Left top edge vertices.push_back(Vector3D(maxPt.x, center.y, maxPt.z)); // Right top edge } } } } catch (...) { // Return empty vector on failure vertices.clear(); } return vertices; } // Compute OBB from vertices using PCA CreoManager::OBB CreoManager::ComputeOBBFromVertices(const std::vector& vertices) { OBB obb; if (vertices.size() < 4) return obb; try { // Compute centroid Vector3D centroid(0, 0, 0); for (const Vector3D& v : vertices) { centroid = centroid + v; } centroid = centroid * (1.0 / vertices.size()); // Compute principal axes using PCA std::vector principalAxes = ComputePrincipalAxes(vertices); if (principalAxes.size() != 3) { // Fallback to axis-aligned obb.center = centroid; obb.axes[0] = Vector3D(1, 0, 0); obb.axes[1] = Vector3D(0, 1, 0); obb.axes[2] = Vector3D(0, 0, 1); // Compute extents in axis-aligned directions Vector3D minPt = vertices[0]; Vector3D maxPt = vertices[0]; for (const Vector3D& v : vertices) { if (v.x < minPt.x) minPt.x = v.x; if (v.y < minPt.y) minPt.y = v.y; if (v.z < minPt.z) minPt.z = v.z; if (v.x > maxPt.x) maxPt.x = v.x; if (v.y > maxPt.y) maxPt.y = v.y; if (v.z > maxPt.z) maxPt.z = v.z; } obb.halfExtents = (maxPt - minPt) * 0.5; return obb; } // Set OBB center and axes obb.center = centroid; obb.axes[0] = principalAxes[0]; obb.axes[1] = principalAxes[1]; obb.axes[2] = principalAxes[2]; // Compute half extents by projecting vertices onto principal axes double minProj[3] = {1e9, 1e9, 1e9}; double maxProj[3] = {-1e9, -1e9, -1e9}; for (const Vector3D& vertex : vertices) { Vector3D relative = vertex - centroid; for (int i = 0; i < 3; ++i) { double proj = relative.dot(obb.axes[i]); if (proj < minProj[i]) minProj[i] = proj; if (proj > maxProj[i]) maxProj[i] = proj; } } obb.halfExtents.x = (maxProj[0] - minProj[0]) * 0.5; obb.halfExtents.y = (maxProj[1] - minProj[1]) * 0.5; obb.halfExtents.z = (maxProj[2] - minProj[2]) * 0.5; } catch (...) { // Return empty OBB on failure obb = OBB(); } return obb; } // Compute principal component axes using PCA algorithm std::vector CreoManager::ComputePrincipalAxes(const std::vector& vertices) { std::vector axes; if (vertices.size() < 4) return axes; try { // Compute centroid Vector3D centroid(0, 0, 0); for (const Vector3D& v : vertices) { centroid = centroid + v; } centroid = centroid * (1.0 / vertices.size()); // Compute covariance matrix double cov[3][3] = {{0}}; for (const Vector3D& v : vertices) { Vector3D diff = v - centroid; cov[0][0] += diff.x * diff.x; cov[0][1] += diff.x * diff.y; cov[0][2] += diff.x * diff.z; cov[1][0] += diff.y * diff.x; cov[1][1] += diff.y * diff.y; cov[1][2] += diff.y * diff.z; cov[2][0] += diff.z * diff.x; cov[2][1] += diff.z * diff.y; cov[2][2] += diff.z * diff.z; } double scale = 1.0 / (vertices.size() - 1); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { cov[i][j] *= scale; } } // Simplified eigenvalue computation for 3x3 matrix // For performance, use simplified approach focusing on dominant eigenvector Vector3D axis1(1, 0, 0); Vector3D axis2(0, 1, 0); Vector3D axis3(0, 0, 1); // Find dominant direction (maximum variance) double maxVar = cov[0][0]; int maxIdx = 0; if (cov[1][1] > maxVar) { maxVar = cov[1][1]; maxIdx = 1; } if (cov[2][2] > maxVar) { maxVar = cov[2][2]; maxIdx = 2; } if (maxIdx == 0) { axis1 = Vector3D(1, 0, 0); axis2 = Vector3D(0, 1, 0); axis3 = Vector3D(0, 0, 1); } else if (maxIdx == 1) { axis1 = Vector3D(0, 1, 0); axis2 = Vector3D(1, 0, 0); axis3 = Vector3D(0, 0, 1); } else { axis1 = Vector3D(0, 0, 1); axis2 = Vector3D(1, 0, 0); axis3 = Vector3D(0, 1, 0); } // Ensure orthogonality axis2 = axis2 - axis1 * (axis2.dot(axis1)); axis2 = axis2.normalize(); axis3 = axis1.cross(axis2).normalize(); axes.push_back(axis1); axes.push_back(axis2); axes.push_back(axis3); } catch (...) { axes.clear(); } return axes; } // Calculate OBB projection support (wrapper for OBB member function) double CreoManager::CalculateOBBProjectionSupport(const OBB& obb, const Vector3D& direction) { return obb.getSupport(direction); } // Calculate OBB thickness in given direction double CreoManager::CalculateOBBThickness(const OBB& obb, const Vector3D& direction) { // Transform world direction to OBB local space Vector3D localDir( direction.dot(obb.axes[0]), direction.dot(obb.axes[1]), direction.dot(obb.axes[2]) ); // Calculate thickness as the extent of OBB in this direction // This gives the full dimension, not just half-extent return 2.0 * (std::abs(localDir.x * obb.halfExtents.x) + std::abs(localDir.y * obb.halfExtents.y) + std::abs(localDir.z * obb.halfExtents.z)); } bool CreoManager::ShouldUseOBB(const ComponentItem& comp) { Vector3D size = comp.worldAABB.diagonal(); double maxDim = std::max({size.x, size.y, size.z}); double minDim = std::min({size.x, size.y, size.z}); double aspectRatio = maxDim / minDim; return aspectRatio > 2.5 || ComponentClassifier::IsElongatedPart(comp.name); } // ComponentClassifier extension for elongated parts bool CreoManager::ComponentClassifier::IsElongatedPart(const std::string& name) { // Common elongated part naming patterns static const std::vector patterns = { "pipe", "tube", "rod", "shaft", "beam", "bar", "cable", "wire", "rail", "strip", "bracket", "arm", "lever", "link", "connector", "hose" }; return MatchesPattern(name, patterns); }