KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pads_common.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
20#include <io/pads/pads_common.h>
21
22#include <cstdint>
23#include <sstream>
24#include <iomanip>
25#include <fstream>
26#include <exception>
27#include <algorithm>
28#include <cmath>
29#include <limits>
30
31
32#include <wx/dir.h>
33#include <wx/filename.h>
34#include <wx/log.h>
35
36namespace PADS_COMMON
37{
38
39KIID GenerateDeterministicUuid( const std::string& aIdentifier )
40{
41 // 64-bit FNV-1a parameters.
42 const uint64_t FNV_PRIME = 0x00000100000001B3ULL;
43 const uint64_t FNV_OFFSET = 0xcbf29ce484222325ULL;
44
45 // Hash the identifier twice with different salts to fill 128 bits.
46 uint64_t hash1 = FNV_OFFSET;
47 uint64_t hash2 = FNV_OFFSET;
48
49 std::string salt1 = "PADS1:" + aIdentifier;
50
51 for( char c : salt1 )
52 {
53 hash1 ^= static_cast<uint8_t>( c );
54 hash1 *= FNV_PRIME;
55 }
56
57 std::string salt2 = "PADS2:" + aIdentifier;
58
59 for( char c : salt2 )
60 {
61 hash2 ^= static_cast<uint8_t>( c );
62 hash2 *= FNV_PRIME;
63 }
64
65 // Emit a version-4 UUID (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx) from the hashes.
66 std::ostringstream ss;
67 ss << std::hex << std::setfill( '0' );
68
69 ss << std::setw( 8 ) << ( hash1 >> 32 );
70 ss << '-';
71
72 ss << std::setw( 4 ) << ( ( hash1 >> 16 ) & 0xFFFF );
73 ss << '-';
74
75 // Force the version-4 nibble.
76 uint16_t version = ( ( hash1 & 0xFFFF ) & 0x0FFF ) | 0x4000;
77 ss << std::setw( 4 ) << version;
78 ss << '-';
79
80 // Force the RFC 4122 10xx variant bits.
81 uint16_t variant = ( ( hash2 >> 48 ) & 0x3FFF ) | 0x8000;
82 ss << std::setw( 4 ) << variant;
83 ss << '-';
84
85 ss << std::setw( 12 ) << ( hash2 & 0xFFFFFFFFFFFFULL );
86
87 return KIID( ss.str() );
88}
89
90
91PADS_FILE_TYPE DetectPadsFileType( const wxString& aFilePath )
92{
93 std::ifstream file( aFilePath.fn_str() );
94
95 if( !file.is_open() )
97
98 std::string line;
99
100 if( !std::getline( file, line ) )
102
103 // The header tag may carry a version and code page suffix, so match the
104 // prefix only and anchor at position 0 to require it at the line start.
105 if( line.rfind( "*PADS-POWERPCB", 0 ) == 0
106 || line.rfind( "!PADS-POWERPCB", 0 ) == 0 )
108
109 if( line.rfind( "*PADS2000", 0 ) == 0
110 || line.rfind( "!PADS2000", 0 ) == 0 )
112
113 if( line.rfind( "*PADS-POWERLOGIC", 0 ) == 0
114 || line.rfind( "!PADS-POWERLOGIC", 0 ) == 0 )
116
117 if( line.rfind( "*PADS-LOGIC", 0 ) == 0
118 || line.rfind( "!PADS-LOGIC", 0 ) == 0 )
120
122}
123
124
125RELATED_FILES FindRelatedPadsFiles( const wxString& aFilePath )
126{
128
129 wxFileName sourceFn( aFilePath );
130
131 if( !sourceFn.IsOk() || !sourceFn.FileExists() )
132 return result;
133
134 PADS_FILE_TYPE sourceType = DetectPadsFileType( aFilePath );
135
136 if( sourceType == PADS_FILE_TYPE::UNKNOWN )
137 return result;
138
139 if( sourceType == PADS_FILE_TYPE::PCB_ASCII )
140 result.pcbFile = aFilePath;
141 else if( sourceType == PADS_FILE_TYPE::SCHEMATIC_ASCII )
142 result.schematicFile = aFilePath;
143
144 wxString sourceDir = sourceFn.GetPath();
145 wxString sourceBase = sourceFn.GetName();
146
147 wxDir dir( sourceDir );
148
149 if( !dir.IsOpened() )
150 return result;
151
152 static const std::vector<wxString> extensions = { wxS( "asc" ), wxS( "ASC" ), wxS( "txt" ),
153 wxS( "TXT" ) };
154
155 wxString filename;
156 bool cont = dir.GetFirst( &filename );
157
158 while( cont )
159 {
160 wxFileName candidateFn( sourceDir, filename );
161 wxString candidatePath = candidateFn.GetFullPath();
162
163 if( candidatePath == aFilePath )
164 {
165 cont = dir.GetNext( &filename );
166 continue;
167 }
168
169 wxString ext = candidateFn.GetExt().Lower();
170 bool validExt = false;
171
172 for( const wxString& validExtension : extensions )
173 {
174 if( ext == validExtension.Lower() )
175 {
176 validExt = true;
177 break;
178 }
179 }
180
181 if( !validExt )
182 {
183 cont = dir.GetNext( &filename );
184 continue;
185 }
186
187 bool matchingBase = ( candidateFn.GetName() == sourceBase );
188
189 PADS_FILE_TYPE candidateType = DetectPadsFileType( candidatePath );
190
191 if( sourceType == PADS_FILE_TYPE::PCB_ASCII
192 && candidateType == PADS_FILE_TYPE::SCHEMATIC_ASCII )
193 {
194 if( matchingBase || result.schematicFile.IsEmpty() )
195 {
196 result.schematicFile = candidatePath;
197
198 // A matching base name is the best possible match, so stop here.
199 if( matchingBase )
200 {
201 cont = dir.GetNext( &filename );
202 continue;
203 }
204 }
205 }
206 else if( sourceType == PADS_FILE_TYPE::SCHEMATIC_ASCII
207 && candidateType == PADS_FILE_TYPE::PCB_ASCII )
208 {
209 if( matchingBase || result.pcbFile.IsEmpty() )
210 {
211 result.pcbFile = candidatePath;
212
213 // A matching base name is the best possible match, so stop here.
214 if( matchingBase )
215 {
216 cont = dir.GetNext( &filename );
217 continue;
218 }
219 }
220 }
221
222 cont = dir.GetNext( &filename );
223 }
224
225 return result;
226}
227
228int ParseInt( const std::string& aStr, int aDefault, const std::string& aContext )
229{
230 try
231 {
232 return std::stoi( aStr );
233 }
234 catch( const std::exception& )
235 {
236 if( !aContext.empty() )
237 {
238 wxLogTrace( wxT( "PADS" ), wxT( "Parse error in %s: '%s' is not a valid integer" ),
239 wxString::FromUTF8( aContext ), wxString::FromUTF8( aStr ) );
240 }
241
242 return aDefault;
243 }
244}
245
246
247double ParseDouble( const std::string& aStr, double aDefault, const std::string& aContext )
248{
249 try
250 {
251 return std::stod( aStr );
252 }
253 catch( const std::exception& )
254 {
255 if( !aContext.empty() )
256 {
257 wxLogTrace( wxT( "PADS" ), wxT( "Parse error in %s: '%s' is not a valid number" ),
258 wxString::FromUTF8( aContext ), wxString::FromUTF8( aStr ) );
259 }
260
261 return aDefault;
262 }
263}
264
265
266wxString ConvertInvertedNetName( const std::string& aNetName )
267{
268 if( aNetName.empty() )
269 return wxString();
270
271 if( aNetName[0] == '/' )
272 return wxT( "~{" ) + ConvertText( aNetName.substr( 1 ) ) + wxT( "}" );
273
274 return ConvertText( aNetName );
275}
276
277
278wxString ConvertText( const std::string& aText )
279{
280 if( aText.empty() )
281 return wxString();
282
283 // PADS ASCII uses an 8-bit codepage, and a UTF-8 conversion drops the whole
284 // string on the first high byte. Length-aware to keep embedded NULs.
285 wxString result = wxString::FromUTF8( aText.data(), aText.size() );
286
287 if( result.IsEmpty() )
288 {
289 wxCSConv cp1252( wxFONTENCODING_CP1252 );
290 result = wxString( aText.data(), cp1252, aText.size() );
291
292 // Latin-1 maps every byte one-to-one when CP1252 has no mapping.
293 if( result.IsEmpty() )
294 result = wxString( aText.data(), wxConvISO8859_1, aText.size() );
295 }
296
297 return result;
298}
299
300
302{
303 int8_t s = static_cast<int8_t>( aPadsStyle & 0xFF );
304
305 switch( s )
306 {
307 case 0: return LINE_STYLE::DASH;
308 case 1: return LINE_STYLE::SOLID;
309 case -1: return LINE_STYLE::SOLID;
310 case -2: return LINE_STYLE::DASH;
311 case -3: return LINE_STYLE::DOT;
312 case -4: return LINE_STYLE::DASHDOT;
313 case -5: return LINE_STYLE::DASHDOTDOT;
314 default: return LINE_STYLE::SOLID;
315 }
316}
317
318
319void DecodeJustification( int aJustification, GR_TEXT_H_ALIGN_T& aHJustify,
320 GR_TEXT_V_ALIGN_T& aVJustify )
321{
322 int hCode = 0;
323 int vGroup = 0;
324
325 if( aJustification >= 8 )
326 {
327 vGroup = 2; // middle
328 hCode = aJustification - 8;
329 }
330 else if( aJustification >= 2 )
331 {
332 vGroup = 1; // top
333 hCode = aJustification - 2;
334 }
335 else
336 {
337 vGroup = 0; // bottom
338 hCode = aJustification;
339 }
340
341 switch( hCode )
342 {
343 default:
344 case 0: aHJustify = GR_TEXT_H_ALIGN_LEFT; break;
345 case 1: aHJustify = GR_TEXT_H_ALIGN_RIGHT; break;
346 case 4: aHJustify = GR_TEXT_H_ALIGN_CENTER; break;
347 }
348
349 switch( vGroup )
350 {
351 default:
352 case 0: aVJustify = GR_TEXT_V_ALIGN_BOTTOM; break;
353 case 1: aVJustify = GR_TEXT_V_ALIGN_TOP; break;
354 case 2: aVJustify = GR_TEXT_V_ALIGN_CENTER; break;
355 }
356}
357
358
359int PadsScaleCoord( double aVal, bool aIsX, double aOriginX, double aOriginY, double aScaleFactor )
360{
361 double origin = aIsX ? aOriginX : aOriginY;
362
363 long long origin_nm = static_cast<long long>( std::round( origin * aScaleFactor ) );
364 long long val_nm = static_cast<long long>( std::round( aVal * aScaleFactor ) );
365
366 long long result = aIsX ? ( val_nm - origin_nm ) : ( origin_nm - val_nm );
367
368 return static_cast<int>( std::clamp<long long>( result, std::numeric_limits<int>::min(),
369 std::numeric_limits<int>::max() ) );
370}
371
372} // namespace PADS_COMMON
Definition kiid.h:46
Common utilities and types for parsing PADS file formats, shared by the PCB and schematic importers.
void DecodeJustification(int aJustification, GR_TEXT_H_ALIGN_T &aHJustify, GR_TEXT_V_ALIGN_T &aVJustify)
Decode a PADS text justification code into KiCad horizontal and vertical alignment.
int PadsScaleCoord(double aVal, bool aIsX, double aOriginX, double aOriginY, double aScaleFactor)
Map a PADS board coordinate to a KiCad internal coordinate.
RELATED_FILES FindRelatedPadsFiles(const wxString &aFilePath)
Find the related PADS project file (schematic for a PCB source, or vice versa) in the same directory,...
int ParseInt(const std::string &aStr, int aDefault, const std::string &aContext)
Parse integer from string with error context.
wxString ConvertText(const std::string &aText)
Decode text from a PADS file, which uses an 8-bit codepage rather than UTF-8.
PADS_FILE_TYPE DetectPadsFileType(const wxString &aFilePath)
Detect the type of a PADS file by examining its header.
wxString ConvertInvertedNetName(const std::string &aNetName)
Convert a PADS net name to KiCad notation.
LINE_STYLE PadsLineStyleToKiCad(int aPadsStyle)
Convert a PADS line style integer to a KiCad LINE_STYLE enum value.
KIID GenerateDeterministicUuid(const std::string &aIdentifier)
Generate a deterministic KIID from a PADS component identifier.
double ParseDouble(const std::string &aStr, double aDefault, const std::string &aContext)
Parse double from string with error context.
LINE_STYLE
Dashed line types.
wxString result
Test unit parsing edge cases and error handling.
GR_TEXT_H_ALIGN_T
This is API surface mapped to common.types.HorizontalAlignment.
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_H_ALIGN_RIGHT
@ GR_TEXT_H_ALIGN_LEFT
GR_TEXT_V_ALIGN_T
This is API surface mapped to common.types.VertialAlignment.
@ GR_TEXT_V_ALIGN_BOTTOM
@ GR_TEXT_V_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_TOP