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 if( line.find( "*PADS-POWERPCB*" ) != std::string::npos
112 || line.find( "!PADS-POWERPCB" ) != std::string::npos )
114
115 // PADS Router PCB files start with *PADS2000* or !PADS2000*
116 if( line.find( "*PADS2000*" ) != std::string::npos
117 || line.find( "!PADS2000" ) != std::string::npos )
119
120 // PADS Logic schematic files start with *PADS-POWERLOGIC* or !PADS-POWERLOGIC*
121 if( line.find( "*PADS-POWERLOGIC*" ) != std::string::npos
122 || line.find( "!PADS-POWERLOGIC" ) != std::string::npos )
124
125 if( line.find( "*PADS-LOGIC*" ) != std::string::npos
126 || line.find( "!PADS-LOGIC" ) != std::string::npos )
128
130}
131
132
133RELATED_FILES FindRelatedPadsFiles( const wxString& aFilePath )
134{
136
137 wxFileName sourceFn( aFilePath );
138
139 if( !sourceFn.IsOk() || !sourceFn.FileExists() )
140 return result;
141
142 PADS_FILE_TYPE sourceType = DetectPadsFileType( aFilePath );
143
144 if( sourceType == PADS_FILE_TYPE::UNKNOWN )
145 return result;
146
147 // Set the source file in the appropriate field
148 if( sourceType == PADS_FILE_TYPE::PCB_ASCII )
149 result.pcbFile = aFilePath;
150 else if( sourceType == PADS_FILE_TYPE::SCHEMATIC_ASCII )
151 result.schematicFile = aFilePath;
152
153 wxString sourceDir = sourceFn.GetPath();
154 wxString sourceBase = sourceFn.GetName();
155
156 // Get list of potential related files in the same directory
157 wxDir dir( sourceDir );
158
159 if( !dir.IsOpened() )
160 return result;
161
162 // Common PADS file extensions to check
163 static const std::vector<wxString> extensions = { wxS( "asc" ), wxS( "ASC" ), wxS( "txt" ),
164 wxS( "TXT" ) };
165
166 wxString filename;
167 bool cont = dir.GetFirst( &filename );
168
169 while( cont )
170 {
171 wxFileName candidateFn( sourceDir, filename );
172 wxString candidatePath = candidateFn.GetFullPath();
173
174 // Skip the source file itself
175 if( candidatePath == aFilePath )
176 {
177 cont = dir.GetNext( &filename );
178 continue;
179 }
180
181 // Check if this file matches any of our extensions
182 wxString ext = candidateFn.GetExt().Lower();
183 bool validExt = false;
184
185 for( const wxString& validExtension : extensions )
186 {
187 if( ext == validExtension.Lower() )
188 {
189 validExt = true;
190 break;
191 }
192 }
193
194 if( !validExt )
195 {
196 cont = dir.GetNext( &filename );
197 continue;
198 }
199
200 // Prioritize files with matching base names
201 bool matchingBase = ( candidateFn.GetName() == sourceBase );
202
203 PADS_FILE_TYPE candidateType = DetectPadsFileType( candidatePath );
204
205 // If we imported PCB, look for schematic
206 if( sourceType == PADS_FILE_TYPE::PCB_ASCII
207 && candidateType == PADS_FILE_TYPE::SCHEMATIC_ASCII )
208 {
209 if( matchingBase || result.schematicFile.IsEmpty() )
210 {
211 result.schematicFile = candidatePath;
212
213 // If we found a matching base name, stop looking
214 if( matchingBase )
215 {
216 cont = dir.GetNext( &filename );
217 continue;
218 }
219 }
220 }
221 // If we imported schematic, look for PCB
222 else if( sourceType == PADS_FILE_TYPE::SCHEMATIC_ASCII
223 && candidateType == PADS_FILE_TYPE::PCB_ASCII )
224 {
225 if( matchingBase || result.pcbFile.IsEmpty() )
226 {
227 result.pcbFile = candidatePath;
228
229 // If we found a matching base name, stop looking
230 if( matchingBase )
231 {
232 cont = dir.GetNext( &filename );
233 continue;
234 }
235 }
236 }
237
238 cont = dir.GetNext( &filename );
239 }
240
241 return result;
242}
243
244int ParseInt( const std::string& aStr, int aDefault, const std::string& aContext )
245{
246 try
247 {
248 return std::stoi( aStr );
249 }
250 catch( const std::exception& )
251 {
252 if( !aContext.empty() )
253 {
254 wxLogTrace( wxT( "PADS" ), wxT( "Parse error in %s: '%s' is not a valid integer" ),
255 wxString::FromUTF8( aContext ), wxString::FromUTF8( aStr ) );
256 }
257
258 return aDefault;
259 }
260}
261
262
263double ParseDouble( const std::string& aStr, double aDefault, const std::string& aContext )
264{
265 try
266 {
267 return std::stod( aStr );
268 }
269 catch( const std::exception& )
270 {
271 if( !aContext.empty() )
272 {
273 wxLogTrace( wxT( "PADS" ), wxT( "Parse error in %s: '%s' is not a valid number" ),
274 wxString::FromUTF8( aContext ), wxString::FromUTF8( aStr ) );
275 }
276
277 return aDefault;
278 }
279}
280
281
282wxString ConvertInvertedNetName( const std::string& aNetName )
283{
284 if( aNetName.empty() )
285 return wxString();
286
287 if( aNetName[0] == '/' )
288 return wxT( "~{" ) + wxString::FromUTF8( aNetName.substr( 1 ) ) + wxT( "}" );
289
290 return wxString::FromUTF8( aNetName );
291}
292
293
295{
296 int8_t s = static_cast<int8_t>( aPadsStyle & 0xFF );
297
298 switch( s )
299 {
300 case 0: return LINE_STYLE::DASH;
301 case 1: return LINE_STYLE::SOLID;
302 case -1: return LINE_STYLE::SOLID;
303 case -2: return LINE_STYLE::DASH;
304 case -3: return LINE_STYLE::DOT;
305 case -4: return LINE_STYLE::DASHDOT;
306 case -5: return LINE_STYLE::DASHDOTDOT;
307 default: return LINE_STYLE::SOLID;
308 }
309}
310
311} // namespace PADS_COMMON
Definition kiid.h:49
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.