You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FMX.Objects.TPath中实现混合绘制顺序下的正确填充?

解决FMX TPath导入DXF后无法自动填充的方案

核心思路

针对TPath自动填充的三个规则(链式连续、方向正确、无多余MoveTo),对DXF导出的分散图元进行预处理,转换成符合要求的路径数据。

具体步骤

1. 提取DXF中的闭合轮廓

  • 遍历DXF文件中的图元(LINE、LWPOLYLINE、POLYLINE等),筛选出闭合轮廓:
    • 对于LWPOLYLINE,检查其Closed标志(DXF中Group Code=70,值含1表示闭合),直接提取顶点序列。
    • 对于独立的LINE集合,判断是否能形成闭合环(所有线段端点首尾相连,最终回到起点)。
    • 忽略非闭合的图元(TPath开放区域无法自动填充)。

2. 将分散图元拼接为链式连续路径

对于由多个独立线段组成的闭合轮廓,消除多余的MoveTo,构建连续的顶点链:

  • 收集所有线段的起点、终点,建立端点映射表(记录每个端点关联的线段)。
  • 从任意一条未使用的线段开始,依次寻找与当前端点重合的下一条线段,将顶点按顺序拼接,直到回到轮廓起点。
  • 拼接过程中仅保留第一个MoveTo(定位轮廓起点),后续全部使用LineTo,避免插入多余的MoveTo。

示例代码(Delphi):

type
  TLineSeg = record
    StartPt: TPointF;
    EndPt: TPointF;
    Used: Boolean;
  end;

// 判断两点是否重合(考虑浮点误差)
function PointsEqual(const A, B: TPointF): Boolean;
const
  Epsilon = 1e-6;
begin
  Result := (Abs(A.X - B.X) < Epsilon) and (Abs(A.Y - B.Y) < Epsilon);
end;

// 从分散线段构建连续闭合轮廓的顶点序列
function BuildContour(var Lines: TArray<TLineSeg>): TArray<TPointF>;
var
  CurrentSegIdx: Integer;
  CurrentPt: TPointF;
  I: Integer;
begin
  SetLength(Result, 0);
  // 找到第一个未使用的线段
  CurrentSegIdx := -1;
  for I := 0 to High(Lines) do
    if not Lines[I].Used then
    begin
      CurrentSegIdx := I;
      Break;
    end;
  if CurrentSegIdx = -1 then Exit;

  // 初始化轮廓起点
  Lines[CurrentSegIdx].Used := True;
  CurrentPt := Lines[CurrentSegIdx].StartPt;
  Result := Result + [CurrentPt];
  CurrentPt := Lines[CurrentSegIdx].EndPt;
  Result := Result + [CurrentPt];

  // 循环拼接后续线段
  while True do
  begin
    var Found := False;
    for I := 0 to High(Lines) do
    begin
      if not Lines[I].Used then
      begin
        // 匹配当前端点作为线段起点
        if PointsEqual(Lines[I].StartPt, CurrentPt) then
        begin
          Lines[I].Used := True;
          CurrentPt := Lines[I].EndPt;
          Result := Result + [CurrentPt];
          Found := True;
          Break;
        end
        // 匹配当前端点作为线段终点(反向拼接)
        else if PointsEqual(Lines[I].EndPt, CurrentPt) then
        begin
          Lines[I].Used := True;
          CurrentPt := Lines[I].StartPt;
          Result := Result + [CurrentPt];
          Found := True;
          Break;
        end;
      end;
    end;
    if not Found then Break;
    // 检查是否回到轮廓起点,完成闭合
    if PointsEqual(CurrentPt, Result[0]) then Break;
  end;
end;

3. 统一路径绘制方向

TPath的填充依赖非零环绕规则,方向错误会导致填充区域异常:

  • 使用鞋带公式计算轮廓的面积符号,判断方向:面积为正表示逆时针,为负表示顺时针。
  • 统一外轮廓方向(比如全部设为逆时针),内轮廓(嵌套在其他轮廓内的)则设置为相反方向(顺时针),确保挖空区域正确填充。

示例代码(计算面积判断方向):

function GetContourArea(const Contour: TArray<TPointF>): Double;
var
  I, N: Integer;
begin
  Result := 0;
  N := Length(Contour);
  if N < 3 then Exit;
  for I := 0 to N-1 do
  begin
    var J := (I + 1) mod N;
    Result := Result + (Contour[I].X * Contour[J].Y) - (Contour[J].X * Contour[I].Y);
  end;
  Result := Result * 0.5;
end;

// 反转轮廓顶点顺序,改变绘制方向
function ReverseContour(const Contour: TArray<TPointF>): TArray<TPointF>;
var
  I: Integer;
begin
  SetLength(Result, Length(Contour));
  for I := 0 to High(Contour) do
    Result[I] := Contour[High(Contour) - I];
end;

4. 生成TPath可识别的路径数据

将处理后的轮廓序列转换为TPath的Data属性:

  • 每个闭合轮廓仅调用一次MoveTo(定位起点),后续顶点全部用LineTo连接。
  • 最后调用ClosePath确保轮廓闭合(也可依赖最后一个顶点与起点重合)。
  • 多个嵌套轮廓依次添加到同一TPathData中,注意方向相反。

示例代码:

procedure LoadProcessedContoursToPath(Path: TPath; const Contours: TArray<TArray<TPointF>>);
var
  PathData: TPathData;
  Contour: TArray<TPointF>;
  Pt: TPointF;
begin
  PathData := TPathData.Create;
  try
    for Contour in Contours do
    begin
      if Length(Contour) < 3 then Continue;
      // 移动到轮廓起点
      PathData.MoveTo(Contour[0]);
      // 绘制所有后续顶点
      for Pt in Contour[1..High(Contour)] do
        PathData.LineTo(Pt);
      // 闭合路径
      PathData.ClosePath;
    end;
    Path.Data := PathData;
  finally
    PathData.Free;
  end;
end;

关键注意事项

  • 处理DXF时要考虑浮点精度问题,判断端点重合时需加入误差阈值(如1e-6)。
  • 嵌套轮廓的包含关系需要用点-in-多边形算法(射线法)判断,确保方向调整正确。
  • 对于DXF中的复杂图元(如ARC、SPLINE),需要先转换为折线(多段LINE)再进行拼接处理。

内容的提问来源于stack exchange,提问作者engerex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 02:07:08