Перепиновка под будущий аналог

+скрипт для конвертации падов под CNC
This commit is contained in:
2026-08-09 10:19:12 +03:00
parent 61d3aeaba6
commit cec997d1d2
6 changed files with 916 additions and 4 deletions

7
.gitignore vendored
View File

@@ -1,7 +1,6 @@
/tests/ /tests/
/PCB/Project Logs*/ __Previews/
/*/__Previews History
/*/History Project Logs*/
/*/Project Logs*/

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,62 @@
# Rivet Via Converter для Altium Designer
Скрипт анализирует все отверстия активного `PcbDoc` и преобразует в размеры
имеющихся заклёпок как сквозные Via, так и все сверлёные Pad. Обрабатываются
свободные объекты на плате и примитивы внутри футпринтов:
| Исходный Hole Size | Новый Hole Size | Диаметр меди | Диаметр зоны изоляции |
|---:|---:|---:|---:|
| `<= 0,3 мм` | `0,9 мм` | `1,7 мм` | `2,2 мм` |
| `<= 1,1 мм` | `1,3 мм` | `2,1 мм` | `2,7 мм` |
| `> 1,1 мм` | `2,0 мм` | `2,8 мм` | `3,7 мм` |
Дополнительный запас к внешнему диаметру не добавляется. Глухие и скрытые Via
не изменяются. Также игнорируются неплакированные Pad и отверстия, у которых
внешний размер площадки равен диаметру отверстия, то есть отсутствует медное
кольцо. SMD Pad с нулевым размером отверстия не изменяются. Диаметр меди всегда
равен `Hole Size + 0,8 мм`. У сверлёных Pad задаются одинаковые X/Y-размеры меди
на Top, Mid и Bottom, но исходная форма площадки сохраняется. Готовые размеры распознаются
отдельно, поэтому скрипт можно запускать повторно без дальнейшего увеличения.
Скрипт проверяет активное общее правило Clearance со Scope `All / All`. Для
каждой группы отдельное правило создаётся или обновляется только тогда, когда
общий clearance меньше требуемого зазора от края меди. Если общий clearance уже
достаточен, правило группы не создаётся, а ранее созданное скриптом правило
удаляется.
Возможные отдельные правила:
- `RIVET_03_RoutingIsolation` — зазор от края меди `0,25 мм`;
- `RIVET_11_RoutingIsolation` — зазор от края меди `0,30 мм`;
- `RIVET_GT11_RoutingIsolation` — зазор от края меди `0,45 мм`.
Область действия первой стороны каждого правила автоматически выбирает Via и
plated Pad с соответствующей парой Hole Size/диаметр. Вторая сторона выбирает
только дорожки, дуги трассировки, медные Fill/Region и Polygon. Поэтому другие
Pad и Via могут располагаться ближе и продолжают использовать обычное правило
clearance платы, например `0,5 мм`.
Например, при общем clearance `0,5 мм` он уже превышает все три требуемых
значения (`0,25 / 0,30 / 0,45 мм`), поэтому ни одно дополнительное правило не
понадобится.
## Запуск
1. Откройте `RivetViaConverter.PrjScr` в Altium Designer.
2. Откройте и сделайте активным нужный `PcbDoc`.
3. Выберите `File -> Run Script`.
4. Для проверки без изменений запустите `AnalyzeVias` — несмотря на историческое
имя процедуры, она проверяет и Via, и сверлёные Pad.
5. Для преобразования и синхронизации правил запустите `ConvertViasToRivets` —
она изменяет оба типа объектов.
6. Проверьте показанную сводку и нажмите `OK`.
Все изменения формируются как одна операция Undo. Скрипт не сохраняет файл
автоматически.
## После преобразования
Запустите DRC: увеличенные площадки могут нарушить Clearance или выйти слишком
близко к контуру платы. Также проверьте настройки Solder Mask Expansion: медь
вокруг заклёпки должна быть открыта с обеих сторон платы. Сам скрипт настройки
паяльной маски и правила PCB не меняет.

View File

@@ -0,0 +1,8 @@
[Design]
Version=1.0
HierarchyMode=0
[Document1]
DocumentPath=RivetViaConverter.pas
AnnotationEnabled=1

View File

@@ -0,0 +1,843 @@
{ 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;