{ RivetViaConverter.pas

  Converts through-hole vias and drilled pads to the available PCB rivet sizes.
  Source classification is based on the object's current hole diameter:

    <= 0.3 mm  ->  drill 0.9 mm, copper diameter 1.7 mm
    <= 1.1 mm  ->  drill 1.3 mm, copper diameter 2.1 mm
     > 1.1 mm  ->  drill 2.0 mm, copper diameter 2.8 mm

  Copper diameter is always drill diameter + 0.8 mm.
  Existing complete hole/diameter target pairs are recognized before source
  thresholds, making repeated runs safe (idempotent).
}

Const
    CLASS_NONE = 0;
    CLASS_03   = 1;
    CLASS_08   = 2;
    CLASS_12   = 3;
    CLASS_NOT_THROUGH = 4;

Var
    GTotalVias       : Integer;
    GChange03        : Integer;
    GChange08        : Integer;
    GChange12        : Integer;
    GAlreadyReady    : Integer;
    GSkippedLarge    : Integer;
    GSkippedNotThru  : Integer;
    GFootprintVias   : Integer;
    GTotalDrilledPads : Integer;
    GFootprintPads    : Integer;
    GSkippedNoRing    : Integer;
    GSkippedNonPlated : Integer;
    GRulesCreated      : Integer;
    GRulesUpdated      : Integer;
    GRulesRemoved      : Integer;
    GRulesNotNeeded    : Integer;
    GDefaultClearance  : TCoord;
    GDefaultRuleFound  : Boolean;

Function CoordIsNear(A, B : TCoord) : Boolean;
Begin
    Result := Abs(A - B) <= MMsToCoord(0.001);
End;

Function IsThroughBoardVia(Via : IPCB_Via) : Boolean;
Begin
    Result := (Via.LowLayer = eTopLayer) And
              (Via.HighLayer = eBottomLayer);
End;

Function ViaHasNoAnnularRing(Via : IPCB_Via) : Boolean;
Begin
    Result := Via.Size <= Via.HoleSize + MMsToCoord(0.001);
End;

Function ClassifyHole(Hole : TCoord;
                      Var TargetHole : TCoord;
                      Var TargetSize : TCoord) : Integer;
Begin
    Result     := CLASS_NONE;
    TargetHole := 0;
    TargetSize := 0;

    If Hole <= MMsToCoord(0.3) Then
    Begin
        Result     := CLASS_03;
        TargetHole := MMsToCoord(0.9);
        TargetSize := MMsToCoord(1.7);
    End
    Else If Hole <= MMsToCoord(1.1) Then
    Begin
        Result     := CLASS_08;
        TargetHole := MMsToCoord(1.3);
        TargetSize := MMsToCoord(2.1);
    End
    Else If Hole > MMsToCoord(1.1) Then
    Begin
        Result     := CLASS_12;
        TargetHole := MMsToCoord(2.0);
        TargetSize := MMsToCoord(2.8);
    End;
End;

Function ViaMatchesKnownTemplate(Via : IPCB_Via) : Boolean;
Begin
    Result :=
        (CoordIsNear(Via.HoleSize, MMsToCoord(0.9)) And
         CoordIsNear(Via.Size, MMsToCoord(1.7))) Or
        (CoordIsNear(Via.HoleSize, MMsToCoord(1.3)) And
         CoordIsNear(Via.Size, MMsToCoord(2.1))) Or
        (CoordIsNear(Via.HoleSize, MMsToCoord(2.0)) And
         CoordIsNear(Via.Size, MMsToCoord(2.8)));
End;

Function ClassifyVia(Via : IPCB_Via;
                     Var TargetHole : TCoord;
                     Var TargetSize : TCoord) : Integer;
Begin
    If Not IsThroughBoardVia(Via) Then
    Begin
        TargetHole := 0;
        TargetSize := 0;
        Result := CLASS_NOT_THROUGH;
        Exit;
    End;

    { Migrate exact target pairs produced by older script versions without
      moving them into a larger group based on their already enlarged drill. }
    If CoordIsNear(Via.HoleSize, MMsToCoord(0.9)) And
       CoordIsNear(Via.Size, MMsToCoord(2.0)) Then
    Begin
        TargetHole := MMsToCoord(0.9);
        TargetSize := MMsToCoord(1.7);
        Result := CLASS_03;
        Exit;
    End;

    If CoordIsNear(Via.HoleSize, MMsToCoord(1.3)) And
       CoordIsNear(Via.Size, MMsToCoord(2.5)) Then
    Begin
        TargetHole := MMsToCoord(1.3);
        TargetSize := MMsToCoord(2.1);
        Result := CLASS_08;
        Exit;
    End;

    If CoordIsNear(Via.HoleSize, MMsToCoord(2.0)) And
       CoordIsNear(Via.Size, MMsToCoord(3.5)) Then
    Begin
        TargetHole := MMsToCoord(2.0);
        TargetSize := MMsToCoord(2.8);
        Result := CLASS_12;
        Exit;
    End;

    Result := ClassifyHole(Via.HoleSize, TargetHole, TargetSize);
End;

Procedure ResetCounts;
Begin
    GTotalVias      := 0;
    GChange03       := 0;
    GChange08       := 0;
    GChange12       := 0;
    GAlreadyReady   := 0;
    GSkippedLarge   := 0;
    GSkippedNotThru := 0;
    GFootprintVias  := 0;
    GTotalDrilledPads := 0;
    GFootprintPads    := 0;
    GSkippedNoRing    := 0;
    GSkippedNonPlated := 0;
End;

Procedure AnalyzeOneVia(Via : IPCB_Via; IsInFootprint : Boolean);
Var
    ViaClass   : Integer;
    TargetHole : TCoord;
    TargetSize : TCoord;
Begin
    Inc(GTotalVias);
    If IsInFootprint Then
        Inc(GFootprintVias);

    If ViaHasNoAnnularRing(Via) Then
    Begin
        Inc(GSkippedNoRing);
        Exit;
    End;

    ViaClass := ClassifyVia(Via, TargetHole, TargetSize);

    If ViaClass = CLASS_NOT_THROUGH Then
        Inc(GSkippedNotThru)
    Else If ViaClass = CLASS_NONE Then
        Inc(GSkippedLarge)
    Else If ViaMatchesKnownTemplate(Via) Then
        Inc(GAlreadyReady)
    Else If ViaClass = CLASS_03 Then
        Inc(GChange03)
    Else If ViaClass = CLASS_08 Then
        Inc(GChange08)
    Else If ViaClass = CLASS_12 Then
        Inc(GChange12);
End;

Function PadHasNoAnnularRing(Pad : IPCB_Pad) : Boolean;
Var
    MaxSize : TCoord;
Begin
    MaxSize := Pad.TopXSize;
    If Pad.TopYSize > MaxSize Then MaxSize := Pad.TopYSize;
    If Pad.MidXSize > MaxSize Then MaxSize := Pad.MidXSize;
    If Pad.MidYSize > MaxSize Then MaxSize := Pad.MidYSize;
    If Pad.BotXSize > MaxSize Then MaxSize := Pad.BotXSize;
    If Pad.BotYSize > MaxSize Then MaxSize := Pad.BotYSize;

    Result := MaxSize <= Pad.HoleSize + MMsToCoord(0.001);
End;

Function PadIsReady(Pad : IPCB_Pad; TargetHole, TargetSize : TCoord) : Boolean;
Begin
    Result := CoordIsNear(Pad.HoleSize, TargetHole) And
              CoordIsNear(Pad.TopXSize, TargetSize) And
              CoordIsNear(Pad.TopYSize, TargetSize) And
              CoordIsNear(Pad.MidXSize, TargetSize) And
              CoordIsNear(Pad.MidYSize, TargetSize) And
              CoordIsNear(Pad.BotXSize, TargetSize) And
              CoordIsNear(Pad.BotYSize, TargetSize);
End;

Function PadMatchesKnownTemplate(Pad : IPCB_Pad) : Boolean;
Begin
    Result :=
        PadIsReady(Pad, MMsToCoord(0.9), MMsToCoord(1.7)) Or
        PadIsReady(Pad, MMsToCoord(1.3), MMsToCoord(2.1)) Or
        PadIsReady(Pad, MMsToCoord(2.0), MMsToCoord(2.8));
End;

Function ClassifyPad(Pad : IPCB_Pad;
                     Var TargetHole : TCoord;
                     Var TargetSize : TCoord) : Integer;
Begin
    { Migrate exact target pairs produced by older script versions. }
    If PadIsReady(Pad, MMsToCoord(0.9), MMsToCoord(2.0)) Then
    Begin
        TargetHole := MMsToCoord(0.9);
        TargetSize := MMsToCoord(1.7);
        Result := CLASS_03;
        Exit;
    End;

    If PadIsReady(Pad, MMsToCoord(1.3), MMsToCoord(2.5)) Then
    Begin
        TargetHole := MMsToCoord(1.3);
        TargetSize := MMsToCoord(2.1);
        Result := CLASS_08;
        Exit;
    End;

    If PadIsReady(Pad, MMsToCoord(2.0), MMsToCoord(3.5)) Then
    Begin
        TargetHole := MMsToCoord(2.0);
        TargetSize := MMsToCoord(2.8);
        Result := CLASS_12;
        Exit;
    End;

    Result := ClassifyHole(Pad.HoleSize, TargetHole, TargetSize);
End;

Procedure AnalyzeOnePad(Pad : IPCB_Pad; IsInFootprint : Boolean);
Var
    PadClass   : Integer;
    TargetHole : TCoord;
    TargetSize : TCoord;
Begin
    { HoleSize = 0 means an SMD pad. It must remain untouched. }
    If Pad.HoleSize <= 0 Then
        Exit;

    Inc(GTotalDrilledPads);
    If IsInFootprint Then
        Inc(GFootprintPads);

    If Not Pad.Plated Then
    Begin
        Inc(GSkippedNonPlated);
        Exit;
    End;

    If PadHasNoAnnularRing(Pad) Then
    Begin
        Inc(GSkippedNoRing);
        Exit;
    End;

    PadClass := ClassifyPad(Pad, TargetHole, TargetSize);

    If PadClass = CLASS_NONE Then
        Inc(GSkippedLarge)
    Else If PadMatchesKnownTemplate(Pad) Then
        Inc(GAlreadyReady)
    Else If PadClass = CLASS_03 Then
        Inc(GChange03)
    Else If PadClass = CLASS_08 Then
        Inc(GChange08)
    Else If PadClass = CLASS_12 Then
        Inc(GChange12);
End;

Procedure AnalyzeBoard(Board : IPCB_Board);
Var
    Iterator          : IPCB_BoardIterator;
    ComponentIterator : IPCB_BoardIterator;
    GroupIterator     : IPCB_GroupIterator;
    Via               : IPCB_Via;
    Pad               : IPCB_Pad;
    Component         : IPCB_Component;
Begin
    ResetCounts;

    { First process free vias placed directly on the board. }
    Iterator := Board.BoardIterator_Create;
    Iterator.AddFilter_ObjectSet(MkSet(eViaObject));
    Iterator.AddFilter_LayerSet(AllLayers);
    Iterator.AddFilter_Method(eProcessAll);

    Try
        Via := Iterator.FirstPCBObject;
        While Via <> Nil Do
        Begin
            If Not Via.InComponent Then
                AnalyzeOneVia(Via, False);
            Via := Iterator.NextPCBObject;
        End;
    Finally
        Board.BoardIterator_Destroy(Iterator);
    End;

    { Process free drilled pads placed directly on the board. }
    Iterator := Board.BoardIterator_Create;
    Iterator.AddFilter_ObjectSet(MkSet(ePadObject));
    Iterator.AddFilter_LayerSet(AllLayers);
    Iterator.AddFilter_Method(eProcessAll);

    Try
        Pad := Iterator.FirstPCBObject;
        While Pad <> Nil Do
        Begin
            If Not Pad.InComponent Then
                AnalyzeOnePad(Pad, False);
            Pad := Iterator.NextPCBObject;
        End;
    Finally
        Board.BoardIterator_Destroy(Iterator);
    End;

    { Board iterators do not expose component child primitives consistently
      across Altium versions. Walk each footprint explicitly. }
    ComponentIterator := Board.BoardIterator_Create;
    ComponentIterator.AddFilter_ObjectSet(MkSet(eComponentObject));
    ComponentIterator.AddFilter_LayerSet(AllLayers);
    ComponentIterator.AddFilter_Method(eProcessAll);

    Try
        Component := ComponentIterator.FirstPCBObject;
        While Component <> Nil Do
        Begin
            GroupIterator := Component.GroupIterator_Create;
            GroupIterator.AddFilter_ObjectSet(MkSet(eViaObject));
            Try
                Via := GroupIterator.FirstPCBObject;
                While Via <> Nil Do
                Begin
                    AnalyzeOneVia(Via, True);
                    Via := GroupIterator.NextPCBObject;
                End;
            Finally
                Component.GroupIterator_Destroy(GroupIterator);
            End;

            GroupIterator := Component.GroupIterator_Create;
            GroupIterator.AddFilter_ObjectSet(MkSet(ePadObject));
            Try
                Pad := GroupIterator.FirstPCBObject;
                While Pad <> Nil Do
                Begin
                    AnalyzeOnePad(Pad, True);
                    Pad := GroupIterator.NextPCBObject;
                End;
            Finally
                Component.GroupIterator_Destroy(GroupIterator);
            End;

            Component := ComponentIterator.NextPCBObject;
        End;
    Finally
        Board.BoardIterator_Destroy(ComponentIterator);
    End;
End;

Function ChangesCount : Integer;
Begin
    Result := GChange03 + GChange08 + GChange12;
End;

Function BuildSummary : String;
Begin
    Result :=
        'Rivet hole analysis' + #13#10 + #13#10 +
        'All vias: ' + IntToStr(GTotalVias) + #13#10 +
        'Vias inside footprints: ' + IntToStr(GFootprintVias) + #13#10 +
        'All drilled pads: ' + IntToStr(GTotalDrilledPads) + #13#10 +
        'Drilled pads inside footprints: ' + IntToStr(GFootprintPads) + #13#10 +
        'Will set 1.7 / 0.9 mm: ' + IntToStr(GChange03) + #13#10 +
        'Will set 2.1 / 1.3 mm: ' + IntToStr(GChange08) + #13#10 +
        'Will set 2.8 / 2.0 mm: ' + IntToStr(GChange12) + #13#10 +
        'Already ready: ' + IntToStr(GAlreadyReady) + #13#10 +
        'Skipped, no annular ring: ' + IntToStr(GSkippedNoRing) + #13#10 +
        'Skipped, non-plated pad: ' + IntToStr(GSkippedNonPlated) + #13#10 +
        'Skipped, unsupported hole: ' + IntToStr(GSkippedLarge) + #13#10 +
        'Skipped, blind/buried via: ' + IntToStr(GSkippedNotThru);
End;

Function ApplyOneVia(Via : IPCB_Via) : Boolean;
Var
    ViaClass   : Integer;
    TargetHole : TCoord;
    TargetSize : TCoord;
Begin
    Result := False;
    If ViaHasNoAnnularRing(Via) Then
        Exit;

    If ViaMatchesKnownTemplate(Via) Then
        Exit;

    ViaClass := ClassifyVia(Via, TargetHole, TargetSize);

    If (ViaClass >= CLASS_03) And (ViaClass <= CLASS_12) And
       (Not (CoordIsNear(Via.HoleSize, TargetHole) And
             CoordIsNear(Via.Size, TargetSize))) Then
    Begin
        PCBServer.SendMessageToRobots(
            Via.I_ObjectAddress,
            c_Broadcast,
            PCBM_BeginModify,
            c_NoEventData);
        Try
            Via.HoleSize := TargetHole;
            Via.Size     := TargetSize;
        Finally
            PCBServer.SendMessageToRobots(
                Via.I_ObjectAddress,
                c_Broadcast,
                PCBM_EndModify,
                c_NoEventData);
        End;
        Result := True;
    End;
End;

Function ApplyOnePad(Pad : IPCB_Pad) : Boolean;
Var
    PadClass   : Integer;
    TargetHole : TCoord;
    TargetSize : TCoord;
Begin
    Result := False;
    If Pad.HoleSize <= 0 Then
        Exit;

    If Not Pad.Plated Then
        Exit;

    If PadHasNoAnnularRing(Pad) Then
        Exit;

    If PadMatchesKnownTemplate(Pad) Then
        Exit;

    PadClass := ClassifyPad(Pad, TargetHole, TargetSize);
    If (PadClass >= CLASS_03) And (PadClass <= CLASS_12) And
       (Not PadIsReady(Pad, TargetHole, TargetSize)) Then
    Begin
        PCBServer.SendMessageToRobots(
            Pad.I_ObjectAddress,
            c_Broadcast,
            PCBM_BeginModify,
            c_NoEventData);
        Try
            Pad.HoleSize := TargetHole;
            Pad.TopXSize := TargetSize;
            Pad.TopYSize := TargetSize;
            Pad.MidXSize := TargetSize;
            Pad.MidYSize := TargetSize;
            Pad.BotXSize := TargetSize;
            Pad.BotYSize := TargetSize;
        Finally
            PCBServer.SendMessageToRobots(
                Pad.I_ObjectAddress,
                c_Broadcast,
                PCBM_EndModify,
                c_NoEventData);
        End;
        Result := True;
    End;
End;

Function FindIsolationRule(Board : IPCB_Board;
                           RuleName : String) : IPCB_ClearanceConstraint;
Var
    Iterator : IPCB_BoardIterator;
    Rule     : IPCB_Rule;
Begin
    Result := Nil;
    Iterator := Board.BoardIterator_Create;
    Iterator.AddFilter_ObjectSet(MkSet(eRuleObject));
    Iterator.AddFilter_LayerSet(AllLayers);
    Iterator.AddFilter_Method(eProcessAll);

    Try
        Rule := Iterator.FirstPCBObject;
        While Rule <> Nil Do
        Begin
            If (Rule.RuleKind = eRule_Clearance) And
               (Rule.Name = RuleName) Then
            Begin
                Result := Rule;
                Exit;
            End;
            Rule := Iterator.NextPCBObject;
        End;
    Finally
        Board.BoardIterator_Destroy(Iterator);
    End;
End;

Function ScopeIsAll(ScopeExpression : String) : Boolean;
Var
    S : String;
Begin
    S := UpperCase(Trim(ScopeExpression));
    Result := (S = 'ALL') Or (S = '');
End;

Function FindDefaultClearance(Board : IPCB_Board;
                              Var DefaultGap : TCoord) : Boolean;
Var
    Iterator : IPCB_BoardIterator;
    Rule     : IPCB_Rule;
    ClearanceRule : IPCB_ClearanceConstraint;
Begin
    Result := False;
    DefaultGap := 0;

    Iterator := Board.BoardIterator_Create;
    Iterator.AddFilter_ObjectSet(MkSet(eRuleObject));
    Iterator.AddFilter_LayerSet(AllLayers);
    Iterator.AddFilter_Method(eProcessAll);

    Try
        { Rule iterators follow the board's rule order. The first enabled
          All/All clearance rule is therefore the active default rule. }
        Rule := Iterator.FirstPCBObject;
        While Rule <> Nil Do
        Begin
            If (Rule.RuleKind = eRule_Clearance) And
               Rule.DRCEnabled And
               ScopeIsAll(Rule.Scope1Expression) And
               ScopeIsAll(Rule.Scope2Expression) Then
            Begin
                ClearanceRule := Rule;
                DefaultGap := ClearanceRule.Gap;
                Result := True;
                Exit;
            End;
            Rule := Iterator.NextPCBObject;
        End;
    Finally
        Board.BoardIterator_Destroy(Iterator);
    End;
End;

Procedure RemoveIsolationRuleIfPresent(Board : IPCB_Board;
                                       RuleName : String);
Var
    Rule : IPCB_ClearanceConstraint;
Begin
    Rule := FindIsolationRule(Board, RuleName);
    If Rule <> Nil Then
    Begin
        Board.RemovePCBObject(Rule);
        Inc(GRulesRemoved);
    End;
End;

Function BuildIsolationScope(HoleMM, CopperMM : String) : String;
Begin
    Result :=
        '((IsVia And (AsMM(HoleSize) = ' + HoleMM + ') And ' +
        '(AsMM(ViaDiameter) = ' + CopperMM + ')) Or ' +
        '(IsPad And (PadIsPlated = ''True'') And ' +
        '(AsMM(HoleSize) = ' + HoleMM + ') And ' +
        '(AsMM(PadXSize_TopLayer) = ' + CopperMM + ') And ' +
        '(AsMM(PadYSize_TopLayer) = ' + CopperMM + ')))';
End;

Procedure EnsureIsolationRule(Board       : IPCB_Board;
                              RuleName    : String;
                              HoleMM      : String;
                              CopperMM    : String;
                              IsolationGap : TCoord;
                              DefaultFound : Boolean;
                              DefaultGap   : TCoord);
Var
    Rule : IPCB_ClearanceConstraint;
Begin
    If DefaultFound And (DefaultGap >= IsolationGap) Then
    Begin
        RemoveIsolationRuleIfPresent(Board, RuleName);
        Inc(GRulesNotNeeded);
        Exit;
    End;

    Rule := FindIsolationRule(Board, RuleName);

    If Rule = Nil Then
    Begin
        Rule := PCBServer.PCBRuleFactory(eRule_Clearance);
        PCBServer.SendMessageToRobots(
            Rule.I_ObjectAddress,
            c_Broadcast,
            PCBM_BeginModify,
            c_NoEventData);
        Try
            Rule.Name := RuleName;
            Rule.Comment :=
                'Rivet routing/polygon isolation. Pads and vias use normal clearance.';
            Rule.DRCEnabled := True;
            Rule.Scope1Expression := BuildIsolationScope(HoleMM, CopperMM);
            Rule.Scope2Expression :=
                'IsTrack Or IsArc Or IsFill Or IsRegion Or InPolygon';
            Rule.Gap := IsolationGap;
            Board.AddPCBObject(Rule);
        Finally
            PCBServer.SendMessageToRobots(
                Rule.I_ObjectAddress,
                c_Broadcast,
                PCBM_EndModify,
                c_NoEventData);
        End;
        Inc(GRulesCreated);
    End
    Else
    Begin
        PCBServer.SendMessageToRobots(
            Rule.I_ObjectAddress,
            c_Broadcast,
            PCBM_BeginModify,
            c_NoEventData);
        Try
            Rule.Comment :=
                'Rivet routing/polygon isolation. Pads and vias use normal clearance.';
            Rule.DRCEnabled := True;
            Rule.Scope1Expression := BuildIsolationScope(HoleMM, CopperMM);
            Rule.Scope2Expression :=
                'IsTrack Or IsArc Or IsFill Or IsRegion Or InPolygon';
            Rule.Gap := IsolationGap;
        Finally
            PCBServer.SendMessageToRobots(
                Rule.I_ObjectAddress,
                c_Broadcast,
                PCBM_EndModify,
                c_NoEventData);
        End;
        Inc(GRulesUpdated);
    End;
End;

Procedure EnsureIsolationRules(Board : IPCB_Board);
Begin
    GRulesCreated := 0;
    GRulesUpdated := 0;
    GRulesRemoved := 0;
    GRulesNotNeeded := 0;
    GDefaultRuleFound := FindDefaultClearance(Board, GDefaultClearance);

    { Overall isolation diameters are 2.2, 2.7 and 3.7 mm.
      Gap is measured from the copper edge, hence (zone - copper) / 2. }
    EnsureIsolationRule(Board, 'RIVET_03_RoutingIsolation',
                        '0.9', '1.7', MMsToCoord(0.25),
                        GDefaultRuleFound, GDefaultClearance);
    EnsureIsolationRule(Board, 'RIVET_11_RoutingIsolation',
                        '1.3', '2.1', MMsToCoord(0.30),
                        GDefaultRuleFound, GDefaultClearance);
    EnsureIsolationRule(Board, 'RIVET_GT11_RoutingIsolation',
                        '2.0', '2.8', MMsToCoord(0.45),
                        GDefaultRuleFound, GDefaultClearance);
End;

Function ApplyChanges(Board : IPCB_Board) : Integer;
Var
    Iterator          : IPCB_BoardIterator;
    ComponentIterator : IPCB_BoardIterator;
    GroupIterator     : IPCB_GroupIterator;
    Via               : IPCB_Via;
    Pad               : IPCB_Pad;
    Component         : IPCB_Component;
Begin
    Result := 0;
    PCBServer.PreProcess;
    Try
        Iterator := Board.BoardIterator_Create;
        Iterator.AddFilter_ObjectSet(MkSet(eViaObject));
        Iterator.AddFilter_LayerSet(AllLayers);
        Iterator.AddFilter_Method(eProcessAll);

        Try
            Via := Iterator.FirstPCBObject;
            While Via <> Nil Do
            Begin
                If (Not Via.InComponent) And ApplyOneVia(Via) Then
                    Inc(Result);
                Via := Iterator.NextPCBObject;
            End;
        Finally
            Board.BoardIterator_Destroy(Iterator);
        End;

        { Process free drilled pads. }
        Iterator := Board.BoardIterator_Create;
        Iterator.AddFilter_ObjectSet(MkSet(ePadObject));
        Iterator.AddFilter_LayerSet(AllLayers);
        Iterator.AddFilter_Method(eProcessAll);

        Try
            Pad := Iterator.FirstPCBObject;
            While Pad <> Nil Do
            Begin
                If (Not Pad.InComponent) And ApplyOnePad(Pad) Then
                    Inc(Result);
                Pad := Iterator.NextPCBObject;
            End;
        Finally
            Board.BoardIterator_Destroy(Iterator);
        End;

        { Process vias and drilled pads embedded in placed footprints. }
        ComponentIterator := Board.BoardIterator_Create;
        ComponentIterator.AddFilter_ObjectSet(MkSet(eComponentObject));
        ComponentIterator.AddFilter_LayerSet(AllLayers);
        ComponentIterator.AddFilter_Method(eProcessAll);

        Try
            Component := ComponentIterator.FirstPCBObject;
            While Component <> Nil Do
            Begin
                GroupIterator := Component.GroupIterator_Create;
                GroupIterator.AddFilter_ObjectSet(MkSet(eViaObject));
                Try
                    Via := GroupIterator.FirstPCBObject;
                    While Via <> Nil Do
                    Begin
                        If ApplyOneVia(Via) Then
                            Inc(Result);
                        Via := GroupIterator.NextPCBObject;
                    End;
                Finally
                    Component.GroupIterator_Destroy(GroupIterator);
                End;

                GroupIterator := Component.GroupIterator_Create;
                GroupIterator.AddFilter_ObjectSet(MkSet(ePadObject));
                Try
                    Pad := GroupIterator.FirstPCBObject;
                    While Pad <> Nil Do
                    Begin
                        If ApplyOnePad(Pad) Then
                            Inc(Result);
                        Pad := GroupIterator.NextPCBObject;
                    End;
                Finally
                    Component.GroupIterator_Destroy(GroupIterator);
                End;

                Component := ComponentIterator.NextPCBObject;
            End;
        Finally
            Board.BoardIterator_Destroy(ComponentIterator);
        End;

        EnsureIsolationRules(Board);
    Finally
        PCBServer.PostProcess;
    End;

    Client.SendMessage('PCB:Zoom', 'Action=Redraw', 255, Client.CurrentView);
End;

Procedure AnalyzeVias;
Var
    Board : IPCB_Board;
Begin
    Board := PCBServer.GetCurrentPCBBoard;
    If Board = Nil Then
    Begin
        ShowMessage('Open and focus a PCB document first.');
        Exit;
    End;

    AnalyzeBoard(Board);
    ShowMessage(BuildSummary);
End;

Procedure ConvertViasToRivets;
Var
    Board        : IPCB_Board;
    ChangedCount : Integer;
    Prompt       : String;
    ResultMessage : String;
Begin
    Board := PCBServer.GetCurrentPCBBoard;
    If Board = Nil Then
    Begin
        ShowMessage('Open and focus a PCB document first.');
        Exit;
    End;

    AnalyzeBoard(Board);
    Prompt := BuildSummary + #13#10 + #13#10;
    If ChangesCount = 0 Then
        Prompt := Prompt + 'Object sizes are ready. Isolation rules will still be reconciled.' +
                  #13#10;
    Prompt := Prompt +
              'Press OK to modify the board and reconcile isolation rules,' +
              #13#10 + 'or Cancel to stop.';
    If MessageDlg(Prompt, mtConfirmation, mbOKCancel, 0) = mrCancel Then
        Exit;

    ChangedCount := ApplyChanges(Board);
    ResultMessage :=
        'Done. Modified vias/pads: ' + IntToStr(ChangedCount) +
        #13#10 + 'Isolation rules created: ' + IntToStr(GRulesCreated) +
        #13#10 + 'Isolation rules updated: ' + IntToStr(GRulesUpdated) +
        #13#10 + 'Isolation rules removed: ' + IntToStr(GRulesRemoved) +
        #13#10 + 'Groups covered by default clearance: ' +
        IntToStr(GRulesNotNeeded);

    If GDefaultRuleFound Then
        ResultMessage := ResultMessage + #13#10 +
            'Default clearance: ' + FloatToStr(CoordToMMs(GDefaultClearance)) + ' mm'
    Else
        ResultMessage := ResultMessage + #13#10 +
            'Default All/All clearance rule was not found.';

    ResultMessage := ResultMessage + #13#10 +
        'Use Undo to revert the complete operation.';
    ShowMessage(ResultMessage);
End;
