From 8fd63500f29d159be060540a8ae78d88eb331ee6 Mon Sep 17 00:00:00 2001 From: sladro Date: Sun, 21 Sep 2025 12:42:57 +0800 Subject: [PATCH] feat: implement component children API with Creo Feature ID support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- ComponentChildrenManager.cpp | 502 +++++++++++++++++++++++++++++++++++ ComponentChildrenManager.h | 118 ++++++++ MFCCreoDll.cpp | 104 ++++++++ MFCCreoDll.vcxproj | 2 + MFCCreoDll.vcxproj.filters | 6 + 5 files changed, 732 insertions(+) create mode 100644 ComponentChildrenManager.cpp create mode 100644 ComponentChildrenManager.h diff --git a/ComponentChildrenManager.cpp b/ComponentChildrenManager.cpp new file mode 100644 index 0000000..74fc046 --- /dev/null +++ b/ComponentChildrenManager.cpp @@ -0,0 +1,502 @@ +#include "pch.h" +#include "ComponentChildrenManager.h" +#include +#include +#include + +// 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 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); + }); +} \ No newline at end of file diff --git a/ComponentChildrenManager.h b/ComponentChildrenManager.h new file mode 100644 index 0000000..c7ac934 --- /dev/null +++ b/ComponentChildrenManager.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include + +// OTK headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ComponentChildrenManager { +public: + // Request data structure + struct ComponentChildrenRequest { + std::string component_path; // Required: component path like "Assembly.asm/SubAsm.asm/Part.prt" + int component_id = -1; // Optional: Creo Feature ID for additional validation (-1 means not specified) + }; + + // Child component info structure + struct ChildComponentInfo { + int id; // Creo Feature ID + std::string filename; // Component filename (e.g., "part.prt") + std::string name; + std::string type; // "part", "assembly" + int level; + std::string path; + std::string full_path; + std::string file_size; + bool is_visible; + std::string model_type; // "MDL_PART", "MDL_ASSEMBLY" + int children_count; + }; + + // Response data structure + struct ComponentChildrenResult { + bool success = false; + std::string message; + int parent_component_id = -1; // Creo Feature ID + std::string parent_component_filename; // Component filename + std::string parent_component_path; + int children_count = 0; + std::vector children; + std::string error_message; + }; + +private: + // Session info structure + struct SessionInfo { + pfcSession_ptr session; + bool is_valid = false; + }; + + // Path parsing structure + struct ParsedPath { + std::vector path_segments; + std::string target_component; + bool is_valid = false; + }; + + // Component match result + struct ComponentMatch { + pfcFeature_ptr feature; + pfcComponentFeat_ptr component_feature; + wfcWAssembly_ptr owner_assembly; + std::string actual_path; + bool found = false; + }; + +private: + // Helper methods + SessionInfo GetSessionInfo(); + ParsedPath ParseComponentPath(const std::string& full_path); + bool FindComponentByPath(wfcWAssembly_ptr assembly, + const ParsedPath& target_path, + ComponentMatch& match); + bool RecursiveSearchComponent(wfcWAssembly_ptr currentAssembly, + const ParsedPath& target_path, + const std::string& pathSoFar, + int currentLevel, + ComponentMatch& match); + ChildComponentInfo CreateChildComponentFromFeature(pfcComponentFeat_ptr compFeat, + int level, + const std::string& parentPath); + pfcModel_ptr LoadComponentModel(pfcComponentFeat_ptr compFeat); + std::string GetModelFileSize(pfcModel_ptr model); + + // String utility methods + std::string XStringToString(const xstring& xstr); + xstring StringToXString(const std::string& str); + bool CaseInsensitiveCompare(const std::string& str1, const std::string& str2); + +public: + // Main interface method + ComponentChildrenResult GetComponentFirstLevelChildren(const ComponentChildrenRequest& request); + + // Singleton pattern + static ComponentChildrenManager& Instance() { + static ComponentChildrenManager instance; + return instance; + } + +private: + // Private constructor for singleton + ComponentChildrenManager() = default; + ComponentChildrenManager(const ComponentChildrenManager&) = delete; + ComponentChildrenManager& operator=(const ComponentChildrenManager&) = delete; +}; diff --git a/MFCCreoDll.cpp b/MFCCreoDll.cpp index 4faf5fd..4da8c3d 100644 --- a/MFCCreoDll.cpp +++ b/MFCCreoDll.cpp @@ -11,6 +11,7 @@ #include "GeometryAnalyzer.h" #include "ModelSearchHandler.h" #include "HierarchyStatisticsAnalyzer.h" +#include "ComponentChildrenManager.h" #include #include #include @@ -1648,6 +1649,108 @@ HttpResponse HierarchyStatisticsHandler(const HttpRequest& request) { return response; } +// Component Children Handler +HttpResponse ComponentChildrenHandler(const HttpRequest& request) { + HttpResponse response; + response.headers["Content-Type"] = "application/json"; + response.headers["Access-Control-Allow-Origin"] = "*"; + response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"; + response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"; + + if (request.method != "POST") { + response.status_code = 405; + response.body = "{\"success\": false, \"error\": \"Method not allowed\"}"; + return response; + } + + try { + // Parse JSON request + ComponentChildrenManager::ComponentChildrenRequest children_request; + + children_request.component_path = ExtractJsonValue(request.body, "component_path"); + + // Parse component_id as integer (Creo Feature ID) + std::string component_id_str = ExtractJsonValue(request.body, "component_id"); + if (!component_id_str.empty()) { + try { + children_request.component_id = std::stoi(component_id_str); + } catch (...) { + response.status_code = 400; + response.body = "{\"success\": false, \"error\": \"Invalid component_id: must be a number\"}"; + return response; + } + } + + if (children_request.component_path.empty()) { + response.status_code = 400; + response.body = "{\"success\": false, \"error\": \"Missing required parameter: component_path\"}"; + return response; + } + + // Call ComponentChildrenManager + ComponentChildrenManager::ComponentChildrenResult result = + ComponentChildrenManager::Instance().GetComponentFirstLevelChildren(children_request); + + if (result.success) { + // Build success response + std::ostringstream json; + json << "{" + << "\"success\": true," + << "\"message\": \"" << EscapeJsonString(result.message) << "\"," + << "\"data\": {" + << "\"parent_component_id\": " << result.parent_component_id << "," + << "\"parent_component_filename\": \"" << EscapeJsonString(result.parent_component_filename) << "\"," + << "\"parent_component_path\": \"" << EscapeJsonString(result.parent_component_path) << "\"," + << "\"children_count\": " << result.children_count << "," + << "\"children\": ["; + + // Build children array + for (size_t i = 0; i < result.children.size(); ++i) { + if (i > 0) json << ","; + const auto& child = result.children[i]; + json << "{" + << "\"id\": " << child.id << "," + << "\"filename\": \"" << EscapeJsonString(child.filename) << "\"," + << "\"name\": \"" << EscapeJsonString(child.name) << "\"," + << "\"type\": \"" << child.type << "\"," + << "\"level\": " << child.level << "," + << "\"path\": \"" << EscapeJsonString(child.path) << "\"," + << "\"full_path\": \"" << EscapeJsonString(child.full_path) << "\"," + << "\"file_size\": \"" << child.file_size << "\"," + << "\"is_visible\": " << (child.is_visible ? "true" : "false") << "," + << "\"model_type\": \"" << child.model_type << "\"," + << "\"children_count\": " << child.children_count + << "}"; + } + + json << "]" + << "}" + << "}"; + + response.body = json.str(); + } else { + // Build error response + std::ostringstream json; + json << "{" + << "\"success\": false," + << "\"data\": null," + << "\"error\": \"" << EscapeJsonString(result.error_message) << "\"" + << "}"; + response.body = json.str(); + response.status_code = 500; + } + + } catch (const std::exception& e) { + response.status_code = 500; + response.body = "{\"success\": false, \"error\": \"Exception: " + std::string(e.what()) + "\"}"; + } catch (...) { + response.status_code = 500; + response.body = "{\"success\": false, \"error\": \"Unknown error during component children retrieval\"}"; + } + + return response; +} + extern "C" int user_initialize( int argc, @@ -1684,6 +1787,7 @@ extern "C" int user_initialize( g_http_server->SetRouteHandler("/api/creo/component/delete-by-path", PathDeleteHandler); g_http_server->SetRouteHandler("/api/search/models", ModelSearchHandler::HandleModelSearchRequest); g_http_server->SetRouteHandler("/api/analysis/hierarchy-statistics", HierarchyStatisticsHandler); + g_http_server->SetRouteHandler("/api/creo/component/children", ComponentChildrenHandler); if (g_http_server->Start()) { diff --git a/MFCCreoDll.vcxproj b/MFCCreoDll.vcxproj index 779ac5d..b922c07 100644 --- a/MFCCreoDll.vcxproj +++ b/MFCCreoDll.vcxproj @@ -207,6 +207,7 @@ ws2_32.lib;%(AdditionalDependencies) + @@ -236,6 +237,7 @@ ws2_32.lib;%(AdditionalDependencies) + diff --git a/MFCCreoDll.vcxproj.filters b/MFCCreoDll.vcxproj.filters index 7636aca..d1b8de4 100644 --- a/MFCCreoDll.vcxproj.filters +++ b/MFCCreoDll.vcxproj.filters @@ -90,6 +90,9 @@ 源文件\src\creo + + 源文件\src\creo + @@ -172,6 +175,9 @@ 源文件\src\creo + + 源文件\src\creo +