- Add ComponentChildrenManager class for retrieving first-level child components - Support path-based component lookup with intelligent top-level assembly handling - Return Creo Feature IDs instead of filenames for precise component identification - Add comprehensive path resolution logic for nested assemblies - Implement new API endpoint /api/creo/component/children 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
502 lines
18 KiB
C++
502 lines
18 KiB
C++
#include "pch.h"
|
|
#include "ComponentChildrenManager.h"
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <iomanip>
|
|
|
|
// Main interface method
|
|
ComponentChildrenManager::ComponentChildrenResult ComponentChildrenManager::GetComponentFirstLevelChildren(const ComponentChildrenRequest& request) {
|
|
ComponentChildrenResult result;
|
|
|
|
if (request.component_path.empty()) {
|
|
result.error_message = "Component path is required";
|
|
return 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 currentAssembly = wfcWAssembly::cast(current_model);
|
|
if (!currentAssembly) {
|
|
result.error_message = "Failed to cast current model to assembly";
|
|
return result;
|
|
}
|
|
|
|
// Check if component_path refers to the current top-level assembly
|
|
std::string currentModelName = "";
|
|
try {
|
|
xstring filename_xstr = current_model->GetFileName();
|
|
currentModelName = XStringToString(filename_xstr);
|
|
} catch (...) {
|
|
// If can't get filename, try origin
|
|
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) {
|
|
currentModelName = origin_str.substr(pos + 1);
|
|
} else {
|
|
currentModelName = origin_str;
|
|
}
|
|
} catch (...) {
|
|
currentModelName = "";
|
|
}
|
|
}
|
|
|
|
// Parse the component path
|
|
std::string actualPath = request.component_path;
|
|
ParsedPath targetPath;
|
|
|
|
if (actualPath.empty()) {
|
|
// Empty path is valid (represents top-level query)
|
|
targetPath.is_valid = true;
|
|
} else {
|
|
targetPath = ParseComponentPath(actualPath);
|
|
if (!targetPath.is_valid) {
|
|
result.error_message = "Invalid component path format";
|
|
return result;
|
|
}
|
|
|
|
// If path starts with current model name, skip the first segment
|
|
if (!targetPath.path_segments.empty() &&
|
|
!currentModelName.empty() &&
|
|
CaseInsensitiveCompare(targetPath.path_segments[0], currentModelName)) {
|
|
// Remove first segment as it refers to current assembly itself
|
|
targetPath.path_segments.erase(targetPath.path_segments.begin());
|
|
if (!targetPath.path_segments.empty()) {
|
|
targetPath.target_component = targetPath.path_segments.back();
|
|
} else {
|
|
// Only top-level name provided, treat as top-level query
|
|
actualPath = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle top-level assembly case (empty path after conversion)
|
|
ComponentMatch match;
|
|
bool found = false;
|
|
|
|
if (actualPath.empty() || targetPath.path_segments.empty()) {
|
|
// Direct query of top-level assembly, no need to search
|
|
match.found = true;
|
|
match.owner_assembly = currentAssembly;
|
|
found = true;
|
|
} else {
|
|
// Search for the component in the assembly
|
|
found = FindComponentByPath(currentAssembly, targetPath, match);
|
|
if (!found || !match.found) {
|
|
result.error_message = "Component not found at path: " + request.component_path;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Validate component ID if provided
|
|
if (request.component_id != -1) {
|
|
int actualFeatureId = -1;
|
|
try {
|
|
if (match.feature) {
|
|
actualFeatureId = match.feature->GetId();
|
|
}
|
|
} catch (...) {
|
|
actualFeatureId = -1;
|
|
}
|
|
|
|
if (actualFeatureId != request.component_id) {
|
|
result.error_message = "Component Feature ID mismatch. Expected: " + std::to_string(request.component_id) +
|
|
", Found: " + std::to_string(actualFeatureId);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Set parent component info
|
|
result.parent_component_path = request.component_path;
|
|
|
|
pfcModel_ptr componentModel = nullptr;
|
|
wfcWAssembly_ptr componentAssembly = nullptr;
|
|
|
|
if (actualPath.empty()) {
|
|
// Top-level assembly case
|
|
result.parent_component_id = -1; // No Feature ID for top-level
|
|
result.parent_component_filename = currentModelName;
|
|
componentModel = current_model;
|
|
componentAssembly = currentAssembly;
|
|
} else {
|
|
// Sub-component case
|
|
try {
|
|
if (match.feature) {
|
|
result.parent_component_id = match.feature->GetId();
|
|
}
|
|
|
|
auto modelDescr = match.component_feature->GetModelDescr();
|
|
if (modelDescr) {
|
|
xstring filename_xstr = modelDescr->GetFileName();
|
|
result.parent_component_filename = XStringToString(filename_xstr);
|
|
}
|
|
} catch (...) {
|
|
result.parent_component_id = -1;
|
|
result.parent_component_filename = "unknown";
|
|
}
|
|
|
|
// Load the component model to get its children
|
|
componentModel = LoadComponentModel(match.component_feature);
|
|
if (!componentModel) {
|
|
result.error_message = "Failed to load component model";
|
|
return result;
|
|
}
|
|
|
|
// Check if the component is an assembly (has children)
|
|
if (componentModel->GetType() != pfcMDL_ASSEMBLY) {
|
|
// Part has no children, return empty list
|
|
result.success = true;
|
|
result.message = "Component is a part (no children)";
|
|
result.children_count = 0;
|
|
return result;
|
|
}
|
|
|
|
// Cast to assembly and get first level children
|
|
componentAssembly = wfcWAssembly::cast(componentModel);
|
|
}
|
|
if (!componentAssembly) {
|
|
result.error_message = "Failed to cast component to assembly";
|
|
return result;
|
|
}
|
|
|
|
// Get all component features at the first level
|
|
pfcFeatures_ptr features = componentAssembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT);
|
|
if (!features) {
|
|
result.success = true;
|
|
result.message = "Assembly has no components";
|
|
result.children_count = 0;
|
|
return result;
|
|
}
|
|
|
|
int features_count = features->getarraysize();
|
|
if (features_count <= 0) {
|
|
result.success = true;
|
|
result.message = "Assembly has no components";
|
|
result.children_count = 0;
|
|
return result;
|
|
}
|
|
|
|
// Collect all first-level children
|
|
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;
|
|
|
|
ChildComponentInfo childInfo = CreateChildComponentFromFeature(compFeat, 1, request.component_path);
|
|
result.children.push_back(childInfo);
|
|
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
result.children_count = result.children.size();
|
|
result.success = true;
|
|
result.message = "Successfully retrieved " + std::to_string(result.children_count) + " child components";
|
|
|
|
} catch (const std::exception& e) {
|
|
result.error_message = "Exception: " + std::string(e.what());
|
|
} catch (...) {
|
|
result.error_message = "Unknown exception occurred";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Helper method implementations
|
|
ComponentChildrenManager::SessionInfo ComponentChildrenManager::GetSessionInfo() {
|
|
SessionInfo info;
|
|
try {
|
|
info.session = pfcGetCurrentSessionWithCompatibility(pfcC4Compatible);
|
|
info.is_valid = (info.session != nullptr);
|
|
} catch (...) {
|
|
info.is_valid = false;
|
|
}
|
|
return info;
|
|
}
|
|
|
|
ComponentChildrenManager::ParsedPath ComponentChildrenManager::ParseComponentPath(const std::string& full_path) {
|
|
ParsedPath result;
|
|
|
|
if (full_path.empty()) {
|
|
return result;
|
|
}
|
|
|
|
// Split path by '/' or '\'
|
|
std::string path = full_path;
|
|
std::replace(path.begin(), path.end(), '\\', '/');
|
|
|
|
std::istringstream iss(path);
|
|
std::string segment;
|
|
|
|
while (std::getline(iss, segment, '/')) {
|
|
if (!segment.empty()) {
|
|
result.path_segments.push_back(segment);
|
|
}
|
|
}
|
|
|
|
if (!result.path_segments.empty()) {
|
|
result.target_component = result.path_segments.back();
|
|
result.is_valid = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool ComponentChildrenManager::FindComponentByPath(wfcWAssembly_ptr assembly,
|
|
const ParsedPath& target_path,
|
|
ComponentMatch& match) {
|
|
if (!assembly || !target_path.is_valid) return false;
|
|
|
|
try {
|
|
return RecursiveSearchComponent(assembly, target_path, "", 0, match);
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ComponentChildrenManager::RecursiveSearchComponent(wfcWAssembly_ptr currentAssembly,
|
|
const ParsedPath& target_path,
|
|
const std::string& pathSoFar,
|
|
int currentLevel,
|
|
ComponentMatch& match) {
|
|
if (!currentAssembly || currentLevel >= (int)target_path.path_segments.size()) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
std::string currentTarget = target_path.path_segments[currentLevel];
|
|
|
|
pfcFeatures_ptr features = currentAssembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT);
|
|
if (!features) return false;
|
|
|
|
int features_count = features->getarraysize();
|
|
|
|
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;
|
|
|
|
auto modelDescr = compFeat->GetModelDescr();
|
|
if (!modelDescr) continue;
|
|
|
|
xstring filename_xstr = modelDescr->GetFileName();
|
|
std::string componentName = XStringToString(filename_xstr);
|
|
|
|
std::string newPath = pathSoFar.empty() ? componentName : pathSoFar + "/" + componentName;
|
|
|
|
if (CaseInsensitiveCompare(componentName, currentTarget)) {
|
|
// Found the target at this level
|
|
if (currentLevel == (int)target_path.path_segments.size() - 1) {
|
|
// This is the final target
|
|
match.feature = feature;
|
|
match.component_feature = compFeat;
|
|
match.owner_assembly = currentAssembly;
|
|
match.actual_path = newPath;
|
|
match.found = true;
|
|
return true;
|
|
} else {
|
|
// Need to go deeper
|
|
pfcModel_ptr childModel = LoadComponentModel(compFeat);
|
|
if (childModel && childModel->GetType() == pfcMDL_ASSEMBLY) {
|
|
wfcWAssembly_ptr childAssembly = wfcWAssembly::cast(childModel);
|
|
if (childAssembly) {
|
|
if (RecursiveSearchComponent(childAssembly, target_path, newPath, currentLevel + 1, match)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ComponentChildrenManager::ChildComponentInfo ComponentChildrenManager::CreateChildComponentFromFeature(pfcComponentFeat_ptr compFeat,
|
|
int level,
|
|
const std::string& parentPath) {
|
|
ChildComponentInfo component;
|
|
component.level = level;
|
|
component.children_count = 0;
|
|
component.is_visible = true;
|
|
component.file_size = "0.0MB";
|
|
component.id = -1; // Default Feature ID
|
|
|
|
try {
|
|
// Get Feature ID first
|
|
pfcFeature_ptr feature = pfcFeature::cast(compFeat);
|
|
if (feature) {
|
|
component.id = feature->GetId();
|
|
}
|
|
|
|
auto modelDescr = compFeat->GetModelDescr();
|
|
if (modelDescr) {
|
|
xstring filename_xstr = modelDescr->GetFileName();
|
|
component.filename = XStringToString(filename_xstr);
|
|
|
|
component.name = component.filename;
|
|
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]);
|
|
}
|
|
|
|
// Build paths
|
|
if (parentPath.empty()) {
|
|
component.path = component.filename;
|
|
component.full_path = component.filename;
|
|
} else {
|
|
component.path = parentPath + "/" + component.filename;
|
|
component.full_path = component.path;
|
|
}
|
|
|
|
// Determine component type
|
|
std::string ext = "";
|
|
if (ext_pos != std::string::npos) {
|
|
ext = component.filename.substr(ext_pos + 1);
|
|
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
|
}
|
|
|
|
if (ext == "asm") {
|
|
component.type = "assembly";
|
|
component.model_type = "MDL_ASSEMBLY";
|
|
} else {
|
|
component.type = "part";
|
|
component.model_type = "MDL_PART";
|
|
}
|
|
|
|
// Try to load model and get additional info
|
|
pfcModel_ptr childModel = LoadComponentModel(compFeat);
|
|
if (childModel) {
|
|
component.file_size = GetModelFileSize(childModel);
|
|
|
|
if (childModel->GetType() == pfcMDL_ASSEMBLY) {
|
|
wfcWAssembly_ptr childAssembly = wfcWAssembly::cast(childModel);
|
|
if (childAssembly) {
|
|
try {
|
|
pfcFeatures_ptr childFeatures = childAssembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT);
|
|
if (childFeatures) {
|
|
component.children_count = childFeatures->getarraysize();
|
|
}
|
|
} catch (...) {
|
|
component.children_count = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (...) {
|
|
component.id = -1;
|
|
component.filename = "unknown_component";
|
|
component.name = "Unknown";
|
|
component.type = "unknown";
|
|
component.model_type = "UNKNOWN";
|
|
}
|
|
|
|
return component;
|
|
}
|
|
|
|
pfcModel_ptr ComponentChildrenManager::LoadComponentModel(pfcComponentFeat_ptr compFeat) {
|
|
if (!compFeat) return nullptr;
|
|
|
|
try {
|
|
auto modelDescr = compFeat->GetModelDescr();
|
|
if (!modelDescr) return nullptr;
|
|
|
|
SessionInfo sessionInfo = GetSessionInfo();
|
|
if (!sessionInfo.is_valid) return nullptr;
|
|
|
|
pfcModel_ptr model = sessionInfo.session->GetModelFromDescr(modelDescr);
|
|
if (!model) {
|
|
model = sessionInfo.session->RetrieveModel(modelDescr);
|
|
}
|
|
|
|
return model;
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
std::string ComponentChildrenManager::GetModelFileSize(pfcModel_ptr model) {
|
|
try {
|
|
return "N/A";
|
|
} catch (...) {
|
|
return "0.0MB";
|
|
}
|
|
}
|
|
|
|
std::string ComponentChildrenManager::XStringToString(const xstring& xstr) {
|
|
try {
|
|
const wchar_t* wstr = xstr;
|
|
if (!wstr) return "";
|
|
|
|
int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
|
|
if (bufferSize <= 0) return "";
|
|
|
|
std::string result(bufferSize - 1, '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &result[0], bufferSize, nullptr, nullptr);
|
|
|
|
return result;
|
|
} catch (...) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
xstring ComponentChildrenManager::StringToXString(const std::string& str) {
|
|
try {
|
|
int bufferSize = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
|
|
if (bufferSize <= 0) return xstring();
|
|
|
|
std::vector<wchar_t> wstr(bufferSize);
|
|
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &wstr[0], bufferSize);
|
|
|
|
return xstring(&wstr[0]);
|
|
} catch (...) {
|
|
return xstring();
|
|
}
|
|
}
|
|
|
|
bool ComponentChildrenManager::CaseInsensitiveCompare(const std::string& str1, const std::string& str2) {
|
|
if (str1.length() != str2.length()) return false;
|
|
|
|
return std::equal(str1.begin(), str1.end(), str2.begin(),
|
|
[](char a, char b) {
|
|
return std::tolower(a) == std::tolower(b);
|
|
});
|
|
} |