844 lines
26 KiB
ObjectPascal
844 lines
26 KiB
ObjectPascal
{ 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;
|