diff --git a/CreoManager.cpp b/CreoManager.cpp index e41db26..c3c783c 100644 --- a/CreoManager.cpp +++ b/CreoManager.cpp @@ -2805,24 +2805,19 @@ std::pair CreoManager::CalculateOBBProjectionRange(const OBB& ob CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly) { ProjectionAnalysisData result; - // Create projection analysis log file - try { - SessionInfo sessionInfo = GetSessionInfo(); - if (sessionInfo.is_valid) { - xstring workdir = sessionInfo.session->GetCurrentDirectory(); - std::string workingDir = XStringToString(workdir); - std::string logPath = workingDir + "\\projection_analysis.txt"; - std::ofstream logFile(logPath); - logFile << "Projection Analysis Started" << std::endl; - logFile << "Time: " << GetCurrentTimeString() << std::endl; - logFile << "Working Directory: " << workingDir << std::endl; - logFile.close(); - } - } catch (...) {} - // Step 1: Collect all components result.components = CollectAllComponents(assembly); + // Log 12v4000g03 component info if found + const ComponentItem* target12v = nullptr; + for (const ComponentItem& comp : result.components) { + if (comp.name.find("12v4000g03") != std::string::npos || + comp.name.find("12V4000G03") != std::string::npos) { + target12v = ∁ + break; + } + } + // Step 2: Calculate global assembly AABB for (const ComponentItem& comp : result.components) { result.globalAABB.expand(comp.worldAABB.minPoint); @@ -2883,8 +2878,13 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti featureIdToName[comp.featureId] = comp.name; } + // Remove 12v4000g03_debug.txt creation - will use visibility file instead + + int directionIndex = 0; + // Step 4: For each direction, determine visible components using 2D grid-based occlusion detection for (const Vector3D& direction : directions) { + directionIndex++; // Create screen grid for this viewing direction ScreenGrid grid(result.globalAABB, direction, SHELL_ANALYSIS_GRID_RESOLUTION); @@ -2934,6 +2934,44 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti auto visibilityCounts = grid.getVisibilityCount(); int totalCells = grid.gridSizeU * grid.gridSizeV; + // Log visibility counts for debugging + if (target12v) { + try { + SessionInfo sessionInfo = GetSessionInfo(); + if (sessionInfo.is_valid) { + xstring workdir = sessionInfo.session->GetCurrentDirectory(); + std::string workingDir = XStringToString(workdir); + std::string logPath = workingDir + "\\12v4000g03_visibility.txt"; + + if (directionIndex == 1) { + // First direction - create file with header + std::ofstream logFile(logPath); + if (logFile.is_open()) { + logFile << "=== 12V4000G03 Analysis ===" << std::endl; + logFile << "Target ID: " << target12v->featureId << std::endl; + logFile << "Target Name: " << target12v->name << std::endl; + logFile << "\n=== Direction Analysis ===" << std::endl; + logFile.close(); + } + } + + // Append direction info + std::ofstream logFile(logPath, std::ios::app); + if (logFile.is_open()) { + auto it = visibilityCounts.find(target12v->featureId); + logFile << "\nDirection " << directionIndex << ":" << std::endl; + if (it != visibilityCounts.end()) { + logFile << " 2D Projection: cells=" << it->second + << ", ratio=" << ((double)it->second / totalCells) << std::endl; + } else { + logFile << " 2D Projection: NOT VISIBLE" << std::endl; + } + logFile.close(); + } + } + } catch (...) {} + } + // Update visibility votes based on grid visibility with ray verification for (const auto& kvp : visibilityCounts) { int componentId = kvp.first; @@ -2968,79 +3006,18 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti } // If blocked by ray test, this direction votes 0 (no increment) - // Debug output for specific component - if (targetComponent->name.find("12v4000g03") != std::string::npos || - targetComponent->name.find("12V4000G03") != std::string::npos) { - static bool firstTime = true; - if (firstTime) { - try { - SessionInfo sessionInfo = GetSessionInfo(); - if (sessionInfo.is_valid) { - xstring workdir = sessionInfo.session->GetCurrentDirectory(); - std::string workingDir = XStringToString(workdir); - std::string logPath = workingDir + "\\12v4000g03_analysis.txt"; - std::ofstream logFile(logPath, std::ios::out | std::ios::trunc); - if (logFile.is_open()) { - logFile << "=== 12V4000G03 Component Visibility Analysis ===" << std::endl; - logFile << "Component Name: " << targetComponent->name << std::endl; - logFile << "Feature ID: " << targetComponent->featureId << std::endl; - logFile << "\nBounding Box:" << std::endl; - logFile << " Min: (" << targetComponent->worldAABB.minPoint.x << ", " - << targetComponent->worldAABB.minPoint.y << ", " - << targetComponent->worldAABB.minPoint.z << ")" << std::endl; - logFile << " Max: (" << targetComponent->worldAABB.maxPoint.x << ", " - << targetComponent->worldAABB.maxPoint.y << ", " - << targetComponent->worldAABB.maxPoint.z << ")" << std::endl; - Vector3D size = targetComponent->worldAABB.diagonal(); - logFile << " Size: " << size.x << " x " << size.y << " x " << size.z << std::endl; - logFile << "\nGlobal Center: (" << globalCenter.x << ", " - << globalCenter.y << ", " << globalCenter.z << ")" << std::endl; - logFile << "\nGlobal AABB:" << std::endl; - logFile << " Min: (" << result.globalAABB.minPoint.x << ", " - << result.globalAABB.minPoint.y << ", " - << result.globalAABB.minPoint.z << ")" << std::endl; - logFile << " Max: (" << result.globalAABB.maxPoint.x << ", " - << result.globalAABB.maxPoint.y << ", " - << result.globalAABB.maxPoint.z << ")" << std::endl; - logFile << "\nGrid Resolution: " << grid.gridSizeU << " x " << grid.gridSizeV << std::endl; - logFile << "Total Cells: " << totalCells << std::endl; - logFile << "Minimum Visibility Ratio Per Direction: " << SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR << std::endl; - logFile << "\nVisibility per direction:" << std::endl; - logFile.close(); - } - } - firstTime = false; - } catch (...) {} - } - - // Append visibility data for each direction + // Log ray test result for target12v in same visibility file + if (target12v && componentId == target12v->featureId) { try { SessionInfo sessionInfo = GetSessionInfo(); if (sessionInfo.is_valid) { xstring workdir = sessionInfo.session->GetCurrentDirectory(); std::string workingDir = XStringToString(workdir); - std::string logPath = workingDir + "\\12v4000g03_analysis.txt"; - std::ofstream logFile(logPath, std::ios::out | std::ios::app); + std::string logPath = workingDir + "\\12v4000g03_visibility.txt"; + std::ofstream logFile(logPath, std::ios::app); if (logFile.is_open()) { - static int directionCount = 0; - directionCount++; - - // Calculate component center for logging - Vector3D componentCenter = Vector3D( - (targetComponent->worldAABB.minPoint.x + targetComponent->worldAABB.maxPoint.x) * 0.5, - (targetComponent->worldAABB.minPoint.y + targetComponent->worldAABB.maxPoint.y) * 0.5, - (targetComponent->worldAABB.minPoint.z + targetComponent->worldAABB.maxPoint.z) * 0.5 - ); - - logFile << "\n--- Direction " << directionCount << " ---" << std::endl; - logFile << "2D Projection: cells=" << cellCount - << ", ratio=" << visibilityRatio - << ", visible=" << (visibilityRatio >= SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR ? "YES" : "NO") << std::endl; - - logFile << "Ray Test: from (" << globalCenter.x << ", " << globalCenter.y << ", " << globalCenter.z - << ") to (" << componentCenter.x << ", " << componentCenter.y << ", " << componentCenter.z << ")" << std::endl; - logFile << "Ray Result: " << (rayBlocked ? "BLOCKED (internal)" : "CLEAR (outer shell)") << std::endl; - logFile << "Final Vote: " << (rayBlocked ? "NO (0)" : "YES (1)") << std::endl; + logFile << " Ray Test: " << (rayBlocked ? "BLOCKED" : "CLEAR") << std::endl; + logFile << " Vote: " << (rayBlocked ? "0" : "1") << std::endl; logFile.close(); } } @@ -3061,6 +3038,27 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti } } + // Log final votes in same visibility file + if (target12v) { + try { + SessionInfo sessionInfo = GetSessionInfo(); + if (sessionInfo.is_valid) { + xstring workdir = sessionInfo.session->GetCurrentDirectory(); + std::string workingDir = XStringToString(workdir); + std::string logPath = workingDir + "\\12v4000g03_visibility.txt"; + std::ofstream logFile(logPath, std::ios::app); + if (logFile.is_open()) { + logFile << "\n=== FINAL RESULTS ===" << std::endl; + auto votesIt = result.visibilityVotes.find(target12v->featureId); + int totalVotes = (votesIt != result.visibilityVotes.end()) ? votesIt->second : 0; + logFile << "Total Votes: " << totalVotes << " out of " << numDirections << " directions" << std::endl; + logFile << "Classification: " << (totalVotes >= minVotes ? "OUTER SHELL" : "INTERNAL") << std::endl; + logFile.close(); + } + } + } catch (...) {} + } + // Step 6 removed: Same-path sharing mechanism deleted to ensure independent component evaluation return result; @@ -3561,12 +3559,22 @@ bool CreoManager::IsComponentBlockedFromCenter(const Vector3D& globalCenter, // We only care about intersections beyond this distance (outward extension) double distToComponentCenter = (componentCenter - globalCenter).length(); + // Debug for 12V4000G03 + bool isTarget12v = (component.name.find("12v4000g03") != std::string::npos || + component.name.find("12V4000G03") != std::string::npos); + + int checkedCount = 0; + int intersectionCount = 0; + std::string nearestBlocker; + double nearestDistance = 1e9; + // Check intersection with all other components for (const ComponentItem& otherComp : allComponents) { // Skip self if (otherComp.featureId == component.featureId) { continue; } + checkedCount++; // AABB ray intersection test // We check if ray intersects other component's AABB beyond the component center @@ -3613,6 +3621,14 @@ bool CreoManager::IsComponentBlockedFromCenter(const Vector3D& globalCenter, if (tMin <= tMax && tMin >= 0) { // Calculate the actual distance of the intersection point double intersectionDistance = tMin; + intersectionCount++; + + // Track nearest potential blocker for debugging + if (isTarget12v && intersectionDistance < nearestDistance) { + nearestDistance = intersectionDistance; + nearestBlocker = otherComp.name + " (ID:" + std::to_string(otherComp.featureId) + + ") at dist=" + std::to_string(intersectionDistance); + } // Only consider it blocked if intersection is beyond the component center if (intersectionDistance > distToComponentCenter) { @@ -3621,6 +3637,7 @@ bool CreoManager::IsComponentBlockedFromCenter(const Vector3D& globalCenter, } } + return false; // Component is not blocked (is outer shell) } diff --git a/CreoManager.cpp.backup b/CreoManager.cpp.backup new file mode 100644 index 0000000..c513e0e --- /dev/null +++ b/CreoManager.cpp.backup @@ -0,0 +1,3648 @@ +#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 +#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 ""; + } +} + +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::BuildComponentPath(wfcWComponentPath_ptr componentPath) { + if (!componentPath) return ""; + + try { + pfcSolid_ptr compSolid = componentPath->GetLeaf(); + if (!compSolid) return ""; + + pfcModel_ptr compModel = pfcModel::cast(compSolid); + if (!compModel) return ""; + + xstring nameXStr = compModel->GetFileName(); + return XStringToString(nameXStr); + } 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; +} + +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; + } + } + + return max_level; + + } + 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; + result.analysis_parameters.analysis_strategy = "external_space_ray_grid_detection"; + + if (!is_assembly) { + result.error_message = "Ray grid 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 RAY GRID ANALYSIS - External Space Detection Algorithm + RayGridAnalysisData rayAnalysis = PerformRayGridAnalysis(assembly); + const std::unordered_set& externalComponentIds = rayAnalysis.externalComponentIds; + const std::vector& allComponents = rayAnalysis.components; + const AABB& globalAABB = rayAnalysis.globalAABB; + + // 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 component path information + item.part_file = BuildComponentPath(wPath); + if (item.part_file.empty()) { + item.part_file = comp_name; // Fallback to component name + } + + // 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.part_path = BuildComponentFullPath(wPath, assemblyName); + if (item.part_path.empty()) { + item.part_path = item.part_file; // Fallback to file name + } + } catch (...) { + item.part_path = item.part_file; // Fallback on error + } + + // RAY GRID ANALYSIS: Simple binary decision based on first contact detection + bool isExternalComponent = (externalComponentIds.find(stableFeatureId) != externalComponentIds.end()); + + if (isExternalComponent) { + // Component is hit by rays from external space - it's part of the shell + item.confidence = 0.0; // No deletion confidence - must preserve + item.recommendation = "KEEP"; + item.reason = "External shell component detected by ray grid analysis from outside"; + item.is_deletable = false; + result.shell_features_count++; + } else { + // Component is never the first contact point - it's internal + item.confidence = 0.95; // High deletion confidence + item.recommendation = "DELETE"; + item.reason = "Internal component not visible from external space"; + item.is_deletable = true; + result.internal_features_count++; + 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 = item.part_file; // Component file name + deletion.part_path = item.part_path; // Component full path + deletion.component_type = "COMPONENT"; + + 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 + } +} + + +// 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; +} + +// DELETED: PerformMultiDirectionalProjectionAnalysis - replaced by RayGridAnalysis +// This function has been removed as part of the algorithm simplification. +// The new ray grid detection algorithm provides better accuracy with simpler logic. +/* +CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly) { + ProjectionAnalysisData result; + + if (!assembly) return result; + + try { + // Define projection data structure for the entire function + struct ProjectionData { + double support; // Projection support value + double thickness; // Component thickness in this direction + int featureId; + }; + + // Step 1: Collect all components + result.components = CollectAllComponents(assembly); + if (result.components.empty()) return result; + + // 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 (96 directions for good coverage) + const int numDirections = 96; + std::vector directions = SampleDirections(numDirections); + + // Voting map: component ID -> number of directions where it's visible + result.visibilityVotes.reserve(result.components.size()); + + // OBB usage statistics for performance analysis + int totalOBBUsage = 0; + int totalAABBUsage = 0; + + // Step 4: Original single-round voting algorithm (DISABLED - replaced by layered algorithm) + // This step is commented out to avoid redundant computation since Step 4.5-4.7 implements + // the improved layered voting algorithm that solves the Top-K "free ride" problem + /* + for (const Vector3D& direction : directions) { + std::vector projections; + projections.reserve(result.components.size()); + + // Calculate projections and thickness for all components + for (const ComponentItem& comp : result.components) { + // Smart selection: use OBB for elongated parts, AABB for regular parts + bool useOBB = ShouldUseOBB(comp); + double support; + + if (useOBB) { + support = CalculateOBBProjectionSupport(comp.worldOBB, direction); + totalOBBUsage++; + } else { + support = CalculateProjectionSupport(comp.worldAABB, direction); + totalAABBUsage++; + } + + // Calculate thickness consistently with support calculation + double thickness; + if (useOBB) { + thickness = CalculateOBBThickness(comp.worldOBB, direction); + } else { + // AABB thickness calculation (original method) + Vector3D minSupport, maxSupport; + minSupport.x = (direction.x < 0) ? comp.worldAABB.maxPoint.x : comp.worldAABB.minPoint.x; + minSupport.y = (direction.y < 0) ? comp.worldAABB.maxPoint.y : comp.worldAABB.minPoint.y; + minSupport.z = (direction.z < 0) ? comp.worldAABB.maxPoint.z : comp.worldAABB.minPoint.z; + maxSupport.x = (direction.x >= 0) ? comp.worldAABB.maxPoint.x : comp.worldAABB.minPoint.x; + maxSupport.y = (direction.y >= 0) ? comp.worldAABB.maxPoint.y : comp.worldAABB.minPoint.y; + maxSupport.z = (direction.z >= 0) ? comp.worldAABB.maxPoint.z : comp.worldAABB.minPoint.z; + thickness = std::abs((maxSupport - minSupport).dot(direction)); + } + + projections.push_back({support, thickness, comp.featureId}); + } + + // Sort by support value (highest first) + std::sort(projections.begin(), projections.end(), + [](const ProjectionData& a, const ProjectionData& b) { + return a.support > b.support; + }); + + if (projections.empty()) continue; + + double bestSupport = projections.front().support; + + // Calculate median thickness for adaptive window + std::vector thicknesses; + thicknesses.reserve(projections.size()); + for (const auto& p : projections) { + thicknesses.push_back(p.thickness); + } + std::nth_element(thicknesses.begin(), + thicknesses.begin() + thicknesses.size() / 2, + thicknesses.end()); + double medianThickness = thicknesses[thicknesses.size() / 2]; + + // Adaptive depth window (max of absolute and relative) + double assemblyDiagonal = result.globalAABB.getDiagonalLength(); + double absoluteWindow = std::max(1e-6, 0.002 * assemblyDiagonal); // 0.2% of diagonal + double relativeWindow = 0.15 * medianThickness; // 15% of median thickness + double depthWindow = std::max(absoluteWindow, relativeWindow); + + // Mark visible components (only within depth window - no Top-K pollution) + for (const auto& proj : projections) { + if (proj.support >= bestSupport - depthWindow) { + result.visibilityVotes[proj.featureId]++; + } else { + break; // Components further back are not visible + } + } + } + */ + + // ===== LAYERED VOTING ALGORITHM ===== + // Clear previous votes and implement two-round layered analysis to solve the "free ride" problem + + // Step 4.5: First Round - Strict outer identification using very small window + std::unordered_map firstRoundVotes; + double assemblyDiagonal = result.globalAABB.getDiagonalLength(); + double strictWindow = assemblyDiagonal * 0.001; // Extremely strict window (0.1% of diagonal) + + for (const Vector3D& direction : directions) { + std::vector projections; + projections.reserve(result.components.size()); + + // Calculate projections for all components + for (const ComponentItem& comp : result.components) { + bool useOBB = ShouldUseOBB(comp); + double support = useOBB ? CalculateOBBProjectionSupport(comp.worldOBB, direction) + : CalculateProjectionSupport(comp.worldAABB, direction); + double thickness = useOBB ? CalculateOBBThickness(comp.worldOBB, direction) + : std::abs((comp.worldAABB.maxPoint - comp.worldAABB.minPoint).dot(direction)); + + projections.push_back({support, thickness, comp.featureId}); + } + + // Sort by support value (highest first) + std::sort(projections.begin(), projections.end(), + [](const ProjectionData& a, const ProjectionData& b) { + return a.support > b.support; + }); + + if (!projections.empty()) { + double bestSupport = projections.front().support; + + // Only vote for absolutely outermost components using strict window + for (const auto& proj : projections) { + if (proj.support >= bestSupport - strictWindow) { + firstRoundVotes[proj.featureId]++; + } else { + break; // Stop at first component outside strict window + } + } + } + } + + // Identify confirmed outer components (need high visibility ratio) + std::unordered_set confirmedOuterIds; + double strictVisibilityRatio = 0.80; // 80% of directions must see it + int strictMinVotes = (int)(strictVisibilityRatio * numDirections); + + for (const auto& kvp : firstRoundVotes) { + if (kvp.second >= strictMinVotes) { + confirmedOuterIds.insert(kvp.first); + } + } + + // Step 4.6: Second Round - Analyze remaining components without outer interference + std::unordered_map secondRoundVotes; + + // Create filtered component list excluding confirmed outer components + std::vector remainingComponents; + for (const ComponentItem& comp : result.components) { + if (confirmedOuterIds.find(comp.featureId) == confirmedOuterIds.end()) { + remainingComponents.push_back(comp); + } + } + + if (!remainingComponents.empty()) { + // Calculate new global AABB for remaining components + AABB remainingAABB; + for (const ComponentItem& comp : remainingComponents) { + remainingAABB.expand(comp.worldAABB.minPoint); + remainingAABB.expand(comp.worldAABB.maxPoint); + } + + for (const Vector3D& direction : directions) { + std::vector projections; + projections.reserve(remainingComponents.size()); + + // Calculate median thickness for adaptive window (using only remaining components) + std::vector thicknesses; + thicknesses.reserve(remainingComponents.size()); + + for (const ComponentItem& comp : remainingComponents) { + bool useOBB = ShouldUseOBB(comp); + double support = useOBB ? CalculateOBBProjectionSupport(comp.worldOBB, direction) + : CalculateProjectionSupport(comp.worldAABB, direction); + double thickness = useOBB ? CalculateOBBThickness(comp.worldOBB, direction) + : std::abs((comp.worldAABB.maxPoint - comp.worldAABB.minPoint).dot(direction)); + + projections.push_back({support, thickness, comp.featureId}); + thicknesses.push_back(thickness); + } + + // Sort by support value + std::sort(projections.begin(), projections.end(), + [](const ProjectionData& a, const ProjectionData& b) { + return a.support > b.support; + }); + + if (!projections.empty() && !thicknesses.empty()) { + std::nth_element(thicknesses.begin(), + thicknesses.begin() + thicknesses.size() / 2, + thicknesses.end()); + double medianThickness = thicknesses[thicknesses.size() / 2]; + + double bestSupport = projections.front().support; + double remainingDiagonal = remainingAABB.getDiagonalLength(); + double absoluteWindow = std::max(1e-6, 0.002 * remainingDiagonal); + double relativeWindow = 0.15 * medianThickness; + double depthWindow = std::max(absoluteWindow, relativeWindow); + + // Vote for visible components in remaining set + for (const auto& proj : projections) { + if (proj.support >= bestSupport - depthWindow) { + secondRoundVotes[proj.featureId]++; + } else { + break; + } + } + } + } + } + + // Step 4.7: Merge results - Confirmed outer components + high-visibility remaining components + // Initialize voting results with layered analysis (no need to clear since original voting is disabled) + + // Add confirmed outer components with max votes (force keep) + for (int outerId : confirmedOuterIds) { + result.visibilityVotes[outerId] = numDirections; // Force maximum visibility + } + + // Add second round votes for remaining components + for (const auto& kvp : secondRoundVotes) { + result.visibilityVotes[kvp.first] = kvp.second; + } + + // Step 5: Determine outer components based on layered voting results + // With layered algorithm protection, we can use more reasonable thresholds + double minVisibilityRatio = 0.10; // 10% of directions (restored from 4% due to layered protection) + int minVotes = std::max(3, (int)(minVisibilityRatio * numDirections)); + + for (const auto& kvp : result.visibilityVotes) { + if (kvp.second >= minVotes) { + result.outerComponentIds.insert(kvp.first); + } + } + + // Step 6: Apply boundary contact exemption as additional safety net + // With layered algorithm providing primary protection, boundary detection serves as backup + double boundaryTolerance = assemblyDiagonal * 0.005; // 0.5% tolerance (relaxed due to layered protection) + + for (const ComponentItem& comp : result.components) { + // Check if component AABB touches assembly boundary + if (abs(comp.worldAABB.minPoint.x - result.globalAABB.minPoint.x) <= boundaryTolerance || + abs(comp.worldAABB.maxPoint.x - result.globalAABB.maxPoint.x) <= boundaryTolerance || + abs(comp.worldAABB.minPoint.y - result.globalAABB.minPoint.y) <= boundaryTolerance || + abs(comp.worldAABB.maxPoint.y - result.globalAABB.maxPoint.y) <= boundaryTolerance || + abs(comp.worldAABB.minPoint.z - result.globalAABB.minPoint.z) <= boundaryTolerance || + abs(comp.worldAABB.maxPoint.z - result.globalAABB.maxPoint.z) <= boundaryTolerance) { + // Force mark as outer component regardless of voting results + result.outerComponentIds.insert(comp.featureId); + } + } + + // Safety check: ensure at least some components are marked as outer + if (result.outerComponentIds.empty() && !result.components.empty()) { + // Emergency fallback: use larger tolerance if nothing was detected + double emergencyTolerance = assemblyDiagonal * 0.01; // 1% emergency tolerance + for (const ComponentItem& comp : result.components) { + if (abs(comp.worldAABB.minPoint.x - result.globalAABB.minPoint.x) <= emergencyTolerance || + abs(comp.worldAABB.maxPoint.x - result.globalAABB.maxPoint.x) <= emergencyTolerance || + abs(comp.worldAABB.minPoint.y - result.globalAABB.minPoint.y) <= emergencyTolerance || + abs(comp.worldAABB.maxPoint.y - result.globalAABB.maxPoint.y) <= emergencyTolerance || + abs(comp.worldAABB.minPoint.z - result.globalAABB.minPoint.z) <= emergencyTolerance || + abs(comp.worldAABB.maxPoint.z - result.globalAABB.maxPoint.z) <= emergencyTolerance) { + result.outerComponentIds.insert(comp.featureId); + } + } + } + + } catch (...) { + // Analysis failed, return empty set (conservative: don't delete anything) + } + + 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)); +} + +// Determine whether to use OBB based on component characteristics +bool CreoManager::ShouldUseOBB(const ComponentItem& comp) { + Vector3D size = comp.worldAABB.diagonal(); + if (size.length() < 1e-6) return false; // Degenerate case + + // Enhanced multi-criteria OBB selection strategy + + // Strategy 1: Geometric complexity analysis + double maxDim = std::max({size.x, size.y, size.z}); + double minDim = std::min({size.x, size.y, size.z}); + double medDim = size.x + size.y + size.z - maxDim - minDim; + + double aspectRatio = (minDim > 1e-6) ? (maxDim / minDim) : 1.0; + double flatnessRatio = (minDim > 1e-6) ? (medDim / minDim) : 1.0; + + // Strategy 2: Size-based efficiency threshold + double volume = size.x * size.y * size.z; + double surfaceArea = 2.0 * (size.x * size.y + size.y * size.z + size.x * size.z); + double compactness = (surfaceArea > 1e-6) ? (volume / surfaceArea) : 0.0; + + // Strategy 3: Component classification + bool isElongated = ComponentClassifier::IsElongatedPart(comp.name); + bool isFastener = ComponentClassifier::IsFastener(comp.name); + bool isComplexShape = aspectRatio > 2.5 || flatnessRatio > 2.0; + + // Decision matrix with weighted criteria + int obbScore = 0; + + // Geometric criteria (primary) + if (aspectRatio > 4.0) obbScore += 3; // Highly elongated + else if (aspectRatio > 2.5) obbScore += 2; // Moderately elongated + else if (aspectRatio > 1.8) obbScore += 1; // Slightly elongated + + if (flatnessRatio > 3.0) obbScore += 2; // Very flat components + else if (flatnessRatio > 2.0) obbScore += 1; // Moderately flat + + // Naming pattern criteria + if (isElongated) obbScore += 2; + if (isFastener && aspectRatio > 2.0) obbScore += 1; // Long fasteners benefit from OBB + + // Size and complexity criteria + if (volume > 1e-2 && compactness < 0.1) obbScore += 1; // Large, non-compact parts + if (volume < 1e-4) obbScore -= 2; // Tiny parts penalized + + // Complex geometric shapes (non-regular bounding boxes) + if (isComplexShape && volume > 1e-3) obbScore += 1; + + // Performance consideration: limit OBB usage for very small components + if (maxDim < 1e-2) obbScore -= 1; // Small components less likely to benefit + + // Final decision: OBB if score >= 2 + return obbScore >= 2; +} + +// 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); +} + +// ======================================================= +// RAY GRID ANALYSIS - EXTERNAL SPACE DETECTION ALGORITHM +// ======================================================= + +CreoManager::RayGridAnalysisData CreoManager::PerformRayGridAnalysis(pfcAssembly_ptr assembly) { + RayGridAnalysisData result; + + if (!assembly) return result; + + try { + // Step 1: Collect all components (reuse existing function) + result.components = CollectAllComponents(assembly); + if (result.components.empty()) return result; + + // 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: Create extended outer boundary box (expand by 10%) + Vector3D center = result.globalAABB.getCenter(); + Vector3D halfSize = (result.globalAABB.maxPoint - result.globalAABB.minPoint) * 0.55; // 0.5 + 0.05 expansion + result.outerBoundingBox.minPoint = center - halfSize; + result.outerBoundingBox.maxPoint = center + halfSize; + + // Step 4: Generate ray grid from 6 faces of outer boundary + auto rays = GenerateRayGrid(result.outerBoundingBox, result.gridDensity); + result.totalRays = (int)rays.size(); + + // Step 5: For each ray, find first contact component + for (const auto& ray : rays) { + int firstContactId = FindFirstContactComponent(ray.first, ray.second, result.components); + if (firstContactId >= 0) { + result.externalComponentIds.insert(firstContactId); + } + } + + return result; + + } catch (...) { + // Return empty result on any error + return RayGridAnalysisData(); + } +} + +std::vector> +CreoManager::GenerateRayGrid(const AABB& outerBox, int density) { + std::vector> rays; + rays.reserve(6 * density * density); // 6 faces * density^2 rays per face + + Vector3D min = outerBox.minPoint; + Vector3D max = outerBox.maxPoint; + + // Generate rays from each face of the outer bounding box + + // Face 1: X-min face (left), rays pointing in +X direction + for (int i = 0; i < density; i++) { + for (int j = 0; j < density; j++) { + double y = min.y + (max.y - min.y) * i / (density - 1); + double z = min.z + (max.z - min.z) * j / (density - 1); + Vector3D origin(min.x, y, z); + Vector3D direction(1.0, 0.0, 0.0); + rays.push_back({origin, direction}); + } + } + + // Face 2: X-max face (right), rays pointing in -X direction + for (int i = 0; i < density; i++) { + for (int j = 0; j < density; j++) { + double y = min.y + (max.y - min.y) * i / (density - 1); + double z = min.z + (max.z - min.z) * j / (density - 1); + Vector3D origin(max.x, y, z); + Vector3D direction(-1.0, 0.0, 0.0); + rays.push_back({origin, direction}); + } + } + + // Face 3: Y-min face (front), rays pointing in +Y direction + for (int i = 0; i < density; i++) { + for (int j = 0; j < density; j++) { + double x = min.x + (max.x - min.x) * i / (density - 1); + double z = min.z + (max.z - min.z) * j / (density - 1); + Vector3D origin(x, min.y, z); + Vector3D direction(0.0, 1.0, 0.0); + rays.push_back({origin, direction}); + } + } + + // Face 4: Y-max face (back), rays pointing in -Y direction + for (int i = 0; i < density; i++) { + for (int j = 0; j < density; j++) { + double x = min.x + (max.x - min.x) * i / (density - 1); + double z = min.z + (max.z - min.z) * j / (density - 1); + Vector3D origin(x, max.y, z); + Vector3D direction(0.0, -1.0, 0.0); + rays.push_back({origin, direction}); + } + } + + // Face 5: Z-min face (bottom), rays pointing in +Z direction + for (int i = 0; i < density; i++) { + for (int j = 0; j < density; j++) { + double x = min.x + (max.x - min.x) * i / (density - 1); + double y = min.y + (max.y - min.y) * j / (density - 1); + Vector3D origin(x, y, min.z); + Vector3D direction(0.0, 0.0, 1.0); + rays.push_back({origin, direction}); + } + } + + // Face 6: Z-max face (top), rays pointing in -Z direction + for (int i = 0; i < density; i++) { + for (int j = 0; j < density; j++) { + double x = min.x + (max.x - min.x) * i / (density - 1); + double y = min.y + (max.y - min.y) * j / (density - 1); + Vector3D origin(x, y, max.z); + Vector3D direction(0.0, 0.0, -1.0); + rays.push_back({origin, direction}); + } + } + + return rays; +} + +int CreoManager::FindFirstContactComponent(const Vector3D& rayOrigin, const Vector3D& rayDirection, + const std::vector& components) { + double maxSupport = -std::numeric_limits::infinity(); + int firstContactId = -1; + + // For each component, calculate its projection support value in the ray direction + // The component with the highest support value is the "first contact" component + for (const ComponentItem& comp : components) { + double support; + + // Use the same smart selection as in projection analysis + bool useOBB = ShouldUseOBB(comp); + if (useOBB) { + support = CalculateOBBProjectionSupport(comp.worldOBB, rayDirection); + } else { + support = CalculateProjectionSupport(comp.worldAABB, rayDirection); + } + + // First contact is the component with maximum projection support + if (support > maxSupport) { + maxSupport = support; + firstContactId = comp.featureId; + } + } + + return firstContactId; +} + diff --git a/MFCCreoDll/x64/Debug/AuthManager.obj b/MFCCreoDll/x64/Debug/AuthManager.obj index 670803c..69692d5 100644 Binary files a/MFCCreoDll/x64/Debug/AuthManager.obj and b/MFCCreoDll/x64/Debug/AuthManager.obj differ diff --git a/MFCCreoDll/x64/Debug/CreoManager.obj b/MFCCreoDll/x64/Debug/CreoManager.obj index 696d64d..f6200f4 100644 Binary files a/MFCCreoDll/x64/Debug/CreoManager.obj and b/MFCCreoDll/x64/Debug/CreoManager.obj differ diff --git a/MFCCreoDll/x64/Debug/GeometryAnalyzer.obj b/MFCCreoDll/x64/Debug/GeometryAnalyzer.obj index 99ed83b..50249d7 100644 Binary files a/MFCCreoDll/x64/Debug/GeometryAnalyzer.obj and b/MFCCreoDll/x64/Debug/GeometryAnalyzer.obj differ diff --git a/MFCCreoDll/x64/Debug/HierarchyStatisticsAnalyzer.obj b/MFCCreoDll/x64/Debug/HierarchyStatisticsAnalyzer.obj index 1ab5c39..1ac490f 100644 Binary files a/MFCCreoDll/x64/Debug/HierarchyStatisticsAnalyzer.obj and b/MFCCreoDll/x64/Debug/HierarchyStatisticsAnalyzer.obj differ diff --git a/MFCCreoDll/x64/Debug/HttpRouter.obj b/MFCCreoDll/x64/Debug/HttpRouter.obj index 8ecec50..d640ff5 100644 Binary files a/MFCCreoDll/x64/Debug/HttpRouter.obj and b/MFCCreoDll/x64/Debug/HttpRouter.obj differ diff --git a/MFCCreoDll/x64/Debug/HttpServer.obj b/MFCCreoDll/x64/Debug/HttpServer.obj index 12edbb0..f7ce1fd 100644 Binary files a/MFCCreoDll/x64/Debug/HttpServer.obj and b/MFCCreoDll/x64/Debug/HttpServer.obj differ diff --git a/MFCCreoDll/x64/Debug/JsonHelper.obj b/MFCCreoDll/x64/Debug/JsonHelper.obj index 9f17fd9..0098db5 100644 Binary files a/MFCCreoDll/x64/Debug/JsonHelper.obj and b/MFCCreoDll/x64/Debug/JsonHelper.obj differ diff --git a/MFCCreoDll/x64/Debug/Logger.obj b/MFCCreoDll/x64/Debug/Logger.obj index 163fe36..9f993c4 100644 Binary files a/MFCCreoDll/x64/Debug/Logger.obj and b/MFCCreoDll/x64/Debug/Logger.obj differ diff --git a/MFCCreoDll/x64/Debug/MFCCreoDll.obj b/MFCCreoDll/x64/Debug/MFCCreoDll.obj index 554034a..ba1eee2 100644 Binary files a/MFCCreoDll/x64/Debug/MFCCreoDll.obj and b/MFCCreoDll/x64/Debug/MFCCreoDll.obj differ diff --git a/MFCCreoDll/x64/Debug/MFCCreoDll.pch b/MFCCreoDll/x64/Debug/MFCCreoDll.pch index f7aa2ce..18e6da0 100644 Binary files a/MFCCreoDll/x64/Debug/MFCCreoDll.pch and b/MFCCreoDll/x64/Debug/MFCCreoDll.pch differ diff --git a/MFCCreoDll/x64/Debug/ModelAnalyzer.obj b/MFCCreoDll/x64/Debug/ModelAnalyzer.obj index 5d1f477..fab5a2b 100644 Binary files a/MFCCreoDll/x64/Debug/ModelAnalyzer.obj and b/MFCCreoDll/x64/Debug/ModelAnalyzer.obj differ diff --git a/MFCCreoDll/x64/Debug/ModelSearchEngine.obj b/MFCCreoDll/x64/Debug/ModelSearchEngine.obj index ba499da..e10677d 100644 Binary files a/MFCCreoDll/x64/Debug/ModelSearchEngine.obj and b/MFCCreoDll/x64/Debug/ModelSearchEngine.obj differ diff --git a/MFCCreoDll/x64/Debug/ModelSearchHandler.obj b/MFCCreoDll/x64/Debug/ModelSearchHandler.obj index 07a20d4..fd4b2c7 100644 Binary files a/MFCCreoDll/x64/Debug/ModelSearchHandler.obj and b/MFCCreoDll/x64/Debug/ModelSearchHandler.obj differ diff --git a/MFCCreoDll/x64/Debug/PathDeleteManager.obj b/MFCCreoDll/x64/Debug/PathDeleteManager.obj index d368960..c179104 100644 Binary files a/MFCCreoDll/x64/Debug/PathDeleteManager.obj and b/MFCCreoDll/x64/Debug/PathDeleteManager.obj differ diff --git a/MFCCreoDll/x64/Debug/ServerManager.obj b/MFCCreoDll/x64/Debug/ServerManager.obj index 1cef835..1b5fd28 100644 Binary files a/MFCCreoDll/x64/Debug/ServerManager.obj and b/MFCCreoDll/x64/Debug/ServerManager.obj differ diff --git a/MFCCreoDll/x64/Debug/ShellExportHandler.obj b/MFCCreoDll/x64/Debug/ShellExportHandler.obj index fa49d1c..df82bd8 100644 Binary files a/MFCCreoDll/x64/Debug/ShellExportHandler.obj and b/MFCCreoDll/x64/Debug/ShellExportHandler.obj differ diff --git a/MFCCreoDll/x64/Debug/ShrinkwrapManager.obj b/MFCCreoDll/x64/Debug/ShrinkwrapManager.obj index 54109c3..6fa7a85 100644 Binary files a/MFCCreoDll/x64/Debug/ShrinkwrapManager.obj and b/MFCCreoDll/x64/Debug/ShrinkwrapManager.obj differ diff --git a/MFCCreoDll/x64/Debug/WebSocketServer.obj b/MFCCreoDll/x64/Debug/WebSocketServer.obj index 273c2b6..e671a6d 100644 Binary files a/MFCCreoDll/x64/Debug/WebSocketServer.obj and b/MFCCreoDll/x64/Debug/WebSocketServer.obj differ diff --git a/MFCCreoDll/x64/Debug/pch.obj b/MFCCreoDll/x64/Debug/pch.obj index b931709..cebf6c5 100644 Binary files a/MFCCreoDll/x64/Debug/pch.obj and b/MFCCreoDll/x64/Debug/pch.obj differ diff --git a/MFCCreoDll/x64/Debug/vc143.idb b/MFCCreoDll/x64/Debug/vc143.idb index 9d7a44c..6890758 100644 Binary files a/MFCCreoDll/x64/Debug/vc143.idb and b/MFCCreoDll/x64/Debug/vc143.idb differ diff --git a/MFCCreoDll/x64/Debug/vc143.pdb b/MFCCreoDll/x64/Debug/vc143.pdb index f05b70d..3f5996f 100644 Binary files a/MFCCreoDll/x64/Debug/vc143.pdb and b/MFCCreoDll/x64/Debug/vc143.pdb differ diff --git a/x64/Debug/MFCCreoDll.dll b/x64/Debug/MFCCreoDll.dll index 52cec80..c74e126 100644 Binary files a/x64/Debug/MFCCreoDll.dll and b/x64/Debug/MFCCreoDll.dll differ diff --git a/x64/Debug/MFCCreoDll.pdb b/x64/Debug/MFCCreoDll.pdb index 87ae2f2..1c98f2d 100644 Binary files a/x64/Debug/MFCCreoDll.pdb and b/x64/Debug/MFCCreoDll.pdb differ