feat: enhance shell analysis with two-step verification and remove conflicting mechanisms

- Implemented ray-based verification as second step after 2D projection
- Each direction now votes 0 or 1 based on both visibility and ray test
- Removed same-path component sharing mechanism for independent evaluation
- Removed IsLikelyInternal geometric rules that conflicted with visibility detection
- Components are now evaluated purely based on visual evidence

This ensures more accurate shell detection by preventing:
- 0-vote components being marked as shell due to path sharing
- 6-vote visible components being marked as internal due to geometry

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sladro 2025-09-20 08:48:12 +08:00
parent 92cd47549f
commit 9b2e60249c
2 changed files with 122 additions and 32 deletions

View File

@ -1732,18 +1732,7 @@ CreoManager::ShellAnalysisResult CreoManager::AnalyzeShellFeaturesEnhanced(const
bool is_outer_component = (outerComponentIds.find(item.feature_id) != outerComponentIds.end());
// Apply rule-based adjustments
// Get component's AABB for geometric rules
AABB compAABB;
auto aabbIter = componentAABBs.find(item.feature_id);
if (aabbIter != componentAABBs.end()) {
compAABB = aabbIter->second;
// Check geometric rules
if (ComponentClassifier::IsLikelyInternal(compAABB, globalAABB)) {
item.confidence = std::min(0.95, item.confidence + 0.2); // Increase deletion confidence
item.reason += " [Geometry: Deep internal]";
}
}
// Geometric rules removed - rely on visibility-based detection instead
// Apply naming pattern rules - use unified component name
std::string unifiedName = comp_name;
@ -2796,6 +2785,13 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti
result.globalAABB.expand(comp.worldAABB.maxPoint);
}
// Calculate global center for ray verification
Vector3D globalCenter = Vector3D(
(result.globalAABB.minPoint.x + result.globalAABB.maxPoint.x) * 0.5,
(result.globalAABB.minPoint.y + result.globalAABB.maxPoint.y) * 0.5,
(result.globalAABB.minPoint.z + result.globalAABB.maxPoint.z) * 0.5
);
// Step 3: Sample directions (good coverage)
const int numDirections = SHELL_ANALYSIS_NUM_DIRECTIONS;
std::vector<Vector3D> directions = SampleDirections(numDirections);
@ -2894,7 +2890,7 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti
auto visibilityCounts = grid.getVisibilityCount();
int totalCells = grid.gridSizeU * grid.gridSizeV;
// Update visibility votes based on grid visibility
// Update visibility votes based on grid visibility with ray verification
for (const auto& kvp : visibilityCounts) {
int componentId = kvp.first;
int cellCount = kvp.second;
@ -2907,9 +2903,23 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti
result.visibilityRatios[componentId] = std::max(result.visibilityRatios[componentId], visibilityRatio);
}
// Vote if component meets minimum visibility threshold for this direction
// Two-step verification: projection visibility + ray verification
if (visibilityRatio >= SHELL_ANALYSIS_MIN_VISIBLE_RATIO_PER_DIR) {
result.visibilityVotes[componentId]++;
// Find the component for ray verification
const ComponentItem* targetComponent = nullptr;
for (const auto& comp : result.components) {
if (comp.featureId == componentId) {
targetComponent = &comp;
break;
}
}
// Perform ray verification from global center
if (targetComponent && !IsComponentBlockedFromCenter(globalCenter, *targetComponent, result.components)) {
// Component passed both 2D projection and ray verification
result.visibilityVotes[componentId]++;
}
// If blocked by ray test, this direction votes 0 (no increment)
// Debug output for specific component
for (const auto& comp : result.components) {
@ -2991,23 +3001,7 @@ CreoManager::ProjectionAnalysisData CreoManager::PerformMultiDirectionalProjecti
}
}
// Step 6: Post-process - if any component with a path is visible, mark all same-path components as visible
std::set<std::string> visiblePaths;
for (int visibleFeatureId : result.outerComponentIds) {
auto pathIter = featureIdToPath.find(visibleFeatureId);
if (pathIter != featureIdToPath.end()) {
visiblePaths.insert(pathIter->second);
}
}
// Add all components with visible paths to outer component set
for (const auto& pathEntry : featureIdToPath) {
int featureId = pathEntry.first;
const std::string& path = pathEntry.second;
if (visiblePaths.find(path) != visiblePaths.end()) {
result.outerComponentIds.insert(featureId);
}
}
// Step 6 removed: Same-path sharing mechanism deleted to ensure independent component evaluation
return result;
}
@ -3486,6 +3480,97 @@ bool CreoManager::ShouldUseOBB(const ComponentItem& comp) {
return aspectRatio > 2.5 || ComponentClassifier::IsElongatedPart(comp.name);
}
// Ray-based verification for shell analysis
bool CreoManager::IsComponentBlockedFromCenter(const Vector3D& globalCenter,
const ComponentItem& component,
const std::vector<ComponentItem>& allComponents) {
// Calculate component center
Vector3D componentCenter = Vector3D(
(component.worldAABB.minPoint.x + component.worldAABB.maxPoint.x) * 0.5,
(component.worldAABB.minPoint.y + component.worldAABB.maxPoint.y) * 0.5,
(component.worldAABB.minPoint.z + component.worldAABB.maxPoint.z) * 0.5
);
// Calculate ray direction (from global center through component center)
Vector3D rayDirection = (componentCenter - globalCenter).normalize();
// Start ray from component center (skip checking between global center and component)
// We want to check if ray is blocked AFTER passing through the component
Vector3D rayStart = componentCenter;
// Check intersection with all other components
for (const ComponentItem& otherComp : allComponents) {
// Skip self
if (otherComp.featureId == component.featureId) {
continue;
}
// Calculate other component center
Vector3D otherCenter = Vector3D(
(otherComp.worldAABB.minPoint.x + otherComp.worldAABB.maxPoint.x) * 0.5,
(otherComp.worldAABB.minPoint.y + otherComp.worldAABB.maxPoint.y) * 0.5,
(otherComp.worldAABB.minPoint.z + otherComp.worldAABB.maxPoint.z) * 0.5
);
// Check if other component is in the outward direction
Vector3D toOther = otherCenter - rayStart;
double dotProduct = toOther.dot(rayDirection);
// If other component is not in the ray direction, skip
if (dotProduct <= 0) {
continue;
}
// Simple AABB ray intersection test
// Check if ray from component center outward intersects other component's AABB
double tMin = 0.0;
double tMax = std::numeric_limits<double>::max();
for (int i = 0; i < 3; i++) {
double origin = (i == 0) ? rayStart.x : (i == 1) ? rayStart.y : rayStart.z;
double dir = (i == 0) ? rayDirection.x : (i == 1) ? rayDirection.y : rayDirection.z;
double minVal = (i == 0) ? otherComp.worldAABB.minPoint.x :
(i == 1) ? otherComp.worldAABB.minPoint.y :
otherComp.worldAABB.minPoint.z;
double maxVal = (i == 0) ? otherComp.worldAABB.maxPoint.x :
(i == 1) ? otherComp.worldAABB.maxPoint.y :
otherComp.worldAABB.maxPoint.z;
if (std::abs(dir) < 1e-10) {
// Ray is parallel to slab
if (origin < minVal || origin > maxVal) {
// Ray is outside slab
tMin = tMax + 1; // Force no intersection
break;
}
} else {
// Compute intersection t values
double t1 = (minVal - origin) / dir;
double t2 = (maxVal - origin) / dir;
if (t1 > t2) {
std::swap(t1, t2);
}
tMin = std::max(tMin, t1);
tMax = std::min(tMax, t2);
if (tMin > tMax) {
// No intersection
break;
}
}
}
// If ray intersects this component's AABB, the component is blocked
if (tMin <= tMax && tMin >= 0) {
return true; // Component is blocked
}
}
return false; // Component is not blocked (is outer shell)
}
// ComponentClassifier extension for elongated parts
bool CreoManager::ComponentClassifier::IsElongatedPart(const std::string& name) {
// Common elongated part naming patterns

View File

@ -875,4 +875,9 @@ private:
double CalculateOBBProjectionSupport(const OBB& obb, const Vector3D& direction);
double CalculateOBBThickness(const OBB& obb, const Vector3D& direction);
bool ShouldUseOBB(const ComponentItem& comp);
// Ray-based verification for shell analysis
bool IsComponentBlockedFromCenter(const Vector3D& globalCenter,
const ComponentItem& component,
const std::vector<ComponentItem>& allComponents);
};