diff --git a/CreoManager.cpp b/CreoManager.cpp index fe56da2..af3e8c3 100644 --- a/CreoManager.cpp +++ b/CreoManager.cpp @@ -16,10 +16,19 @@ #include #include +#define _USE_MATH_DEFINES +#include +#include #include #include + +// Define PI constant for compatibility +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif #include #include +#include #include #include #include @@ -1486,7 +1495,7 @@ CreoManager::HierarchyDeleteResult CreoManager::DeleteHierarchyComponents(const return result; } -// Enhanced Shell Analysis implementation using real geometry APIs +// Multi-directional extreme value projection shell analysis implementation CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeaturesEnhanced(const ShellAnalysisRequest& request) { ShellAnalysisResult result; @@ -1515,275 +1524,140 @@ CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeaturesEnhanced(const result.analysis_parameters.confidence_threshold = request.confidence_threshold; result.analysis_parameters.assembly_analysis = is_assembly; - // Core analysis based on model type if (!is_assembly) { - // ======== PART MODEL ANALYSIS ======== - // Identify shell features based on surface visibility + result.error_message = "Multi-directional projection analysis is designed for assembly models only"; + return result; + } - pfcPart_ptr part = pfcPart::cast(currentModel); - pfcSolid_ptr solid = pfcSolid::cast(part); + // Cast to assembly + pfcAssembly_ptr assembly = pfcAssembly::cast(currentModel); + if (!assembly) { + result.error_message = "Cannot cast model to assembly"; + return result; + } - if (!solid) { - result.error_message = "Cannot cast model to solid"; - return result; - } + // Execute multi-directional extreme value projection algorithm + std::unordered_set outerComponentIds = PerformMultiDirectionalProjectionAnalysis(assembly); - // Get all features from the solid - pfcFeatures_ptr features = solid->ListFeaturesByType(xfalse); - if (!features) { - result.error_message = "Cannot get features from model"; - return result; - } + // 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; + } - int total_features = features->getarraysize(); - result.total_features_analyzed = total_features; + wfcWComponentPaths_ptr componentPaths = wAssembly->ListDisplayedComponents(); + if (!componentPaths) { + result.error_message = "Cannot get component paths from assembly"; + return result; + } - // Analyze each feature - for (int i = 0; i < total_features; i++) { - try { - pfcFeature_ptr feature = features->get(i); + int total_components = componentPaths->getarraysize(); + result.total_features_analyzed = total_components; + + // Build feature ID map first (same approach as CollectAllComponents) + std::map modelNameToFeatureId; + try { + pfcFeatures_ptr features = assembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT); + if (features) { + for (int j = 0; j < features->getarraysize(); j++) { + pfcFeature_ptr feature = features->get(j); if (!feature) continue; - // Skip non-geometric features - pfcFeatureType feat_type = feature->GetFeatType(); - 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) { - continue; // Skip datum features + pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature); + if (!compFeat) continue; + + pfcModelDescriptor_ptr modelDesc = compFeat->GetModelDescr(); + if (!modelDesc) continue; + + std::string modelName = XStringToString(modelDesc->GetFileName()); + if (!modelName.empty()) { + modelNameToFeatureId[modelName] = feature->GetId(); } - - // Get feature name - std::string feat_name; - try { - xstring xstr = feature->GetName(); - feat_name = XStringToString(xstr); - if (feat_name.empty()) { - feat_name = "FEATURE_" + std::to_string(i + 1); - } - } catch (...) { - feat_name = "FEATURE_" + std::to_string(i + 1); - } - - // Core decision: Is this a shell feature? - bool is_shell = IsShellFeature(feature, solid); - - // Create analysis item - ShellAnalysisItem item; - item.name = feat_name; - item.type = GetFeatureTypeName(feat_type); - item.feature_id = i; - - if (is_shell) { - // Shell feature - must keep - item.confidence = 20.0; // Low confidence for deletion - item.recommendation = "KEEP"; - item.reason = "Creates visible external surfaces"; - result.shell_features_count++; - } else { - // Internal feature - can delete - item.confidence = 80.0; // High confidence for deletion - item.recommendation = "DELETE"; - item.reason = "Internal feature with no visible surfaces"; - result.internal_features_count++; - } - - // Apply user preferences - if (request.preserve_external_surfaces && is_shell) { - 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 features - continue; } } + } catch (...) { + // Continue with fallback approach if feature listing fails + } - } else { - // ======== ASSEMBLY MODEL ANALYSIS ======== - // Identify occluded components + // Build analysis result from projection analysis + for (int i = 0; i < total_components; i++) { + try { + wfcWComponentPath_ptr wPath = componentPaths->get(i); + if (!wPath) continue; - wfcWAssembly_ptr assembly = wfcWAssembly::cast(currentModel); - pfcSolid_ptr assemblySolid = pfcSolid::cast(assembly); + // Get component solid and model (same as in CollectAllComponents) + pfcSolid_ptr compSolid = wPath->GetLeaf(); + if (!compSolid) continue; - if (!assemblySolid) { - result.error_message = "Cannot cast assembly to solid"; - return result; - } + pfcModel_ptr compModel = pfcModel::cast(compSolid); + if (!compModel) continue; - // Get all components - pfcFeatures_ptr components = assembly->ListFeaturesByType(xfalse, pfcFeatureType::pfcFEATTYPE_COMPONENT); - if (!components) { - result.error_message = "Cannot get components from assembly"; - return result; - } - - int total_components = components->getarraysize(); - result.total_features_analyzed = total_components; - - // Global interference analysis for assembly-wide context - pfcGlobalInterferences_ptr globalInterferences = AnalyzeGlobalInterferences(currentModel); - if (globalInterferences) { - // Enhanced accuracy through global interference context - result.analysis_parameters.assembly_analysis = true; - } - - // Analyze each component with deep feature-level analysis - for (int i = 0; i < total_components; i++) { + // Get component name + std::string comp_name; try { - pfcFeature_ptr comp_feature = components->get(i); - if (!comp_feature) continue; - - pfcComponentFeat_ptr comp = pfcComponentFeat::cast(comp_feature); - if (!comp) continue; - - // Get component name - std::string comp_name; - try { - xstring xstr = comp->GetName(); - comp_name = XStringToString(xstr); - if (comp_name.empty()) { - comp_name = "COMPONENT_" + std::to_string(i + 1); - } - } catch (...) { + xstring nameXStr = compModel->GetFileName(); + comp_name = XStringToString(nameXStr); + if (comp_name.empty()) { comp_name = "COMPONENT_" + std::to_string(i + 1); } - - // Load component model - pfcModel_ptr compModel = LoadComponentModel(comp); - if (!compModel) continue; - - pfcSolid_ptr compSolid = pfcSolid::cast(compModel); - if (!compSolid) continue; - - // Component-level occlusion analysis using advanced interference detection - bool component_occluded = IsComponentOccludedAdvanced(compSolid, assemblySolid); - - // Deep feature-level analysis for this component - pfcFeatures_ptr comp_features = compSolid->ListFeaturesByType(xfalse); - if (comp_features) { - int comp_feature_count = comp_features->getarraysize(); - - for (int j = 0; j < comp_feature_count; j++) { - try { - pfcFeature_ptr feature = comp_features->get(j); - if (!feature) continue; - - // Skip non-geometric features - pfcFeatureType feat_type = feature->GetFeatType(); - 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) { - continue; - } - - // Get feature name - std::string feat_name; - try { - xstring xstr = feature->GetName(); - feat_name = XStringToString(xstr); - if (feat_name.empty()) { - feat_name = comp_name + "_FEATURE_" + std::to_string(j + 1); - } - } catch (...) { - feat_name = comp_name + "_FEATURE_" + std::to_string(j + 1); - } - - // Feature-level shell analysis - bool is_shell_feature = IsShellFeature(feature, compSolid); - - // Create analysis item for component feature - ShellAnalysisItem item; - item.name = feat_name; - item.type = GetFeatureTypeName(feat_type); - item.feature_id = j; - - // Enhanced decision logic based on real occlusion analysis - if (component_occluded && !is_shell_feature) { - // Component occluded AND feature is internal = highest confidence deletion - item.confidence = 90.0; - item.recommendation = "DELETE"; - item.reason = "Internal feature in occluded component (real geometry analysis)"; - result.internal_features_count++; - } else if (component_occluded && is_shell_feature) { - // Component occluded BUT feature creates external surfaces = moderate confidence - // May be visible from certain angles despite overall occlusion - item.confidence = 65.0; - item.recommendation = "DELETE"; - item.reason = "External feature in occluded component (moderate confidence)"; - result.internal_features_count++; - } else if (!component_occluded && !is_shell_feature) { - // Component visible BUT feature is internal = standard deletion confidence - item.confidence = 80.0; - item.recommendation = "DELETE"; - item.reason = "Internal feature in visible component"; - result.internal_features_count++; - } else { - // Component visible AND feature creates external surfaces = keep with high confidence - item.confidence = 10.0; // Very low deletion confidence = high keep confidence - item.recommendation = "KEEP"; - item.reason = "External feature in visible component"; - result.shell_features_count++; - } - - // Apply user preferences - if (request.preserve_external_surfaces && is_shell_feature) { - 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 features - continue; - } - } - } else { - // Component has no features, treat as single item - ShellAnalysisItem item; - item.name = comp_name; - item.type = "COMPONENT"; - item.feature_id = i; - - if (component_occluded) { - item.confidence = 75.0; - item.recommendation = "DELETE"; - item.reason = "Component is occluded by other components"; - result.internal_features_count++; - } else { - item.confidence = 25.0; - item.recommendation = "KEEP"; - item.reason = "Component is visible on assembly boundary"; - result.shell_features_count++; - } - - if (item.confidence >= request.confidence_threshold) { - item.is_deletable = true; - result.total_deletable++; - } - - result.features.push_back(item); - } - } catch (...) { - // Skip problematic components - continue; + comp_name = "COMPONENT_" + std::to_string(i + 1); } + + // Get stable feature ID from map or use fallback + int stableFeatureId = -1; + if (modelNameToFeatureId.find(comp_name) != modelNameToFeatureId.end()) { + stableFeatureId = modelNameToFeatureId[comp_name]; + } else { + // Fallback: use component path IDs + xintsequence_ptr componentIds = wPath->GetComponentIds(); + if (componentIds && componentIds->getarraysize() > 0) { + stableFeatureId = componentIds->get(componentIds->getarraysize() - 1); + } else { + stableFeatureId = i; // Last resort: use index + } + } + + // Create analysis item + ShellAnalysisItem item; + item.name = comp_name; + item.type = "COMPONENT"; + item.feature_id = stableFeatureId; // Use stable feature ID + + // Determine if this component is on outer surface based on projection analysis + bool is_outer_component = (outerComponentIds.find(item.feature_id) != outerComponentIds.end()); + + if (is_outer_component) { + // Outer surface component - low deletion confidence + item.confidence = 15.0; + item.recommendation = "KEEP"; + item.reason = "Component on assembly outer surface (multi-directional projection analysis)"; + result.shell_features_count++; + } else { + // Internal component - high deletion confidence + item.confidence = 85.0; + item.recommendation = "DELETE"; + item.reason = "Internal component not visible from any direction (multi-directional projection analysis)"; + result.internal_features_count++; + } + + // 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; } } @@ -1803,7 +1677,7 @@ CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeaturesEnhanced(const } catch (const std::exception& e) { result.error_message = "Exception: " + std::string(e.what()); } catch (...) { - result.error_message = "Unknown error during shell analysis"; + result.error_message = "Unknown error during multi-directional projection analysis"; } return result; @@ -2478,237 +2352,306 @@ bool CreoManager::HasInterferenceWith(pfcFeature_ptr comp1, pfcFeature_ptr comp2 } // ===================================================== -// NEW SHELL ANALYSIS CORE FUNCTIONS USING REAL OTK API +// MULTI-DIRECTIONAL EXTREME VALUE PROJECTION ALGORITHM // ===================================================== -// Enhanced surface boundary detection using document APIs -bool CreoManager::IsVisibleSurface(pfcSurface_ptr surface) { - if (!surface) return false; +// 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 { - // Step 1: Basic visibility check - if (!surface->GetIsVisible()) { - return false; // Not visible = definitely internal + // Transform all 8 corners of AABB + std::vector corners = { + Vector3D(localAABB.minPoint.x, localAABB.minPoint.y, localAABB.minPoint.z), + Vector3D(localAABB.maxPoint.x, localAABB.minPoint.y, localAABB.minPoint.z), + Vector3D(localAABB.minPoint.x, localAABB.maxPoint.y, localAABB.minPoint.z), + Vector3D(localAABB.maxPoint.x, localAABB.maxPoint.y, localAABB.minPoint.z), + Vector3D(localAABB.minPoint.x, localAABB.minPoint.y, localAABB.maxPoint.z), + Vector3D(localAABB.maxPoint.x, localAABB.minPoint.y, localAABB.maxPoint.z), + Vector3D(localAABB.minPoint.x, localAABB.maxPoint.y, localAABB.maxPoint.z), + Vector3D(localAABB.maxPoint.x, localAABB.maxPoint.y, localAABB.maxPoint.z) + }; + + AABB worldAABB; + + for (const Vector3D& corner : corners) { + // Create pfcPoint3D + pfcPoint3D_ptr localPoint = pfcPoint3D::create(); + localPoint->set(0, corner.x); + localPoint->set(1, corner.y); + localPoint->set(2, corner.z); + + // Transform point + pfcPoint3D_ptr worldPoint = transform->TransformPoint(localPoint); + + // Convert back to Vector3D and expand AABB + Vector3D worldCorner = PfcPointToVector3D(worldPoint); + worldAABB.expand(worldCorner); } - // Step 2: Advanced contour analysis (per document requirements) - pfcContours_ptr contours = surface->ListContours(); - if (!contours) { - return false; // No contours = cannot determine boundary - } + return worldAABB; - // Step 3: Check for external contours (document algorithm) - bool has_external_contour = false; - for (int i = 0; i < contours->getarraysize(); i++) { - pfcContour_ptr contour = contours->get(i); - if (contour) { - // External contour = not internal traversal - if (!contour->GetInternalTraversal()) { - has_external_contour = true; + } catch (...) { + return localAABB; // Fallback to local AABB if transform fails + } +} - // Step 4: Boundary edge detection (document algorithm) - pfcEdges_ptr edges = contour->ListElements(); - if (edges) { - for (int j = 0; j < edges->getarraysize(); j++) { - pfcEdge_ptr edge = edges->get(j); - if (edge) { - // Boundary edge = edge with only one surface - if (edge->GetSurface2() == nullptr) { - return true; // Found boundary edge = external surface - } - } - } +// Sample directions using Fibonacci sphere distribution +std::vector CreoManager::SampleDirections(int count) { + std::vector directions; + directions.reserve(count); + + double phi = (1.0 + sqrt(5.0)) / 2.0; // Golden ratio + + for (int i = 0; i < count; ++i) { + double t = (double(i) + 0.5) / double(count); + double z = 1.0 - 2.0 * t; // z from 1 to -1 (full sphere) + + // No clamping - use full sphere sampling + double r = sqrt(std::max(0.0, 1.0 - z * z)); + double azimuth = 2.0 * M_PI * i / phi; + + Vector3D dir(r * cos(azimuth), r * sin(azimuth), z); + directions.push_back(dir.normalize()); + } + + return directions; +} + +// Calculate support point projection for AABB in given direction +double CreoManager::CalculateProjectionSupport(const AABB& aabb, const Vector3D& direction) { + // Support function for AABB: choose the corner that maximizes dot product with direction + Vector3D supportPoint; + supportPoint.x = (direction.x >= 0) ? aabb.maxPoint.x : aabb.minPoint.x; + supportPoint.y = (direction.y >= 0) ? aabb.maxPoint.y : aabb.minPoint.y; + supportPoint.z = (direction.z >= 0) ? aabb.maxPoint.z : aabb.minPoint.z; + + return supportPoint.dot(direction); +} + +// Collect all components from assembly (no recursion needed) +std::vector CreoManager::CollectAllComponents(pfcAssembly_ptr assembly) { + std::vector components; + + if (!assembly) return components; + + try { + // Step 1: Build a map of all component features and their IDs + std::map modelNameToFeatureId; + std::map modelNameToCompFeat; + + try { + pfcFeatures_ptr features = assembly->ListFeaturesByType(xfalse, pfcFEATTYPE_COMPONENT); + if (features) { + for (int i = 0; i < features->getarraysize(); i++) { + pfcFeature_ptr feature = features->get(i); + if (!feature) continue; + + pfcComponentFeat_ptr compFeat = pfcComponentFeat::cast(feature); + if (!compFeat) continue; + + // Get model descriptor to get the model name + pfcModelDescriptor_ptr modelDesc = compFeat->GetModelDescr(); + if (!modelDesc) continue; + + std::string modelName = XStringToString(modelDesc->GetFileName()); + if (!modelName.empty()) { + int featureId = feature->GetId(); + modelNameToFeatureId[modelName] = featureId; + modelNameToCompFeat[modelName] = compFeat; } } } + } catch (...) { + // If we can't get features, fall back to using component IDs + } + + // Step 2: Use wfcWAssembly to get component paths with transforms + wfcWAssembly_ptr wAssembly = wfcWAssembly::cast(assembly); + if (!wAssembly) return components; + + // Get component paths - this already returns full depth paths from root + wfcWComponentPaths_ptr componentPaths = wAssembly->ListDisplayedComponents(); + if (!componentPaths) return components; + + for (int i = 0; i < componentPaths->getarraysize(); ++i) { + wfcWComponentPath_ptr wPath = componentPaths->get(i); + if (!wPath) continue; + + try { + // Get component solid from path + pfcSolid_ptr compSolid = wPath->GetLeaf(); + if (!compSolid) continue; + + pfcModel_ptr compModel = pfcModel::cast(compSolid); + if (!compModel) continue; + + // Get component name + std::string compName; + try { + xstring nameXStr = compModel->GetFileName(); + compName = XStringToString(nameXStr); + if (compName.empty()) { + compName = "COMPONENT_" + std::to_string(i + 1); + } + } catch (...) { + compName = "COMPONENT_" + std::to_string(i + 1); + } + + // Get stable feature ID from our map, or use component path ID as fallback + int stableFeatureId = -1; + pfcComponentFeat_ptr compFeat = nullptr; + + if (modelNameToFeatureId.find(compName) != modelNameToFeatureId.end()) { + // Found in our feature map - use the stable feature ID + stableFeatureId = modelNameToFeatureId[compName]; + compFeat = modelNameToCompFeat[compName]; + } else { + // Fallback: use the last component ID from the path + xintsequence_ptr componentIds = wPath->GetComponentIds(); + if (componentIds && componentIds->getarraysize() > 0) { + stableFeatureId = componentIds->get(componentIds->getarraysize() - 1); + } else { + stableFeatureId = i; // Last resort: use index + } + } + + // 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 = compFeat; // May be nullptr if not found in map + item.solid = compSolid; + item.path = nullptr; // We have wfcWComponentPath, different type + item.worldAABB = worldAABB; + item.featureId = stableFeatureId; // Use stable feature ID when available + 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; +} + +// Main multi-directional extreme value projection analysis +std::unordered_set CreoManager::PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly) { + std::unordered_set outerComponentIds; + + if (!assembly) return outerComponentIds; + + try { + // Step 1: Collect all components recursively + std::vector components = CollectAllComponents(assembly); + if (components.empty()) return outerComponentIds; + + // Step 2: Calculate global assembly AABB + AABB globalAABB; + for (const ComponentItem& comp : components) { + globalAABB.expand(comp.worldAABB.minPoint); + globalAABB.expand(comp.worldAABB.maxPoint); + } + + // Step 3: Calculate tolerance based on assembly size + double assemblyDiagonal = globalAABB.getDiagonalLength(); + double tolerance = std::max(1e-6, assemblyDiagonal * 0.001); // 0.1% of assembly diagonal for better precision + + // Step 4: Sample directions (96 directions for good coverage) + std::vector directions = SampleDirections(96); + + // Step 5: For each direction, find extreme value components + for (const Vector3D& direction : directions) { + double maxProjection = -std::numeric_limits::infinity(); + std::vector candidateIds; + + // Find maximum projection in this direction + for (const ComponentItem& comp : components) { + double projection = CalculateProjectionSupport(comp.worldAABB, direction); + if (projection > maxProjection) { + maxProjection = projection; + candidateIds.clear(); + candidateIds.push_back(comp.featureId); + } else if (abs(projection - maxProjection) <= tolerance) { + // Within tolerance of maximum - also consider as frontmost + candidateIds.push_back(comp.featureId); + } + } + + // Add all frontmost components in this direction to outer surface set + for (int id : candidateIds) { + outerComponentIds.insert(id); + } + } + + // Step 6: Apply safety check - ensure at least some components are marked as outer + if (outerComponentIds.empty() && !components.empty()) { + // Fallback: mark components on assembly boundary as outer + for (const ComponentItem& comp : components) { + // Check if component AABB touches assembly boundary + double boundaryTolerance = assemblyDiagonal * 0.005; // 0.5% tolerance for tighter boundary detection + if (abs(comp.worldAABB.minPoint.x - globalAABB.minPoint.x) <= boundaryTolerance || + abs(comp.worldAABB.maxPoint.x - globalAABB.maxPoint.x) <= boundaryTolerance || + abs(comp.worldAABB.minPoint.y - globalAABB.minPoint.y) <= boundaryTolerance || + abs(comp.worldAABB.maxPoint.y - globalAABB.maxPoint.y) <= boundaryTolerance || + abs(comp.worldAABB.minPoint.z - globalAABB.minPoint.z) <= boundaryTolerance || + abs(comp.worldAABB.maxPoint.z - globalAABB.maxPoint.z) <= boundaryTolerance) { + outerComponentIds.insert(comp.featureId); + } + } } - // Surface is visible and has external contours but no boundary edges - return has_external_contour; - } catch (...) { - return false; // Analysis failed, assume internal + // Analysis failed, return empty set (conservative: don't delete anything) } -} -// Check if a feature creates any visible surfaces (shell feature) -bool CreoManager::IsShellFeature(pfcFeature_ptr feature, pfcSolid_ptr solid) { - if (!feature || !solid) return false; - - try { - // Get all surfaces in the model - pfcModelItems_ptr surfaces = solid->ListItems(pfcITEM_SURFACE); - if (!surfaces) return false; - - // Check if this feature created any visible surfaces - for (int i = 0; i < surfaces->getarraysize(); i++) { - pfcModelItem_ptr item = surfaces->get(i); - pfcSurface_ptr surf = pfcSurface::cast(item); - - if (surf) { - // Check if this surface was created by the feature - pfcFeature_ptr surf_feature = surf->GetFeature(); - if (surf_feature == feature) { - // Check if the surface is visible - if (IsVisibleSurface(surf)) { - return true; // Feature creates visible surface = shell feature - } - } - } - } - - return false; // Feature doesn't create any visible surfaces = internal feature - - } catch (...) { - // Analysis failed, report error immediately - return false; - } -} - -// ===================================================== -// PHASE 2: ADVANCED INTERFERENCE DETECTION APIs (2025-01-18) -// ===================================================== - -// Advanced component occlusion detection using real interference APIs -bool CreoManager::IsComponentOccludedAdvanced(pfcSolid_ptr targetComponent, pfcSolid_ptr assembly) { - if (!targetComponent || !assembly) return false; - - try { - // Cast assembly to get component features - pfcAssembly_ptr asmModel = pfcAssembly::cast(assembly); - if (!asmModel) return false; - - // Get all components in assembly - pfcFeatures_ptr allComponents = asmModel->ListFeaturesByType(xfalse, pfcFeatureType::pfcFEATTYPE_COMPONENT); - if (!allComponents) return false; - - double targetVolume = targetComponent->GetMassProperty()->GetVolume(); - if (targetVolume <= 0.0) return false; - - double totalOccludedVolume = 0.0; - int occludingComponentsCount = 0; - - // Check interference with each other component in assembly - for (int i = 0; i < allComponents->getarraysize(); i++) { - pfcFeature_ptr compFeature = allComponents->get(i); - if (!compFeature) continue; - - pfcComponentFeat_ptr comp = pfcComponentFeat::cast(compFeature); - if (!comp) continue; - - // Load other component model - pfcModel_ptr otherCompModel = LoadComponentModel(comp); - if (!otherCompModel) continue; - - pfcSolid_ptr otherCompSolid = pfcSolid::cast(otherCompModel); - if (!otherCompSolid) continue; - - // Skip self-comparison - if (otherCompSolid == targetComponent) continue; - - // Calculate interference between target and this other component - double interferenceVolume = CalculateInterferenceVolume(targetComponent, otherCompSolid); - if (interferenceVolume > 0.0) { - totalOccludedVolume += interferenceVolume; - occludingComponentsCount++; - } - } - - // Determine occlusion based on geometry analysis - if (occludingComponentsCount == 0) { - return false; // No interfering components = not occluded - } - - double occlusionRatio = totalOccludedVolume / targetVolume; - - // Dynamic occlusion threshold based on real geometry analysis - if (occludingComponentsCount == 1) { - // Single component interference: higher threshold (50%) - return occlusionRatio > 0.5; - } else if (occludingComponentsCount <= 3) { - // Multiple component interference: moderate threshold (35%) - return occlusionRatio > 0.35; - } else { - // Many components interfering: lower threshold (25%) - likely fully internal - return occlusionRatio > 0.25; - } - - } catch (...) { - // Real geometry analysis failed, cannot determine occlusion - return false; - } -} - -// Calculate interference volume between two separate solids -double CreoManager::CalculateInterferenceVolume(pfcSolid_ptr target, pfcSolid_ptr occluder) { - if (!target || !occluder) return 0.0; - - try { - // Create selections for interference measurement between separate components - pfcSelection_ptr targetSel = pfcCreateModelItemSelection(pfcModelItem::cast(target), nullptr); - pfcSelection_ptr occluderSel = pfcCreateModelItemSelection(pfcModelItem::cast(occluder), nullptr); - - if (!targetSel || !occluderSel) return 0.0; - - // Create evaluator for interference calculation - pfcSelectionPair_ptr selPair = pfcSelectionPair::Create(targetSel, occluderSel); - pfcSelectionEvaluator_ptr evaluator = pfcCreateSelectionEvaluator(selPair); - - if (!evaluator) return 0.0; - - // Calculate interference volume between the two components - pfcInterferenceVolume_ptr interferenceVol = evaluator->ComputeInterference(true); - if (!interferenceVol) return 0.0; - - double interferenceVolume = interferenceVol->ComputeVolume(); - - // Return actual interference volume (not ratio) - return interferenceVolume; - - } catch (...) { - // Interference calculation failed, return no interference - return 0.0; - } -} - -// Calculate precise interference ratio between two separate solids -double CreoManager::CalculateInterferenceRatio(pfcSolid_ptr target, pfcSolid_ptr occluder) { - if (!target || !occluder) return 0.0; - - // Get interference volume between the two separate components - double interferenceVolume = CalculateInterferenceVolume(target, occluder); - if (interferenceVolume <= 0.0) return 0.0; - - // Get target component volume - double targetVolume = target->GetMassProperty()->GetVolume(); - if (targetVolume <= 0.0) return 0.0; - - // Return precise interference ratio (0.0 = no interference, 1.0 = complete overlap) - return interferenceVolume / targetVolume; -} - -// Analyze global interferences in assembly -pfcGlobalInterferences_ptr CreoManager::AnalyzeGlobalInterferences(pfcModel_ptr assembly) { - if (!assembly) return nullptr; - - try { - // Create global evaluator for assembly-wide interference analysis - cast model to assembly - pfcAssembly_ptr assemblyModel = pfcAssembly::cast(assembly); - if (!assemblyModel) return nullptr; - - pfcGlobalEvaluator_ptr globalEvaluator = pfcCreateGlobalEvaluator(assemblyModel); - if (!globalEvaluator) return nullptr; - - // Compute all interferences within the assembly with volume calculation - pfcGlobalInterferences_ptr globalInterferences = globalEvaluator->ComputeGlobalInterference(true); - - // Return interference results for further analysis - return globalInterferences; - - } catch (...) { - // Global interference analysis failed - return nullptr; - } + return outerComponentIds; } // Get feature type name string @@ -2753,52 +2696,3 @@ std::string CreoManager::GetFeatureTypeName(pfcFeatureType feat_type) { } } -// Check if a component is occluded by other components in assembly -bool CreoManager::IsComponentOccluded(pfcSolid_ptr component, pfcSolid_ptr assembly) { - if (!component || !assembly) return false; - - try { - // Create selection objects for interference check - pfcSelection_ptr sel_comp = pfcCreateModelItemSelection( - pfcModelItem::cast(component), nullptr); - pfcSelection_ptr sel_asm = pfcCreateModelItemSelection( - pfcModelItem::cast(assembly), nullptr); - - if (!sel_comp || !sel_asm) return false; - - // Create selection pair - pfcSelectionPair_ptr pair = pfcSelectionPair::Create(sel_comp, sel_asm); - if (!pair) return false; - - // Create evaluator for interference calculation - pfcSelectionEvaluator_ptr evaluator = pfcCreateSelectionEvaluator(pair); - if (!evaluator) return false; - - // Calculate interference volume - pfcInterferenceVolume_ptr interference = evaluator->ComputeInterference(true); - - if (interference) { - // Get component volume - pfcMassProperty_ptr mass_prop = component->GetMassProperty(); - if (!mass_prop) return false; - - double comp_volume = mass_prop->GetVolume(); - if (comp_volume <= 0) return false; - - // Calculate interference volume - double inter_volume = interference->ComputeVolume(); - - // Calculate occlusion ratio - double occlusion_ratio = inter_volume / comp_volume; - - // Component is occluded if more than 30% is inside other components - return occlusion_ratio > 0.3; - } - - return false; // No interference = not occluded - - } catch (...) { - // Analysis failed, report error immediately - return false; - } -} diff --git a/CreoManager.h b/CreoManager.h index c8862ed..312d131 100644 --- a/CreoManager.h +++ b/CreoManager.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -407,6 +408,12 @@ public: std::string GetModelFileSize(pfcModel_ptr model); std::string CalculateAssemblyTotalSize(pfcModel_ptr model); double ParseMBFromSizeString(const std::string& size_str); + + // Component analysis helper methods + std::string EvaluateDeletionSafety(const ComponentInfo& component); + std::string GetComponentFileSize(wfcWComponentPath_ptr component_path); + std::string GetModelTypeString(pfcModelType model_type); + int CountChildComponents(wfcWComponentPath_ptr component_path); private: CreoManager(); // 需要自定义构造函数来设置配置 @@ -509,47 +516,75 @@ private: // 真实几何分析方法 bool AnalyzeFeatureGeometry(pfcFeature_ptr feature, pfcOutline3D_ptr globalOutline, double tolerance); - // New Shell Analysis core functions using real OTK APIs - bool IsVisibleSurface(pfcSurface_ptr surface); - bool IsShellFeature(pfcFeature_ptr feature, pfcSolid_ptr solid); - bool IsComponentOccluded(pfcSolid_ptr component, pfcSolid_ptr assembly); + // Multi-directional extreme value projection algorithm structures and functions + struct Vector3D { + double x, y, z; + Vector3D() : x(0), y(0), z(0) {} + Vector3D(double x_, double y_, double z_) : x(x_), y(y_), z(z_) {} - // Phase 2: Advanced interference detection APIs (2025-01-18) - bool IsComponentOccludedAdvanced(pfcSolid_ptr targetComponent, pfcSolid_ptr assembly); - double CalculateInterferenceRatio(pfcSolid_ptr target, pfcSolid_ptr occluder); - double CalculateInterferenceVolume(pfcSolid_ptr target, pfcSolid_ptr occluder); - pfcGlobalInterferences_ptr AnalyzeGlobalInterferences(pfcModel_ptr assembly); - - - // 旧方法 (保留用于兼容) - void TraverseAssemblyLevels(wfcWAssembly_ptr assembly, - int current_level, - const std::string& parent_path, - std::vector>& hierarchy_levels, - std::vector& all_components); - - void ClearStaticVisitedModels(); - - ComponentInfo CreateComponentInfo(wfcWComponentPath_ptr component_path, - int level, const std::string& parent_path); - - // 新增CREOSON风格的安全方法 - ComponentInfo CreateComponentInfoSafe(wfcWComponentPath_ptr component_path, - int level, const std::string& parent_path); - - bool CheckCanRecurseSafely(wfcWComponentPath_ptr component_path); - - wfcWAssembly_ptr GetSubAssemblySafely(wfcWComponentPath_ptr component_path); - - std::string GetComponentTypeSafe(wfcWComponentPath_ptr component_path); - - std::string GenerateComponentPathId(wfcWComponentPath_ptr component_path); - - std::string EvaluateDeletionSafety(const ComponentInfo& component); - - std::string GetComponentFileSize(wfcWComponentPath_ptr component_path); - - std::string GetModelTypeString(pfcModelType model_type); - - int CountChildComponents(wfcWComponentPath_ptr component_path); + Vector3D operator-(const Vector3D& other) const { + return Vector3D(x - other.x, y - other.y, z - other.z); + } + + double dot(const Vector3D& other) const { + return x * other.x + y * other.y + z * other.z; + } + + double length() const { + return sqrt(x * x + y * y + z * z); + } + + Vector3D normalize() const { + double len = length(); + if (len > 1e-10) { + return Vector3D(x / len, y / len, z / len); + } + return Vector3D(0, 0, 1); + } + }; + + struct AABB { + Vector3D minPoint, maxPoint; + + AABB() : minPoint(1e9, 1e9, 1e9), maxPoint(-1e9, -1e9, -1e9) {} + + AABB(const Vector3D& min_pt, const Vector3D& max_pt) + : minPoint(min_pt), maxPoint(max_pt) {} + + void expand(const Vector3D& point) { + if (point.x < minPoint.x) minPoint.x = point.x; + if (point.y < minPoint.y) minPoint.y = point.y; + if (point.z < minPoint.z) minPoint.z = point.z; + if (point.x > maxPoint.x) maxPoint.x = point.x; + if (point.y > maxPoint.y) maxPoint.y = point.y; + if (point.z > maxPoint.z) maxPoint.z = point.z; + } + + Vector3D diagonal() const { + return maxPoint - minPoint; + } + + double getDiagonalLength() const { + return diagonal().length(); + } + }; + + struct ComponentItem { + pfcComponentFeat_ptr component; + pfcSolid_ptr solid; + pfcComponentPath_ptr path; + AABB worldAABB; + int featureId; + std::string name; + }; + + // Multi-directional projection algorithm functions + std::unordered_set PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly); + std::vector CollectAllComponents(pfcAssembly_ptr assembly); + std::vector SampleDirections(int count = 96); + AABB TransformAABB(const AABB& localAABB, pfcTransform3D_ptr transform); + pfcTransform3D_ptr GetComponentWorldTransform(pfcComponentPath_ptr path); + double CalculateProjectionSupport(const AABB& aabb, const Vector3D& direction); + Vector3D PfcPointToVector3D(pfcPoint3D_ptr point); + AABB PfcOutlineToAABB(pfcOutline3D_ptr outline); };