KiCad PCB EDA Suite
Loading...
Searching...
No Matches
drc_re_rule_saver.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "drc_re_rule_saver.h"
25
26#include <board.h>
27#include <lset.h>
28#include <string_utils.h>
29#include <wx/ffile.h>
30
33
34
38
39
40bool DRC_RULE_SAVER::SaveFile( const wxString& aPath,
41 const std::vector<DRC_RE_LOADED_PANEL_ENTRY>& aEntries,
42 const BOARD* aBoard )
43{
44 wxFFile file( aPath, "w" );
45
46 if( !file.IsOpened() )
47 return false;
48
49 wxString content = GenerateRulesText( aEntries, aBoard );
50 file.Write( content );
51 file.Close();
52
53 return true;
54}
55
56
57wxString DRC_RULE_SAVER::GenerateRulesText( const std::vector<DRC_RE_LOADED_PANEL_ENTRY>& aEntries,
58 const BOARD* aBoard )
59{
60 wxString result = "(version 1)\n";
61
62 // Group entries by (ruleName, condition) for merging same-name same-condition rules
63 // Use a vector to preserve insertion order
64 std::vector<std::pair<std::pair<wxString, wxString>, std::vector<const DRC_RE_LOADED_PANEL_ENTRY*>>>
65 groupedEntries;
66 std::map<std::pair<wxString, wxString>, size_t> groupIndex;
67
68 for( const DRC_RE_LOADED_PANEL_ENTRY& entry : aEntries )
69 {
70 auto key = std::make_pair( entry.ruleName, entry.condition );
71 auto it = groupIndex.find( key );
72
73 if( it == groupIndex.end() )
74 {
75 groupIndex[key] = groupedEntries.size();
76 groupedEntries.push_back( { key, { &entry } } );
77 }
78 else
79 {
80 groupedEntries[it->second].second.push_back( &entry );
81 }
82 }
83
84 // Generate rule text for each group
85 for( const auto& [key, entries] : groupedEntries )
86 {
87 wxString ruleText;
88
89 if( entries.size() == 1 )
90 {
91 // Single entry, no merge needed
92 ruleText = generateRuleText( *entries[0], aBoard );
93 }
94 else
95 {
96 // Multiple entries with same name/condition need merging
97 ruleText = generateMergedRuleText( entries, aBoard );
98 }
99
100 if( !ruleText.IsEmpty() )
101 result += ruleText + "\n";
102 }
103
104 return result;
105}
106
107
109 const BOARD* aBoard )
110{
111 // Round-trip preservation: return original text if not edited
112 if( !aEntry.wasEdited && !aEntry.originalRuleText.IsEmpty() )
113 return aEntry.originalRuleText;
114
115 // Otherwise, regenerate from panel data
116 if( !aEntry.constraintData )
117 return wxEmptyString;
118
119 wxString ruleText = aEntry.constraintData->GetGeneratedRule();
120
121 if( ruleText.IsEmpty() )
122 {
124 ctx.ruleName = aEntry.ruleName;
125 ctx.conditionExpression = aEntry.condition;
127 ctx.comment = aEntry.constraintData->GetComment();
128
129 // Generate layer clause if layers are specified
130 if( aEntry.layerCondition.any() && aBoard )
131 ctx.layerClause = generateLayerClause( aEntry.layerCondition, aBoard );
132
133 ruleText = aEntry.constraintData->GenerateRule( ctx );
134 }
135
136 // If severity is specified and not default, we need to inject it
137 // The GenerateRule method should handle this, but we verify here
139 {
140 wxString severityClause = generateSeverityClause( aEntry.severity );
141
142 if( !severityClause.IsEmpty() && !ruleText.Contains( "(severity" ) )
143 {
144 // Insert severity clause before the closing paren
145 size_t lastParen = ruleText.rfind( ')' );
146
147 if( lastParen != wxString::npos )
148 {
149 ruleText = ruleText.Left( lastParen ) + "\n\t" + severityClause + ")";
150 }
151 }
152 }
153
154 return ruleText;
155}
156
157
158wxString DRC_RULE_SAVER::generateLayerClause( const LSET& aLayers, const BOARD* aBoard )
159{
160 if( !aBoard || !aLayers.any() )
161 return wxEmptyString;
162
163 wxString layerStr = "(layer";
164
165 for( PCB_LAYER_ID layer : aLayers.Seq() )
166 layerStr += " \"" + aBoard->GetLayerName( layer ) + "\"";
167
168 layerStr += ")";
169
170 return layerStr;
171}
172
173
175{
176 switch( aSeverity )
177 {
178 case RPT_SEVERITY_IGNORE: return "(severity ignore)";
179 case RPT_SEVERITY_WARNING: return "(severity warning)";
180 case RPT_SEVERITY_ERROR: return "(severity error)";
181 case RPT_SEVERITY_EXCLUSION: return "(severity exclusion)";
182 default: return wxEmptyString;
183 }
184}
185
186
188 const std::vector<const DRC_RE_LOADED_PANEL_ENTRY*>& aEntries,
189 const BOARD* aBoard )
190{
191 if( aEntries.empty() )
192 return wxEmptyString;
193
194 // Check if all entries are unedited and the first one has original text
195 // If so, we can use round-trip preservation
196 bool allUnedited = true;
197
198 for( const auto* entry : aEntries )
199 {
200 if( entry->wasEdited )
201 {
202 allUnedited = false;
203 break;
204 }
205 }
206
207 if( allUnedited && !aEntries[0]->originalRuleText.IsEmpty() )
208 return aEntries[0]->originalRuleText;
209
210 // Otherwise, merge constraint clauses from all entries
211 const DRC_RE_LOADED_PANEL_ENTRY* firstEntry = aEntries[0];
212
214 ctx.ruleName = firstEntry->ruleName;
215 ctx.conditionExpression = firstEntry->condition;
216
217 // Generate layer clause if layers are specified on any entry
218 for( const auto* entry : aEntries )
219 {
220 if( entry->layerCondition.any() && aBoard )
221 {
222 ctx.layerClause = generateLayerClause( entry->layerCondition, aBoard );
223 break;
224 }
225 }
226
227 // Collect all constraint clauses from all entries
228 std::vector<wxString> allClauses;
229
230 for( const auto* entry : aEntries )
231 {
232 if( entry->constraintData )
233 {
235 entryCtx.ruleName = entry->ruleName;
236 entryCtx.conditionExpression = entry->condition;
237 entryCtx.constraintCode = entry->constraintData->GetConstraintCode();
238
239 auto clauses = entry->constraintData->GetConstraintClauses( entryCtx );
240
241 for( const wxString& clause : clauses )
242 {
243 if( !clause.IsEmpty() )
244 allClauses.push_back( clause );
245 }
246 }
247 }
248
249 // Build the merged rule
250 wxString rule;
251 rule << wxS( "(rule " ) << DRC_RE_BASE_CONSTRAINT_DATA::sanitizeRuleName( ctx.ruleName )
252 << wxS( "\n" );
253
254 if( !ctx.layerClause.IsEmpty() )
255 rule << wxS( "\t" ) << ctx.layerClause << wxS( "\n" );
256
257 for( const wxString& clause : allClauses )
258 rule << wxS( "\t" ) << clause << wxS( "\n" );
259
260 if( !ctx.conditionExpression.IsEmpty() )
261 {
262 rule << wxS( "\t(condition \"" )
263 << EscapeString( ctx.conditionExpression, CTX_QUOTED_STR ) << wxS( "\")\n" );
264 }
265
266 // Add severity if any entry has non-default severity
267 for( const auto* entry : aEntries )
268 {
269 if( entry->severity != RPT_SEVERITY_UNDEFINED && entry->severity != RPT_SEVERITY_ERROR )
270 {
271 rule << wxS( "\t" ) << generateSeverityClause( entry->severity ) << wxS( "\n" );
272 break;
273 }
274 }
275
276 rule << wxS( ")" );
277
278 return rule;
279}
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
const wxString GetLayerName(PCB_LAYER_ID aLayer) const
Return the name of a aLayer.
Definition board.cpp:728
static wxString sanitizeRuleName(const wxString &aRuleName)
Sanitize a rule name for use in S-expression output.
virtual wxString GenerateRule(const RULE_GENERATION_CONTEXT &aContext)
bool SaveFile(const wxString &aPath, const std::vector< DRC_RE_LOADED_PANEL_ENTRY > &aEntries, const BOARD *aBoard=nullptr)
Save all panel entries to a file.
wxString generateLayerClause(const LSET &aLayers, const BOARD *aBoard)
Generate a layer clause from an LSET.
wxString generateRuleText(const DRC_RE_LOADED_PANEL_ENTRY &aEntry, const BOARD *aBoard)
Generate the rule text for a single panel entry.
wxString GenerateRulesText(const std::vector< DRC_RE_LOADED_PANEL_ENTRY > &aEntries, const BOARD *aBoard=nullptr)
Generate rule text from panel entries.
wxString generateMergedRuleText(const std::vector< const DRC_RE_LOADED_PANEL_ENTRY * > &aEntries, const BOARD *aBoard)
Generate a merged rule text from multiple panel entries with the same name/condition.
wxString generateSeverityClause(SEVERITY aSeverity)
Generate a severity clause.
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
LSEQ Seq(const LSEQ &aSequence) const
Return an LSEQ from the union of this LSET and a desired sequence.
Definition lset.cpp:313
wxString GetComment()
Get the comment associated with the rule.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
SEVERITY
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_UNDEFINED
@ RPT_SEVERITY_EXCLUSION
@ RPT_SEVERITY_IGNORE
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_QUOTED_STR
Represents a rule loaded from a .kicad_dru file and mapped to a panel.
wxString ruleName
wxString originalRuleText
wxString condition
bool wasEdited
LSET layerCondition
std::shared_ptr< DRC_RE_BASE_CONSTRAINT_DATA > constraintData
SEVERITY severity
wxString result
Test unit parsing edge cases and error handling.