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 along
17 * with this program. If not, see <http://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
28
29#include <wx/dir.h>
30#include <wx/filename.h>
31#include <wx/log.h>
32
33namespace PADS_COMMON
34{
35
36KIID GenerateDeterministicUuid( const std::string& aIdentifier )
37{
38 // Use FNV-1a hash algorithm to generate a 128-bit hash from the identifier string.
39 // This produces deterministic output for the same input, enabling cross-probe
40 // linking between schematic and PCB imports.
41
42 // FNV-1a parameters for 64-bit
43 const uint64_t FNV_PRIME = 0x00000100000001B3ULL;
44 const uint64_t FNV_OFFSET = 0xcbf29ce484222325ULL;
45
46 // Hash the identifier twice with different salts to get 128 bits
47 uint64_t hash1 = FNV_OFFSET;
48 uint64_t hash2 = FNV_OFFSET;
49
50 // First hash with "PADS1:" prefix
51 std::string salt1 = "PADS1:" + aIdentifier;
52
53 for( char c : salt1 )
54 {
55 hash1 ^= static_cast<uint8_t>( c );
56 hash1 *= FNV_PRIME;
57 }
58
59 // Second hash with "PADS2:" prefix
60 std::string salt2 = "PADS2:" + aIdentifier;
61
62 for( char c : salt2 )
63 {
64 hash2 ^= static_cast<uint8_t>( c );
65 hash2 *= FNV_PRIME;
66 }
67
68 // Format as UUID string (xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
69 // Version 4 UUID format with deterministic values
70 std::ostringstream ss;
71 ss << std::hex << std::setfill( '0' );
72
73 // First 8 hex chars from hash1
74 ss << std::setw( 8 ) << ( hash1 >> 32 );
75 ss << '-';
76
77 // Next 4 hex chars from hash1
78 ss << std::setw( 4 ) << ( ( hash1 >> 16 ) & 0xFFFF );
79 ss << '-';
80
81 // Version 4 UUID: set version bits
82 uint16_t version = ( ( hash1 & 0xFFFF ) & 0x0FFF ) | 0x4000;
83 ss << std::setw( 4 ) << version;
84 ss << '-';
85
86 // Variant bits: set to 10xx (RFC 4122 variant)
87 uint16_t variant = ( ( hash2 >> 48 ) & 0x3FFF ) | 0x8000;
88 ss << std::setw( 4 ) << variant;
89 ss << '-';
90
91 // Last 12 hex chars from hash2
92 ss << std::setw( 12 ) << ( hash2 & 0xFFFFFFFFFFFFULL );
93
94 return KIID( ss.str() );
95}
96
97
98PADS_FILE_TYPE DetectPadsFileType( const wxString& aFilePath )
99{
100 std::ifstream file( aFilePath.fn_str() );
101
102 if( !file.is_open() )
104
105 std::string line;
106
107 if( !std::getline( file, line ) )
109
110 // PADS PowerPCB (PCB) files start with *PADS-POWERPCB or !PADS-POWERPCB,
111 // optionally followed by a version and code page suffix before the
112 // closing asterisk.
113 if( line.rfind( "*PADS-POWERPCB", 0 ) == 0
114 || line.rfind( "!PADS-POWERPCB", 0 ) == 0 )
116
117 // PADS Router PCB files start with *PADS2000 or !PADS2000, with an
118 // optional version suffix.
119 if( line.rfind( "*PADS2000", 0 ) == 0
120 || line.rfind( "!PADS2000", 0 ) == 0 )
122
123 // PADS Logic schematic files start with *PADS-POWERLOGIC or !PADS-POWERLOGIC.
124 // The header tag may include a version and code page suffix before the
125 // closing asterisk (e.g. *PADS-POWERLOGIC-V9.5* or *PADS-LOGIC-V9.0-CP1250*),
126 // so match the prefix only. Anchor at position 0 to avoid matching the
127 // token anywhere other than the start of the header line.
128 if( line.rfind( "*PADS-POWERLOGIC", 0 ) == 0
129 || line.rfind( "!PADS-POWERLOGIC", 0 ) == 0 )
131
132 if( line.rfind( "*PADS-LOGIC", 0 ) == 0
133 || line.rfind( "!PADS-LOGIC", 0 ) == 0 )
135
137}
138
139
140RELATED_FILES FindRelatedPadsFiles( const wxString& aFilePath )
141{
143
144 wxFileName sourceFn( aFilePath );
145
146 if( !sourceFn.IsOk() || !sourceFn.FileExists() )
147 return result;
148
149 PADS_FILE_TYPE sourceType = DetectPadsFileType( aFilePath );
150
151 if( sourceType == PADS_FILE_TYPE::UNKNOWN )
152 return result;
153
154 // Set the source file in the appropriate field
155 if( sourceType == PADS_FILE_TYPE::PCB_ASCII )
156 result.pcbFile = aFilePath;
157 else if( sourceType == PADS_FILE_TYPE::SCHEMATIC_ASCII )
158 result.schematicFile = aFilePath;
159
160 wxString sourceDir = sourceFn.GetPath();
161 wxString sourceBase = sourceFn.GetName();
162
163 // Get list of potential related files in the same directory
164 wxDir dir( sourceDir );
165
166 if( !dir.IsOpened() )
167 return result;
168
169 // Common PADS file extensions to check
170 static const std::vector<wxString> extensions = { wxS( "asc" ), wxS( "ASC" ), wxS( "txt" ),
171 wxS( "TXT" ) };
172
173 wxString filename;
174 bool cont = dir.GetFirst( &filename );
175
176 while( cont )
177 {
178 wxFileName candidateFn( sourceDir, filename );
179 wxString candidatePath = candidateFn.GetFullPath();
180
181 // Skip the source file itself
182 if( candidatePath == aFilePath )
183 {
184 cont = dir.GetNext( &filename );
185 continue;
186 }
187
188 // Check if this file matches any of our extensions
189 wxString ext = candidateFn.GetExt().Lower();
190 bool validExt = false;
191
192 for( const wxString& validExtension : extensions )
193 {
194 if( ext == validExtension.Lower() )
195 {
196 validExt = true;
197 break;
198 }
199 }
200
201 if( !validExt )
202 {
203 cont = dir.GetNext( &filename );
204 continue;
205 }
206
207 // Prioritize files with matching base names
208 bool matchingBase = ( candidateFn.GetName() == sourceBase );
209
210 PADS_FILE_TYPE candidateType = DetectPadsFileType( candidatePath );
211
212 // If we imported PCB, look for schematic
213 if( sourceType == PADS_FILE_TYPE::PCB_ASCII
214 && candidateType == PADS_FILE_TYPE::SCHEMATIC_ASCII )
215 {
216 if( matchingBase || result.schematicFile.IsEmpty() )
217 {
218 result.schematicFile = candidatePath;
219
220 // If we found a matching base name, stop looking
221 if( matchingBase )
222 {
223 cont = dir.GetNext( &filename );
224 continue;
225 }
226 }
227 }
228 // If we imported schematic, look for PCB
229 else if( sourceType == PADS_FILE_TYPE::SCHEMATIC_ASCII
230 && candidateType == PADS_FILE_TYPE::PCB_ASCII )
231 {
232 if( matchingBase || result.pcbFile.IsEmpty() )
233 {
234 result.pcbFile = candidatePath;
235
236 // If we found a matching base name, stop looking
237 if( matchingBase )
238 {
239 cont = dir.GetNext( &filename );
240 continue;
241 }
242 }
243 }
244
245 cont = dir.GetNext( &filename );
246 }
247
248 return result;
249}
250
251int ParseInt( const std::string& aStr, int aDefault, const std::string& aContext )
252{
253 try
254 {
255 return std::stoi( aStr );
256 }
257 catch( const std::exception& )
258 {
259 if( !aContext.empty() )
260 {
261 wxLogTrace( wxT( "PADS" ), wxT( "Parse error in %s: '%s' is not a valid integer" ),
262 wxString::FromUTF8( aContext ), wxString::FromUTF8( aStr ) );
263 }
264
265 return aDefault;
266 }
267}
268
269
270double ParseDouble( const std::string& aStr, double aDefault, const std::string& aContext )
271{
272 try
273 {
274 return std::stod( aStr );
275 }
276 catch( const std::exception& )
277 {
278 if( !aContext.empty() )
279 {
280 wxLogTrace( wxT( "PADS" ), wxT( "Parse error in %s: '%s' is not a valid number" ),
281 wxString::FromUTF8( aContext ), wxString::FromUTF8( aStr ) );
282 }
283
284 return aDefault;
285 }
286}
287
288
289wxString ConvertInvertedNetName( const std::string& aNetName )
290{
291 if( aNetName.empty() )
292 return wxString();
293
294 if( aNetName[0] == '/' )
295 return wxT( "~{" ) + wxString::FromUTF8( aNetName.substr( 1 ) ) + wxT( "}" );
296
297 return wxString::FromUTF8( aNetName );
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} // namespace PADS_COMMON
Definition kiid.h:48
PADS_FILE_TYPE
Types of PADS files that can be detected.
Definition pads_common.h:62
@ PCB_ASCII
PADS PowerPCB ASCII (.asc)
Definition pads_common.h:64
@ SCHEMATIC_ASCII
PADS Logic ASCII (.asc or .txt)
Definition pads_common.h:65
RELATED_FILES FindRelatedPadsFiles(const wxString &aFilePath)
Find related PADS project files from a given source file.
int ParseInt(const std::string &aStr, int aDefault, const std::string &aContext)
Parse integer from string with error context.
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 format, handling inverted signal 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.
Common utilities and types for parsing PADS file formats.
LINE_STYLE
Dashed line types.
Result of detecting related PADS project files.
Definition pads_common.h:73
wxString result
Test unit parsing edge cases and error handling.