refactor: implement multi-directional extreme value projection for shell analysis

- Replace geometry-based analysis with 96-direction projection algorithm
- Use Fibonacci sphere sampling for uniform direction distribution
- Calculate extreme components in each direction with 0.1% tolerance
- Identify outer surface components based on projection analysis
- Add fallback boundary detection with 0.5% tolerance

This improves accuracy of identifying internal vs external components in assemblies.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sladro 2025-09-18 19:40:20 +08:00
parent 80292530d2
commit 1003178e17
2 changed files with 486 additions and 557 deletions

View File

@ -16,10 +16,19 @@
#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>
#include <limits>
#include <map>
#include <string>
#include <set>
@ -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<int> 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<std::string, int> 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<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);
}
// 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::Vector3D> CreoManager::SampleDirections(int count) {
std::vector<Vector3D> 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::ComponentItem> CreoManager::CollectAllComponents(pfcAssembly_ptr assembly) {
std::vector<ComponentItem> components;
if (!assembly) return components;
try {
// Step 1: Build a map of all component features and their IDs
std::map<std::string, int> modelNameToFeatureId;
std::map<std::string, pfcComponentFeat_ptr> 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<int> CreoManager::PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly) {
std::unordered_set<int> outerComponentIds;
if (!assembly) return outerComponentIds;
try {
// Step 1: Collect all components recursively
std::vector<ComponentItem> 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<Vector3D> directions = SampleDirections(96);
// Step 5: For each direction, find extreme value components
for (const Vector3D& direction : directions) {
double maxProjection = -std::numeric_limits<double>::infinity();
std::vector<int> 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;
}
}

View File

@ -22,6 +22,7 @@
#include <vector>
#include <map>
#include <set>
#include <unordered_set>
#include <algorithm>
#include <cmath>
@ -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<std::vector<ComponentInfo>>& hierarchy_levels,
std::vector<ComponentInfo>& 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<int> PerformMultiDirectionalProjectionAnalysis(pfcAssembly_ptr assembly);
std::vector<ComponentItem> CollectAllComponents(pfcAssembly_ptr assembly);
std::vector<Vector3D> 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);
};