 #region 一、族名称提取

 /// <summary>
 /// 返回其中含有尺寸连接符的列表:X
 /// </summary>
 /// <param name="textNotes">CAD文字列表</param>
 /// <returns>含有尺寸连接符的文字列表</returns>
 public List<TextNote> GenTns(List<TextNote> textNotes)
 {
     List<TextNote> result = new List<TextNote>();
     foreach (TextNote textNote in textNotes)
     {
         if (textNote == null || string.IsNullOrEmpty(textNote.Text)) continue;
         string content = textNote.Text.Trim();
         foreach (string sep in MultipleChars)
         {
             if (content.Contains(sep))
             {
                 result.Add(textNote);
                 break;
             }
         }
     }
     return result;
 }

 /// <summary>
 /// 根据TextNote的文字内容查找对应的族类型
 /// </summary>
 /// <param name="doc">Revit文档</param>
 /// <param name="textNote">CAD导入的TextNote图元</param>
 /// <returns>族类型，找不到返回null</returns>
 public FamilySymbol GetFamilySymbolByTextNote(Document doc, TextNote textNote)
 {
     if (textNote == null || string.IsNullOrEmpty(textNote.Text)) return null;
     // 1. 从文字内容解析出族类型名称
     string textContent = textNote.Text.Trim();
     string cadTypeName = "";
     if (textContent.Contains("x") && textContent.Contains("L"))
     {
         // 原位标注: "KL3(5) 300x750" → 取空格前部分
         string[] parts = textContent.Split(' ');
         cadTypeName = parts[0].Trim();
     }
     else if (textContent.Contains("L"))
     {
         // 集中标注(无尺寸): "KL3(5)" → 直接就是名称
         cadTypeName = textContent;
     }
     else if (textContent.Contains("x") && !textContent.Contains("L"))
     {
         // 只有尺寸: "300x750" → 直接就是族类型名
         cadTypeName = textContent;
     }
     if (string.IsNullOrEmpty(cadTypeName)) return null;

     // 2. 获取当前视图名称
     string levelName = doc.ActiveView.Name;
     // 3. 找到当前视图名称对应的族
     FilteredElementCollector familyCollector = new FilteredElementCollector(doc);
     familyCollector.OfClass(typeof(Family));
     Family targetFamily = null;
     foreach (Family family in familyCollector)
     {
         if (family.Name  == levelName)
         {
             targetFamily = family;
             break;
         }
     }
     if (targetFamily == null) return null;
     // 4. 在该族中查找名称匹配的族类型
     FamilySymbol typeName = null;
     foreach (ElementId symbolId in targetFamily.GetFamilySymbolIds())//由于名称长，所有采用id的模式
     {
         FamilySymbol symbol = doc.GetElement(symbolId) as FamilySymbol;
         if (symbol.Name == cadTypeName)
         {
             typeName = symbol;
         }
     }
     return typeName;
 }

 #endregion

 #region 二、Edge

 /// <summary>
 /// 是否共线:未用向量写(Z值相同)
 /// </summary>
 /// <param name="targetCurve">targetCurve</param>
 /// <param name="curve">curve</param>
 /// <returns>bool</returns>
 public bool IsCollinear(Curve targetCurve, Curve curve)
 {
     XYZ targetStart = targetCurve.GetEndPoint(0);
     XYZ targetEnd = targetCurve.GetEndPoint(1);
     XYZ curveStart = curve.GetEndPoint(0);
     double targetLength = targetCurve.Length;
     double distB1A1 = targetStart.DistanceTo(curveStart);
     double distB1A2 = targetEnd.DistanceTo(curveStart);
     double diff = Math.Abs(distB1A1 - distB1A2);
     return Math.Abs(diff - targetLength) < 0.001 / 304.8;
 }

 /// <summary>
 /// 两条线直接求中心线（起点平均、终点平均）
 /// </summary>
 private Line GetCenterLineFromTwoLines(Curve curve1, Curve curve2)
 {
     // 取起点和终点
     XYZ start1 = curve1.GetEndPoint(0);
     XYZ end1 = curve1.GetEndPoint(1);
     XYZ start2 = curve2.GetEndPoint(0);
     XYZ end2 = curve2.GetEndPoint(1);

     // 确保两条线方向一致
     XYZ dir1 = (end1 - start1).Normalize();
     XYZ dir2 = (end2 - start2).Normalize();

     if (dir1.DotProduct(dir2) < 0)
     {
         // 方向相反，翻转第二条线
         XYZ temp = start2;
         start2 = end2;
         end2 = temp;
     }

     // 起点平均、终点平均
     XYZ centerStart = (start1 + start2) * 0.5;
     XYZ centerEnd = (end1 + end2) * 0.5;

     return Line.CreateBound(centerStart, centerEnd);
 }

 /// <summary>
 /// 合并一组共线的线段，返回一条通长的直线
 /// </summary>
 /// <param name="collinearLines">共线的线段集合</param>
 /// <returns>合并后的通长直线</returns>
 private Curve MergeCollinearLines(List<Curve> collinearLines)
 {
     if (collinearLines == null || collinearLines.Count == 0)
         return null;

     // 1. 收集所有端点
     List<XYZ> points = new List<XYZ>();
     foreach (Curve line in collinearLines)
     {
         points.Add(line.GetEndPoint(0));
         points.Add(line.GetEndPoint(1));
     }

     // 2. 找出距离最远的两个点
     XYZ start = points[0];
     XYZ end = points[1];
     double maxDist = start.DistanceTo(end);

     for (int i = 0; i < points.Count; i++)
     {
         for (int j = i + 1; j < points.Count; j++)
         {
             double dist = points[i].DistanceTo(points[j]);
             if (dist > maxDist)
             {
                 maxDist = dist;
                 start = points[i];
                 end = points[j];
             }
         }
     }

     // 3. 生成直线
     return Line.CreateBound(start, end);
 }

 /// <summary>
 /// 多条线：共线分组、合并、取最长两条求中心线
 /// </summary>
 private Line GetCenterLineFromMultiLines(List<Curve> allLines)
 {
     // 1. 按共线分组
     List<List<Curve>> groups = GroupByCollinear(allLines);

     // 2. 每组合并成一条通长线
     List<Curve> mergedLines = new List<Curve>();
     foreach (List<Curve> group in groups)
     {
         Curve merged = MergeCollinearLines(group);
         if (merged != null)
             mergedLines.Add(merged);
     }

     if (mergedLines.Count < 2)
     {
         TaskDialog.Show("错误", "无法找到两条共线组");
         return null;
     }

     // 3. 按长度排序，取最长的两条
     mergedLines.Sort((a, b) => b.Length.CompareTo(a.Length));
     Curve side1 = mergedLines[0];
     Curve side2 = mergedLines[1];

     // 4. 用两条线求中心线
     return GetCenterLineFromTwoLines(side1, side2);
 }

 /// <summary>
 /// 将一组直线按共线关系分组
 /// </summary>
 /// <param name="curves">所有选中的直线</param>
 /// <returns>共线分组列表，每组都是共线的线段集合</returns>
 private List<List<Curve>> GroupByCollinear(List<Curve> curves)
 {
     List<List<Curve>> groups = new List<List<Curve>>();// 1. 存放最终结果：一个大列表，里面每个小列表是一组
     HashSet<Curve> processed = new HashSet<Curve>();// 2. 记录哪些线已经处理过了（避免重复处理）

     foreach (Curve curve in curves)
     {
         // 已经处理过的跳过
         if (processed.Contains(curve))
             continue;

         // 新建一组
         List<Curve> group = new List<Curve>();
         group.Add(curve);
         processed.Add(curve);// 标记为已处理

         // 找所有与line共线的线
         foreach (Curve other in curves)
         {
             if (processed.Contains(other))
                 continue;// 跳过已处理的
             if (other == curve)
                 continue; // 跳过自己
             // 判断是否共线（方向相同且在同一直线上）
             if (IsCollinear(curve, other))
             {
                 group.Add(other);
                 processed.Add(other); // 标记为已处理
             }
         }
         groups.Add(group);
     }
     return groups;
 }

 /// <summary>
 /// 从选中的CAD线中获取梁定位中心线
 /// 支持两种情况：
 /// 1. 用户只选了两条线（一对一）-> 直接平均起点终点
 /// 2. 用户选了多条线（多对多）-> 共线分组、合并、取最长两条
 /// </summary>
 /// <param name="selectedCurves">用户框选或点选的所有CAD线（导入Revit后的Curve）</param>
 /// <returns>梁的定位中心线</returns>
 public Line GetBeamCenterLineFromSelectedCurves(List<Curve> selectedCurves)
 {
     // 过滤出直线（CAD导入Revit后可能是Line或Arc，这里只处理Line）
     List<Curve> allLines = new List<Curve>();
     foreach (Curve cv in selectedCurves)
     {
         if (cv is Line)
             allLines.Add(cv);
     }

     if (allLines.Count < 2)
     {
         TaskDialog.Show("错误", "请至少选择两条直线");
         return null;
     }

     // 情况1：只选了两条线 -> 直接平均
     if (allLines.Count == 2)
     {
         return GetCenterLineFromTwoLines(allLines[0], allLines[1]);
     }

     // 情况2：选了多条线 -> 共线分组、合并、取最长两条
     return GetCenterLineFromMultiLines(allLines);
 }

 /// <summary>
 /// 检查字符串列表中是否有重复项
 /// </summary>
 /// <param name="stringList">要检查的字符串列表</param>
 /// <returns>有重复返回true，没有重复返回false</returns>
 public bool HasDuplicateChars(List<string> stringList)
 {
     if (stringList == null || stringList.Count == 0)
         return false;

     HashSet<string> set = new HashSet<string>(stringList);
     return set.Count != stringList.Count;
 }

 /// <summary>
 /// 集中标注：多选模式
 /// </summary>
 /// <param name="uIApp">uIApp</param>
 public void MultiplePickGenTns(UIApplication uIApp)
 {
     UIDocument uIDoc = uIApp.ActiveUIDocument;
     Document doc = uIDoc.Document;
     try
     {
         //1. 框选文字（多选）
         IList<Reference> refs = uIDoc.Selection.PickObjects(ObjectType.Element, new FilterText(), "请框选梁集中标注：");
         List<TextNote> textNotes = new List<TextNote>();
         foreach (Reference item in refs)
         {
             TextNote text = doc.GetElement(item) as TextNote;
             textNotes.Add(text);
         }
         //2. 筛选出带尺寸的文字
         List<TextNote> genTns = GenTns(textNotes);
         //3. 不等于1就重新选择：递归
         if (genTns.Count != 1)
         {
             MultiplePickGenTns(uIApp);
             return;
         }
         TextNote textNote = genTns[0];
         FamilySymbol symbol = GetFamilySymbolByTextNote(doc, textNote);
         if (symbol == null)
         {
             MultiplePickGenTns(uIApp);
             return;
         }
         // 4. 选边线
         IList<Reference> lineRefs = uIDoc.Selection.PickObjects(ObjectType.Element, new FilterCurve(), "请框选梁边线");
         if (lineRefs == null || lineRefs.Count < 2)
         {
             MultiplePickGenTns(uIApp);
             return;
         }
         // 5. 转Curve
         List<Curve> curves = new List<Curve>();
         foreach (Reference refe in lineRefs)
         {
             Element elem = doc.GetElement(refe);
             if (elem is DetailCurve detailCurve) curves.Add(detailCurve.GeometryCurve);
             else if (elem is ModelCurve modelCurve) curves.Add(modelCurve.GeometryCurve);
         }
         // 6. 计算中心线
         Line centerLine = GetBeamCenterLineFromSelectedCurves(curves);
         if (centerLine == null)
         {
             MultiplePickGenTns(uIApp);
             return;
         }
         // 7. 画预览线
         ClearPreviewLines(doc, previewLines);

         DetailCurve preview = DrawPreviewLine(doc, centerLine);
         previewLines.Add(preview);
         // 8. 画梁
         CreateBeam(doc, centerLine, symbol);
         // 9. 自动下一根
         MultiplePickGenTns(uIApp);
     }
     catch (OperationCanceledException)
     {
         ClearPreviewLines(doc, previewLines);
         return;
     }
 }

 #endregion

 #region 三、 效验线

 /// <summary>
 /// 中心线预览
 /// </summary>
 /// <param name="doc">doc</param>
 /// <param name="curve">curve</param>
 /// <returns></returns>
 public DetailCurve DrawPreviewLine(Document doc, Curve curve)
 {
     Transaction trans = new Transaction(doc, "绘制预览线");
     trans.Start();

     OverrideGraphicSettings ogs = new OverrideGraphicSettings();
     ogs.SetProjectionLineColor(new Color(255, 0, 0)); // 红色
     ogs.SetProjectionLineWeight(5); // 粗线

     DetailCurve detailCurve = doc.Create.NewDetailCurve(doc.ActiveView, curve);
     doc.ActiveView.SetElementOverrides(detailCurve.Id, ogs);

     trans.Commit();
     return detailCurve;
 }

 /// <summary>
 /// 清空预览线（画下一根之前，把上一根的预览删掉）
 /// </summary>
 /// <param name="doc"></param>
 /// <param name="previewLines"></param>
 public void ClearPreviewLines(Document doc, List<DetailCurve> previewLines)
 {
     Transaction trans = new Transaction(doc, "清空预览线");
     trans.Start();
     foreach (DetailCurve dc in previewLines)
     {
         doc.Delete(dc.Id);
     }
     trans.Commit();
     previewLines.Clear();
 }

 /// <summary>
 /// 创建梁实例
 /// </summary>
 /// <param name="doc">Revit文档</param>
 /// <param name="curve">梁定位线</param>
 /// <param name="symbol">梁族类型</param>
 /// <returns>创建的梁实例</returns>
 public void CreateBeam(Document doc, Curve curve, FamilySymbol symbol)
 {
     using (Transaction trans = new Transaction(doc, "创建梁"))
     {
         trans.Start(); // 确保族类型被激活：才可以访问
         if (!symbol.IsActive)
         {
             symbol.Activate();
         }
         // 创建梁实例
         FamilyInstance beam = doc.Create.NewFamilyInstance(curve, symbol, doc.ActiveView.GenLevel, Autodesk.Revit.DB.Structure.StructuralType.Beam);
         trans.Commit();
     }
 }

 #endregion