KiCad PCB EDA Suite
Loading...
Searching...
No Matches
ipc2581_function_mode.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <array>
23#include <utility>
24
25#include <wx/translation.h>
26
27namespace IPC2581
28{
29
30namespace
31{
32
33constexpr size_t SECTION_COUNT = static_cast<size_t>( SECTION::COUNT );
34constexpr size_t MODE_COUNT = static_cast<size_t>( MODE::COUNT );
35
39
40// IPC-2581C Table 4 p 80
41// The rows follow SECTION and the columns follow MODE
42constexpr std::array<std::array<SECTION_RULE, MODE_COUNT>, SECTION_COUNT> TABLE_4 = { {
43 // UserDef BOM Stackup Fab Assembly Test Stencil DFX
44 /* K */ { { O, N, N, O, O, N, N, N } },
45 /* B */ { { O, Y, O, O, Y, Y, N, N } },
46 /* C */ { { O, N, N, N, Y, Y, O, N } },
47 /* A */ { { O, N, N, N, Y, Y, N, N } },
48 /* S */ { { O, N, Y, Y, N, N, N, N } },
49 /* U */ { { O, N, O, Y, Y, Y, Y, N } },
50 /* M */ { { O, N, N, Y, N, N, O, N } },
51 /* P */ { { O, N, N, N, O, N, Y, N } },
52 /* L */ { { O, N, N, Y, Y, Y, O, N } },
53 /* R */ { { O, N, O, Y, Y, Y, O, N } },
54 /* D */ { { O, N, O, O, O, O, O, N } },
55 /* O */ { { O, N, Y, Y, Y, Y, O, N } },
56 /* I */ { { O, N, Y, Y, N, N, N, N } },
57 /* E */ { { O, N, O, O, N, N, N, N } },
58 /* F */ { { O, N, O, O, N, N, N, N } },
59 /* G */ { { O, N, N, O, O, O, N, N } },
60 /* Y */ { { O, N, N, Y, O, Y, N, N } },
61 /* X */ { { O, O, O, O, O, O, O, Y } },
62} };
63
64constexpr std::array<char, SECTION_COUNT> SECTION_KEYS = {
65 'K', 'B', 'C', 'A', 'S', 'U', 'M', 'P', 'L', 'R', 'D', 'O', 'I', 'E', 'F', 'G', 'Y', 'X'
66};
67
68constexpr std::array<const char*, MODE_COUNT> MODE_TOKENS = {
69 "USERDEF", "BOM", "STACKUP", "FABRICATION", "ASSEMBLY", "TEST", "STENCIL", "DFX"
70};
71
72// A new function mode in the middle of MODE moves each token after it
73static_assert( MODE_TOKENS[static_cast<size_t>( MODE::DFX )][0] == 'D',
74 "MODE_TOKENS is out of step with MODE" );
75
76// Each layerFunction value that KiCad writes with its Table 4 schema section
77constexpr std::array<std::pair<const char*, SECTION>, 27> LAYER_FUNCTION_SECTIONS = { {
78 // EDGE_CHAMFER is in section 4.1.1.10 and in section 4.1.1.15
79 // Table 4 makes R necessary for FABRICATION but F is optional
80 { "DRILL", SECTION::DRILL_ROUT },
81 { "ROUT", SECTION::DRILL_ROUT },
82 { "V_CUT", SECTION::DRILL_ROUT },
83 { "EDGE_CHAMFER", SECTION::DRILL_ROUT },
84 { "SOLDERMASK", SECTION::SOLDERMASK },
85 { "SOLDERPASTE", SECTION::SOLDERPASTE },
86 { "SILKSCREEN", SECTION::SILKSCREEN },
87 { "LEGEND", SECTION::SILKSCREEN },
88 { "DOCUMENT", SECTION::DOCUMENTATION },
89 { "GRAPHIC", SECTION::DOCUMENTATION },
90 { "BOARD_OUTLINE", SECTION::DOCUMENTATION },
91 { "REWORK", SECTION::DOCUMENTATION },
92 { "FIXTURE", SECTION::DOCUMENTATION },
93 { "PROBE", SECTION::DOCUMENTATION },
94 { "COURTYARD", SECTION::DOCUMENTATION },
95 { "ASSEMBLY", SECTION::COMPONENTS },
96 { "COATINGCOND", SECTION::MISC_FAB },
97 { "COATINGNONCOND", SECTION::MISC_FAB },
98 { "CONDUCTIVE_ADHESIVE", SECTION::MISC_FAB },
99 { "GLUE", SECTION::MISC_FAB },
100 { "HOLEFILL", SECTION::MISC_FAB },
101 { "SOLDERBUMP", SECTION::MISC_FAB },
102 { "THIEVING_KEEP_INOUT", SECTION::MISC_FAB },
103 { "EDGE_PLATING", SECTION::MISC_FAB },
104 { "STIFFENER", SECTION::MISC_FAB },
105 { "CAPACITIVE", SECTION::MISC_FAB },
106 { "RESISTIVE", SECTION::MISC_FAB },
107} };
108
109
110SECTION_SET sectionsWithRule( MODE aMode, SECTION_RULE aRule )
111{
112 SECTION_SET set;
113
114 for( size_t ii = 0; ii < SECTION_COUNT; ++ii )
115 {
116 SECTION section = static_cast<SECTION>( ii );
117
118 if( SectionRule( aMode, section ) == aRule )
119 set.Set( section );
120 }
121
122 return set;
123}
124
125} // namespace
126
127
128char SectionKeyChar( SECTION aSection )
129{
130 return SECTION_KEYS[static_cast<size_t>( aSection )];
131}
132
133
134std::optional<SECTION> SectionFromKeyChar( char aKey )
135{
136 for( size_t ii = 0; ii < SECTION_COUNT; ++ii )
137 {
138 if( SECTION_KEYS[ii] == aKey )
139 return static_cast<SECTION>( ii );
140 }
141
142 return std::nullopt;
143}
144
145
147{
148 return TABLE_4[static_cast<size_t>( aSection )][static_cast<size_t>( aMode )];
149}
150
151
153{
154 return sectionsWithRule( aMode, SECTION_RULE::REQUIRED );
155}
156
157
159{
160 return sectionsWithRule( aMode, SECTION_RULE::RULE_OPTIONAL );
161}
162
163
165{
166 switch( aMode )
167 {
168 // This set keeps an unconfigured export the same as before
169 case MODE::USERDEF:
170 return LegacySections();
171
172 case MODE::STACKUP:
173 return { SECTION::DIELECTRIC };
174
175 // Section 4.1.3.3 gives KSUMLROIEF as its FABRICATION example
178
179 case MODE::ASSEMBLY:
181
182 default:
183 return {};
184 }
185}
186
187
189{
190 return { SECTION::DFX };
191}
192
193
195{
196 SECTION_SET set;
197 set.set();
198
199 // We didn't emit these previously, so maintain the same output
200 // until/unless this is set by user
201 set.Set( SECTION::LOGICAL_NET, false );
202 set.Set( SECTION::PHYSICAL_NET, false );
203
204 return set & ~UnsupportedSections();
205}
206
207
208bool NeedsCadData( const SECTION_SET& aSet )
209{
210 SECTION_SET inCadData = aSet;
211 inCadData.Set( SECTION::BOM_AVL, false );
212
213 return inCadData.any();
214}
215
216
217wxString SectionKeyString( const SECTION_SET& aSet )
218{
219 wxString key;
220
221 for( size_t ii = 0; ii < SECTION_COUNT; ++ii )
222 {
223 if( aSet.test( ii ) )
224 key << SECTION_KEYS[ii];
225 }
226
227 return key;
228}
229
230
231bool SectionSetFromKeyString( const wxString& aKey, SECTION_SET& aResult )
232{
233 SECTION_SET set;
234
235 for( wxUniChar ch : aKey )
236 {
237 if( !ch.IsAscii() )
238 return false;
239
240 std::optional<SECTION> section = SectionFromKeyChar( static_cast<char>( ch ) );
241
242 if( !section )
243 return false;
244
245 set.Set( *section );
246 }
247
248 aResult = set;
249 return true;
250}
251
252
253wxString ModeToken( MODE aMode )
254{
255 return wxString::FromAscii( MODE_TOKENS[static_cast<size_t>( aMode )] );
256}
257
258
259std::optional<MODE> ModeFromToken( const wxString& aToken )
260{
261 // wxString::Upper() uses the locale
262 // In tr_TR it changes i to a dotted capital and no lowercase name agrees
263 wxString token;
264
265 for( wxUniChar ch : aToken )
266 {
267 if( ch >= 'a' && ch <= 'z' )
268 token << static_cast<wxChar>( ch - 'a' + 'A' );
269 else
270 token << ch;
271 }
272
273 for( size_t ii = 0; ii < MODE_COUNT; ++ii )
274 {
275 if( token == wxString::FromAscii( MODE_TOKENS[ii] ) )
276 return static_cast<MODE>( ii );
277 }
278
279 return std::nullopt;
280}
281
282
284{
285 // Revision B has no DFX function mode and KiCad writes no Dfx element
286 return aMode != MODE::DFX;
287}
288
289
290std::optional<SECTION> SectionForBoardLayer( PCB_LAYER_ID aLayer )
291{
292 if( IsCopperLayer( aLayer ) )
294
295 switch( aLayer )
296 {
297 case F_Adhes:
298 case B_Adhes:
299 return SECTION::MISC_FAB;
300
301 case F_Paste:
302 case B_Paste:
304
305 case F_SilkS:
306 case B_SilkS:
307 return SECTION::SILKSCREEN;
308
309 case F_Mask:
310 case B_Mask:
311 return SECTION::SOLDERMASK;
312
313 case Edge_Cuts:
314 return SECTION::PROFILE;
315
316 case F_CrtYd:
317 case B_CrtYd:
318 case Margin:
320
321 // Section 4.1.1.4 puts layerFunction ASSEMBLY in Component Assembly Data
322 case F_Fab:
323 case B_Fab:
324 return SECTION::COMPONENTS;
325
326 default:
327 if( IsUserLayer( aLayer ) )
329
330 return std::nullopt;
331 }
332}
333
334
335std::optional<SECTION> SectionForLayerFunction( const wxString& aLayerFunction,
336 const wxString& aSide )
337{
338 for( const auto& entry : LAYER_FUNCTION_SECTIONS )
339 {
340 if( aLayerFunction == wxString::FromAscii( entry.first ) )
341 return entry.second;
342 }
343
344 if( aLayerFunction.StartsWith( wxT( "DIEL" ) ) )
345 return SECTION::DIELECTRIC;
346
347 // Revision B writes EMBEDDED_COMPONENT and revision C writes COMPONENT_EMBEDDED
348 // because why not?
349 if( aLayerFunction.StartsWith( wxT( "COMPONENT" ) )
350 || aLayerFunction == wxT( "EMBEDDED_COMPONENT" ) )
351 {
352 return SECTION::COMPONENTS;
353 }
354
355 // Sections 4.1.1.12 and 4.1.1.13 divide the conductor functions by side
356 static const std::array<const char*, 6> conductors = { "CONDUCTOR", "CONDFILM", "CONDFOIL",
357 "PLANE", "SIGNAL", "MIXED" };
358
359 for( const char* function : conductors )
360 {
361 if( aLayerFunction == wxString::FromAscii( function ) )
362 {
363 if( aSide == wxT( "INTERNAL" ) )
365
366 return aSide.IsEmpty() ? std::nullopt
367 : std::optional<SECTION>( SECTION::OUTER_COPPER );
368 }
369 }
370
371 return std::nullopt;
372}
373
374
375RESOLVE_RESULT ResolveSections( REVISION aRevision, MODE aMode, const SECTION_SET& aRequested )
376{
378
379 if( !ModeSupported( aMode, aRevision ) )
380 {
381 result.m_conflicts.push_back( { std::nullopt, std::nullopt, _( "Cannot produce this IPC-2581 data set." ) } );
382 return result;
383 }
384
385 // A requested section stays only if the function mode makes it optional
386 result.m_included = ( ( aRequested & OptionalSections( aMode ) ) | RequiredSections( aMode ) )
388
389 if( result.m_included.none() )
390 {
391 result.m_conflicts.push_back( { std::nullopt, std::nullopt, _( "No IPC-2581 content selected." ) } );
392 return result;
393 }
394
395 const bool haveBom = result.m_included.Contains( SECTION::BOM_AVL );
396 const bool havePackages = result.m_included.Contains( SECTION::PACKAGES );
397 const bool haveComponents = result.m_included.Contains( SECTION::COMPONENTS );
398 const bool havePadstacks = result.m_included.Contains( SECTION::PADSTACKS );
399 const bool haveOuterCopper = result.m_included.Contains( SECTION::OUTER_COPPER );
400
401 // Component@layerRef is mandatory and names the copper layer of the footprint
402 // You cannot remove it and it must not dangle
403 if( haveComponents && !haveOuterCopper )
404 {
405 result.m_conflicts.push_back(
407 _( "Cannot export assembly data without outer copper layers." ) } );
408 }
409
410 // Each physical net point names an outer copper layer
411 if( result.m_included.Contains( SECTION::PHYSICAL_NET ) && !haveOuterCopper )
412 {
413 result.m_conflicts.push_back(
415 _( "Cannot export physical netlist without outer copper layers." ) } );
416 }
417
418 if( !havePadstacks )
419 result.m_suppressions.Set( SUPPRESS::PAD_PADSTACKDEFREF );
420
421 // No keyref selects RefDes@packageRef in the two revisions
422 // Remove it to prevent a dangling name
423 if( haveBom && !havePackages )
424 result.m_suppressions.Set( SUPPRESS::BOM_REFDES_PACKAGEREF );
425
426 // RefDes@layerRef names an outer copper layer and revision C puts it in layerKeyRef
427 if( haveBom && !haveOuterCopper )
428 result.m_suppressions.Set( SUPPRESS::BOM_REFDES_LAYERREF );
429
430 if( aRevision == REVISION::C )
431 {
432 if( !haveBom )
433 {
434 result.m_suppressions.Set( SUPPRESS::COMPONENT_REFDES );
435 result.m_suppressions.Set( SUPPRESS::PINREF_COMPONENTREF );
436 }
437
438 if( !havePackages )
439 result.m_suppressions.Set( SUPPRESS::COMPONENT_PACKAGEREF );
440 }
441 else
442 {
443 // Can't remove Component@packageRef in RevB
444 if( haveComponents && !havePackages )
445 {
446 result.m_conflicts.push_back(
448 _( "Component assembly data cannot be exported without component "
449 "packages in IPC-2581B." ) } );
450 }
451
452 // We need component for RefDes and we store this in PinRef
453 if( !haveComponents )
454 {
455 result.m_suppressions.Set( SUPPRESS::PINREF_COMPONENTREF );
456
457 if( haveBom )
458 result.m_suppressions.Set( SUPPRESS::BOM_REFDES );
459 }
460 }
461
462 if( !NeedsCadData( result.m_included ) )
463 result.m_suppressions.Set( SUPPRESS::BOM_HEADER_STEPREF );
464
465 return result;
466}
467
468} // namespace IPC2581
Set of schema sections.
SECTION_SET & Set(SECTION aSection, bool aOn=true)
#define _(s)
bool IsUserLayer(PCB_LAYER_ID aLayerId)
Test whether a layer is a non copper and a non tech layer.
Definition layer_ids.h:785
bool IsCopperLayer(int aLayerId)
Test whether a layer is a copper layer.
Definition layer_ids.h:703
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:714
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ B_Adhes
Definition layer_ids.h:99
@ Edge_Cuts
Definition layer_ids.h:108
@ F_Paste
Definition layer_ids.h:100
@ F_Adhes
Definition layer_ids.h:98
@ B_Mask
Definition layer_ids.h:94
@ F_Mask
Definition layer_ids.h:93
@ B_Paste
Definition layer_ids.h:101
@ F_Fab
Definition layer_ids.h:115
@ Margin
Definition layer_ids.h:109
@ F_SilkS
Definition layer_ids.h:96
@ B_CrtYd
Definition layer_ids.h:111
@ B_SilkS
Definition layer_ids.h:97
@ B_Fab
Definition layer_ids.h:114
MODE
Columns of Table 4.
SECTION_SET RecommendedOptionalSections(MODE aMode)
Optional schema sections that aMode selects by default.
wxString ModeToken(MODE aMode)
Table 4 function mode token such as FABRICATION.
SECTION_SET UnsupportedSections()
Schema sections that no KiCad board supplies.
bool SectionSetFromKeyString(const wxString &aKey, SECTION_SET &aResult)
Read a sectionKey attribute value Return false for an unknown character.
std::optional< SECTION > SectionFromKeyChar(char aKey)
Return the schema section for a Table 4 key character.
SECTION_SET RequiredSections(MODE aMode)
Schema sections that Table 4 marks Y for aMode.
SECTION_SET LegacySections()
Schema sections of the content that KiCad wrote before the function modes.
SECTION_RULE
Table 4 cell values N O and Y.
SECTION
Schema sections of the IPC-2581C function mode table, Table 4 p 80.
RESOLVE_RESULT ResolveSections(REVISION aRevision, MODE aMode, const SECTION_SET &aRequested)
Compare aRequested with Table 4 and give the attributes to remove and the conflicts.
char SectionKeyChar(SECTION aSection)
Return the Table 4 key character for aSection.
REVISION
Schema revision The two revisions declare different identity constraints.
std::optional< SECTION > SectionForLayerFunction(const wxString &aLayerFunction, const wxString &aSide)
Schema section that holds the layers with aLayerFunction.
bool NeedsCadData(const SECTION_SET &aSet)
True if an included schema section needs an Ecad/CadData element.
bool ModeSupported(MODE aMode, REVISION)
True if KiCad can write aMode for aRevision.
@ PINREF_COMPONENTREF
Pad/PinRef@componentRef and LogicalNet/PinRef@componentRef.
@ BOM_HEADER_STEPREF
Bom/BomHeader/StepRef.
@ COMPONENT_REFDES
Component@refDes.
@ PAD_PADSTACKDEFREF
Pad@padstackDefRef, and the PadStackDef elements themselves.
@ COMPONENT_PACKAGEREF
Component@packageRef.
@ BOM_REFDES_PACKAGEREF
Bom/BomItem/RefDes@packageRef.
@ BOM_REFDES
Bom/BomItem/RefDes and all its attributes.
@ BOM_REFDES_LAYERREF
Bom/BomItem/RefDes@layerRef.
wxString SectionKeyString(const SECTION_SET &aSet)
Give aSet as a sectionKey attribute value.
SECTION_RULE SectionRule(MODE aMode, SECTION aSection)
Return the Table 4 cell for aMode and aSection.
SECTION_SET OptionalSections(MODE aMode)
Schema sections that Table 4 marks O for aMode.
std::optional< MODE > ModeFromToken(const wxString &aToken)
Read a Table 4 function mode token in upper case or in lower case.
std::optional< SECTION > SectionForBoardLayer(PCB_LAYER_ID aLayer)
Schema section that holds the artwork of aLayer.
wxString result
Test unit parsing edge cases and error handling.