diff --git a/.gitignore b/.gitignore index 06fddbc..e9c978b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /tests/ -/PCB/Project Logs*/ -/*/__Previews -/*/History -/*/Project Logs*/ +__Previews/ +History +Project Logs*/ diff --git a/PCB/OptoTest.PcbDoc b/PCB/OptoTest.PcbDoc index 48aa996..54fdcad 100644 Binary files a/PCB/OptoTest.PcbDoc and b/PCB/OptoTest.PcbDoc differ diff --git a/PCB/OptoTest.SchDoc b/PCB/OptoTest.SchDoc index ba8391b..12f9541 100644 Binary files a/PCB/OptoTest.SchDoc and b/PCB/OptoTest.SchDoc differ diff --git a/PCB/Scripts/RivetViaConverter/README.md b/PCB/Scripts/RivetViaConverter/README.md new file mode 100644 index 0000000..e52ef4b --- /dev/null +++ b/PCB/Scripts/RivetViaConverter/README.md @@ -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 не меняет. diff --git a/PCB/Scripts/RivetViaConverter/RivetViaConverter.PrjScr b/PCB/Scripts/RivetViaConverter/RivetViaConverter.PrjScr new file mode 100644 index 0000000..a95472f --- /dev/null +++ b/PCB/Scripts/RivetViaConverter/RivetViaConverter.PrjScr @@ -0,0 +1,8 @@ +[Design] +Version=1.0 +HierarchyMode=0 + +[Document1] +DocumentPath=RivetViaConverter.pas +AnnotationEnabled=1 + diff --git a/PCB/Scripts/RivetViaConverter/RivetViaConverter.pas b/PCB/Scripts/RivetViaConverter/RivetViaConverter.pas new file mode 100644 index 0000000..8e59c84 --- /dev/null +++ b/PCB/Scripts/RivetViaConverter/RivetViaConverter.pas @@ -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;