using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.BoundaryRepresentation;
using BrepFace = Autodesk.AutoCAD.BoundaryRepresentation.Face;
namespace FurnitureExporter;
///
/// 孔位检测器 - 从板材实体中提取圆柱形孔位
/// 通过分析 Solid3d 的 Brep(边界表示)来识别布尔运算产生的孔
///
public class HoleDetector
{
private readonly Database _db;
private Point3d _originOffset = Point3d.Origin;
// 孔位检测参数
private const double MIN_HOLE_DIAMETER = 2; // 最小孔径 2mm
private const double MAX_HOLE_DIAMETER = 100; // 最大孔径 100mm
private const double MIN_HOLE_DEPTH = 3; // 最小孔深 3mm
public HoleDetector(Database db)
{
_db = db;
}
///
/// 从板材实体中检测所有孔位
///
/// 板材实体 ID
/// 板材 ID
/// 原点偏移
/// 孔位信息列表
public List DetectHoles(ObjectId solidId, string panelId, Point3d? originOffset = null)
{
_originOffset = originOffset ?? Point3d.Origin;
var holes = new List();
using var tr = _db.TransactionManager.StartTransaction();
var solid = tr.GetObject(solidId, OpenMode.ForRead) as Solid3d;
if (solid == null)
{
tr.Commit();
return holes;
}
holes = DetectHolesFromSolid(solid, panelId);
tr.Commit();
return holes;
}
///
/// 从多个板材实体中检测所有孔位
///
public List DetectAllHoles(ObjectId[] solidIds, List panels, Point3d? originOffset = null)
{
_originOffset = originOffset ?? Point3d.Origin;
var allHoles = new List();
using var tr = _db.TransactionManager.StartTransaction();
for (int i = 0; i < solidIds.Length && i < panels.Count; i++)
{
var solid = tr.GetObject(solidIds[i], OpenMode.ForRead) as Solid3d;
if (solid == null) continue;
var holes = DetectHolesFromSolid(solid, panels[i].Id);
allHoles.AddRange(holes);
}
tr.Commit();
return allHoles;
}
///
/// 从单个 Solid3d 实体检测孔位
///
private List DetectHolesFromSolid(Solid3d solid, string panelId)
{
var holes = new List();
try
{
// 使用 Brep API 分析实体表面
using var brep = new Brep(solid);
// 收集所有圆柱形面
var cylindricalFaces = new List<(BrepFace Face, Cylinder Cylinder)>();
foreach (var face in brep.Faces)
{
try
{
// face.Surface 常常是 ExternalBoundedSurface 等包装类型,需剥离到基础几何表面
var surface = BrepSurfaceUtils.Unwrap(face.Surface);
if (surface is Cylinder cylinder)
{
// 验证是否为内凹面(孔)而非外凸面
if (IsConcaveCylinder(face, cylinder))
{
cylindricalFaces.Add((face, cylinder));
}
}
}
catch
{
// 某些面可能无法获取表面信息,跳过
continue;
}
}
// 将相同圆柱配对(孔通常有两个端面)
var processedFaces = new HashSet();
int holeIndex = 0;
foreach (var (face, cylinder) in cylindricalFaces)
{
if (processedFaces.Contains(face.GetHashCode()))
continue;
var hole = ExtractHoleInfo(face, cylinder, solid, panelId, holeIndex);
if (hole != null && IsValidHole(hole))
{
holes.Add(hole);
holeIndex++;
}
processedFaces.Add(face.GetHashCode());
}
// 去重(合并相近的孔)
holes = MergeNearbyHoles(holes);
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[HoleDetector] Error: {ex.Message}");
}
return holes;
}
///
/// 判断圆柱面是否为内凹(孔)
///
private bool IsConcaveCylinder(BrepFace face, Cylinder cylinder)
{
// 简化判断:根据圆柱半径和板材尺寸判断是否可能是孔
// 圆柱半径在合理范围内即认为是孔
var radius = cylinder.Radius;
return radius >= MIN_HOLE_DIAMETER / 2 && radius <= MAX_HOLE_DIAMETER / 2;
}
///
/// 从圆柱面提取孔位信息
///
private HoleInfo? ExtractHoleInfo(BrepFace face, Cylinder cylinder, Solid3d solid, string panelId, int index)
{
try
{
BoundingBox3D? panelBoundingBox = null;
try
{
var ext = solid.GeometricExtents;
panelBoundingBox = new BoundingBox3D
{
Min = new[]
{
Math.Round(ext.MinPoint.X + _originOffset.X, 2),
Math.Round(ext.MinPoint.Y + _originOffset.Y, 2),
Math.Round(ext.MinPoint.Z + _originOffset.Z, 2),
},
Max = new[]
{
Math.Round(ext.MaxPoint.X + _originOffset.X, 2),
Math.Round(ext.MaxPoint.Y + _originOffset.Y, 2),
Math.Round(ext.MaxPoint.Z + _originOffset.Z, 2),
},
};
}
catch
{
panelBoundingBox = null;
}
var radius = cylinder.Radius;
var diameter = radius * 2;
// 获取圆柱的轴向(孔的方向)
var axis = cylinder.AxisOfSymmetry.GetNormal();
// 获取孔的位置
var origin = cylinder.Origin;
// 计算孔深度
var depth = CalculateHoleDepth(face, solid, cylinder);
// 应用原点偏移
var offsetPosition = new Point3d(
origin.X + _originOffset.X,
origin.Y + _originOffset.Y,
origin.Z + _originOffset.Z
);
// 判断孔类型(通孔/盲孔)
string holeType = DetermineHoleType(face, solid, cylinder, depth);
return new HoleInfo
{
Id = $"{panelId}_hole_{index:D3}",
Position = new double[]
{
Math.Round(offsetPosition.X, 2),
Math.Round(offsetPosition.Y, 2),
Math.Round(offsetPosition.Z, 2)
},
Normal = new double[]
{
Math.Round(axis.X, 4),
Math.Round(axis.Y, 4),
Math.Round(axis.Z, 4)
},
Diameter = Math.Round(diameter, 2),
Depth = Math.Round(depth, 2),
PanelId = panelId,
Type = holeType,
Radius = Math.Round(radius, 2),
SurfaceFaces = PanelFaceSemanticResolver.ResolveSurfaceFaces(
panelBoundingBox,
new[] { offsetPosition.X, offsetPosition.Y, offsetPosition.Z },
new[] { axis.X, axis.Y, axis.Z },
depth)
};
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[HoleDetector] ExtractHoleInfo error: {ex.Message}");
return null;
}
}
///
/// 计算孔深度
///
private double CalculateHoleDepth(BrepFace face, Solid3d solid, Cylinder cylinder)
{
try
{
// 使用板材厚度作为孔深度的参考
var solidExt = solid.GeometricExtents;
var solidSize = solidExt.MaxPoint - solidExt.MinPoint;
var thickness = Math.Min(solidSize.X, Math.Min(solidSize.Y, solidSize.Z));
// 默认返回板材厚度(假设为通孔)
return thickness;
}
catch
{
return 0;
}
}
///
/// 判断孔类型(通孔/盲孔)
///
private string DetermineHoleType(BrepFace face, Solid3d solid, Cylinder cylinder, double depth)
{
try
{
var solidExt = solid.GeometricExtents;
var solidSize = solidExt.MaxPoint - solidExt.MinPoint;
// 获取板材厚度(最小尺寸)
var thickness = Math.Min(solidSize.X, Math.Min(solidSize.Y, solidSize.Z));
// 如果孔深度接近板材厚度(误差 2mm),认为是通孔
if (Math.Abs(depth - thickness) < 2)
{
return "through";
}
return "blind";
}
catch
{
return "blind";
}
}
///
/// 验证孔位是否有效
///
private bool IsValidHole(HoleInfo hole)
{
// 检查直径范围
if (hole.Diameter < MIN_HOLE_DIAMETER || hole.Diameter > MAX_HOLE_DIAMETER)
return false;
// 检查深度
if (hole.Depth < MIN_HOLE_DEPTH)
return false;
return true;
}
///
/// 合并相近的孔位(去重)
///
private List MergeNearbyHoles(List holes)
{
if (holes.Count <= 1)
return holes;
var merged = new List();
var processed = new HashSet();
const double MERGE_TOLERANCE = 1.0; // 1mm 容差
for (int i = 0; i < holes.Count; i++)
{
if (processed.Contains(i))
continue;
var current = holes[i];
processed.Add(i);
// 查找相近的孔
for (int j = i + 1; j < holes.Count; j++)
{
if (processed.Contains(j))
continue;
var other = holes[j];
var distance = Math.Sqrt(
Math.Pow(current.Position[0] - other.Position[0], 2) +
Math.Pow(current.Position[1] - other.Position[1], 2) +
Math.Pow(current.Position[2] - other.Position[2], 2)
);
// 如果位置相近且直径相同,认为是同一个孔
if (distance < MERGE_TOLERANCE && Math.Abs(current.Diameter - other.Diameter) < 0.5)
{
processed.Add(j);
// 保留深度较大的那个
if (other.Depth > current.Depth)
{
current = other;
}
}
}
merged.Add(current);
}
return merged;
}
///
/// 根据直径推断孔类型(五金件类型)
///
public static string InferHoleUsage(double diameter)
{
// 从大到小、从具体到模糊排列,避免匹配重叠
if (diameter >= 34 && diameter <= 36) return "hinge"; // 铰链杯孔
if (diameter >= 20 && diameter <= 100) return "cable"; // 过线孔
if (diameter >= 14 && diameter <= 16) return "connector"; // 偏心轮孔
if (diameter >= 9.5 && diameter <= 10.5) return "dowel_10"; // 木榫孔 10mm
if (diameter >= 7.5 && diameter <= 8.5) return "dowel_8"; // 木榫孔 8mm
if (diameter >= 7 && diameter <= 9) return "connector_small"; // 三合一孔
if (diameter >= 5.5 && diameter <= 6.5) return "dowel_6"; // 木榫孔 6mm
// v2.3:孔径 4.8mm 在实际导出中非常常见(更像 M6 的导孔/预钻孔),
// 仅靠“孔径”无法稳定区分“层板托孔”和“螺丝导孔”,因此这里返回更中性的用途,
// 具体候选螺丝直径由 FastenerGuesser 在 hole.SuggestedFasteners 中给出。
if (diameter >= 4.5 && diameter <= 5.3) return "screw_or_shelf_pin";
if (diameter >= 3.0 && diameter < 4.5) return "screw"; // 螺丝预钻孔(常见给 M4/M5)
if (diameter >= 2.0 && diameter < 3.0) return "micro_screw"; // 小螺丝/定位孔(保守兜底)
return "unknown";
}
}