added table export, try to fix escaped expression uuid bug
This commit is contained in:
committed by
Jeff Young
parent
26531bb25d
commit
cdc1c417ff
@@ -115,6 +115,7 @@ wxString ExpandTextVars( const wxString& aSource, const std::function<bool( wxSt
|
||||
else
|
||||
newbuf.append( wxT( "AT{" ) );
|
||||
i++; // Skip the '{'
|
||||
braceDepth++; // Account for the '{' we just skipped
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -317,6 +317,172 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ESERIES_UTILS
|
||||
{
|
||||
private:
|
||||
// E24 series values in 100-999 decade (2 significant figures)
|
||||
static constexpr std::array<uint16_t, 24> s_e24 = {
|
||||
100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300,
|
||||
330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910
|
||||
};
|
||||
|
||||
// E192 series values in 100-999 decade (3 significant figures)
|
||||
static constexpr std::array<uint16_t, 192> s_e192 = {
|
||||
100, 101, 102, 104, 105, 106, 107, 109, 110, 111, 113, 114, 115, 117, 118, 120, 121, 123,
|
||||
124, 126, 127, 129, 130, 132, 133, 135, 137, 138, 140, 142, 143, 145, 147, 149, 150, 152,
|
||||
154, 156, 158, 160, 162, 164, 165, 167, 169, 172, 174, 176, 178, 180, 182, 184, 187, 189,
|
||||
191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 221, 223, 226, 229, 232, 234,
|
||||
237, 240, 243, 246, 249, 252, 255, 258, 261, 264, 267, 271, 274, 277, 280, 284, 287, 291,
|
||||
294, 298, 301, 305, 309, 312, 316, 320, 324, 328, 332, 336, 340, 344, 348, 352, 357, 361,
|
||||
365, 370, 374, 379, 383, 388, 392, 397, 402, 407, 412, 417, 422, 427, 432, 437, 442, 448,
|
||||
453, 459, 464, 470, 475, 481, 487, 493, 499, 505, 511, 517, 523, 530, 536, 542, 549, 556,
|
||||
562, 569, 576, 583, 590, 597, 604, 612, 619, 626, 634, 642, 649, 657, 665, 673, 681, 690,
|
||||
698, 706, 715, 723, 732, 741, 750, 759, 768, 777, 787, 796, 806, 816, 825, 835, 845, 856,
|
||||
866, 876, 887, 898, 909, 920, 931, 942, 953, 965, 976, 988
|
||||
};
|
||||
|
||||
static auto parseSeriesString( const std::string& aSeries ) -> int
|
||||
{
|
||||
if( aSeries == "E3" || aSeries == "e3" )
|
||||
return 3;
|
||||
else if( aSeries == "E6" || aSeries == "e6" )
|
||||
return 6;
|
||||
else if( aSeries == "E12" || aSeries == "e12" )
|
||||
return 12;
|
||||
else if( aSeries == "E24" || aSeries == "e24" )
|
||||
return 24;
|
||||
else if( aSeries == "E48" || aSeries == "e48" )
|
||||
return 48;
|
||||
else if( aSeries == "E96" || aSeries == "e96" )
|
||||
return 96;
|
||||
else if( aSeries == "E192" || aSeries == "e192" )
|
||||
return 192;
|
||||
else
|
||||
return -1; // Invalid series
|
||||
}
|
||||
|
||||
static auto getSeriesValue( int aSeries, size_t aIndex ) -> uint16_t
|
||||
{
|
||||
// E1, E3, E6, E12, E24 are derived from E24
|
||||
if( aSeries <= 24 )
|
||||
{
|
||||
const size_t skipValue = 24 / aSeries;
|
||||
return s_e24[aIndex * skipValue];
|
||||
}
|
||||
// E48, E96, E192 are derived from E192
|
||||
else
|
||||
{
|
||||
const size_t skipValue = 192 / aSeries;
|
||||
return s_e192[aIndex * skipValue];
|
||||
}
|
||||
}
|
||||
|
||||
static auto getSeriesSize( int aSeries ) -> size_t
|
||||
{
|
||||
return static_cast<size_t>( aSeries );
|
||||
}
|
||||
|
||||
public:
|
||||
static auto FindNearest( double aValue, const std::string& aSeries ) -> std::optional<double>
|
||||
{
|
||||
const int series = parseSeriesString( aSeries );
|
||||
if( series < 0 )
|
||||
return std::nullopt;
|
||||
|
||||
if( aValue <= 0.0 )
|
||||
return std::nullopt;
|
||||
|
||||
// Scale value to 100-999 decade
|
||||
const double logValue = std::log10( aValue );
|
||||
const int decade = static_cast<int>( std::floor( logValue ) );
|
||||
const double scaledValue = aValue / std::pow( 10.0, decade );
|
||||
const double normalized = scaledValue * 100.0;
|
||||
|
||||
// Find nearest value in series
|
||||
const size_t seriesSize = getSeriesSize( series );
|
||||
double minDiff = std::numeric_limits<double>::max();
|
||||
uint16_t nearest = 100;
|
||||
|
||||
for( size_t i = 0; i < seriesSize; ++i )
|
||||
{
|
||||
const uint16_t val = getSeriesValue( series, i );
|
||||
const double diff = std::abs( normalized - val );
|
||||
if( diff < minDiff )
|
||||
{
|
||||
minDiff = diff;
|
||||
nearest = val;
|
||||
}
|
||||
}
|
||||
|
||||
// Scale back to original decade
|
||||
return ( nearest / 100.0 ) * std::pow( 10.0, decade );
|
||||
}
|
||||
|
||||
static auto FindUp( double aValue, const std::string& aSeries ) -> std::optional<double>
|
||||
{
|
||||
const int series = parseSeriesString( aSeries );
|
||||
if( series < 0 )
|
||||
return std::nullopt;
|
||||
|
||||
if( aValue <= 0.0 )
|
||||
return std::nullopt;
|
||||
|
||||
// Scale value to 100-999 decade
|
||||
const double logValue = std::log10( aValue );
|
||||
const int decade = static_cast<int>( std::floor( logValue ) );
|
||||
const double scaledValue = aValue / std::pow( 10.0, decade );
|
||||
const double normalized = scaledValue * 100.0;
|
||||
|
||||
// Find next higher value in series
|
||||
const size_t seriesSize = getSeriesSize( series );
|
||||
|
||||
// Check current decade
|
||||
for( size_t i = 0; i < seriesSize; ++i )
|
||||
{
|
||||
const uint16_t val = getSeriesValue( series, i );
|
||||
if( val > normalized )
|
||||
return ( val / 100.0 ) * std::pow( 10.0, decade );
|
||||
}
|
||||
|
||||
// Wrap to next decade
|
||||
const uint16_t firstVal = getSeriesValue( series, 0 );
|
||||
return ( firstVal / 100.0 ) * std::pow( 10.0, decade + 1 );
|
||||
}
|
||||
|
||||
static auto FindDown( double aValue, const std::string& aSeries ) -> std::optional<double>
|
||||
{
|
||||
const int series = parseSeriesString( aSeries );
|
||||
if( series < 0 )
|
||||
return std::nullopt;
|
||||
|
||||
if( aValue <= 0.0 )
|
||||
return std::nullopt;
|
||||
|
||||
// Scale value to 100-999 decade
|
||||
const double logValue = std::log10( aValue );
|
||||
const int decade = static_cast<int>( std::floor( logValue ) );
|
||||
const double scaledValue = aValue / std::pow( 10.0, decade );
|
||||
const double normalized = scaledValue * 100.0;
|
||||
|
||||
// Find next lower value in series
|
||||
const size_t seriesSize = getSeriesSize( series );
|
||||
|
||||
// Check current decade (search backwards)
|
||||
for( int i = seriesSize - 1; i >= 0; --i )
|
||||
{
|
||||
const uint16_t val = getSeriesValue( series, i );
|
||||
if( val < normalized )
|
||||
return ( val / 100.0 ) * std::pow( 10.0, decade );
|
||||
}
|
||||
|
||||
// Wrap to previous decade
|
||||
const uint16_t lastVal = getSeriesValue( series, seriesSize - 1 );
|
||||
return ( lastVal / 100.0 ) * std::pow( 10.0, decade - 1 );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
EVAL_VISITOR::EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector ) :
|
||||
m_variableCallback( std::move( aVariableCallback ) ),
|
||||
m_errors( aErrorCollector ),
|
||||
@@ -571,6 +737,30 @@ auto EVAL_VISITOR::evaluateFunction( const FUNC_DATA& aFunc ) const -> Result<Va
|
||||
return MakeValue<Value>( condition ? argValues[1] : argValues[2] );
|
||||
}
|
||||
|
||||
// E-series functions (handle value as number, series as string)
|
||||
else if( ( name == "enearest" || name == "eup" || name == "edown" ) && argc >= 1 && argc <= 2 )
|
||||
{
|
||||
auto valueResult = VALUE_UTILS::ToDouble( argValues[0] );
|
||||
if( !valueResult )
|
||||
return MakeError<Value>( valueResult.GetError() );
|
||||
|
||||
const auto value = valueResult.GetValue();
|
||||
const auto series = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "E24";
|
||||
std::optional<double> result;
|
||||
|
||||
if( name == "enearest" )
|
||||
result = ESERIES_UTILS::FindNearest( value, series );
|
||||
else if( name == "eup" )
|
||||
result = ESERIES_UTILS::FindUp( value, series );
|
||||
else if( name == "edown" )
|
||||
result = ESERIES_UTILS::FindDown( value, series );
|
||||
|
||||
if( !result )
|
||||
return MakeError<Value>( fmt::format( "Invalid E-series: {}", series ) );
|
||||
|
||||
return MakeValue<Value>( result.value() );
|
||||
}
|
||||
|
||||
// Mathematical functions (return numbers) - convert args to doubles first
|
||||
std::vector<double> numArgs;
|
||||
for( const auto& val : argValues )
|
||||
@@ -616,6 +806,45 @@ auto EVAL_VISITOR::evaluateFunction( const FUNC_DATA& aFunc ) const -> Result<Va
|
||||
const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
|
||||
return MakeValue<Value>( sum / static_cast<double>( argc ) );
|
||||
}
|
||||
else if( name == "shunt" && argc == 2 )
|
||||
{
|
||||
const auto r1 = numArgs[0];
|
||||
const auto r2 = numArgs[1];
|
||||
const auto sum = r1 + r2;
|
||||
|
||||
// Calculate parallel resistance: (r1*r2)/(r1+r2)
|
||||
// If sum is not positive, return 0.0 (handles edge cases like shunt(0,0))
|
||||
if( sum > 0.0 )
|
||||
return MakeValue<Value>( ( r1 * r2 ) / sum );
|
||||
else
|
||||
return MakeValue<Value>( 0.0 );
|
||||
}
|
||||
else if( name == "db" && argc == 1 )
|
||||
{
|
||||
// Power ratio to dB: 10*log10(ratio)
|
||||
if( numArgs[0] <= 0.0 )
|
||||
return MakeError<Value>( "db() argument must be positive" );
|
||||
|
||||
return MakeValue<Value>( 10.0 * std::log10( numArgs[0] ) );
|
||||
}
|
||||
else if( name == "dbv" && argc == 1 )
|
||||
{
|
||||
// Voltage/current ratio to dB: 20*log10(ratio)
|
||||
if( numArgs[0] <= 0.0 )
|
||||
return MakeError<Value>( "dbv() argument must be positive" );
|
||||
|
||||
return MakeValue<Value>( 20.0 * std::log10( numArgs[0] ) );
|
||||
}
|
||||
else if( name == "fromdb" && argc == 1 )
|
||||
{
|
||||
// dB to power ratio: 10^(dB/10)
|
||||
return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 10.0 ) );
|
||||
}
|
||||
else if( name == "fromdbv" && argc == 1 )
|
||||
{
|
||||
// dB to voltage/current ratio: 10^(dB/20)
|
||||
return MakeValue<Value>( std::pow( 10.0, numArgs[0] / 20.0 ) );
|
||||
}
|
||||
|
||||
return MakeError<Value>( fmt::format( "Unknown function: {} with {} arguments", name, argc ) );
|
||||
}
|
||||
|
||||
@@ -568,6 +568,13 @@ TOOL_ACTION ACTIONS::editTable( TOOL_ACTION_ARGS()
|
||||
.FriendlyName( _( "Edit Table..." ) )
|
||||
.Icon( BITMAPS::table_edit ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::exportTableCSV( TOOL_ACTION_ARGS()
|
||||
.Name( "common.TableEditor.exportTableCSV" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.MenuText( _( "Export Table to CSV..." ) )
|
||||
.Tooltip( _( "Export table contents to CSV file with resolved text variables" ) )
|
||||
.Icon( BITMAPS::export_file ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::activatePointEditor( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Control.activatePointEditor" )
|
||||
.ToolbarState( TOOLBAR_STATE::HIDDEN )
|
||||
|
||||
@@ -813,7 +813,9 @@ bool SCH_LABEL_BASE::ResolveTextVar( const SCH_SHEET_PATH* aPath, wxString* toke
|
||||
return true;
|
||||
}
|
||||
|
||||
if( token->Contains( ':' ) )
|
||||
// Don't process cross-references if the token contains escape markers
|
||||
// (from escaped variables like \${R1:VALUE})
|
||||
if( token->Contains( ':' ) && !token->Contains( wxT( "\x01ESC_" ) ) )
|
||||
{
|
||||
if( schematic->ResolveCrossReference( token, aDepth + 1 ) )
|
||||
return true;
|
||||
|
||||
@@ -237,7 +237,9 @@ bool SCH_SHEET::ResolveTextVar( const SCH_SHEET_PATH* aPath, wxString* token, in
|
||||
if( !schematic )
|
||||
return false;
|
||||
|
||||
if( token->Contains( ':' ) )
|
||||
// Don't process cross-references if the token contains escape markers
|
||||
// (from escaped variables like \${R1:VALUE})
|
||||
if( token->Contains( ':' ) && !token->Contains( wxT( "\x01ESC_" ) ) )
|
||||
{
|
||||
if( schematic->ResolveCrossReference( token, aDepth + 1 ) )
|
||||
return true;
|
||||
|
||||
+43
-15
@@ -1516,6 +1516,8 @@ void SCH_SYMBOL::GetContextualTextVars( wxArrayString* aVars ) const
|
||||
aVars->push_back( wxT( "NET_NAME(<pin_number>)" ) );
|
||||
aVars->push_back( wxT( "NET_CLASS(<pin_number>)" ) );
|
||||
aVars->push_back( wxT( "PIN_NAME(<pin_number>)" ) );
|
||||
aVars->push_back( wxT( "REFERENCE(<pin_number>)" ) );
|
||||
aVars->push_back( wxT( "SHORT_REFERENCE(<pin_number>)" ) );
|
||||
aVars->push_back( wxT( "UNIT(<pin_number>)" ) );
|
||||
}
|
||||
|
||||
@@ -1606,7 +1608,9 @@ bool SCH_SYMBOL::ResolveTextVar( const SCH_SHEET_PATH* aPath, wxString* token, i
|
||||
return true;
|
||||
}
|
||||
|
||||
if( token->Contains( ':' ) )
|
||||
// Don't process cross-references if the token contains escape markers
|
||||
// (from escaped variables like \${R1:VALUE})
|
||||
if( token->Contains( ':' ) && !token->Contains( wxT( "\x01ESC_" ) ) )
|
||||
{
|
||||
if( schematic->ResolveCrossReference( token, aDepth + 1 ) )
|
||||
return true;
|
||||
@@ -1751,30 +1755,51 @@ bool SCH_SYMBOL::ResolveTextVar( const SCH_SHEET_PATH* aPath, wxString* token, i
|
||||
else if( token->StartsWith( wxT( "SHORT_NET_NAME(" ) ) || token->StartsWith( wxT( "NET_NAME(" ) )
|
||||
|| token->StartsWith( wxT( "NET_CLASS(" ) ) || token->StartsWith( wxT( "PIN_NAME(" ) )
|
||||
|| token->StartsWith( wxT( "PIN_BASE_NAME(" ) ) || token->StartsWith( wxT( "PIN_ALT_LIST(" ) )
|
||||
|| token->StartsWith( wxT( "REFERENCE(" ) ) || token->StartsWith( wxT( "SHORT_REFERENCE(" ) )
|
||||
|| token->StartsWith( wxT( "UNIT(" ) ) )
|
||||
{
|
||||
wxString pinNumber = token->AfterFirst( '(' );
|
||||
pinNumber = pinNumber.BeforeLast( ')' );
|
||||
|
||||
bool isReferenceFunction = token->StartsWith( wxT( "REFERENCE(" ) );
|
||||
bool isShortReferenceFunction = token->StartsWith( wxT( "SHORT_REFERENCE(" ) );
|
||||
bool isUnitFunction = token->StartsWith( wxT( "UNIT(" ) );
|
||||
|
||||
// First, try to find the pin in the current unit (for backward compatibility)
|
||||
// For UNIT(), always search all pins to find which unit the pin belongs to
|
||||
std::vector<SCH_PIN*> pinsToSearch = isUnitFunction ? GetAllLibPins() : GetPins( aPath );
|
||||
// For REFERENCE/SHORT_REFERENCE/UNIT functions, always search all pins to find which unit the pin belongs to
|
||||
std::vector<SCH_PIN*> pinsToSearch = ( isReferenceFunction || isShortReferenceFunction || isUnitFunction )
|
||||
? GetAllLibPins() : GetPins( aPath );
|
||||
|
||||
for( SCH_PIN* pin : pinsToSearch )
|
||||
{
|
||||
if( pin->GetNumber() == pinNumber )
|
||||
{
|
||||
if( isUnitFunction )
|
||||
if( isReferenceFunction || isShortReferenceFunction || isUnitFunction )
|
||||
{
|
||||
// Return the full unit reference (e.g., "J601A")
|
||||
int pinUnit = pin->GetUnit();
|
||||
wxString result;
|
||||
if( pinUnit > 0 )
|
||||
result = GetRef( aPath, false ) + SubReference( pinUnit, false );
|
||||
else
|
||||
|
||||
if( isReferenceFunction )
|
||||
{
|
||||
// Return the full unit reference (e.g., "J601A")
|
||||
if( pinUnit > 0 )
|
||||
result = GetRef( aPath, false ) + SubReference( pinUnit, false );
|
||||
else
|
||||
result = GetRef( aPath, false );
|
||||
}
|
||||
else if( isShortReferenceFunction )
|
||||
{
|
||||
// Return the reference without unit (e.g., "J601")
|
||||
result = GetRef( aPath, false );
|
||||
}
|
||||
else if( isUnitFunction )
|
||||
{
|
||||
// Return only the unit letter (e.g., "A")
|
||||
if( pinUnit > 0 )
|
||||
result = SubReference( pinUnit, false );
|
||||
else
|
||||
result = wxEmptyString;
|
||||
}
|
||||
|
||||
*token = result;
|
||||
return true;
|
||||
@@ -1791,13 +1816,14 @@ bool SCH_SYMBOL::ResolveTextVar( const SCH_SHEET_PATH* aPath, wxString* token, i
|
||||
}
|
||||
else if( token->StartsWith( wxT( "PIN_ALT_LIST" ) ) )
|
||||
{
|
||||
// Build complete list: base name first, then all alternates
|
||||
wxString altList = pin->GetBaseName();
|
||||
// Build list of alternate names only (no base name)
|
||||
wxString altList;
|
||||
|
||||
const std::map<wxString, SCH_PIN::ALT>& alts = pin->GetAlternates();
|
||||
for( const auto& [altName, altDef] : alts )
|
||||
{
|
||||
altList += wxT( ", " );
|
||||
if( !altList.IsEmpty() )
|
||||
altList += wxT( ", " );
|
||||
altList += altName;
|
||||
}
|
||||
|
||||
@@ -1827,7 +1853,8 @@ bool SCH_SYMBOL::ResolveTextVar( const SCH_SHEET_PATH* aPath, wxString* token, i
|
||||
}
|
||||
|
||||
// If pin not found in current unit, search all units (auto-resolution)
|
||||
if( !isUnitFunction )
|
||||
// Skip this for REFERENCE/SHORT_REFERENCE/UNIT functions as they already searched all units
|
||||
if( !isReferenceFunction && !isShortReferenceFunction && !isUnitFunction )
|
||||
{
|
||||
for( SCH_PIN* pin : GetAllLibPins() )
|
||||
{
|
||||
@@ -1841,13 +1868,14 @@ bool SCH_SYMBOL::ResolveTextVar( const SCH_SHEET_PATH* aPath, wxString* token, i
|
||||
}
|
||||
else if( token->StartsWith( wxT( "PIN_ALT_LIST" ) ) )
|
||||
{
|
||||
// Build complete list: base name first, then all alternates
|
||||
wxString altList = pin->GetBaseName();
|
||||
// Build list of alternate names only (no base name)
|
||||
wxString altList;
|
||||
|
||||
const std::map<wxString, SCH_PIN::ALT>& alts = pin->GetAlternates();
|
||||
for( const auto& [altName, altDef] : alts )
|
||||
{
|
||||
altList += wxT( ", " );
|
||||
if( !altList.IsEmpty() )
|
||||
altList += wxT( ", " );
|
||||
altList += altName;
|
||||
}
|
||||
|
||||
|
||||
@@ -138,15 +138,35 @@
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${refdes:REFERENCE(pin)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>Full reference with unit for pin</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${J1:REFERENCE(3)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>J1B</samp> (for multi-unit symbol)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${refdes:SHORT_REFERENCE(pin)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>Reference without unit letter for pin</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${J1:SHORT_REFERENCE(3)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>J1</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${refdes:UNIT(pin)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>Unit designation for pin</samp></td>
|
||||
<td> <br><samp>Unit letter only for pin</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${J1:UNIT(3)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>J1B</samp> (for multi-unit symbol)</td>
|
||||
<td> <br><samp>B</samp> (unit letter for pin 3)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${refdes:NET_NAME(pin)}</samp></td>
|
||||
@@ -181,12 +201,12 @@
|
||||
<tr>
|
||||
<td> <br><samp>${refdes:PIN_ALT_LIST(pin)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>All available pin functions (base name + alternates)</samp></td>
|
||||
<td> <br><samp>All alternate pin functions (excludes base name)</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${U1:PIN_ALT_LIST(5)}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>PA9, USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>
|
||||
<td> <br><samp>USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${refdes:SHORT_NET_NAME(pin)}</samp></td>
|
||||
@@ -240,7 +260,7 @@
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> <br><samp>${J1:UNIT(@{${ROW}+2})}</samp></td>
|
||||
<td> <br><samp>${J1:REFERENCE(@{${ROW}+2})}</samp></td>
|
||||
<td></td>
|
||||
<td> <br><samp>J1B</samp> (when ROW=0, pin 2 in unit B)</td>
|
||||
</tr>
|
||||
|
||||
@@ -140,14 +140,34 @@ _HKI( "<table>\n"
|
||||
" <th></th>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${refdes:REFERENCE(pin)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>Full reference with unit for pin</samp></td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${J1:REFERENCE(3)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>J1B</samp> (for multi-unit symbol)</td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${refdes:SHORT_REFERENCE(pin)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>Reference without unit letter for pin</samp></td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${J1:SHORT_REFERENCE(3)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>J1</samp></td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${refdes:UNIT(pin)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>Unit designation for pin</samp></td>\n"
|
||||
" <td> <br><samp>Unit letter only for pin</samp></td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${J1:UNIT(3)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>J1B</samp> (for multi-unit symbol)</td>\n"
|
||||
" <td> <br><samp>B</samp> (unit letter for pin 3)</td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${refdes:NET_NAME(pin)}</samp></td>\n"
|
||||
@@ -182,12 +202,12 @@ _HKI( "<table>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${refdes:PIN_ALT_LIST(pin)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>All available pin functions (base name + alternates)</samp></td>\n"
|
||||
" <td> <br><samp>All alternate pin functions (excludes base name)</samp></td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${U1:PIN_ALT_LIST(5)}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>PA9, USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>\n"
|
||||
" <td> <br><samp>USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${refdes:SHORT_NET_NAME(pin)}</samp></td>\n"
|
||||
@@ -241,7 +261,7 @@ _HKI( "<table>\n"
|
||||
" <th></th>\n"
|
||||
" </tr>\n"
|
||||
" <tr>\n"
|
||||
" <td> <br><samp>${J1:UNIT(@{${ROW}+2})}</samp></td>\n"
|
||||
" <td> <br><samp>${J1:REFERENCE(@{${ROW}+2})}</samp></td>\n"
|
||||
" <td></td>\n"
|
||||
" <td> <br><samp>J1B</samp> (when ROW=0, pin 2 in unit B)</td>\n"
|
||||
" </tr>\n"
|
||||
|
||||
@@ -700,6 +700,11 @@ std::set<wxString> SCHEMATIC::GetNetClassAssignmentCandidates()
|
||||
|
||||
bool SCHEMATIC::ResolveCrossReference( wxString* token, int aDepth ) const
|
||||
{
|
||||
// Don't process cross-references if the token contains escape markers
|
||||
// (from escaped variables like \${R1:VALUE})
|
||||
if( token->Contains( wxT( "\x01ESC_" ) ) )
|
||||
return false;
|
||||
|
||||
wxString remainder;
|
||||
wxString ref = token->BeforeFirst( ':', &remainder );
|
||||
KIID_PATH path( ref );
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <sch_actions.h>
|
||||
#include <tools/sch_edit_table_tool.h>
|
||||
#include <dialogs/dialog_table_properties.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <fstream>
|
||||
#include <sch_sheet_path.h>
|
||||
|
||||
|
||||
SCH_EDIT_TABLE_TOOL::SCH_EDIT_TABLE_TOOL() :
|
||||
@@ -81,6 +84,111 @@ int SCH_EDIT_TABLE_TOOL::EditTable( const TOOL_EVENT& aEvent )
|
||||
}
|
||||
|
||||
|
||||
int SCH_EDIT_TABLE_TOOL::ExportTableToCSV( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
SCH_SELECTION& selection = m_selectionTool->RequestSelection( SCH_COLLECTOR::EditableItems );
|
||||
bool clearSelection = selection.IsHover();
|
||||
SCH_TABLE* parentTable = nullptr;
|
||||
|
||||
// Find the table from the selection
|
||||
for( EDA_ITEM* item : selection.Items() )
|
||||
{
|
||||
if( item->Type() != SCH_TABLECELL_T )
|
||||
return 0;
|
||||
|
||||
SCH_TABLE* table = static_cast<SCH_TABLE*>( item->GetParent() );
|
||||
|
||||
if( !parentTable )
|
||||
{
|
||||
parentTable = table;
|
||||
}
|
||||
else if( parentTable != table )
|
||||
{
|
||||
parentTable = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( !parentTable )
|
||||
return 0;
|
||||
|
||||
// Get current sheet path for variable resolution
|
||||
SCH_SHEET_PATH& currentSheet = m_frame->GetCurrentSheet();
|
||||
|
||||
// Show file save dialog
|
||||
wxFileDialog saveDialog( m_frame, _( "Export Table to CSV" ),
|
||||
wxEmptyString, wxEmptyString,
|
||||
_( "CSV files (*.csv)|*.csv" ),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
|
||||
|
||||
if( saveDialog.ShowModal() == wxID_CANCEL )
|
||||
{
|
||||
if( clearSelection )
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
return 0;
|
||||
}
|
||||
|
||||
wxString filePath = saveDialog.GetPath();
|
||||
|
||||
// Open file for writing
|
||||
std::ofstream outFile( filePath.ToStdString() );
|
||||
|
||||
if( !outFile.is_open() )
|
||||
{
|
||||
wxMessageBox( wxString::Format( _( "Failed to open file:\n%s" ), filePath ),
|
||||
_( "Export Error" ), wxOK | wxICON_ERROR, m_frame );
|
||||
|
||||
if( clearSelection )
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Helper function to escape CSV fields
|
||||
auto escapeCSV = []( const wxString& field ) -> wxString
|
||||
{
|
||||
wxString escaped = field;
|
||||
|
||||
// If field contains comma, quote, or newline, wrap in quotes and escape quotes
|
||||
if( escaped.Contains( ',' ) || escaped.Contains( '\"' ) || escaped.Contains( '\n' ) )
|
||||
{
|
||||
escaped.Replace( "\"", "\"\"" ); // Escape quotes by doubling them
|
||||
escaped = "\"" + escaped + "\"";
|
||||
}
|
||||
|
||||
return escaped;
|
||||
};
|
||||
|
||||
// Export table data
|
||||
for( int row = 0; row < parentTable->GetRowCount(); ++row )
|
||||
{
|
||||
for( int col = 0; col < parentTable->GetColCount(); ++col )
|
||||
{
|
||||
SCH_TABLECELL* cell = parentTable->GetCell( row, col );
|
||||
|
||||
// Get resolved text (with variables expanded)
|
||||
wxString cellText = cell->GetShownText( nullptr, ¤tSheet, false, 0 );
|
||||
|
||||
// Write escaped cell text
|
||||
outFile << escapeCSV( cellText ).ToStdString();
|
||||
|
||||
// Add comma separator unless it's the last column
|
||||
if( col < parentTable->GetColCount() - 1 )
|
||||
outFile << ',';
|
||||
}
|
||||
|
||||
// End of row
|
||||
outFile << '\n';
|
||||
}
|
||||
|
||||
outFile.close();
|
||||
|
||||
if( clearSelection )
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
SCH_TABLECELL* SCH_EDIT_TABLE_TOOL::copyCell( SCH_TABLECELL* aSource )
|
||||
{
|
||||
// Use copy constructor to copy all formatting properties (font, colors, borders, etc.)
|
||||
@@ -118,4 +226,5 @@ void SCH_EDIT_TABLE_TOOL::setTransitions()
|
||||
Go( &SCH_EDIT_TABLE_TOOL::UnmergeCells, ACTIONS::unmergeCells.MakeEvent() );
|
||||
|
||||
Go( &SCH_EDIT_TABLE_TOOL::EditTable, ACTIONS::editTable.MakeEvent() );
|
||||
Go( &SCH_EDIT_TABLE_TOOL::ExportTableToCSV, ACTIONS::exportTableCSV.MakeEvent() );
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
int UnmergeCells( const TOOL_EVENT& aEvent ) { return doUnmergeCells( aEvent ); }
|
||||
|
||||
int EditTable( const TOOL_EVENT& aEvent );
|
||||
int ExportTableToCSV( const TOOL_EVENT& aEvent );
|
||||
|
||||
private:
|
||||
///< Set up handlers for various events.
|
||||
|
||||
@@ -110,6 +110,7 @@ public:
|
||||
static TOOL_ACTION mergeCells;
|
||||
static TOOL_ACTION unmergeCells;
|
||||
static TOOL_ACTION editTable;
|
||||
static TOOL_ACTION exportTableCSV;
|
||||
|
||||
// Find and Replace
|
||||
static TOOL_ACTION showSearch;
|
||||
|
||||
@@ -108,6 +108,9 @@ protected:
|
||||
selToolMenu.AddSeparator( 100 );
|
||||
selToolMenu.AddItem( ACTIONS::editTable, cellSelection && SELECTION_CONDITIONS::Idle, 100 );
|
||||
|
||||
selToolMenu.AddSeparator( 100 );
|
||||
selToolMenu.AddItem( ACTIONS::exportTableCSV, cellSelection && SELECTION_CONDITIONS::Idle, 100 );
|
||||
|
||||
selToolMenu.AddSeparator( 100 );
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#include <collectors.h>
|
||||
#include <dialogs/dialog_table_properties.h>
|
||||
#include <tools/pcb_edit_table_tool.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
PCB_EDIT_TABLE_TOOL::PCB_EDIT_TABLE_TOOL() :
|
||||
@@ -122,6 +125,108 @@ int PCB_EDIT_TABLE_TOOL::EditTable( const TOOL_EVENT& aEvent )
|
||||
}
|
||||
|
||||
|
||||
int PCB_EDIT_TABLE_TOOL::ExportTableToCSV( const TOOL_EVENT& aEvent )
|
||||
{
|
||||
const SELECTION& selection = getTableCellSelection();
|
||||
bool clearSelection = selection.IsHover();
|
||||
PCB_TABLE* parentTable = nullptr;
|
||||
|
||||
// Find the table from the selection
|
||||
for( EDA_ITEM* item : selection.Items() )
|
||||
{
|
||||
if( item->Type() != PCB_TABLECELL_T )
|
||||
return 0;
|
||||
|
||||
PCB_TABLE* table = static_cast<PCB_TABLE*>( item->GetParent() );
|
||||
|
||||
if( !parentTable )
|
||||
{
|
||||
parentTable = table;
|
||||
}
|
||||
else if( parentTable != table )
|
||||
{
|
||||
parentTable = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( !parentTable )
|
||||
return 0;
|
||||
|
||||
// Show file save dialog
|
||||
wxFileDialog saveDialog( frame(), _( "Export Table to CSV" ),
|
||||
wxEmptyString, wxEmptyString,
|
||||
_( "CSV files (*.csv)|*.csv" ),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
|
||||
|
||||
if( saveDialog.ShowModal() == wxID_CANCEL )
|
||||
{
|
||||
if( clearSelection )
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
return 0;
|
||||
}
|
||||
|
||||
wxString filePath = saveDialog.GetPath();
|
||||
|
||||
// Open file for writing
|
||||
std::ofstream outFile( filePath.ToStdString() );
|
||||
|
||||
if( !outFile.is_open() )
|
||||
{
|
||||
wxMessageBox( wxString::Format( _( "Failed to open file:\n%s" ), filePath ),
|
||||
_( "Export Error" ), wxOK | wxICON_ERROR, frame() );
|
||||
|
||||
if( clearSelection )
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Helper function to escape CSV fields
|
||||
auto escapeCSV = []( const wxString& field ) -> wxString
|
||||
{
|
||||
wxString escaped = field;
|
||||
|
||||
// If field contains comma, quote, or newline, wrap in quotes and escape quotes
|
||||
if( escaped.Contains( ',' ) || escaped.Contains( '\"' ) || escaped.Contains( '\n' ) )
|
||||
{
|
||||
escaped.Replace( "\"", "\"\"" ); // Escape quotes by doubling them
|
||||
escaped = "\"" + escaped + "\"";
|
||||
}
|
||||
|
||||
return escaped;
|
||||
};
|
||||
|
||||
// Export table data
|
||||
for( int row = 0; row < parentTable->GetRowCount(); ++row )
|
||||
{
|
||||
for( int col = 0; col < parentTable->GetColCount(); ++col )
|
||||
{
|
||||
PCB_TABLECELL* cell = parentTable->GetCell( row, col );
|
||||
|
||||
// Get resolved text (with variables expanded)
|
||||
wxString cellText = cell->GetShownText( false, 0 );
|
||||
|
||||
// Write escaped cell text
|
||||
outFile << escapeCSV( cellText ).ToStdString();
|
||||
|
||||
// Add comma separator unless it's the last column
|
||||
if( col < parentTable->GetColCount() - 1 )
|
||||
outFile << ',';
|
||||
}
|
||||
|
||||
// End of row
|
||||
outFile << '\n';
|
||||
}
|
||||
|
||||
outFile.close();
|
||||
|
||||
if( clearSelection )
|
||||
m_toolMgr->RunAction( ACTIONS::selectionClear );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void PCB_EDIT_TABLE_TOOL::setTransitions()
|
||||
{
|
||||
Go( &PCB_EDIT_TABLE_TOOL::AddRowAbove, ACTIONS::addRowAbove.MakeEvent() );
|
||||
@@ -137,4 +242,5 @@ void PCB_EDIT_TABLE_TOOL::setTransitions()
|
||||
Go( &PCB_EDIT_TABLE_TOOL::UnmergeCells, ACTIONS::unmergeCells.MakeEvent() );
|
||||
|
||||
Go( &PCB_EDIT_TABLE_TOOL::EditTable, ACTIONS::editTable.MakeEvent() );
|
||||
Go( &PCB_EDIT_TABLE_TOOL::ExportTableToCSV, ACTIONS::exportTableCSV.MakeEvent() );
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
int UnmergeCells( const TOOL_EVENT& aEvent ) { return doUnmergeCells( aEvent ); }
|
||||
|
||||
int EditTable( const TOOL_EVENT& aEvent );
|
||||
int ExportTableToCSV( const TOOL_EVENT& aEvent );
|
||||
|
||||
private:
|
||||
///< Set up handlers for various events.
|
||||
|
||||
@@ -64,6 +64,15 @@ Integration tests simulating real-world KiCad usage scenarios:
|
||||
- `max(...)` - Maximum of multiple values
|
||||
- `sum(...)` - Sum of multiple values
|
||||
- `avg(...)` - Average of multiple values
|
||||
- `shunt(r1, r2)` - Parallel resistance calculation: (r1×r2)/(r1+r2)
|
||||
- `db(ratio)` - Power ratio to decibels: 10×log₁₀(ratio)
|
||||
- `dbv(ratio)` - Voltage/current ratio to decibels: 20×log₁₀(ratio)
|
||||
- `fromdb(db)` - Decibels to power ratio: 10^(dB/10)
|
||||
- `fromdbv(db)` - Decibels to voltage/current ratio: 10^(dB/20)
|
||||
- `enearest(value, [series])` - Nearest E-series standard value (default: E24)
|
||||
- `eup(value, [series])` - Next higher E-series standard value (default: E24)
|
||||
- `edown(value, [series])` - Next lower E-series standard value (default: E24)
|
||||
- Series options: "E3", "E6", "E12", "E24", "E48", "E96", "E192"
|
||||
|
||||
### String Functions
|
||||
- `upper(str)` - Convert to uppercase
|
||||
|
||||
Reference in New Issue
Block a user