Перепиновка под будущий аналог
+скрипт для конвертации падов под CNC
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -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.
62
PCB/Scripts/RivetViaConverter/README.md
Normal file
62
PCB/Scripts/RivetViaConverter/README.md
Normal 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 не меняет.
|
||||||
8
PCB/Scripts/RivetViaConverter/RivetViaConverter.PrjScr
Normal file
8
PCB/Scripts/RivetViaConverter/RivetViaConverter.PrjScr
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[Design]
|
||||||
|
Version=1.0
|
||||||
|
HierarchyMode=0
|
||||||
|
|
||||||
|
[Document1]
|
||||||
|
DocumentPath=RivetViaConverter.pas
|
||||||
|
AnnotationEnabled=1
|
||||||
|
|
||||||
843
PCB/Scripts/RivetViaConverter/RivetViaConverter.pas
Normal file
843
PCB/Scripts/RivetViaConverter/RivetViaConverter.pas
Normal 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;
|
||||||
Reference in New Issue
Block a user