{ 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;