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"; } }