- Ray now properly starts from global center through component center - Only checks intersections beyond component center (outward extension) - Removed incorrect center-point direction check - Directly tests AABB intersection without pre-filtering This fixes the issue where internal components were incorrectly marked as shell due to skipping the portion between global center and component center. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
3578 lines
144 KiB
C++
3578 lines
144 KiB
C++
#include "pch.h"
|
||
#include "CreoManager.h"
|
||
#include <wfcAssembly.h>
|
||
#include <pfcBase.h>
|
||
#include <pfcGlobal.h>
|
||
#include <pfcExport.h>
|
||
#include <pfcModel.h>
|
||
#include <pfcFeature.h>
|
||
#include <pfcSolid.h>
|
||
#include <wfcSolid.h>
|
||
#include <wfcFeatureInstructions.h>
|
||
#include <pfcInterference.h>
|
||
#include <pfcSelect.h>
|
||
#include <pfcGeometry.h>
|
||
#include <stdcols.h>
|
||
#include <ctime>
|
||
#include <sstream>
|
||
|
||
#define _USE_MATH_DEFINES
|
||
#include <cmath>
|
||
#include <cstdlib>
|
||
#include <iomanip>
|
||
#include <fstream>
|
||
|
||
// Define PI constant for compatibility
|
||
#ifndef M_PI
|
||
#define M_PI 3.14159265358979323846
|
||
#endif
|
||
#include <windows.h>
|
||
#include <vector>
|
||
|
||
// Shell Analysis Algorithm Constants
|
||
const int SHELL_ANALYSIS_NUM_DIRECTIONS = 6; // 6-direction orthogonal projection sampling
|
||
const int SHELL_ANALYSIS_GRID_RESOLUTION = 96; // 网格分辨率96x96
|
||
const double SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR = 0.001; // 单方向最小可见占比0.1%
|
||
const double SHELL_ANALYSIS_MIN_VISIBILITY_RATIO = 0.03; // 最小可见率:3%方向可见即为外壳候选(降低阈值)
|
||
const int SHELL_ANALYSIS_MIN_VOTES = 3; // 最少得票数:至少3个方向可见(提高要求)
|
||
const double SHELL_ANALYSIS_HIGH_VISIBILITY_THRESHOLD = 0.20; // 高可见度阈值:20%以上强制保留
|
||
const double SHELL_ANALYSIS_MEDIUM_VISIBILITY_THRESHOLD = 0.06; // 中等可见度阈值:6-20%需要审查
|
||
const double SHELL_ANALYSIS_OCCLUSION_TOLERANCE = 0.5; // 遮挡容差(当前未使用)
|
||
#include <limits>
|
||
#include <map>
|
||
#include <unordered_map>
|
||
#include <string>
|
||
#include <set>
|
||
#include <unordered_set>
|
||
#include <cstdlib>
|
||
#include <algorithm>
|
||
#include <functional>
|
||
#include <cmath>
|
||
|
||
// 防止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<int>(wstr.length());
|
||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr_len, NULL, 0, NULL, NULL);
|
||
if (size_needed <= 0 || size_needed > 65535) { // 安全边界检查
|
||
return "";
|
||
}
|
||
|
||
std::string result(size_needed, 0);
|
||
int convert_result = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr_len, &result[0], size_needed, NULL, NULL);
|
||
if (convert_result != size_needed) {
|
||
return ""; // 转换失败
|
||
}
|
||
|
||
return result;
|
||
}
|
||
catch (const std::bad_alloc&) {
|
||
return ""; // 内存分配失败
|
||
}
|
||
catch (const std::length_error&) {
|
||
return ""; // 字符串长度错误
|
||
}
|
||
catch (...) {
|
||
return "";
|
||
}
|
||
}
|
||
|
||
std::string CreoManager::BuildComponentFullPath(wfcWComponentPath_ptr componentPath, const std::string& assemblyName) {
|
||
if (!componentPath) return "";
|
||
|
||
std::vector<std::string> pathComponents;
|
||
|
||
// 添加根装配体名称
|
||
if (!assemblyName.empty()) {
|
||
pathComponents.push_back(assemblyName);
|
||
}
|
||
|
||
// 获取路径中的组件ID序列
|
||
xintsequence_ptr componentIds = componentPath->GetComponentIds();
|
||
pfcAssembly_ptr rootAsm = componentPath->GetRoot();
|
||
|
||
// 遍历每个组件ID,获取对应的名称
|
||
pfcAssembly_ptr currentAsm = rootAsm;
|
||
|
||
for (int i = 0; i < componentIds->getarraysize(); i++) {
|
||
int compId = componentIds->get(i);
|
||
|
||
// 从当前装配体获取组件特征
|
||
pfcFeature_ptr feat = currentAsm->GetFeatureById(compId);
|
||
pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feat);
|
||
|
||
// 获取组件名称
|
||
pfcModelDescriptor_ptr modelDesc = compFeat->GetModelDescr();
|
||
xstring nameXStr = modelDesc->GetFileName();
|
||
std::string compName = XStringToString(nameXStr);
|
||
pathComponents.push_back(compName);
|
||
|
||
// 如果是子装配体,进入下一层
|
||
if (i < componentIds->getarraysize() - 1) {
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
pfcModel_ptr nextModel = sessionInfo.session->GetModelFromDescr(modelDesc);
|
||
if (nextModel->GetType() == pfcMDL_ASSEMBLY) {
|
||
currentAsm = pfcAssembly::cast(nextModel);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 连接路径
|
||
std::string fullPath;
|
||
for (size_t i = 0; i < pathComponents.size(); i++) {
|
||
if (i > 0) fullPath += "/";
|
||
fullPath += pathComponents[i];
|
||
}
|
||
|
||
return fullPath;
|
||
}
|
||
|
||
xstring CreoManager::StringToXString(const std::string& str) {
|
||
try {
|
||
if (str.empty()) {
|
||
return xstring();
|
||
}
|
||
|
||
// 长度限制检查,防止过长字符串导致内存问题
|
||
if (str.length() > 65535) { // 合理的长度限制
|
||
return xstring();
|
||
}
|
||
|
||
// 验证输入字符串是否包含无效字符
|
||
for (char c : str) {
|
||
if (c == '\0' && &c != &str.back()) { // 中间包含null字符
|
||
return xstring();
|
||
}
|
||
}
|
||
|
||
// 使用MultiByteToWideChar进行正确的UTF-8解码转换
|
||
int str_len = static_cast<int>(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<std::string, std::string> CreoManager::ParseFilePath(const std::string& file_path) {
|
||
std::string dirname, filename;
|
||
|
||
if (file_path.empty()) {
|
||
return std::make_pair("", "");
|
||
}
|
||
|
||
// 支持Windows和Unix路径分隔符
|
||
size_t pos = file_path.find_last_of("/\\");
|
||
if (pos != std::string::npos) {
|
||
dirname = file_path.substr(0, pos);
|
||
filename = file_path.substr(pos + 1);
|
||
} else {
|
||
dirname = "";
|
||
filename = file_path;
|
||
}
|
||
|
||
// 基本验证:文件名不能为空
|
||
if (filename.empty()) {
|
||
return std::make_pair("", "");
|
||
}
|
||
|
||
// 验证文件名不包含非法字符
|
||
const std::string invalid_chars = "<>:\"|?*";
|
||
if (filename.find_first_of(invalid_chars) != std::string::npos) {
|
||
return std::make_pair("", "");
|
||
}
|
||
|
||
return std::make_pair(dirname, filename);
|
||
}
|
||
|
||
std::string CreoManager::GetCurrentTimeString() {
|
||
std::time_t now = std::time(nullptr);
|
||
std::tm* local_tm = std::localtime(&now);
|
||
|
||
std::ostringstream oss;
|
||
oss << std::put_time(local_tm, "%Y-%m-%d %H:%M:%S");
|
||
return oss.str();
|
||
}
|
||
|
||
std::string CreoManager::GetCurrentTimeStringISO() {
|
||
std::time_t now = std::time(nullptr);
|
||
std::tm* utc_tm = std::gmtime(&now);
|
||
|
||
std::ostringstream oss;
|
||
oss << std::put_time(utc_tm, "%Y-%m-%dT%H:%M:%S");
|
||
oss << ".000000Z"; // 添加微秒和UTC标识
|
||
return oss.str();
|
||
}
|
||
|
||
CreoManager::SessionInfo CreoManager::GetSessionInfo() {
|
||
SessionInfo info;
|
||
info.is_valid = false;
|
||
|
||
try {
|
||
info.session = pfcGetCurrentSessionWithCompatibility(pfcC4Compatible);
|
||
if (info.session) {
|
||
info.wSession = wfcWSession::cast(info.session);
|
||
if (info.wSession) {
|
||
// 获取版本信息
|
||
int version_num = info.wSession->GetReleaseNumericVersion();
|
||
xstring date_code = info.wSession->GetDisplayDateCode();
|
||
|
||
// 尝试获取真实的软件名称
|
||
std::string software_name = "Creo"; // 基础名称,如果无法获取更详细的名称
|
||
|
||
std::ostringstream version_str;
|
||
version_str << software_name << " " << version_num << ".0";
|
||
info.version = version_str.str();
|
||
info.build = XStringToString(date_code);
|
||
info.is_valid = true;
|
||
} else {
|
||
info.version = "Failed to get version";
|
||
info.build = "Failed to get build";
|
||
}
|
||
} else {
|
||
info.version = "Failed to connect to Creo";
|
||
info.build = "Failed to connect to Creo";
|
||
}
|
||
}
|
||
catch (...) {
|
||
info.version = "Failed to get version";
|
||
info.build = "Failed to get build";
|
||
}
|
||
|
||
return info;
|
||
}
|
||
|
||
std::string CreoManager::GetFileSize(const std::string& filepath) {
|
||
try {
|
||
if (filepath.empty()) {
|
||
return "Empty filepath";
|
||
}
|
||
|
||
// 复用现有的字符串转换逻辑
|
||
xstring xpath = StringToXString(filepath);
|
||
std::wstring wpath(xpath);
|
||
|
||
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
|
||
if (GetFileAttributesExW(wpath.c_str(), GetFileExInfoStandard, &fileInfo)) {
|
||
LARGE_INTEGER size;
|
||
size.HighPart = fileInfo.nFileSizeHigh;
|
||
size.LowPart = fileInfo.nFileSizeLow;
|
||
|
||
double file_size_mb = static_cast<double>(size.QuadPart) / (1024.0 * 1024.0);
|
||
|
||
std::ostringstream oss;
|
||
oss << std::fixed << std::setprecision(1) << file_size_mb << "MB";
|
||
return oss.str();
|
||
} else {
|
||
DWORD error = GetLastError();
|
||
std::ostringstream oss;
|
||
oss << "File access failed (Error: " << error << ") Path: " << filepath;
|
||
return oss.str();
|
||
}
|
||
}
|
||
catch (...) {
|
||
return "Exception in GetFileSize";
|
||
}
|
||
}
|
||
|
||
int CreoManager::SafeCalculateAssemblyLevels(wfcWAssembly_ptr assembly) {
|
||
try {
|
||
if (!assembly) {
|
||
return 1;
|
||
}
|
||
|
||
// 使用ComponentPath分析装配体层级深度
|
||
wfcWComponentPaths_ptr components = assembly->ListDisplayedComponents();
|
||
if (!components) {
|
||
return 1;
|
||
}
|
||
|
||
int component_count = components->getarraysize();
|
||
if (component_count == 0) {
|
||
return 1;
|
||
}
|
||
|
||
int max_level = 1;
|
||
// 移除组件数量限制,检查所有组件
|
||
|
||
for (int i = 0; i < component_count; i++) {
|
||
try {
|
||
wfcWComponentPath_ptr comp_path = components->get(i);
|
||
if (comp_path) {
|
||
// 使用GetComponentIds获取组件路径
|
||
xintsequence_ptr ids = comp_path->GetComponentIds();
|
||
if (ids) {
|
||
// 路径深度就是装配体层级
|
||
int path_depth = ids->getarraysize();
|
||
if (path_depth > max_level) {
|
||
max_level = path_depth;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (...) {
|
||
// 跳过有问题的组件
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// Add 1 to include root assembly level, consistent with hierarchy analysis
|
||
return max_level + 1;
|
||
|
||
}
|
||
catch (...) {
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
std::string CreoManager::GetModelFileSize(pfcModel_ptr model) {
|
||
try {
|
||
if (!model) {
|
||
return "0.0MB";
|
||
}
|
||
|
||
// 先检查模型是否可以安全调用GetDescr
|
||
try {
|
||
pfcModelDescriptor_ptr descr = model->GetDescr();
|
||
if (!descr) {
|
||
return "0.0MB";
|
||
}
|
||
} catch (...) {
|
||
// 如果GetDescr失败,可能是轻量级模型,尝试其他方法
|
||
try {
|
||
xstring origin = model->GetOrigin();
|
||
std::string origin_str = XStringToString(origin);
|
||
if (!origin_str.empty()) {
|
||
return GetFileSize(origin_str);
|
||
}
|
||
} catch (...) {
|
||
// 所有方法都失败,返回默认值
|
||
return "0.0MB";
|
||
}
|
||
return "0.0MB";
|
||
}
|
||
|
||
// 使用origin路径获取文件大小
|
||
try {
|
||
xstring origin = model->GetOrigin();
|
||
std::string origin_str = XStringToString(origin);
|
||
if (!origin_str.empty()) {
|
||
return GetFileSize(origin_str);
|
||
}
|
||
} catch (...) {
|
||
return "0.0MB";
|
||
}
|
||
|
||
return "0.0MB";
|
||
}
|
||
catch (...) {
|
||
return "0.0MB";
|
||
}
|
||
}
|
||
|
||
double CreoManager::ParseMBFromSizeString(const std::string& size_str) {
|
||
try {
|
||
if (size_str.find("MB") != std::string::npos) {
|
||
size_t mb_pos = size_str.find("MB");
|
||
std::string size_num = size_str.substr(0, mb_pos);
|
||
return std::stod(size_num);
|
||
}
|
||
return 0.0;
|
||
}
|
||
catch (...) {
|
||
return 0.0;
|
||
}
|
||
}
|
||
|
||
std::string CreoManager::CalculateAssemblyTotalSize(pfcModel_ptr model) {
|
||
try {
|
||
wfcWAssembly_ptr assembly = wfcWAssembly::cast(model);
|
||
if (!assembly) {
|
||
return "Not an assembly";
|
||
}
|
||
|
||
double total_size_bytes = 0;
|
||
int processed_count = 0;
|
||
|
||
// 首先添加主装配体文件大小
|
||
std::string main_size = GetModelFileSize(model);
|
||
double main_mb = ParseMBFromSizeString(main_size);
|
||
if (main_mb > 0) {
|
||
total_size_bytes += main_mb * 1024 * 1024;
|
||
processed_count++;
|
||
}
|
||
|
||
// 使用ListDisplayedComponents获取组件(保持原有逻辑)
|
||
wfcWComponentPaths_ptr components = assembly->ListDisplayedComponents();
|
||
if (components) {
|
||
int component_count = components->getarraysize();
|
||
|
||
for (int i = 0; i < component_count; i++) {
|
||
try {
|
||
wfcWComponentPath_ptr comp_path = components->get(i);
|
||
if (comp_path) {
|
||
pfcSolid_ptr leaf_solid = comp_path->GetLeaf();
|
||
if (leaf_solid) {
|
||
pfcModel_ptr comp_model = pfcModel::cast(leaf_solid);
|
||
if (comp_model) {
|
||
std::string comp_size = GetModelFileSize(comp_model);
|
||
double comp_mb = ParseMBFromSizeString(comp_size);
|
||
// Always count valid components, regardless of file size
|
||
processed_count++;
|
||
if (comp_mb > 0) {
|
||
total_size_bytes += comp_mb * 1024 * 1024;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (...) {
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 转换为MB并返回
|
||
double total_mb = total_size_bytes / (1024.0 * 1024.0);
|
||
std::ostringstream oss;
|
||
oss << std::fixed << std::setprecision(1) << total_mb << "MB (from " << processed_count << " files)";
|
||
return oss.str();
|
||
|
||
}
|
||
catch (...) {
|
||
return "Exception in CalculateAssemblyTotalSize";
|
||
}
|
||
}
|
||
|
||
ExportResult CreoManager::ExportModelToSTEP(const std::string& export_path, const std::string& geom_flags) {
|
||
ExportResult result;
|
||
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
|
||
if (!sessionInfo.is_valid) {
|
||
result.error_message = "Creo session not available";
|
||
return result;
|
||
}
|
||
|
||
try {
|
||
pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel();
|
||
if (!current_model) {
|
||
result.error_message = "No current model loaded";
|
||
return result;
|
||
}
|
||
|
||
// 检查导出路径是否有效
|
||
if (export_path.empty()) {
|
||
result.error_message = "Invalid export path";
|
||
return result;
|
||
}
|
||
|
||
// 检查模型类型是否支持导出
|
||
pfcModelType model_type = current_model->GetType();
|
||
if (model_type != pfcMDL_PART && model_type != pfcMDL_ASSEMBLY) {
|
||
result.error_message = "Model type not supported for export";
|
||
return result;
|
||
}
|
||
|
||
// 创建几何导出标志
|
||
pfcGeomExportFlags_ptr geometryFlags = pfcGeomExportFlags::Create();
|
||
|
||
// 创建STEP导出指令
|
||
pfcSTEPExportInstructions_ptr exportInstructions =
|
||
pfcSTEPExportInstructions::Create(geometryFlags);
|
||
|
||
// 执行导出 - 使用xrstring类型
|
||
xrstring export_path_xrstr = export_path.c_str();
|
||
current_model->Export(export_path_xrstr, pfcExportInstructions::cast(exportInstructions));
|
||
|
||
// 检查导出文件是否存在(使用Windows API)
|
||
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
|
||
if (GetFileAttributesExA(export_path.c_str(), GetFileExInfoStandard, &fileInfo)) {
|
||
result.success = true;
|
||
result.export_path = export_path;
|
||
result.file_size = GetFileSize(export_path);
|
||
result.format = "step";
|
||
result.export_time = GetCurrentTimeStringISO();
|
||
result.software = "Creo Parametric";
|
||
|
||
// 获取原始文件信息
|
||
try {
|
||
xstring name_xstr = current_model->GetFileName();
|
||
result.original_file = XStringToString(name_xstr);
|
||
} catch (...) {
|
||
result.original_file = "Unknown";
|
||
}
|
||
|
||
// 解析目录和文件名
|
||
size_t last_slash = export_path.find_last_of("\\/");
|
||
if (last_slash != std::string::npos) {
|
||
result.dirname = export_path.substr(0, last_slash);
|
||
result.filename = export_path.substr(last_slash + 1);
|
||
} else {
|
||
result.dirname = "";
|
||
result.filename = export_path;
|
||
}
|
||
|
||
} else {
|
||
result.error_message = "Export file not created";
|
||
}
|
||
|
||
}
|
||
catch (...) {
|
||
result.error_message = "Export operation failed";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 保存模型功能实现
|
||
SaveResult CreoManager::SaveModel() {
|
||
SaveResult result;
|
||
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
|
||
if (!sessionInfo.is_valid) {
|
||
result.error_message = "Creo session not available";
|
||
return result;
|
||
}
|
||
|
||
try {
|
||
pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel();
|
||
if (!current_model) {
|
||
result.error_message = "No current model loaded";
|
||
return result;
|
||
}
|
||
|
||
// 获取文件信息
|
||
xstring original_name = current_model->GetFileName();
|
||
result.original_file = XStringToString(original_name);
|
||
result.software = "Creo Parametric";
|
||
|
||
// 执行保存操作
|
||
current_model->Save();
|
||
|
||
// 设置成功结果
|
||
result.save_time = GetCurrentTimeStringISO();
|
||
result.file_size = GetModelFileSize(current_model);
|
||
result.success = true;
|
||
|
||
}
|
||
catch (const pfcXToolkitBadInputs&) {
|
||
result.error_message = "Bad input parameters";
|
||
}
|
||
catch (const pfcXToolkitGeneralError&) {
|
||
result.error_message = "Creo toolkit error";
|
||
}
|
||
catch (const pfcXToolkitInvalidName&) {
|
||
result.error_message = "Invalid file name";
|
||
}
|
||
catch (const pfcXToolkitCantWrite&) {
|
||
result.error_message = "Cannot write to file";
|
||
}
|
||
catch (const pfcXToolkitCantOpen&) {
|
||
result.error_message = "Cannot open file";
|
||
}
|
||
catch (const std::exception& e) {
|
||
result.error_message = "Standard error: " + std::string(e.what());
|
||
}
|
||
catch (...) {
|
||
result.error_message = "Unknown error during save operation";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 关闭模型功能实现
|
||
CloseResult CreoManager::CloseModel(bool force_close) {
|
||
CloseResult result;
|
||
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
|
||
if (!sessionInfo.is_valid) {
|
||
result.error_message = "Creo session not available";
|
||
return result;
|
||
}
|
||
|
||
try {
|
||
pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel();
|
||
if (!current_model) {
|
||
result.error_message = "No current model loaded";
|
||
return result;
|
||
}
|
||
|
||
// 获取模型信息
|
||
xstring model_name_xstr = current_model->GetFileName();
|
||
result.model_name = XStringToString(model_name_xstr);
|
||
|
||
// 检查模型是否已修改
|
||
result.was_modified = current_model->GetIsModified();
|
||
|
||
// 如果模型已修改且不是强制关闭,则需要处理
|
||
if (result.was_modified && !force_close) {
|
||
result.error_message = "Model has unsaved changes. Use force_close=true to close without saving.";
|
||
return result;
|
||
}
|
||
|
||
// 执行关闭操作
|
||
current_model->Erase();
|
||
|
||
// 设置成功结果
|
||
result.close_time = GetCurrentTimeStringISO();
|
||
result.success = true;
|
||
|
||
}
|
||
catch (const pfcXToolkitBadInputs&) {
|
||
result.error_message = "Bad input parameters";
|
||
}
|
||
catch (const pfcXToolkitGeneralError&) {
|
||
result.error_message = "Creo toolkit error";
|
||
}
|
||
catch (const pfcXToolkitInvalidName&) {
|
||
result.error_message = "Invalid model name";
|
||
}
|
||
catch (const std::exception& e) {
|
||
result.error_message = "Standard error: " + std::string(e.what());
|
||
}
|
||
catch (...) {
|
||
result.error_message = "Unknown error during close operation";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 打开模型功能
|
||
OpenResult CreoManager::OpenModel(const std::string& file_path, const std::string& open_mode) {
|
||
OpenResult result;
|
||
|
||
// 分离目录和文件名(内联实现,避免成员函数调用问题)
|
||
std::string dirname, filename;
|
||
size_t pos = file_path.find_last_of("/\\");
|
||
if (pos != std::string::npos) {
|
||
dirname = file_path.substr(0, pos);
|
||
filename = file_path.substr(pos + 1);
|
||
} else {
|
||
dirname = "";
|
||
filename = file_path;
|
||
}
|
||
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
|
||
if (!sessionInfo.is_valid) {
|
||
result.error_message = "Creo session not available";
|
||
return result;
|
||
}
|
||
|
||
try {
|
||
// 验证路径解析结果
|
||
if (filename.empty()) {
|
||
result.error_message = "Invalid file path: " + file_path;
|
||
return result;
|
||
}
|
||
|
||
// 创建模型描述符用于检查(符合CREOSON标准)
|
||
xstring filename_xstr = StringToXString(filename);
|
||
pfcModelDescriptor_ptr checkDesc = pfcModelDescriptor::CreateFromFileName(filename_xstr);
|
||
|
||
// 检查模型是否已在会话中打开(使用描述符,符合CREOSON标准模式)
|
||
// 对应CREOSON中的: session.getModelFromDescr(descr)
|
||
pfcModel_ptr opened_model = nullptr;
|
||
try {
|
||
opened_model = sessionInfo.session->GetModelFromDescr(checkDesc);
|
||
} catch (...) {
|
||
// 模型未在内存中,稍后需要从磁盘加载
|
||
opened_model = nullptr;
|
||
}
|
||
|
||
if (!dirname.empty()) {
|
||
xstring workdir = StringToXString(dirname);
|
||
sessionInfo.session->ChangeDirectory(workdir);
|
||
}
|
||
|
||
// 重用已创建的模型描述符(避免重复创建)
|
||
// 使用RetrieveModel打开模型(对应CREOSON的session.retrieveModel(descr))
|
||
|
||
opened_model = sessionInfo.session->RetrieveModel(checkDesc);
|
||
|
||
|
||
// 检查模型是否成功打开
|
||
if (!opened_model) {
|
||
result.error_message = "Failed to open model '" + filename + "' from directory '" + dirname + "'";
|
||
return result;
|
||
}
|
||
|
||
// 设置返回结果
|
||
result.success = true;
|
||
result.model_name = XStringToString(opened_model->GetFileName());
|
||
result.file_path = file_path;
|
||
result.open_time = GetCurrentTimeStringISO();
|
||
|
||
// 确定模型类型
|
||
try {
|
||
pfcModelType model_type = opened_model->GetType();
|
||
switch (model_type) {
|
||
case pfcMDL_ASSEMBLY:
|
||
result.model_type = "assembly";
|
||
result.is_assembly = true;
|
||
try {
|
||
pfcSolid_ptr solid = pfcSolid::cast(opened_model);
|
||
if (solid) {
|
||
pfcFeatures_ptr features = solid->ListFeaturesByType(false, pfcFEATTYPE_COMPONENT);
|
||
result.total_parts = features ? features->getarraysize() : 0;
|
||
}
|
||
} catch (...) {
|
||
result.total_parts = 0;
|
||
}
|
||
break;
|
||
case pfcMDL_PART:
|
||
result.model_type = "part";
|
||
result.is_assembly = false;
|
||
result.total_parts = 0;
|
||
break;
|
||
case pfcMDL_DRAWING:
|
||
result.model_type = "drawing";
|
||
result.is_assembly = false;
|
||
result.total_parts = 0;
|
||
break;
|
||
default:
|
||
result.model_type = "unknown";
|
||
result.is_assembly = false;
|
||
result.total_parts = 0;
|
||
break;
|
||
}
|
||
} catch (...) {
|
||
result.model_type = "unknown";
|
||
result.is_assembly = false;
|
||
result.total_parts = 0;
|
||
}
|
||
|
||
result.file_size = GetModelFileSize(opened_model);
|
||
result.model_in_session = true;
|
||
result.window_model_match = true;
|
||
|
||
}
|
||
catch (const xthrowable& e) {
|
||
result.error_message = "OTK error opening model '" + filename + "': Creo API exception occurred";
|
||
}
|
||
catch (const std::exception& e) {
|
||
result.error_message = "Standard exception opening model '" + filename + "': " + std::string(e.what());
|
||
}
|
||
catch (...) {
|
||
result.error_message = "Unknown error opening model '" + filename + "' from directory '" + dirname + "'";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 层级分析主方法
|
||
HierarchyAnalysisResult CreoManager::AnalyzeModelHierarchy(const HierarchyAnalysisRequest& request) {
|
||
HierarchyAnalysisResult result;
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
|
||
if (!sessionInfo.is_valid) {
|
||
result.error_message = "Creo session not available";
|
||
return result;
|
||
}
|
||
|
||
try {
|
||
pfcModel_ptr current_model = sessionInfo.session->GetCurrentModel();
|
||
if (!current_model) {
|
||
result.error_message = "No current model loaded";
|
||
return result;
|
||
}
|
||
|
||
// 检查是否为装配体
|
||
if (current_model->GetType() != pfcMDL_ASSEMBLY) {
|
||
result.error_message = "Current model is not an assembly";
|
||
return result;
|
||
}
|
||
|
||
// 转换为装配体
|
||
wfcWAssembly_ptr assembly = wfcWAssembly::cast(current_model);
|
||
if (!assembly) {
|
||
result.error_message = "Failed to cast model to assembly";
|
||
return result;
|
||
}
|
||
|
||
// 初始化结果(SOTA算法)
|
||
result.project_name = request.project_name.empty() ?
|
||
XStringToString(current_model->GetFileName()) : request.project_name;
|
||
result.total_levels = 0;
|
||
result.total_components = 0;
|
||
result.hierarchy.clear();
|
||
|
||
// 创建根装配体组件信息
|
||
ComponentInfo root_component;
|
||
|
||
// 获取根装配体文件名
|
||
try {
|
||
xstring filename_xstr = current_model->GetFileName();
|
||
root_component.id = XStringToString(filename_xstr);
|
||
} catch (...) {
|
||
try {
|
||
xstring origin = current_model->GetOrigin();
|
||
std::string origin_str = XStringToString(origin);
|
||
size_t pos = origin_str.find_last_of("/\\");
|
||
if (pos != std::string::npos) {
|
||
root_component.id = origin_str.substr(pos + 1);
|
||
} else {
|
||
root_component.id = origin_str;
|
||
}
|
||
} catch (...) {
|
||
root_component.id = "root_assembly.asm";
|
||
}
|
||
}
|
||
|
||
// 设置根装配体属性
|
||
root_component.name = root_component.id;
|
||
root_component.type = "assembly";
|
||
root_component.level = 0;
|
||
root_component.path = root_component.id;
|
||
root_component.full_path = root_component.id;
|
||
root_component.file_size = GetModelFileSize(current_model);
|
||
root_component.deletion_safety = "forbidden";
|
||
root_component.is_visible = true;
|
||
root_component.model_type = "MDL_ASSEMBLY";
|
||
root_component.children_count = 0; // 将在递归后计算
|
||
|
||
// 初始化层级0并添加根装配体(只在target_level为-1或0时添加)
|
||
if (request.target_level == -1 || request.target_level == 0) {
|
||
result.hierarchy.push_back(std::vector<ComponentInfo>());
|
||
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<ComponentInfo>());
|
||
}
|
||
}
|
||
|
||
// 使用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<int, std::vector<std::string>> componentsToDeleteByLevel;
|
||
int total_deleted = 0;
|
||
int successful_count = 0;
|
||
int failed_count = 0;
|
||
|
||
// 递归遍历到指定层级直接删除
|
||
std::function<void(wfcWAssembly_ptr, int)> 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<std::string> 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<std::string>();
|
||
}
|
||
componentsToDeleteByLevel[deleteLevel + 1].insert(
|
||
componentsToDeleteByLevel[deleteLevel + 1].end(),
|
||
levelComponents.begin(),
|
||
levelComponents.end()
|
||
);
|
||
} else {
|
||
failed_count += featIds->getarraysize();
|
||
}
|
||
} catch (...) {
|
||
failed_count += featIds->getarraysize();
|
||
}
|
||
}
|
||
} else if (currentLevel < deleteLevel) {
|
||
// 还没到目标层级:只对装配体组件继续递归
|
||
for (int i = 0; i < features->getarraysize(); i++) {
|
||
pfcFeature_ptr feature = features->get(i);
|
||
pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature);
|
||
if (compFeat) {
|
||
auto modelDescr = compFeat->GetModelDescr();
|
||
if (modelDescr && modelDescr->GetType() == pfcMDL_ASSEMBLY) {
|
||
pfcModel_ptr childModel = LoadComponentModel(compFeat);
|
||
if (childModel && childModel->GetType() == pfcMDL_ASSEMBLY) {
|
||
wfcWAssembly_ptr childAssembly = wfcWAssembly::cast(childModel);
|
||
if (childAssembly) {
|
||
deleteAtLevel(childAssembly, currentLevel + 1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (...) {
|
||
// 忽略单个装配体的错误
|
||
}
|
||
};
|
||
|
||
// 从根装配体开始删除(层级0)
|
||
deleteAtLevel(assembly, 0);
|
||
|
||
result.deleted_components = componentsToDeleteByLevel;
|
||
result.total_deleted = total_deleted;
|
||
result.original_levels = 0; // 临时设置,避免异常值
|
||
|
||
// 删除完成后重新生成模型
|
||
if (successful_count > 0) {
|
||
try {
|
||
assembly->Regenerate(nullptr);
|
||
} catch (...) {
|
||
// 重新生成失败不影响删除结果
|
||
}
|
||
}
|
||
|
||
result.successful = successful_count;
|
||
result.failed = failed_count;
|
||
result.final_levels = target_level + 1; // 删除后的层级数
|
||
|
||
if (failed_count == 0) {
|
||
result.success = true;
|
||
result.message = "All components suppressed successfully (safer than deletion)";
|
||
} else {
|
||
result.success = (successful_count > 0);
|
||
result.message = "Suppression completed with " + std::to_string(failed_count) + " failures";
|
||
}
|
||
|
||
} catch (const std::exception& e) {
|
||
result.error_message = "Exception during suppression: " + std::string(e.what());
|
||
} catch (...) {
|
||
result.error_message = "Unknown error during suppression";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// Multi-directional extreme value projection shell analysis implementation
|
||
CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeaturesEnhanced(const ShellAnalysisRequest& request) {
|
||
ShellAnalysisResult result;
|
||
|
||
try {
|
||
// Get session
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
if (!sessionInfo.is_valid) {
|
||
result.error_message = "Cannot connect to Creo session";
|
||
return result;
|
||
}
|
||
|
||
// Get current model
|
||
pfcModel_ptr currentModel = sessionInfo.session->GetCurrentModel();
|
||
if (!currentModel) {
|
||
result.error_message = "No model is open in Creo";
|
||
return result;
|
||
}
|
||
|
||
// Model type detection
|
||
pfcModelType modelType = currentModel->GetType();
|
||
bool is_assembly = (modelType == pfcModelType::pfcMDL_ASSEMBLY);
|
||
|
||
// Set analysis parameters
|
||
result.analysis_parameters.preserve_external_surfaces = request.preserve_external_surfaces;
|
||
result.analysis_parameters.min_wall_thickness = request.min_wall_thickness;
|
||
result.analysis_parameters.confidence_threshold = request.confidence_threshold;
|
||
result.analysis_parameters.assembly_analysis = is_assembly;
|
||
|
||
if (!is_assembly) {
|
||
result.error_message = "Multi-directional projection analysis is designed for assembly models only";
|
||
return result;
|
||
}
|
||
|
||
// Cast to assembly
|
||
pfcAssembly_ptr assembly = pfcAssembly::cast(currentModel);
|
||
if (!assembly) {
|
||
result.error_message = "Cannot cast model to assembly";
|
||
return result;
|
||
}
|
||
|
||
// Execute multi-directional extreme value projection algorithm (now returns complete analysis data)
|
||
ProjectionAnalysisData analysisData = PerformMultiDirectionalProjectionAnalysis(assembly);
|
||
const std::unordered_set<int>& outerComponentIds = analysisData.outerComponentIds;
|
||
const std::unordered_map<int, int>& visibilityVotes = analysisData.visibilityVotes;
|
||
const std::vector<ComponentItem>& allComponents = analysisData.components;
|
||
const AABB& globalAABB = analysisData.globalAABB;
|
||
const int numDirections = SHELL_ANALYSIS_NUM_DIRECTIONS;
|
||
|
||
// Build component AABB mapping for quick lookup
|
||
std::unordered_map<int, AABB> componentAABBs;
|
||
std::unordered_map<int, std::string> 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);
|
||
}
|
||
|
||
// Skip assembly components - only analyze parts
|
||
if (comp_name.find(".asm") != std::string::npos ||
|
||
comp_name.find(".ASM") != std::string::npos) {
|
||
continue;
|
||
}
|
||
|
||
// Get stable feature ID from component path - use the leaf component ID
|
||
int stableFeatureId = -1;
|
||
xintsequence_ptr componentIds = wPath->GetComponentIds();
|
||
if (componentIds && componentIds->getarraysize() > 0) {
|
||
// Use the last ID in the path which represents the leaf component
|
||
stableFeatureId = componentIds->get(componentIds->getarraysize() - 1);
|
||
} else {
|
||
// Should not happen, but use index as last resort
|
||
stableFeatureId = i;
|
||
}
|
||
|
||
// Create analysis item
|
||
ShellAnalysisItem item;
|
||
item.name = comp_name;
|
||
item.type = "COMPONENT";
|
||
item.feature_id = stableFeatureId; // Use stable feature ID
|
||
|
||
// Build full path including assembly hierarchy
|
||
try {
|
||
pfcModel_ptr currentModel = sessionInfo.session->GetCurrentModel();
|
||
std::string assemblyName = "";
|
||
if (currentModel) {
|
||
xstring assemblyXStr = currentModel->GetFileName();
|
||
assemblyName = XStringToString(assemblyXStr);
|
||
}
|
||
item.path = BuildComponentFullPath(wPath, assemblyName);
|
||
if (item.path.empty()) {
|
||
item.path = comp_name; // Fallback to component name
|
||
}
|
||
} catch (...) {
|
||
item.path = comp_name; // Fallback on error
|
||
}
|
||
|
||
// Calculate visibility ratio for this component
|
||
double visibilityRatio = 0.0;
|
||
auto votesIter = visibilityVotes.find(item.feature_id);
|
||
if (votesIter != visibilityVotes.end()) {
|
||
visibilityRatio = (double)votesIter->second / numDirections;
|
||
}
|
||
|
||
// Determine confidence based on visibility ratio
|
||
if (visibilityRatio >= SHELL_ANALYSIS_HIGH_VISIBILITY_THRESHOLD) {
|
||
// Highly visible component - clearly on outer surface
|
||
item.confidence = 0.1; // Very low deletion confidence
|
||
item.recommendation = "KEEP";
|
||
item.reason = "Component highly visible from multiple directions (visibility: " +
|
||
std::to_string((int)(visibilityRatio * 100)) + "%)";
|
||
result.shell_features_count++;
|
||
} else if (visibilityRatio >= SHELL_ANALYSIS_MEDIUM_VISIBILITY_THRESHOLD) {
|
||
// Partially visible component - likely on outer surface or important structure
|
||
item.confidence = 0.4; // Medium deletion confidence
|
||
item.recommendation = "REVIEW";
|
||
item.reason = "Component partially visible (visibility: " +
|
||
std::to_string((int)(visibilityRatio * 100)) + "%)";
|
||
result.shell_features_count++;
|
||
} else {
|
||
// Internal component - mostly not visible
|
||
item.confidence = 0.85; // High deletion confidence
|
||
item.recommendation = "DELETE";
|
||
item.reason = "Internal component with minimal visibility (visibility: " +
|
||
std::to_string((int)(visibilityRatio * 100)) + "%)";
|
||
result.internal_features_count++;
|
||
}
|
||
|
||
// Check if it was identified as outer component by the main algorithm
|
||
bool is_outer_component = (outerComponentIds.find(item.feature_id) != outerComponentIds.end());
|
||
|
||
// Apply rule-based adjustments
|
||
// Geometric rules removed - rely on visibility-based detection instead
|
||
|
||
// Apply naming pattern rules - use unified component name
|
||
std::string unifiedName = comp_name;
|
||
auto nameIter = componentNames.find(item.feature_id);
|
||
if (nameIter != componentNames.end()) {
|
||
unifiedName = nameIter->second; // Use name from ComponentItem for consistency
|
||
}
|
||
|
||
if (ComponentClassifier::IsFastener(unifiedName)) {
|
||
item.confidence = std::min(0.95, item.confidence + 0.3); // Fasteners are typically deletable
|
||
item.reason += " [Pattern: Fastener]";
|
||
if (item.recommendation == "KEEP") {
|
||
item.recommendation = "REVIEW"; // Reconsider visible fasteners
|
||
}
|
||
} else if (ComponentClassifier::IsInternalStructure(unifiedName)) {
|
||
item.confidence = std::min(0.90, item.confidence + 0.25); // Internal structures likely deletable
|
||
item.reason += " [Pattern: Internal structure]";
|
||
} else if (ComponentClassifier::IsExternalShell(unifiedName)) {
|
||
item.confidence = std::max(0.1, item.confidence - 0.5); // External shells should be preserved
|
||
item.reason += " [Pattern: External shell]";
|
||
if (item.recommendation == "DELETE") {
|
||
item.recommendation = "REVIEW"; // Reconsider deletion of shells
|
||
}
|
||
}
|
||
|
||
// Apply user preferences
|
||
if (request.preserve_external_surfaces && is_outer_component) {
|
||
item.confidence = 0.0; // Force keep
|
||
}
|
||
|
||
// Check confidence threshold
|
||
if (item.confidence >= request.confidence_threshold) {
|
||
item.is_deletable = true;
|
||
result.total_deletable++;
|
||
}
|
||
|
||
result.features.push_back(item);
|
||
|
||
} catch (...) {
|
||
// Skip problematic components
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// Sort results by confidence (highest first)
|
||
std::sort(result.features.begin(), result.features.end(),
|
||
[](const ShellAnalysisItem& a, const ShellAnalysisItem& b) -> bool {
|
||
return a.confidence > b.confidence;
|
||
});
|
||
|
||
// Convert features to categorized deletion lists
|
||
for (const auto& item : result.features) {
|
||
FeatureDeletion deletion;
|
||
deletion.id = item.feature_id;
|
||
deletion.name = item.name;
|
||
deletion.type = item.type;
|
||
deletion.reason = item.reason;
|
||
deletion.confidence = item.confidence;
|
||
deletion.volume_reduction = 0.0; // Not calculated in projection analysis
|
||
deletion.part_file = ""; // Component name is already in 'name'
|
||
deletion.part_path = item.path; // Use full component path
|
||
deletion.component_type = "COMPONENT";
|
||
|
||
// Set vote count from projection analysis
|
||
auto votesIter = visibilityVotes.find(item.feature_id);
|
||
deletion.vote_count = (votesIter != visibilityVotes.end()) ? votesIter->second : 0;
|
||
|
||
if (item.confidence >= 0.8) {
|
||
// Safe deletion - high confidence internal components
|
||
result.safe_deletions.push_back(deletion);
|
||
} else if (item.confidence >= 0.5) {
|
||
// Suggested deletion - medium confidence components
|
||
result.suggested_deletions.push_back(deletion);
|
||
} else {
|
||
// Preserve - low confidence or outer surface components
|
||
result.preserve_list.push_back(deletion);
|
||
}
|
||
}
|
||
|
||
// Update statistics fields
|
||
result.analysis_parameters.total_features = result.total_features_analyzed;
|
||
result.analysis_parameters.deletable_features = result.safe_deletions.size() + result.suggested_deletions.size();
|
||
result.analysis_parameters.preserved_features = result.preserve_list.size();
|
||
result.analysis_parameters.surface_count = result.total_features_analyzed; // All components analyzed
|
||
result.analysis_parameters.shell_surfaces = result.shell_features_count; // Outer surface components
|
||
result.analysis_parameters.internal_surfaces = result.internal_features_count; // Internal components
|
||
|
||
// Calculate estimated reduction (simplified for projection analysis)
|
||
if (result.total_features_analyzed > 0) {
|
||
double safe_reduction_pct = (double)result.safe_deletions.size() / result.total_features_analyzed * 100.0;
|
||
double suggested_reduction_pct = (double)result.suggested_deletions.size() / result.total_features_analyzed * 100.0;
|
||
|
||
std::ostringstream volume_str, filesize_str, performance_str;
|
||
volume_str << std::fixed << std::setprecision(1) << (safe_reduction_pct + suggested_reduction_pct * 0.5) << "%";
|
||
filesize_str << std::fixed << std::setprecision(1) << (safe_reduction_pct * 0.7 + suggested_reduction_pct * 0.3) << "%";
|
||
performance_str << std::fixed << std::setprecision(0) << (safe_reduction_pct * 1.5 + suggested_reduction_pct * 0.8) << "%";
|
||
|
||
result.estimated_reduction.volume_reduction = volume_str.str();
|
||
result.estimated_reduction.file_size_reduction = filesize_str.str();
|
||
result.estimated_reduction.performance_improvement = performance_str.str();
|
||
} else {
|
||
result.estimated_reduction.volume_reduction = "0%";
|
||
result.estimated_reduction.file_size_reduction = "0%";
|
||
result.estimated_reduction.performance_improvement = "0%";
|
||
}
|
||
|
||
// Calculate statistics
|
||
result.deletion_percentage = (result.total_features_analyzed > 0) ?
|
||
(double(result.total_deletable) / result.total_features_analyzed * 100.0) : 0.0;
|
||
|
||
result.success = true;
|
||
result.model_name = XStringToString(currentModel->GetFullName());
|
||
|
||
} catch (const std::exception& e) {
|
||
result.error_message = "Exception: " + std::string(e.what());
|
||
} catch (...) {
|
||
result.error_message = "Unknown error during multi-directional projection analysis";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// Shell Analysis implementation using real OTK geometry APIs
|
||
CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeatures(const ShellAnalysisRequest& request) {
|
||
return AnalyzeShellFeaturesEnhanced(request);
|
||
}
|
||
// Geometry analysis: determine if feature touches model boundary
|
||
bool CreoManager::AnalyzeFeatureGeometry(pfcFeature_ptr feature, pfcOutline3D_ptr globalOutline, double tolerance) {
|
||
if (!feature || !globalOutline) {
|
||
return false; // Cannot analyze, assume not touching boundary
|
||
}
|
||
|
||
try {
|
||
// Get global bounding box
|
||
pfcPoint3D_ptr globalMin = globalOutline->get(0);
|
||
pfcPoint3D_ptr globalMax = globalOutline->get(1);
|
||
|
||
// Since OTK lacks direct feature bbox API, use improved heuristics
|
||
// This is enhanced semantic analysis based on feature types and relationships
|
||
|
||
pfcFeatureType feat_type = feature->GetFeatType();
|
||
|
||
// === Improved feature boundary analysis ===
|
||
|
||
// 1. Features that build outer shape typically touch boundary
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_PROTRUSION ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_SHELL ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_DOME ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_TORUS) {
|
||
return true; // These features define model shape
|
||
}
|
||
|
||
// 2. Base feature typically on boundary
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_FIRST) {
|
||
return true; // Base feature on boundary
|
||
}
|
||
|
||
// 3. Machining features - need careful analysis
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_CUT ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_HOLE) {
|
||
// CUT and HOLE features can be external or internal
|
||
// Without geometry API, conservatively assume internal
|
||
return false; // Typically internal features
|
||
}
|
||
|
||
// 4. ROUND and CHAMFER - can be anywhere
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_ROUND ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_CHAMFER) {
|
||
// Cannot assume all rounds/chamfers are on boundary
|
||
// Many internal edges also have rounds/chamfers
|
||
// Need parent feature analysis for accurate determination
|
||
try {
|
||
// Try to check parent features for context
|
||
pfcFeatures_ptr parents = feature->ListParents();
|
||
if (parents && parents->getarraysize() > 0) {
|
||
// If parent is internal, this is likely internal too
|
||
for (int i = 0; i < parents->getarraysize(); i++) {
|
||
pfcFeature_ptr parent = parents->get(i);
|
||
if (parent) {
|
||
pfcFeatureType parent_type = parent->GetFeatType();
|
||
if (parent_type == pfcFeatureType::pfcFEATTYPE_CUT ||
|
||
parent_type == pfcFeatureType::pfcFEATTYPE_HOLE) {
|
||
return false; // Round/chamfer on internal feature
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (...) {
|
||
// Parent analysis failed, use conservative approach
|
||
}
|
||
// Without clear internal parent, conservatively assume boundary
|
||
// to avoid deleting important external features
|
||
return true;
|
||
}
|
||
|
||
// 5. RIB and DRAFT features
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_RIB ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_DRAFT) {
|
||
return false; // Typically internal strengthening features
|
||
}
|
||
|
||
// 6. Datum features do not affect geometry
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_PLANE ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_AXIS ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_POINT ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_COORD_SYS) {
|
||
return false; // Datum features don't affect boundary
|
||
}
|
||
|
||
// 7. Component features in assembly
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_COMPONENT) {
|
||
// Component position analysis would require transform matrices
|
||
// For now, use conservative approach
|
||
// TODO: Implement component bbox vs assembly bbox comparison
|
||
return false; // Assume internal by default, let hierarchy analysis decide
|
||
}
|
||
|
||
// 8. Pattern features
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_PATTERN) {
|
||
// Pattern boundary depends on leader feature
|
||
try {
|
||
// Cast to pattern type - pfcFeaturePattern should be in pfcFeature.h
|
||
pfcFeaturePattern_ptr pattern = pfcFeaturePattern::cast(feature);
|
||
if (pattern) {
|
||
pfcFeature_ptr leader = pattern->GetPatternLeader();
|
||
if (leader) {
|
||
// Recursively check leader feature
|
||
return AnalyzeFeatureGeometry(leader, globalOutline, tolerance);
|
||
}
|
||
}
|
||
} catch (...) {
|
||
// Pattern analysis failed
|
||
}
|
||
return false; // Conservative: assume internal
|
||
}
|
||
|
||
// 9. Default for unknown features
|
||
// Use conservative approach to avoid deleting important features
|
||
return false; // Changed from true to false for safety
|
||
|
||
} catch (...) {
|
||
// Analysis failed, use conservative strategy
|
||
return true; // Assume touching boundary to avoid incorrect deletion
|
||
}
|
||
}
|
||
|
||
// === Smart Boundary Detection Algorithm ===
|
||
|
||
// 1. Get optimal tolerance based on assembly size
|
||
double CreoManager::GetOptimalTolerance(pfcOutline3D_ptr assembly_bbox) {
|
||
try {
|
||
if (!assembly_bbox) return 0.1;
|
||
|
||
pfcPoint3D_ptr minPt = assembly_bbox->get(0);
|
||
pfcPoint3D_ptr maxPt = assembly_bbox->get(1);
|
||
|
||
// Calculate assembly diagonal length
|
||
double diagonal = sqrt(
|
||
pow(maxPt->get(0) - minPt->get(0), 2) +
|
||
pow(maxPt->get(1) - minPt->get(1), 2) +
|
||
pow(maxPt->get(2) - minPt->get(2), 2)
|
||
);
|
||
|
||
// Tolerance is 1% of diagonal, with min/max limits
|
||
double tolerance = diagonal * 0.01;
|
||
return std::max(0.1, std::min(tolerance, 50.0));
|
||
|
||
} catch (...) {
|
||
return 0.1; // Default fallback
|
||
}
|
||
}
|
||
|
||
// 2. Calculate boundary overlap percentage
|
||
double CreoManager::CalculateBoundaryOverlap(pfcOutline3D_ptr comp_bbox, pfcOutline3D_ptr assembly_bbox, double tolerance) {
|
||
try {
|
||
if (!comp_bbox || !assembly_bbox) return 0.0;
|
||
|
||
pfcPoint3D_ptr comp_min = comp_bbox->get(0);
|
||
pfcPoint3D_ptr comp_max = comp_bbox->get(1);
|
||
pfcPoint3D_ptr asm_min = assembly_bbox->get(0);
|
||
pfcPoint3D_ptr asm_max = assembly_bbox->get(1);
|
||
|
||
double overlap_count = 0.0;
|
||
double total_faces = 6.0; // 6 faces of bounding box
|
||
|
||
// Check each face for boundary overlap
|
||
// X-min face
|
||
if (abs(comp_min->get(0) - asm_min->get(0)) <= tolerance) overlap_count += 1.0;
|
||
// X-max face
|
||
if (abs(comp_max->get(0) - asm_max->get(0)) <= tolerance) overlap_count += 1.0;
|
||
// Y-min face
|
||
if (abs(comp_min->get(1) - asm_min->get(1)) <= tolerance) overlap_count += 1.0;
|
||
// Y-max face
|
||
if (abs(comp_max->get(1) - asm_max->get(1)) <= tolerance) overlap_count += 1.0;
|
||
// Z-min face
|
||
if (abs(comp_min->get(2) - asm_min->get(2)) <= tolerance) overlap_count += 1.0;
|
||
// Z-max face
|
||
if (abs(comp_max->get(2) - asm_max->get(2)) <= tolerance) overlap_count += 1.0;
|
||
|
||
return overlap_count / total_faces; // Return overlap percentage (0.0 to 1.0)
|
||
|
||
} catch (...) {
|
||
return 0.0; // Safe fallback
|
||
}
|
||
}
|
||
|
||
// 3. Check if component is on assembly boundary
|
||
bool CreoManager::IsOnAssemblyBoundary(pfcFeature_ptr component, pfcOutline3D_ptr assembly_bbox, double tolerance) {
|
||
try {
|
||
if (!component || !assembly_bbox) return true; // Conservative: assume on boundary
|
||
|
||
pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(component);
|
||
if (!compFeat) return true;
|
||
|
||
// Get component model
|
||
pfcModelDescriptor_ptr modelDesc = compFeat->GetModelDescr();
|
||
if (!modelDesc) return true;
|
||
|
||
pfcSession_ptr session = pfcGetCurrentSession();
|
||
if (!session) return true;
|
||
|
||
pfcModel_ptr componentModel = session->GetModelFromDescr(modelDesc);
|
||
if (!componentModel || componentModel->GetType() != pfcModelType::pfcMDL_PART) {
|
||
return true; // Assemblies are considered boundary components
|
||
}
|
||
|
||
pfcSolid_ptr componentSolid = pfcSolid::cast(componentModel);
|
||
if (!componentSolid) return true;
|
||
|
||
pfcOutline3D_ptr comp_bbox = componentSolid->EvalOutline(nullptr);
|
||
if (!comp_bbox) return true;
|
||
|
||
// Calculate boundary overlap
|
||
double overlap = CalculateBoundaryOverlap(comp_bbox, assembly_bbox, tolerance);
|
||
|
||
// Component is on boundary if it overlaps with any boundary face
|
||
// Threshold: >= 16.7% (1 out of 6 faces)
|
||
return overlap >= 0.167;
|
||
|
||
} catch (...) {
|
||
return true; // Conservative: assume on boundary to avoid deletion
|
||
}
|
||
}
|
||
|
||
// Enhanced geometric boundary detection for part features
|
||
bool CreoManager::AnalyzeFeatureGeometryEnhanced(
|
||
pfcFeature_ptr feature,
|
||
pfcSolid_ptr solid,
|
||
pfcOutline3D_ptr globalOutline,
|
||
double tolerance) {
|
||
|
||
if (!feature || !solid || !globalOutline) {
|
||
return false; // Conservative: assume internal
|
||
}
|
||
|
||
try {
|
||
pfcFeatureType feat_type = feature->GetFeatType();
|
||
|
||
// Step 1: Quick type-based filtering for non-geometric features
|
||
if (feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_PLANE ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_AXIS ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_DATUM_POINT ||
|
||
feat_type == pfcFeatureType::pfcFEATTYPE_COORD_SYS) {
|
||
return false; // Datum features don't affect geometry
|
||
}
|
||
|
||
// Step 2: Get surfaces affected by this feature
|
||
std::vector<pfcSurface_ptr> 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<pfcSurface_ptr> CreoManager::GetFeatureAffectedSurfaces(pfcFeature_ptr feature, pfcSolid_ptr solid) {
|
||
std::vector<pfcSurface_ptr> 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<std::pair<pfcFeature_ptr, std::string>>& 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<Vector3D> corners = {
|
||
Vector3D(localAABB.minPoint.x, localAABB.minPoint.y, localAABB.minPoint.z),
|
||
Vector3D(localAABB.maxPoint.x, localAABB.minPoint.y, localAABB.minPoint.z),
|
||
Vector3D(localAABB.minPoint.x, localAABB.maxPoint.y, localAABB.minPoint.z),
|
||
Vector3D(localAABB.maxPoint.x, localAABB.maxPoint.y, localAABB.minPoint.z),
|
||
Vector3D(localAABB.minPoint.x, localAABB.minPoint.y, localAABB.maxPoint.z),
|
||
Vector3D(localAABB.maxPoint.x, localAABB.minPoint.y, localAABB.maxPoint.z),
|
||
Vector3D(localAABB.minPoint.x, localAABB.maxPoint.y, localAABB.maxPoint.z),
|
||
Vector3D(localAABB.maxPoint.x, localAABB.maxPoint.y, localAABB.maxPoint.z)
|
||
};
|
||
|
||
AABB worldAABB;
|
||
|
||
for (const Vector3D& corner : corners) {
|
||
// Create pfcPoint3D
|
||
pfcPoint3D_ptr localPoint = pfcPoint3D::create();
|
||
localPoint->set(0, corner.x);
|
||
localPoint->set(1, corner.y);
|
||
localPoint->set(2, corner.z);
|
||
|
||
// Transform point
|
||
pfcPoint3D_ptr worldPoint = transform->TransformPoint(localPoint);
|
||
|
||
// Convert back to Vector3D and expand AABB
|
||
Vector3D worldCorner = PfcPointToVector3D(worldPoint);
|
||
worldAABB.expand(worldCorner);
|
||
}
|
||
|
||
return worldAABB;
|
||
|
||
} catch (...) {
|
||
return localAABB; // Fallback to local AABB if transform fails
|
||
}
|
||
}
|
||
|
||
// Sample 6 orthogonal directions (front/back, left/right, up/down)
|
||
std::vector<CreoManager::Vector3D> CreoManager::SampleDirections(int count) {
|
||
std::vector<Vector3D> directions;
|
||
directions.reserve(6);
|
||
|
||
// Six orthogonal directions for comprehensive coverage
|
||
// X-axis: front and back
|
||
directions.push_back(Vector3D(1.0, 0.0, 0.0)); // Front (+X)
|
||
directions.push_back(Vector3D(-1.0, 0.0, 0.0)); // Back (-X)
|
||
|
||
// Y-axis: left and right
|
||
directions.push_back(Vector3D(0.0, 1.0, 0.0)); // Right (+Y)
|
||
directions.push_back(Vector3D(0.0, -1.0, 0.0)); // Left (-Y)
|
||
|
||
// Z-axis: up and down
|
||
directions.push_back(Vector3D(0.0, 0.0, 1.0)); // Up (+Z)
|
||
directions.push_back(Vector3D(0.0, 0.0, -1.0)); // Down (-Z)
|
||
|
||
return directions;
|
||
}
|
||
|
||
// Calculate support point projection for AABB in given direction
|
||
double CreoManager::CalculateProjectionSupport(const AABB& aabb, const Vector3D& direction) {
|
||
// Support function for AABB: choose the corner that maximizes dot product with direction
|
||
Vector3D supportPoint;
|
||
supportPoint.x = (direction.x >= 0) ? aabb.maxPoint.x : aabb.minPoint.x;
|
||
supportPoint.y = (direction.y >= 0) ? aabb.maxPoint.y : aabb.minPoint.y;
|
||
supportPoint.z = (direction.z >= 0) ? aabb.maxPoint.z : aabb.minPoint.z;
|
||
|
||
return supportPoint.dot(direction);
|
||
}
|
||
|
||
// Collect all components from assembly (no recursion needed)
|
||
std::vector<CreoManager::ComponentItem> CreoManager::CollectAllComponents(pfcAssembly_ptr assembly) {
|
||
std::vector<ComponentItem> 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);
|
||
}
|
||
|
||
// Skip assembly components - only analyze parts
|
||
if (compName.find(".asm") != std::string::npos ||
|
||
compName.find(".ASM") != std::string::npos) {
|
||
continue;
|
||
}
|
||
|
||
// Get stable feature ID from component path - use the leaf component ID
|
||
int stableFeatureId = -1;
|
||
xintsequence_ptr componentIds = wPath->GetComponentIds();
|
||
if (componentIds && componentIds->getarraysize() > 0) {
|
||
// Use the last ID in the path which represents the leaf component
|
||
stableFeatureId = componentIds->get(componentIds->getarraysize() - 1);
|
||
} else {
|
||
// Should not happen, but use index as last resort
|
||
stableFeatureId = i;
|
||
}
|
||
|
||
// Get local AABB
|
||
pfcOutline3D_ptr localOutline = compSolid->EvalOutline(nullptr);
|
||
if (!localOutline) continue;
|
||
|
||
AABB localAABB = PfcOutlineToAABB(localOutline);
|
||
|
||
// Get world transform from component path - relative to root assembly
|
||
pfcTransform3D_ptr worldTransform = nullptr;
|
||
try {
|
||
worldTransform = wPath->GetTransform(xfalse); // From root assembly to component
|
||
} catch (...) {
|
||
// Transform failed, use identity
|
||
}
|
||
|
||
AABB worldAABB = TransformAABB(localAABB, worldTransform);
|
||
|
||
// Create component item with complete information
|
||
ComponentItem item;
|
||
item.component = nullptr; // We don't have pfcComponentFeat directly
|
||
item.solid = compSolid;
|
||
item.path = nullptr; // We have wfcWComponentPath, different type
|
||
item.worldAABB = worldAABB; // Fast rough filtering
|
||
// Create preliminary item for OBB decision
|
||
ComponentItem tempItem;
|
||
tempItem.worldAABB = worldAABB;
|
||
tempItem.name = compName;
|
||
|
||
// Enhanced OBB computation: use unified decision logic
|
||
if (ShouldUseOBB(tempItem)) {
|
||
item.worldOBB = ComputePCABasedOBB(compSolid, worldTransform);
|
||
} else {
|
||
item.worldOBB = ExtractOBBFromTransform(localAABB, worldTransform);
|
||
}
|
||
item.featureId = stableFeatureId; // Use stable feature ID from component path
|
||
item.name = compName;
|
||
|
||
components.push_back(item);
|
||
|
||
// No recursion needed - ListDisplayedComponents already returns full depth
|
||
|
||
} catch (...) {
|
||
// Skip this component if any operation fails
|
||
continue;
|
||
}
|
||
}
|
||
|
||
} catch (...) {
|
||
// Collection failed, return what we have
|
||
}
|
||
|
||
return components;
|
||
}
|
||
|
||
std::pair<double, double> CreoManager::CalculateAABBProjectionRange(const AABB& aabb, const Vector3D& direction) {
|
||
Vector3D corners[8] = {
|
||
{aabb.minPoint.x, aabb.minPoint.y, aabb.minPoint.z},
|
||
{aabb.maxPoint.x, aabb.minPoint.y, aabb.minPoint.z},
|
||
{aabb.minPoint.x, aabb.maxPoint.y, aabb.minPoint.z},
|
||
{aabb.maxPoint.x, aabb.maxPoint.y, aabb.minPoint.z},
|
||
{aabb.minPoint.x, aabb.minPoint.y, aabb.maxPoint.z},
|
||
{aabb.maxPoint.x, aabb.minPoint.y, aabb.maxPoint.z},
|
||
{aabb.minPoint.x, aabb.maxPoint.y, aabb.maxPoint.z},
|
||
{aabb.maxPoint.x, aabb.maxPoint.y, aabb.maxPoint.z}
|
||
};
|
||
|
||
double minProj = corners[0].dot(direction);
|
||
double maxProj = minProj;
|
||
|
||
for (int i = 1; i < 8; i++) {
|
||
double proj = corners[i].dot(direction);
|
||
minProj = std::min(minProj, proj);
|
||
maxProj = std::max(maxProj, proj);
|
||
}
|
||
|
||
return {minProj, maxProj};
|
||
}
|
||
|
||
std::pair<double, double> CreoManager::CalculateOBBProjectionRange(const OBB& obb, const Vector3D& direction) {
|
||
std::vector<Vector3D> corners = obb.getCorners();
|
||
double minProj = corners[0].dot(direction);
|
||
double maxProj = minProj;
|
||
|
||
for (size_t i = 1; i < corners.size(); i++) {
|
||
double proj = corners[i].dot(direction);
|
||
minProj = std::min(minProj, proj);
|
||
maxProj = std::max(maxProj, proj);
|
||
}
|
||
|
||
return {minProj, maxProj};
|
||
}
|
||
|
||
// Main multi-directional extreme value projection analysis with depth window and voting
|
||
CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly) {
|
||
ProjectionAnalysisData result;
|
||
|
||
// Step 1: Collect all components
|
||
result.components = CollectAllComponents(assembly);
|
||
|
||
// Step 2: Calculate global assembly AABB
|
||
for (const ComponentItem& comp : result.components) {
|
||
result.globalAABB.expand(comp.worldAABB.minPoint);
|
||
result.globalAABB.expand(comp.worldAABB.maxPoint);
|
||
}
|
||
|
||
// Calculate global center for ray verification
|
||
Vector3D globalCenter = Vector3D(
|
||
(result.globalAABB.minPoint.x + result.globalAABB.maxPoint.x) * 0.5,
|
||
(result.globalAABB.minPoint.y + result.globalAABB.maxPoint.y) * 0.5,
|
||
(result.globalAABB.minPoint.z + result.globalAABB.maxPoint.z) * 0.5
|
||
);
|
||
|
||
// Step 3: Sample directions (good coverage)
|
||
const int numDirections = SHELL_ANALYSIS_NUM_DIRECTIONS;
|
||
std::vector<Vector3D> directions = SampleDirections(numDirections);
|
||
|
||
// Voting map: component ID -> number of directions where it's visible
|
||
result.visibilityVotes.reserve(result.components.size());
|
||
|
||
|
||
// Create featureId to path mapping for visibility logic
|
||
std::unordered_map<int, std::string> featureIdToPath;
|
||
std::unordered_map<int, std::string> featureIdToName;
|
||
|
||
// Build path mapping for each component
|
||
wfcWAssembly_ptr wAssembly = wfcWAssembly::cast(assembly);
|
||
if (wAssembly) {
|
||
wfcWComponentPaths_ptr componentPaths = wAssembly->ListDisplayedComponents();
|
||
if (componentPaths) {
|
||
SessionInfo sessionInfo = GetSessionInfo();
|
||
std::string assemblyName = "";
|
||
if (sessionInfo.is_valid) {
|
||
pfcModel_ptr currentModel = sessionInfo.session->GetCurrentModel();
|
||
if (currentModel) {
|
||
xstring assemblyXStr = currentModel->GetFileName();
|
||
assemblyName = XStringToString(assemblyXStr);
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < componentPaths->getarraysize(); ++i) {
|
||
wfcWComponentPath_ptr wPath = componentPaths->get(i);
|
||
if (wPath) {
|
||
// Get feature ID
|
||
xintsequence_ptr componentIds = wPath->GetComponentIds();
|
||
if (componentIds && componentIds->getarraysize() > 0) {
|
||
int featureId = componentIds->get(componentIds->getarraysize() - 1);
|
||
// Build full path for this component
|
||
std::string fullPath = BuildComponentFullPath(wPath, assemblyName);
|
||
featureIdToPath[featureId] = fullPath;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
for (const ComponentItem& comp : result.components) {
|
||
featureIdToName[comp.featureId] = comp.name;
|
||
}
|
||
|
||
// Step 4: For each direction, determine visible components using 2D grid-based occlusion detection
|
||
for (const Vector3D& direction : directions) {
|
||
// Create screen grid for this viewing direction
|
||
ScreenGrid grid(result.globalAABB, direction, SHELL_ANALYSIS_GRID_RESOLUTION);
|
||
|
||
// Structure to hold projection data
|
||
struct ProjectionData {
|
||
double minProj; // Minimum projection value (depth)
|
||
int featureId;
|
||
bool useOBB;
|
||
const ComponentItem* comp;
|
||
};
|
||
|
||
std::vector<ProjectionData> projections;
|
||
projections.reserve(result.components.size());
|
||
|
||
// Calculate projection depths for all components
|
||
for (const ComponentItem& comp : result.components) {
|
||
bool useOBB = ShouldUseOBB(comp);
|
||
double minProj;
|
||
|
||
if (useOBB) {
|
||
auto range = CalculateOBBProjectionRange(comp.worldOBB, direction);
|
||
minProj = range.first;
|
||
} else {
|
||
auto range = CalculateAABBProjectionRange(comp.worldAABB, direction);
|
||
minProj = range.first;
|
||
}
|
||
|
||
projections.push_back({minProj, comp.featureId, useOBB, &comp});
|
||
}
|
||
|
||
// Sort by minimum projection value (nearest first) for efficient processing
|
||
std::sort(projections.begin(), projections.end(),
|
||
[](const ProjectionData& a, const ProjectionData& b) {
|
||
return a.minProj < b.minProj;
|
||
});
|
||
|
||
// Project components to grid (front to back order)
|
||
for (const auto& proj : projections) {
|
||
if (proj.useOBB) {
|
||
grid.projectOBB(proj.comp->worldOBB, direction, proj.minProj, proj.featureId);
|
||
} else {
|
||
grid.projectAABB(proj.comp->worldAABB, direction, proj.minProj, proj.featureId);
|
||
}
|
||
}
|
||
|
||
// Get visibility counts from grid
|
||
auto visibilityCounts = grid.getVisibilityCount();
|
||
int totalCells = grid.gridSizeU * grid.gridSizeV;
|
||
|
||
// Update visibility votes based on grid visibility with ray verification
|
||
for (const auto& kvp : visibilityCounts) {
|
||
int componentId = kvp.first;
|
||
int cellCount = kvp.second;
|
||
double visibilityRatio = (double)cellCount / totalCells;
|
||
|
||
// Update continuous visibility ratio (max across all directions)
|
||
if (result.visibilityRatios.find(componentId) == result.visibilityRatios.end()) {
|
||
result.visibilityRatios[componentId] = visibilityRatio;
|
||
} else {
|
||
result.visibilityRatios[componentId] = std::max(result.visibilityRatios[componentId], visibilityRatio);
|
||
}
|
||
|
||
// Two-step verification: projection visibility + ray verification
|
||
if (visibilityRatio >= SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR) {
|
||
// Find the component for ray verification
|
||
const ComponentItem* targetComponent = nullptr;
|
||
for (const auto& comp : result.components) {
|
||
if (comp.featureId == componentId) {
|
||
targetComponent = ∁
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Perform ray verification from global center
|
||
if (targetComponent && !IsComponentBlockedFromCenter(globalCenter, *targetComponent, result.components)) {
|
||
// Component passed both 2D projection and ray verification
|
||
result.visibilityVotes[componentId]++;
|
||
}
|
||
// If blocked by ray test, this direction votes 0 (no increment)
|
||
|
||
// Debug output for specific component
|
||
for (const auto& comp : result.components) {
|
||
if (comp.featureId == componentId &&
|
||
(comp.name.find("12v4000g03_herhang") != std::string::npos ||
|
||
comp.name.find("12V4000G03_HERHANG") != 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_HERHANG.prt Visibility Analysis ===" << std::endl;
|
||
logFile << "Component Name: " << comp.name << std::endl;
|
||
logFile << "Feature ID: " << comp.featureId << std::endl;
|
||
logFile << "\nBounding Box:" << std::endl;
|
||
logFile << " Min: (" << comp.worldAABB.minPoint.x << ", "
|
||
<< comp.worldAABB.minPoint.y << ", "
|
||
<< comp.worldAABB.minPoint.z << ")" << std::endl;
|
||
logFile << " Max: (" << comp.worldAABB.maxPoint.x << ", "
|
||
<< comp.worldAABB.maxPoint.y << ", "
|
||
<< comp.worldAABB.maxPoint.z << ")" << std::endl;
|
||
Vector3D size = comp.worldAABB.diagonal();
|
||
logFile << " Size: " << size.x << " x " << size.y << " x " << size.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
|
||
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);
|
||
if (logFile.is_open()) {
|
||
static int directionCount = 0;
|
||
directionCount++;
|
||
logFile << "Direction " << directionCount << ": cells=" << cellCount
|
||
<< ", ratio=" << visibilityRatio
|
||
<< ", voted=" << (visibilityRatio >= SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR ? "YES" : "NO")
|
||
<< std::endl;
|
||
logFile.close();
|
||
}
|
||
}
|
||
} catch (...) {}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Step 5: Determine outer components based on voting threshold
|
||
double minVisibilityRatio = SHELL_ANALYSIS_MIN_VISIBILITY_RATIO;
|
||
int minVotes = std::max(SHELL_ANALYSIS_MIN_VOTES, (int)(minVisibilityRatio * numDirections));
|
||
|
||
for (const auto& kvp : result.visibilityVotes) {
|
||
if (kvp.second >= minVotes) {
|
||
result.outerComponentIds.insert(kvp.first);
|
||
}
|
||
}
|
||
|
||
// Step 6 removed: Same-path sharing mechanism deleted to ensure independent component evaluation
|
||
|
||
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<std::string>& 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<std::string> 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<std::string> 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<std::string> 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<Vector3D> 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<Vector3D> 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::Vector3D> CreoManager::ExtractSolidVertices(pfcSolid_ptr solid, int maxVertices) {
|
||
std::vector<Vector3D> 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<Vector3D>& 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<Vector3D> 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::Vector3D> CreoManager::ComputePrincipalAxes(const std::vector<Vector3D>& vertices) {
|
||
std::vector<Vector3D> axes;
|
||
|
||
if (vertices.size() < 4) return axes;
|
||
|
||
try {
|
||
// Compute centroid
|
||
Vector3D centroid(0, 0, 0);
|
||
for (const Vector3D& v : vertices) {
|
||
centroid = centroid + v;
|
||
}
|
||
centroid = centroid * (1.0 / vertices.size());
|
||
|
||
// Compute covariance matrix
|
||
double cov[3][3] = {{0}};
|
||
for (const Vector3D& v : vertices) {
|
||
Vector3D diff = v - centroid;
|
||
cov[0][0] += diff.x * diff.x;
|
||
cov[0][1] += diff.x * diff.y;
|
||
cov[0][2] += diff.x * diff.z;
|
||
cov[1][0] += diff.y * diff.x;
|
||
cov[1][1] += diff.y * diff.y;
|
||
cov[1][2] += diff.y * diff.z;
|
||
cov[2][0] += diff.z * diff.x;
|
||
cov[2][1] += diff.z * diff.y;
|
||
cov[2][2] += diff.z * diff.z;
|
||
}
|
||
|
||
double scale = 1.0 / (vertices.size() - 1);
|
||
for (int i = 0; i < 3; ++i) {
|
||
for (int j = 0; j < 3; ++j) {
|
||
cov[i][j] *= scale;
|
||
}
|
||
}
|
||
|
||
// Simplified eigenvalue computation for 3x3 matrix
|
||
// For performance, use simplified approach focusing on dominant eigenvector
|
||
Vector3D axis1(1, 0, 0);
|
||
Vector3D axis2(0, 1, 0);
|
||
Vector3D axis3(0, 0, 1);
|
||
|
||
// Find dominant direction (maximum variance)
|
||
double maxVar = cov[0][0];
|
||
int maxIdx = 0;
|
||
if (cov[1][1] > maxVar) { maxVar = cov[1][1]; maxIdx = 1; }
|
||
if (cov[2][2] > maxVar) { maxVar = cov[2][2]; maxIdx = 2; }
|
||
|
||
if (maxIdx == 0) {
|
||
axis1 = Vector3D(1, 0, 0);
|
||
axis2 = Vector3D(0, 1, 0);
|
||
axis3 = Vector3D(0, 0, 1);
|
||
} else if (maxIdx == 1) {
|
||
axis1 = Vector3D(0, 1, 0);
|
||
axis2 = Vector3D(1, 0, 0);
|
||
axis3 = Vector3D(0, 0, 1);
|
||
} else {
|
||
axis1 = Vector3D(0, 0, 1);
|
||
axis2 = Vector3D(1, 0, 0);
|
||
axis3 = Vector3D(0, 1, 0);
|
||
}
|
||
|
||
// Ensure orthogonality
|
||
axis2 = axis2 - axis1 * (axis2.dot(axis1));
|
||
axis2 = axis2.normalize();
|
||
axis3 = axis1.cross(axis2).normalize();
|
||
|
||
axes.push_back(axis1);
|
||
axes.push_back(axis2);
|
||
axes.push_back(axis3);
|
||
|
||
} catch (...) {
|
||
axes.clear();
|
||
}
|
||
|
||
return axes;
|
||
}
|
||
|
||
// Calculate OBB projection support (wrapper for OBB member function)
|
||
double CreoManager::CalculateOBBProjectionSupport(const OBB& obb, const Vector3D& direction) {
|
||
return obb.getSupport(direction);
|
||
}
|
||
|
||
// Calculate OBB thickness in given direction
|
||
double CreoManager::CalculateOBBThickness(const OBB& obb, const Vector3D& direction) {
|
||
// Transform world direction to OBB local space
|
||
Vector3D localDir(
|
||
direction.dot(obb.axes[0]),
|
||
direction.dot(obb.axes[1]),
|
||
direction.dot(obb.axes[2])
|
||
);
|
||
|
||
// Calculate thickness as the extent of OBB in this direction
|
||
// This gives the full dimension, not just half-extent
|
||
return 2.0 * (std::abs(localDir.x * obb.halfExtents.x) +
|
||
std::abs(localDir.y * obb.halfExtents.y) +
|
||
std::abs(localDir.z * obb.halfExtents.z));
|
||
}
|
||
|
||
bool CreoManager::ShouldUseOBB(const ComponentItem& comp) {
|
||
Vector3D size = comp.worldAABB.diagonal();
|
||
double maxDim = std::max({size.x, size.y, size.z});
|
||
double minDim = std::min({size.x, size.y, size.z});
|
||
double aspectRatio = maxDim / minDim;
|
||
return aspectRatio > 2.5 || ComponentClassifier::IsElongatedPart(comp.name);
|
||
}
|
||
|
||
// Ray-based verification for shell analysis
|
||
bool CreoManager::IsComponentBlockedFromCenter(const Vector3D& globalCenter,
|
||
const ComponentItem& component,
|
||
const std::vector<ComponentItem>& allComponents) {
|
||
// Calculate component center
|
||
Vector3D componentCenter = Vector3D(
|
||
(component.worldAABB.minPoint.x + component.worldAABB.maxPoint.x) * 0.5,
|
||
(component.worldAABB.minPoint.y + component.worldAABB.maxPoint.y) * 0.5,
|
||
(component.worldAABB.minPoint.z + component.worldAABB.maxPoint.z) * 0.5
|
||
);
|
||
|
||
// Calculate ray direction (from global center through component center)
|
||
Vector3D rayDirection = (componentCenter - globalCenter).normalize();
|
||
|
||
// Start ray from global center to check all components along the path
|
||
Vector3D rayStart = globalCenter;
|
||
|
||
// Calculate distance from global center to component center
|
||
// We only care about intersections beyond this distance (outward extension)
|
||
double distToComponentCenter = (componentCenter - globalCenter).length();
|
||
|
||
// Check intersection with all other components
|
||
for (const ComponentItem& otherComp : allComponents) {
|
||
// Skip self
|
||
if (otherComp.featureId == component.featureId) {
|
||
continue;
|
||
}
|
||
|
||
// AABB ray intersection test
|
||
// We check if ray intersects other component's AABB beyond the component center
|
||
double tMin = 0.0;
|
||
double tMax = std::numeric_limits<double>::max();
|
||
|
||
for (int i = 0; i < 3; i++) {
|
||
double origin = (i == 0) ? rayStart.x : (i == 1) ? rayStart.y : rayStart.z;
|
||
double dir = (i == 0) ? rayDirection.x : (i == 1) ? rayDirection.y : rayDirection.z;
|
||
double minVal = (i == 0) ? otherComp.worldAABB.minPoint.x :
|
||
(i == 1) ? otherComp.worldAABB.minPoint.y :
|
||
otherComp.worldAABB.minPoint.z;
|
||
double maxVal = (i == 0) ? otherComp.worldAABB.maxPoint.x :
|
||
(i == 1) ? otherComp.worldAABB.maxPoint.y :
|
||
otherComp.worldAABB.maxPoint.z;
|
||
|
||
if (std::abs(dir) < 1e-10) {
|
||
// Ray is parallel to slab
|
||
if (origin < minVal || origin > maxVal) {
|
||
// Ray is outside slab
|
||
tMin = tMax + 1; // Force no intersection
|
||
break;
|
||
}
|
||
} else {
|
||
// Compute intersection t values
|
||
double t1 = (minVal - origin) / dir;
|
||
double t2 = (maxVal - origin) / dir;
|
||
|
||
if (t1 > t2) {
|
||
std::swap(t1, t2);
|
||
}
|
||
|
||
tMin = std::max(tMin, t1);
|
||
tMax = std::min(tMax, t2);
|
||
|
||
if (tMin > tMax) {
|
||
// No intersection
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check if intersection exists and is beyond the component center (outward extension)
|
||
if (tMin <= tMax && tMin >= 0) {
|
||
// Calculate the actual distance of the intersection point
|
||
double intersectionDistance = tMin;
|
||
|
||
// Only consider it blocked if intersection is beyond the component center
|
||
if (intersectionDistance > distToComponentCenter) {
|
||
return true; // Component is blocked by something in the outward direction
|
||
}
|
||
}
|
||
}
|
||
|
||
return false; // Component is not blocked (is outer shell)
|
||
}
|
||
|
||
// ComponentClassifier extension for elongated parts
|
||
bool CreoManager::ComponentClassifier::IsElongatedPart(const std::string& name) {
|
||
// Common elongated part naming patterns
|
||
static const std::vector<std::string> patterns = {
|
||
"pipe", "tube", "rod", "shaft", "beam", "bar",
|
||
"cable", "wire", "rail", "strip", "bracket",
|
||
"arm", "lever", "link", "connector", "hose"
|
||
};
|
||
return MatchesPattern(name, patterns);
|
||
}
|
||
|