added table export, try to fix escaped expression uuid bug

This commit is contained in:
Magnus Lundmark
2025-11-13 01:02:57 +01:00
committed by Jeff Young
parent 26531bb25d
commit cdc1c417ff
16 changed files with 571 additions and 27 deletions
+1
View File
@@ -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
{
+229
View File
@@ -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 ) );
}
+7
View File
@@ -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 )
+3 -1
View File
@@ -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;
+3 -1
View File
@@ -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
View File
@@ -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;
}
+25 -5
View File
@@ -138,15 +138,35 @@
<th></th>
<th></th>
</tr>
<tr>
<td>&nbsp;<br><samp>${refdes:REFERENCE(pin)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>Full reference with unit for pin</samp></td>
</tr>
<tr>
<td>&nbsp;<br><samp>${J1:REFERENCE(3)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>J1B</samp> (for multi-unit symbol)</td>
</tr>
<tr>
<td>&nbsp;<br><samp>${refdes:SHORT_REFERENCE(pin)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>Reference without unit letter for pin</samp></td>
</tr>
<tr>
<td>&nbsp;<br><samp>${J1:SHORT_REFERENCE(3)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>J1</samp></td>
</tr>
<tr>
<td>&nbsp;<br><samp>${refdes:UNIT(pin)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>Unit designation for pin</samp></td>
<td>&nbsp;<br><samp>Unit letter only for pin</samp></td>
</tr>
<tr>
<td>&nbsp;<br><samp>${J1:UNIT(3)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>J1B</samp> (for multi-unit symbol)</td>
<td>&nbsp;<br><samp>B</samp> (unit letter for pin 3)</td>
</tr>
<tr>
<td>&nbsp;<br><samp>${refdes:NET_NAME(pin)}</samp></td>
@@ -181,12 +201,12 @@
<tr>
<td>&nbsp;<br><samp>${refdes:PIN_ALT_LIST(pin)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>All available pin functions (base name + alternates)</samp></td>
<td>&nbsp;<br><samp>All alternate pin functions (excludes base name)</samp></td>
</tr>
<tr>
<td>&nbsp;<br><samp>${U1:PIN_ALT_LIST(5)}</samp></td>
<td></td>
<td>&nbsp;<br><samp>PA9, USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>
<td>&nbsp;<br><samp>USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>
</tr>
<tr>
<td>&nbsp;<br><samp>${refdes:SHORT_NET_NAME(pin)}</samp></td>
@@ -240,7 +260,7 @@
<th></th>
</tr>
<tr>
<td>&nbsp;<br><samp>${J1:UNIT(@{${ROW}+2})}</samp></td>
<td>&nbsp;<br><samp>${J1:REFERENCE(@{${ROW}+2})}</samp></td>
<td></td>
<td>&nbsp;<br><samp>J1B</samp> (when ROW=0, pin 2 in unit B)</td>
</tr>
+25 -5
View File
@@ -140,14 +140,34 @@ _HKI( "<table>\n"
" <th></th>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${refdes:REFERENCE(pin)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>Full reference with unit for pin</samp></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${J1:REFERENCE(3)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>J1B</samp> (for multi-unit symbol)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${refdes:SHORT_REFERENCE(pin)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>Reference without unit letter for pin</samp></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${J1:SHORT_REFERENCE(3)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>J1</samp></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${refdes:UNIT(pin)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>Unit designation for pin</samp></td>\n"
" <td>&nbsp;<br><samp>Unit letter only for pin</samp></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${J1:UNIT(3)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>J1B</samp> (for multi-unit symbol)</td>\n"
" <td>&nbsp;<br><samp>B</samp> (unit letter for pin 3)</td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${refdes:NET_NAME(pin)}</samp></td>\n"
@@ -182,12 +202,12 @@ _HKI( "<table>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${refdes:PIN_ALT_LIST(pin)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>All available pin functions (base name + alternates)</samp></td>\n"
" <td>&nbsp;<br><samp>All alternate pin functions (excludes base name)</samp></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<br><samp>${U1:PIN_ALT_LIST(5)}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>PA9, USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>\n"
" <td>&nbsp;<br><samp>USART1_TX, TIM1_CH2, I2C1_SCL</samp></td>\n"
" </tr>\n"
" <tr>\n"
" <td>&nbsp;<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>&nbsp;<br><samp>${J1:UNIT(@{${ROW}+2})}</samp></td>\n"
" <td>&nbsp;<br><samp>${J1:REFERENCE(@{${ROW}+2})}</samp></td>\n"
" <td></td>\n"
" <td>&nbsp;<br><samp>J1B</samp> (when ROW=0, pin 2 in unit B)</td>\n"
" </tr>\n"
+5
View File
@@ -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 );
+109
View File
@@ -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, &currentSheet, 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() );
}
+1
View File
@@ -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.
+1
View File
@@ -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;
+3
View File
@@ -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 );
}
+106
View File
@@ -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() );
}
+1
View File
@@ -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.
+9
View File
@@ -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