Compare commits

...

9 Commits

Author SHA1 Message Date
Fabien Corona
28e2f770da Merge branch 'ibis_core' into 'master'
Ibis: update core model, don't use hardcoded stimuli

See merge request kicad/code/kicad!2176
2025-09-12 13:45:13 +00:00
Seth Hillbrand
c64f99c57a ADDED: VCSHASH and VCSSHORTHASH
Right now, git hashes only resolved by the variables ${VCSHASH} or
${VCSSHORTHASH}
2025-09-12 06:00:27 -07:00
Seth Hillbrand
5952c2fb9a ADDED: Pad edit table
Allows editing common pad properties in table format

Fixes https://gitlab.com/kicad/code/kicad/issues/10789
2025-09-12 05:39:25 -07:00
Jeff Young
0b6de964fd Fix initialization order. 2025-09-12 13:32:17 +01:00
Jeff Young
976278e5a6 ADDED: extra info for shape, pin and field differences.
Fixes https://gitlab.com/kicad/code/kicad/-/issues/21647
2025-09-12 13:32:17 +01:00
Roberto Fernandez Bautista
8d8bd7253f Windows CI: custom vcpkg triplet to ensure shareable binary cache 2025-09-12 11:18:50 +00:00
Seth Hillbrand
5dc6d43f43 Add via tenting/plugging representation to 3d-viewer
Fixes https://gitlab.com/kicad/code/kicad/issues/21704
2025-09-12 01:23:41 -07:00
jean-pierre charras
98dd5a68eb Fix a compil issue on gcc/msys2
gcc does not like implicit conversion from wxString to char*
2025-09-12 08:20:49 +02:00
Fabien Corona
12ebc54874 Ibis: update core model, don't use hardcoded stimuli 2025-02-28 17:31:54 +01:00
28 changed files with 1393 additions and 134 deletions

View File

@ -9,7 +9,6 @@ win64_build:
image: registry.gitlab.com/kicad/kicad-ci/windows-build-image/ltsc2022-msvc:latest
variables:
VCPKG_BINARY_SOURCES: 'nuget,gitlab,readwrite'
VCPKG_DISABLE_COMPILER_TRACKING: '1'
# Switch the compressor to fastzip and reduce the compression level
FF_USE_FASTZIP: "true"
CACHE_COMPRESSION_LEVEL: "fast"
@ -37,6 +36,8 @@ win64_build:
-DKICAD_BUILD_PNS_DEBUG_TOOL=ON `
-DKICAD_USE_3DCONNEXION=ON `
-DVCPKG_BUILD_TYPE=debug `
-DVCPKG_INSTALL_OPTIONS="--x-abi-tools-use-exact-versions" `
-DVCPKG_OVERLAY_TRIPLETS="$Env:CI_PROJECT_DIR/tools/custom_vcpkg_triplets" `
../../
- cmake --build . 2>&1 | tee compilation_log.txt
- cd ../../

View File

@ -26,6 +26,7 @@
#include "render_3d_opengl.h"
#include <board.h>
#include <footprint.h>
#include <pcb_track.h>
#include "../../3d_math.h"
#include "convert_basic_shapes_to_polygon.h"
#include <lset.h>
@ -726,6 +727,54 @@ void RENDER_3D_OPENGL::generateCylinder( const SFVEC2F& aCenter, float aInnerRad
}
void RENDER_3D_OPENGL::generateDisk( const SFVEC2F& aCenter, float aRadius, float aZ,
unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST* aDstLayer,
bool aTop )
{
const float delta = 2.0f * glm::pi<float>() / (float) aNr_sides_per_circle;
for( unsigned int i = 0; i < aNr_sides_per_circle; ++i )
{
float a0 = delta * i;
float a1 = delta * ( i + 1 );
const SFVEC3F p0( aCenter.x + cosf( a0 ) * aRadius,
aCenter.y + sinf( a0 ) * aRadius, aZ );
const SFVEC3F p1( aCenter.x + cosf( a1 ) * aRadius,
aCenter.y + sinf( a1 ) * aRadius, aZ );
const SFVEC3F c( aCenter.x, aCenter.y, aZ );
if( aTop )
aDstLayer->m_layer_top_triangles->AddTriangle( p1, p0, c );
else
aDstLayer->m_layer_bot_triangles->AddTriangle( p0, p1, c );
}
}
void RENDER_3D_OPENGL::generateDimple( const SFVEC2F& aCenter, float aRadius, float aZ,
float aDepth, unsigned int aNr_sides_per_circle,
TRIANGLE_DISPLAY_LIST* aDstLayer, bool aTop )
{
const float delta = 2.0f * glm::pi<float>() / (float) aNr_sides_per_circle;
const SFVEC3F c( aCenter.x, aCenter.y, aTop ? aZ - aDepth : aZ + aDepth );
for( unsigned int i = 0; i < aNr_sides_per_circle; ++i )
{
float a0 = delta * i;
float a1 = delta * ( i + 1 );
const SFVEC3F p0( aCenter.x + cosf( a0 ) * aRadius,
aCenter.y + sinf( a0 ) * aRadius, aZ );
const SFVEC3F p1( aCenter.x + cosf( a1 ) * aRadius,
aCenter.y + sinf( a1 ) * aRadius, aZ );
if( aTop )
aDstLayer->m_layer_top_triangles->AddTriangle( p0, p1, c );
else
aDstLayer->m_layer_bot_triangles->AddTriangle( p1, p0, c );
}
}
void RENDER_3D_OPENGL::generateViasAndPads()
{
if( !m_boardAdapter.GetBoard() )
@ -882,6 +931,63 @@ void RENDER_3D_OPENGL::generateViasAndPads()
delete layerTriangles;
}
}
TRIANGLE_DISPLAY_LIST* frontCover = new TRIANGLE_DISPLAY_LIST( m_boardAdapter.GetViaCount() );
TRIANGLE_DISPLAY_LIST* backCover = new TRIANGLE_DISPLAY_LIST( m_boardAdapter.GetViaCount() );
for( const PCB_TRACK* track : m_boardAdapter.GetBoard()->Tracks() )
{
if( track->Type() != PCB_VIA_T )
continue;
const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
const float holediameter = via->GetDrillValue() * m_boardAdapter.BiuTo3dUnits();
const float hole_radius = holediameter / 2.0f + 2.0 * platingThickness3d;
const SFVEC2F center( via->GetStart().x * m_boardAdapter.BiuTo3dUnits(),
-via->GetStart().y * m_boardAdapter.BiuTo3dUnits() );
unsigned int seg = m_boardAdapter.GetCircleSegmentCount( via->GetDrillValue() );
PCB_LAYER_ID top_layer, bottom_layer;
via->LayerPair( &top_layer, &bottom_layer );
float ztop, zbot, dummy;
getLayerZPos( top_layer, ztop, dummy );
getLayerZPos( bottom_layer, dummy, zbot );
bool frontCovering = via->GetFrontCoveringMode() == COVERING_MODE::COVERED || via->IsTented( F_Mask );
bool backCovering = via->GetBackCoveringMode() == COVERING_MODE::COVERED || via->IsTented( B_Mask );
bool frontPlugged = via->GetFrontPluggingMode() == PLUGGING_MODE::PLUGGED;
bool backPlugged = via->GetBackPluggingMode() == PLUGGING_MODE::PLUGGED;
bool filled = via->GetFillingMode() == FILLING_MODE::FILLED
|| via->GetCappingMode() == CAPPING_MODE::CAPPED;
const float depth = hole_radius * 0.3f;
if( frontCovering )
{
if( filled || !frontPlugged )
generateDisk( center, hole_radius, ztop, seg, frontCover, true );
else
generateDimple( center, hole_radius, ztop, depth, seg, frontCover, true );
}
if( backCovering )
{
if( filled || !backPlugged )
generateDisk( center, hole_radius, zbot, seg, backCover, false );
else
generateDimple( center, hole_radius, zbot, depth, seg, backCover, false );
}
}
if( frontCover->m_layer_top_triangles->GetVertexSize() > 0 )
m_viaFrontCover = new OPENGL_RENDER_LIST( *frontCover, 0, 0.0f, 0.0f );
if( backCover->m_layer_bot_triangles->GetVertexSize() > 0 )
m_viaBackCover = new OPENGL_RENDER_LIST( *backCover, 0, 0.0f, 0.0f );
delete frontCover;
delete backCover;
}

View File

@ -72,6 +72,8 @@ RENDER_3D_OPENGL::RENDER_3D_OPENGL( EDA_3D_CANVAS* aCanvas, BOARD_ADAPTER& aAdap
m_outerViaThroughHoles = nullptr;
m_microviaHoles = nullptr;
m_padHoles = nullptr;
m_viaFrontCover = nullptr;
m_viaBackCover = nullptr;
m_circleTexture = 0;
m_grid = 0;
@ -947,6 +949,8 @@ void RENDER_3D_OPENGL::freeAllLists()
DELETE_AND_FREE( m_microviaHoles )
DELETE_AND_FREE( m_padHoles )
DELETE_AND_FREE( m_viaFrontCover )
DELETE_AND_FREE( m_viaBackCover )
}
@ -968,6 +972,17 @@ void RENDER_3D_OPENGL::renderSolderMaskLayer( PCB_LAYER_ID aLayerID, float aZPos
setLayerMaterial( aLayerID );
m_board->SetItIsTransparent( true );
m_board->DrawCulled( aShowThickness, solder_mask, via_holes );
if( aLayerID == F_Mask && m_viaFrontCover )
{
m_viaFrontCover->ApplyScalePosition( aZPos, 4 * m_boardAdapter.GetNonCopperLayerThickness() );
m_viaFrontCover->DrawTop();
}
else if( aLayerID == B_Mask && m_viaBackCover )
{
m_viaBackCover->ApplyScalePosition( aZPos, 4 * m_boardAdapter.GetNonCopperLayerThickness() );
m_viaBackCover->DrawBot();
}
}
}

View File

@ -123,6 +123,14 @@ private:
float aZtop, float aZbot, unsigned int aNr_sides_per_circle,
TRIANGLE_DISPLAY_LIST* aDstLayer );
void generateDisk( const SFVEC2F& aCenter, float aRadius, float aZ,
unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST* aDstLayer,
bool aTop );
void generateDimple( const SFVEC2F& aCenter, float aRadius, float aZ, float aDepth,
unsigned int aNr_sides_per_circle, TRIANGLE_DISPLAY_LIST* aDstLayer,
bool aTop );
void generateViasAndPads();
/**
@ -236,6 +244,8 @@ private:
OPENGL_RENDER_LIST* m_microviaHoles;
OPENGL_RENDER_LIST* m_padHoles;
OPENGL_RENDER_LIST* m_viaFrontCover;
OPENGL_RENDER_LIST* m_viaBackCover;
// Caches
std::map<wxString, MODEL_3D*> m_3dModelMap;

View File

@ -6,7 +6,12 @@
},
{
"environment": "vcpkg",
"VcPkgDir": "D:/vcpkg/"
"VcPkgDir": "D:/vcpkg/",
"VCPKG_BINARY_SOURCES": "nuget,kicad-gitlab,read"
},
{
"environment": "swig",
"SwigExePath": "D:/swigwin-4.1.1/swig.exe"
}
],
"configurations": [
@ -14,7 +19,7 @@
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64", "vcpkg" ],
"inheritEnvironments": [ "msvc_x64_x64", "vcpkg", "swig"],
"buildRoot": "${env.BuildDir}\\${name}",
"installRoot": "${env.InstallDir}\\${name}",
"cmakeCommandArgs": "",
@ -30,6 +35,21 @@
"name": "KICAD_WIN32_DPI_AWARE",
"value": "ON",
"type": "BOOL"
},
{
"name": "SWIG_EXECUTABLE",
"value": "${env.SwigExePath}",
"type": "STRING"
},
{
"name": "VCPKG_OVERLAY_TRIPLETS",
"value": "${workspaceRoot}/tools/custom_vcpkg_triplets",
"type": "STRING"
},
{
"name": "VCPKG_INSTALL_OPTIONS",
"value": "--x-abi-tools-use-exact-versions",
"type": "STRING"
}
],
"cmakeToolchain": "${env.VcPkgDir}/scripts/buildsystems/vcpkg.cmake"
@ -38,7 +58,7 @@
"name": "x64-Release",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"inheritEnvironments": [ "msvc_x64_x64", "vcpkg" ],
"inheritEnvironments": [ "msvc_x64_x64", "vcpkg", "swig"],
"buildRoot": "${env.BuildDir}\\${name}",
"installRoot": "${env.InstallDir}\\${name}",
"cmakeCommandArgs": "",
@ -54,6 +74,21 @@
"name": "KICAD_WIN32_DPI_AWARE",
"value": "ON",
"type": "BOOL"
},
{
"name": "SWIG_EXECUTABLE",
"value": "${env.SwigExePath}",
"type": "STRING"
},
{
"name": "VCPKG_OVERLAY_TRIPLETS",
"value": "${workspaceRoot}/tools/custom_vcpkg_triplets",
"type": "STRING"
},
{
"name": "VCPKG_INSTALL_OPTIONS",
"value": "--x-abi-tools-use-exact-versions",
"type": "STRING"
}
],
"cmakeToolchain": "${env.VcPkgDir}/scripts/buildsystems/vcpkg.cmake"

View File

@ -24,6 +24,9 @@
#include "project_git_utils.h"
#include "git_backend.h"
#include <wx/string.h>
#include <string_utils.h>
namespace KIGIT
{
@ -43,4 +46,34 @@ bool PROJECT_GIT_UTILS::RemoveVCS( git_repository*& aRepo, const wxString& aProj
return GetGitBackend()->RemoveVCS( aRepo, aProjectPath, aRemoveGitDir, aErrors );
}
wxString PROJECT_GIT_UTILS::GetCurrentHash( const wxString& aProjectFile, bool aShort )
{
wxString result = wxT( "no hash" );
git_repository* repo = PROJECT_GIT_UTILS::GetRepositoryForFile( TO_UTF8( aProjectFile ) );
if( repo )
{
git_reference* head = nullptr;
if( git_repository_head( &head, repo ) == 0 )
{
const git_oid* oid = git_reference_target( head );
if( oid )
{
char buf[41];
size_t len = aShort ? 8 : 41;
git_oid_tostr( buf, len, oid );
result = wxString::FromUTF8( buf );
}
git_reference_free( head );
}
git_repository_free( repo );
}
return result;
}
} // namespace KIGIT

View File

@ -51,6 +51,16 @@ public:
*/
static int CreateBranch( git_repository* aRepo, const wxString& aBranchName );
/**
* Return the current HEAD commit hash for the repository containing aProjectFile.
*
* @param aProjectFile Absolute path to any file within the repository (typically the
* project file path).
* @param aShort If true, return the short (8 char) hash, otherwise full hash.
* @return wxString containing the hash or "no hash" if unavailable.
*/
static wxString GetCurrentHash( const wxString& aProjectFile, bool aShort );
/**
* Remove version control from a directory by freeing the repository and
* optionally removing the .git directory.

View File

@ -36,6 +36,8 @@
#include <kiway.h>
#include <lockfile.h>
#include <macros.h>
#include <git/project_git_utils.h>
#include <git2.h>
#include <project.h>
#include <project/project_file.h>
#include <trace_helpers.h>
@ -45,6 +47,7 @@
#include <title_block.h>
PROJECT::PROJECT() :
m_readOnly( false ),
m_textVarsTicker( 0 ),
@ -85,6 +88,16 @@ bool PROJECT::TextVarResolver( wxString* aToken ) const
*aToken = TITLE_BLOCK::GetCurrentDate();
return true;
}
else if( aToken->IsSameAs( wxT( "VCSHASH" ) ) )
{
*aToken = KIGIT::PROJECT_GIT_UTILS::GetCurrentHash( GetProjectFullName(), false );
return true;
}
else if( aToken->IsSameAs( wxT( "VCSSHORTHASH" ) ) )
{
*aToken = KIGIT::PROJECT_GIT_UTILS::GetCurrentHash( GetProjectFullName(), true );
return true;
}
else if( GetTextVars().count( *aToken ) > 0 )
{
*aToken = GetTextVars().at( *aToken );

View File

@ -152,10 +152,10 @@ const wxAuiPaneInfo& defaultDesignBlocksPaneInfo( wxWindow* aWindow )
EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() :
APP_SETTINGS_BASE( "eeschema", eeschemaSchemaVersion ),
m_Appearance(),
m_AutoplaceFields(),
m_Drawing(),
m_FindReplaceExtra(),
m_Input(),
m_AutoplaceFields(),
m_Selection(),
m_PageSettings(),
m_AnnotatePanel(),
m_BomPanel(),
@ -163,8 +163,9 @@ EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() :
m_LibViewPanel(),
m_NetlistPanel(),
m_SymChooserPanel(),
m_FindReplaceExtra(),
m_ERCDialog(),
m_ImportGraphics(),
m_Selection(),
m_Simulator(),
m_RescueNeverShow( false )
{

View File

@ -1542,7 +1542,7 @@ std::vector<LIB_SYMBOL_UNIT> LIB_SYMBOL::GetUnitDrawItems()
#define REPORT( msg ) { if( aReporter ) aReporter->Report( msg ); }
#define ITEM_DESC( item ) ( item )->GetItemDescription( &unitsProvider, true )
#define ITEM_DESC( item ) ( item )->GetItemDescription( &unitsProvider, false )
int LIB_SYMBOL::Compare( const LIB_SYMBOL& aRhs, int aCompareFlags, REPORTER* aReporter ) const
{
@ -1586,35 +1586,34 @@ int LIB_SYMBOL::Compare( const LIB_SYMBOL& aRhs, int aCompareFlags, REPORTER* aR
return retv;
}
// Make sure shapes and pins are sorted. No need with fields as those are
// matched by id/name.
// Make sure shapes are sorted. No need with fields or pins as those are matched by id/name and number.
std::set<const SCH_ITEM*, SCH_ITEM::cmp_items> aShapes;
std::set<const SCH_ITEM*> aFields;
std::set<const SCH_ITEM*, SCH_ITEM::cmp_items> aPins;
std::set<const SCH_FIELD*> aFields;
std::set<const SCH_PIN*> aPins;
for( auto it = m_drawings.begin(); it != m_drawings.end(); ++it )
{
if( it->Type() == SCH_SHAPE_T )
aShapes.insert( &(*it) );
else if( it->Type() == SCH_FIELD_T )
aFields.insert( &(*it) );
aFields.insert( static_cast<const SCH_FIELD*>( &(*it) ) );
else if( it->Type() == SCH_PIN_T )
aPins.insert( &(*it) );
aPins.insert( static_cast<const SCH_PIN*>( &(*it) ) );
}
std::set<const SCH_ITEM*, SCH_ITEM::cmp_items> bShapes;
std::set<const SCH_ITEM*> bFields;
std::set<const SCH_ITEM*, SCH_ITEM::cmp_items> bPins;
std::set<const SCH_FIELD*> bFields;
std::set<const SCH_PIN*> bPins;
for( auto it = aRhs.m_drawings.begin(); it != aRhs.m_drawings.end(); ++it )
{
if( it->Type() == SCH_SHAPE_T )
bShapes.insert( &(*it) );
else if( it->Type() == SCH_FIELD_T )
bFields.insert( &(*it) );
bFields.insert( static_cast<const SCH_FIELD*>( &(*it) ) );
else if( it->Type() == SCH_PIN_T )
bPins.insert( &(*it) );
bPins.insert( static_cast<const SCH_PIN*>( &(*it) ) );
}
if( int tmp = static_cast<int>( aShapes.size() - bShapes.size() ) )
@ -1632,7 +1631,9 @@ int LIB_SYMBOL::Compare( const LIB_SYMBOL& aRhs, int aCompareFlags, REPORTER* aR
if( int tmp2 = (*aIt)->compare( *(*bIt), aCompareFlags ) )
{
retv = tmp2;
REPORT( wxString::Format( _( "%s differs." ), ITEM_DESC( *aIt ) ) );
REPORT( wxString::Format( _( "Graphic item differs: %s; %s." ),
ITEM_DESC( *aIt ),
ITEM_DESC( *bIt ) ) );
if( !aReporter )
return retv;
@ -1640,46 +1641,48 @@ int LIB_SYMBOL::Compare( const LIB_SYMBOL& aRhs, int aCompareFlags, REPORTER* aR
}
}
if( int tmp = static_cast<int>( aPins.size() - bPins.size() ) )
for( const SCH_PIN* aPin : aPins )
{
retv = tmp;
REPORT( _( "Pin count differs." ) );
const SCH_PIN* bPin = aRhs.GetPin( aPin->GetNumber(), aPin->GetUnit(), aPin->GetBodyStyle() );
if( !aReporter )
return retv;
}
else
{
for( const SCH_ITEM* aPinItem : aPins )
if( !bPin )
{
const SCH_PIN* aPin = static_cast<const SCH_PIN*>( aPinItem );
const SCH_PIN* bPin = aRhs.GetPin( aPin->GetNumber(), aPin->GetUnit(),
aPin->GetBodyStyle() );
retv = 1;
REPORT( wxString::Format( _( "Extra pin in schematic symbol: %s." ), ITEM_DESC( aPin ) ) );
if( !bPin )
{
retv = 1;
REPORT( wxString::Format( _( "Pin %s not found." ), aPin->GetNumber() ) );
if( !aReporter )
return retv;
}
else if( int tmp = aPin->SCH_ITEM::compare( *bPin, aCompareFlags ) )
{
retv = tmp;
REPORT( wxString::Format( _( "Pin %s differs: %s; %s" ),
aPin->GetNumber(),
ITEM_DESC( aPin ),
ITEM_DESC( bPin ) ) );
if( !aReporter )
return retv;
}
else if( int tmp2 = aPinItem->compare( *bPin, aCompareFlags ) )
{
retv = tmp2;
REPORT( wxString::Format( _( "Pin %s differs." ), aPin->GetNumber() ) );
if( !aReporter )
return retv;
}
if( !aReporter )
return retv;
}
}
for( const SCH_ITEM* aFieldItem : aFields )
for( const SCH_PIN* bPin : bPins )
{
const SCH_PIN* aPin = aRhs.GetPin( bPin->GetNumber(), bPin->GetUnit(), bPin->GetBodyStyle() );
if( !aPin )
{
retv = 1;
REPORT( wxString::Format( _( "Missing pin in schematic symbol: %s." ), ITEM_DESC( bPin ) ) );
if( !aReporter )
return retv;
}
}
for( const SCH_FIELD* aField : aFields )
{
const SCH_FIELD* aField = static_cast<const SCH_FIELD*>( aFieldItem );
const SCH_FIELD* bField = nullptr;
int tmp = 0;
if( aField->IsMandatory() )
bField = aRhs.GetField( aField->GetId() );
@ -1687,33 +1690,49 @@ int LIB_SYMBOL::Compare( const LIB_SYMBOL& aRhs, int aCompareFlags, REPORTER* aR
bField = aRhs.GetField( aField->GetName() );
if( !bField )
tmp = 1;
else
tmp = aFieldItem->compare( *bField, aCompareFlags );
{
retv = 1;
REPORT( wxString::Format( _( "Extra field in schematic symbol: %s." ), ITEM_DESC( aField ) ) );
if( tmp )
if( !aReporter )
return retv;
}
else if( int tmp = aField->SCH_ITEM::compare( *bField, aCompareFlags ) )
{
retv = tmp;
REPORT( wxString::Format( _( "%s field differs." ), aField->GetName( false ) ) );
REPORT( wxString::Format( _( "Field '%s' differs: %s; %s." ),
aField->GetName( false ),
ITEM_DESC( aField ),
ITEM_DESC( bField ) ) );
if( !aReporter )
return retv;
}
}
if( int tmp = static_cast<int>( aFields.size() - bFields.size() ) )
for( const SCH_FIELD* bField : bFields )
{
retv = tmp;
REPORT( _( "Field count differs." ) );
const SCH_FIELD* aField = nullptr;
if( !aReporter )
return retv;
if( bField->IsMandatory() )
aField = aRhs.GetField( bField->GetId() );
else
aField = aRhs.GetField( bField->GetName() );
if( !aField )
{
retv = 1;
REPORT( wxString::Format( _( "Missing field in schematic symbol: %s." ), ITEM_DESC( bField ) ) );
if( !aReporter )
return retv;
}
}
if( int tmp = static_cast<int>( m_fpFilters.GetCount() - aRhs.m_fpFilters.GetCount() ) )
{
retv = tmp;
REPORT( _( "Footprint filters differs." ) );
REPORT( _( "Footprint filter count differs." ) );
if( !aReporter )
return retv;

View File

@ -974,9 +974,19 @@ void SCH_FIELD::CalcEdit( const VECTOR2I& aPosition )
wxString SCH_FIELD::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
{
return wxString::Format( _( "Field %s '%s'" ),
UnescapeString( GetName() ),
aFull ? GetShownText( false ) : KIUI::EllipsizeMenuText( GetText() ) );
wxString content = aFull ? GetShownText( false ) : KIUI::EllipsizeMenuText( GetText() );
if( content.IsEmpty() )
{
return wxString::Format( _( "Field %s (empty)" ),
UnescapeString( GetName() ) );
}
else
{
return wxString::Format( _( "Field %s '%s'" ),
UnescapeString( GetName() ),
content );
}
}

View File

@ -1937,10 +1937,20 @@ wxString SCH_DIRECTIVE_LABEL::GetItemDescription( UNITS_PROVIDER* aUnitsProvider
}
else
{
return wxString::Format( _( "Directive Label [%s %s]" ),
UnescapeString( m_fields[0].GetName() ),
aFull ? m_fields[0].GetShownText( false )
: KIUI::EllipsizeMenuText( m_fields[0].GetText() ) );
const SCH_FIELD& firstField = m_fields[0];
wxString content = aFull ? firstField.GetShownText( false ) : KIUI::EllipsizeMenuText( firstField.GetText() );
if( content.IsEmpty() )
{
return wxString::Format( _( "Directive Label [%s (empty)]" ),
UnescapeString( m_fields[0].GetName() ) );
}
else
{
return wxString::Format( _( "Directive Label [%s %s]" ),
UnescapeString( m_fields[0].GetName() ),
content );
}
}
}

View File

@ -1169,7 +1169,7 @@ wxString SCH_SHEET::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFu
{
const SCH_FIELD* sheetnameField = GetField( FIELD_T::SHEET_NAME );
return wxString::Format( _( "Hierarchical Sheet %s" ),
return wxString::Format( _( "Hierarchical Sheet '%s'" ),
aFull ? sheetnameField->GetShownText( false )
: KIUI::EllipsizeMenuText( sheetnameField->GetText() ) );
}

View File

@ -328,7 +328,7 @@ void SCH_SHEET_PIN::GetEndPoints( std::vector<DANGLING_END_ITEM>& aItemList )
wxString SCH_SHEET_PIN::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
{
return wxString::Format( _( "Hierarchical Sheet Pin %s" ),
return wxString::Format( _( "Hierarchical Sheet Pin '%s'" ),
aFull ? GetShownText( false ) : KIUI::EllipsizeMenuText( GetText() ) );
}

View File

@ -422,6 +422,7 @@ std::string KIBIS_MODEL::SpiceDie( const KIBIS_PARAMETER& aParam, int aIndex ) c
result += m_pulldown.Spice( aIndex * 4 + 3, DIEBUFF, PD_GND, PD, supply );
result += "VmeasPD GND " + PD_GND + " 0\n";
result += "BKD GND " + DIE + " i=( i(VmeasPD) * v(KD) )\n";
result += "RKD GND " + DIE + " 100MEG\n"; // For convergence if KU == 0 and KD == 0
}
if( HasPullup() )
@ -429,6 +430,7 @@ std::string KIBIS_MODEL::SpiceDie( const KIBIS_PARAMETER& aParam, int aIndex ) c
result += m_pullup.Spice( aIndex * 4 + 4, PU_PWR, DIEBUFF, PU, supply );
result += "VmeasPU POWER " + PU_PWR + " 0\n";
result += "BKU POWER " + DIE + " i=( -i(VmeasPU) * v(KU) )\n";
result += "RKU POWER " + DIE + " 100MEG\n"; // For convergence if KU == 0 and KD == 0
}
if ( HasPullup() || HasPulldown() )
@ -497,6 +499,24 @@ bool KIBIS_MODEL::HasPOWERClamp() const
return m_POWERClamp.m_entries.size() > 0;
}
std::string KIBIS_MODEL::generateWF( const IbisWaveform& aWF, const KIBIS_PARAMETER& aParam )
{
const IBIS_CORNER supply = aParam.m_supply;
std::string simul = "";
simul += "Vstimuli DIE0 0 pwl(+\n";
IbisWaveform WF = TrimWaveform( aWF );
for( VTtableEntry& entry : WF.m_table.m_entries )
{
simul += doubleToString( entry.t );
simul += " ";
simul += doubleToString( entry.V.value[supply] );
simul += "\n+";
}
simul += ")\n";
return simul;
}
std::string KIBIS_MODEL::generateSquareWave( const std::string& aNode1, const std::string& aNode2,
const std::vector<std::pair<int, double>>& aBits,
@ -778,9 +798,7 @@ std::string KIBIS_PIN::KuKdDriver( KIBIS_MODEL&
case KIBIS_WAVEFORM_TYPE::PRBS:
{
wave->Check( risingWF, fallingWF );
std::vector<std::pair<int, double>> bits = wave->GenerateBitSequence();
bits = SimplifyBitSequence( bits );
simul += aModel.generateSquareWave( "DIE0", "GND", bits, aPair, aParam );
simul += aModel.generateWF( *risingWF, aParam );
break;
}
case KIBIS_WAVEFORM_TYPE::STUCK_HIGH:
@ -1127,6 +1145,74 @@ void KIBIS_PIN::getKuKdTwoWaveforms( KIBIS_MODEL&
}
}
void KIBIS_PIN::WritePinParasitics( std::string& aDest, const KIBIS_PARAMETER& aParam )
{
if( m_Lpin.value[aParam.m_Lpin] <= 0 )
{
aDest += "RPIN DIE0 PIN ";
aDest += doubleToString( m_Rpin.value[aParam.m_Rpin] );
aDest += "\n";
}
else if( m_Rpin.value[aParam.m_Rpin] <= 0 )
{
aDest += "LPIN DIE0 PIN ";
aDest += doubleToString( m_Lpin.value[aParam.m_Lpin] );
aDest += "\nRPIN DIE0 PIN 20\n"; // Damping
}
else
{
aDest += "RPIN DIE0 PARA0 ";
aDest += doubleToString( m_Rpin.value[aParam.m_Rpin] );
aDest += "\nLPIN PARA0 PIN ";
aDest += doubleToString( m_Lpin.value[aParam.m_Lpin] );
aDest += "\nRDAMP PARA0 PIN 20\n"; // Damping
}
if( m_Cpin.value[aParam.m_Cpin] >= 0 )
{
aDest += "CPIN PIN GND ";
aDest += doubleToString( m_Cpin.value[aParam.m_Cpin] );
aDest += "\n";
}
}
void KIBIS_PIN::getKuKd( KIBIS_MODEL& aModel, const KIBIS_PARAMETER& aParam )
{
std::vector<std::pair<IbisWaveform*, IbisWaveform*>> wfPairs = aModel.waveformPairs();
KIBIS_ACCURACY accuracy = aParam.m_accuracy;
IBIS_CORNER supply = aParam.m_supply;
if( true || wfPairs.size() < 1 || accuracy <= KIBIS_ACCURACY::LEVEL_0 )
{
m_KuR.push_back( 0 );
m_KuR.push_back( 1 );
m_KdR.push_back( 1 );
m_KdR.push_back( 0 );
m_tR.push_back( 0 );
m_tR.push_back( aModel.m_ramp.m_rising.value[supply].m_dt / 0.6 );
// We divide by 0.6 because the rise time in ibis is the 20% -> 80% rise time
m_KuF.push_back( 1 );
m_KuF.push_back( 0 );
m_KdF.push_back( 0 );
m_KdF.push_back( 1 );
m_tF.push_back( 0 );
m_tF.push_back( aModel.m_ramp.m_falling.value[supply].m_dt / 0.6 );
}
else if( wfPairs.size() == 1 || accuracy <= KIBIS_ACCURACY::LEVEL_1 )
{
getKuKdOneWaveform( aModel, wfPairs.at( 0 ), aParam );
}
else
{
if( wfPairs.size() > 2 || accuracy <= KIBIS_ACCURACY::LEVEL_2 )
{
Report( _( "Model has more than 2 waveform pairs, using the first two." ),
RPT_SEVERITY_WARNING );
}
getKuKdTwoWaveforms( aModel, wfPairs.at( 0 ), wfPairs.at( 1 ), aParam );
}
}
bool KIBIS_PIN::writeSpiceDriver( std::string& aDest, const std::string& aName, KIBIS_MODEL& aModel,
const KIBIS_PARAMETER& aParam )
@ -1151,6 +1237,10 @@ bool KIBIS_PIN::writeSpiceDriver( std::string& aDest, const std::string& aName,
std::string result;
std::string tmp;
std::pair<std::vector<double>, std::vector<double>> Ks;
getKuKd( aModel, aParam );
result = "\n*Driver model generated by Kicad using Ibis data. ";
result += "\n*Pin number: ";
@ -1164,69 +1254,193 @@ bool KIBIS_PIN::writeSpiceDriver( std::string& aDest, const std::string& aName,
result += " GND PIN \n";
result += "\n";
result += "RPIN 1 PIN ";
result += doubleToString( m_Rpin.value[aParam.m_Rpin] );
result += "\n";
result += "LPIN DIE0 1 ";
result += doubleToString( m_Lpin.value[aParam.m_Lpin] );
result += "\n";
result += "CPIN PIN GND ";
result += doubleToString( m_Cpin.value[aParam.m_Cpin] );
result += "\n";
WritePinParasitics( result, aParam );
std::vector<std::pair<IbisWaveform*, IbisWaveform*>> wfPairs = aModel.waveformPairs();
KIBIS_ACCURACY accuracy = aParam.m_accuracy;
if( wfPairs.size() < 1 || accuracy <= KIBIS_ACCURACY::LEVEL_0 )
bool driving = true;
switch( aParam.m_waveform->GetType() )
{
if( accuracy > KIBIS_ACCURACY::LEVEL_0 )
case KIBIS_WAVEFORM_TYPE::NONE: // Used for three state
driving = false;
result += "VKU KU 0 0\n";
result += "VKD KD 0 0\n";
break;
case KIBIS_WAVEFORM_TYPE::PRBS:
{
KIBIS_WAVEFORM_PRBS* wf = dynamic_cast<KIBIS_WAVEFORM_PRBS*>( aParam.m_waveform );
if( !wf || wf->m_bitrate <= 0 )
{
Report( _( "Model has no waveform pair, using [Ramp] instead, poor accuracy" ),
RPT_SEVERITY_INFO );
result += "VSTIMUL STIMULI 0 0\n";
break;
}
//result += "VSTIMUL STIMULI 0 0 \n";
// Linear feedback shift register
double halfperiod = 0.5 / wf->m_bitrate;
result += ".model flopPRBS0 d_dff(clk_delay=10p set_delay =10p reset_delay=0 ic=0 "
"rise_delay=0 fall_delay=0)\n";
result += ".model flopPRBS1 d_dff(clk_delay=10p set_delay =10p reset_delay=0 ic=1 "
"rise_delay=0 fall_delay=0)\n";
getKuKdNoWaveform( aModel, aParam );
}
else if( wfPairs.size() == 1 || accuracy <= KIBIS_ACCURACY::LEVEL_1 )
{
getKuKdOneWaveform( aModel, wfPairs.at( 0 ), aParam );
}
else
{
if( wfPairs.size() > 2 || accuracy <= KIBIS_ACCURACY::LEVEL_2 )
result += "aprbs1 PRBS1 prbs_clk NULL0PRBS1 NULL1PRBS1 PRBS2 NULL3PRBS1 flopPRBS1\n";
result += "aprbs2 PRBS2 prbs_clk NULL0PRBS2 NULL1PRBS2 PRBS3 NULL3PRBS2 flopPRBS0\n";
result += "aprbs3 PRBS3 prbs_clk NULL0PRBS3 NULL1PRBS3 PRBS4 NULL3PRBS3 flopPRBS0\n";
result += "aprbs4 PRBS4 prbs_clk NULL0PRBS4 NULL1PRBS4 PRBS5 NULL3PRBS4 flopPRBS1\n";
result += ".model xorPRBS d_xor(rise_delay = ";
result += doubleToString( halfperiod ) + " fall_delay = ";
result += doubleToString( halfperiod ) + " input_load = 0.5e-12)\n";
result += "axorPRBS1 [PRBS4 PRBS5] PRBS1 xorPRBS\n";
result += ".model prbs_clock d_osc( cntl_array=[-1 1] freq_array=[1e7 1e7] "
"duty_cycle=0.5 init_phase=0 rise_delay=0 fall_delay=0 )\n";
result += "aprbsclk 0 prbs_clk prbs_clock\n";
result += ".model dac_prbs dac_bridge(out_low =-1 out_high=1 out_undef=1e-12 t_rise "
"=1e-12 t_fall=1e-12)\n";
result += "AbridgePRBS [PRBS5] [STIMULI] dac_prbs\n";
break;
}
case KIBIS_WAVEFORM_TYPE::RECTANGULAR:
{
Report( _( "Model has more than 2 waveform pairs, using the first two." ),
RPT_SEVERITY_WARNING );
KIBIS_WAVEFORM_RECTANGULAR* wf =
dynamic_cast<KIBIS_WAVEFORM_RECTANGULAR*>( aParam.m_waveform );
if( !wf )
{
result += "VSTIMUL STIMULI 0 0\n";
break;
}
result += "VSTIMUL STIMULI 0 PULSE( 0 1 0 0 0 ";
result += doubleToString( wf->m_ton ) + " ";
result += doubleToString( wf->m_toff + wf->m_ton ) + " ";
result += doubleToString( wf->m_cycles ) + ")\n";
break;
}
getKuKdTwoWaveforms( aModel, wfPairs.at( 0 ), wfPairs.at( 1 ), aParam );
case KIBIS_WAVEFORM_TYPE::STUCK_HIGH:
driving = false;
result += "VKU KU 0 1\n";
result += "VKD KD 0 0\n";
break;
case KIBIS_WAVEFORM_TYPE::HIGH_Z:
driving = false;
result += "VKU KU 0 0\n";
result += "VKD KD 0 0\n";
break;
case KIBIS_WAVEFORM_TYPE::STUCK_LOW:
default:
driving = false;
result += "VKU KU 0 0\n";
result += "VKD KD 0 1\n";
break;
}
result += "Vku KU GND pwl ( ";
for( size_t i = 0; i < m_t.size(); i++ )
if( driving )
{
result += doubleToString( m_t.at( i ) );
result += " ";
result += doubleToString( m_Ku.at( i ) );
result += " ";
// A lot of this part could be replace if ngspice implements a STATE() function as implemented in Pspice
// STATE() returns the previous value of a node.
// This is useful to detect rising / falling edges
// Instead, we use 2 ADCs, one for the rising edge threshold, the other for the falling edge threshold
// Then we apply some logic to detect rising edge
// STATE() would also avoid arbritrary delays.
// ADC with rising threshold
result += ".model adc_buff_r adc_bridge(in_low =0.7 in_high=0.7 rise_delay=0 "
"fall_delay=0)\n";
// ADC with falling threshold
result += ".model adc_buff_f adc_bridge(in_low =0.3 in_high=0.3 rise_delay=0 "
"fall_delay=0)\n";
// DAC model
result += ".model dac_buff dac_bridge(out_low =-1 out_high=1 out_undef=0 t_rise =0 "
"t_fall=0)\n";
// Digital elements with some arbritary values...
result += ".model flop1 d_dff(clk_delay=10p set_delay =10p reset_delay=0 ic=2 "
"rise_delay=0 fall_delay=0)\n";
result += ".model and1 d_and(rise_delay=0 fall_delay=0)\n";
result += ".model inv1 d_inverter(rise_delay=0 fall_delay=0)\n";
result += ".model mydel1 delay( delay=3n )\n";
result += ".model mydel2 delay( delay=1p )\n";
// Convert the analog stimuli to digital
result += "AbridgeR [STIMULI] [STIMULIDR] adc_buff_r\n";
// Do the same for falling threshold, but we need to invert it
result += "AbridgeF [STIMULI] [STIMULIDFTMP] adc_buff_f\n";
result += "ainv STIMULIDFTMP STIMULIDF inv1\n";
// Sampling clock -> required because there is no STATE() function in ngspice
result += ".model var_clock d_osc( cntl_array=[-1 1] freq_array=[10e9 10e9] "
"duty_cycle=0.5 init_phase=0 rise_delay=0 fall_delay=0 )\n";
result += "aclk 0 clk var_clock\n";
// Rising edge detection
result += "affr STIMULIDR clk NULL0R NULL1R STIMULIDDR NULL3R flop1\n";
result += "affr2 STIMULIDDR clk NULL4R NULL5R NULL6R STIMULIDDR_DELAYED flop1\n";
result += "aandr [STIMULIDDR STIMULIDDR_DELAYED] OUTPUTR and1\n";
// Falling edge detection
result += "afff STIMULIDF clk NULL0F NULL1F STIMULIDDF NULL3F flop1\n";
result += "afff2 STIMULIDDF clk NULL4F NULL5F NULL6F STIMULIDDF_DELAYED flop1\n";
result += "aandf [STIMULIDDF STIMULIDDF_DELAYED] OUTPUTF and1\n";
//result += "adelay1 STIMULI STIMULI_DELAYED 0 mydel1\n";
// recover the current bit being sent ( 1 or 0 )
result += "addm NULL0M NULL1M OUTPUTR OUTPUTF MODED NULL1M flop1\n";
// Convert to analog domain
result += "Abridge2 [OUTPUTR] [A_RISING_EDGE] dac_buff\n";
result += "Abridge3 [OUTPUTF] [A_FALLING_EDGE] dac_buff\n";
result += "Abridge4 [MODED] [A_MODE] dac_buff\n";
// Store the current time
result += "BTIMESTAMP TIMESTAMP 0 V=TIME\n";
// Store the time the last rising edge occured. This will be used as an offset in the KD/KU vs time curve
result += "adelayrise LAST_RISE LAST_RISE_DELAYED 0 mydel2\n";
result += "ELR LAST_RISE 0 vol = ( V( A_RISING_EDGE ) > 0.2 ? V(TIMESTAMP) : "
"V(LAST_RISE_DELAYED) )\n";
// Store the time the last falling edge occured. This will be used as an offset in the KD/KU vs time curve
result += "adelayfall LAST_FALL LAST_FALL_DELAYED 0 mydel2\n";
result += "ELF LAST_FALL 0 vol = ( V( A_FALLING_EDGE ) > 0.2 ? V(TIMESTAMP) : "
"V(LAST_FALL_DELAYED) )\n";
result += "EFALLTIME FALLTIME 0 vol=V(TIMESTAMP,LAST_FALL)\n";
result += "ERISETIME RISETIME 0 vol=V(TIMESTAMP,LAST_RISE)\n";
// Generate the rising / falling waveform for KU
result += "ERISEWFKU RISEWFKU 0 TABLE {V(RISETIME)} =";
for( int i = 0; i < m_tR.size(); i++ )
{
result += " ( " + doubleToString( m_tR.at( i ) ) + " , "
+ doubleToString( m_KuR.at( i ) ) + " )";
}
result += "\n";
result += "EFALLWFKU FALLWFKU 0 TABLE {V(FALLTIME)} =";
for( int i = 0; i < m_tR.size(); i++ )
{
result += " ( " + doubleToString( m_tF.at( i ) ) + " , "
+ doubleToString( m_KuF.at( i ) ) + " )";
}
result += "\n\n";
result += "ERISEWFKD RISEWFKD 0 TABLE {V(RISETIME)} =";
for( int i = 0; i < m_tR.size(); i++ )
{
result += " ( " + doubleToString( m_tR.at( i ) ) + " , "
+ doubleToString( m_KdR.at( i ) ) + " )";
}
result += "\n";
result += "EFALLWFKD FALLWFKD 0 TABLE {V(FALLTIME)} =";
for( int i = 0; i < m_tR.size(); i++ )
{
result += " ( " + doubleToString( m_tF.at( i ) ) + " , "
+ doubleToString( m_KdF.at( i ) ) + " )";
}
result += "\n";
// Use the correct waveform depending if the last edge was rising or falling
result += "EKU KU 0 vol= ( V(A_MODE) == 1 ? V(RISEWFKU) : V(A_MODE) == -1 ? "
"V(FALLWFKU) : 0 )\n";
result += "EKD KD 0 vol= ( V(A_MODE) == 1 ? V(RISEWFKD) : V(A_MODE) == -1 ? "
"V(FALLWFKD) : 0 )\n";
}
result += ") \n";
result += "Vkd KD GND pwl ( ";
for( size_t i = 0; i < m_t.size(); i++ )
{
result += doubleToString( m_t.at( i ) );
result += " ";
result += doubleToString( m_Kd.at( i ) );
result += " ";
}
result += ") \n";
result += aModel.SpiceDie( aParam, 0 );
result += "\n.ENDS DRIVER\n\n";
@ -1265,16 +1479,7 @@ bool KIBIS_PIN::writeSpiceDevice( std::string& aDest, const std::string& aName,
result += aName;
result += " GND PIN\n";
result += "\n";
result += "\n";
result += "RPIN 1 PIN ";
result += doubleToString( m_Rpin.value[aParam.m_Rpin] );
result += "\n";
result += "LPIN DIE0 1 ";
result += doubleToString( m_Lpin.value[aParam.m_Lpin] );
result += "\n";
result += "CPIN PIN GND ";
result += doubleToString( m_Cpin.value[aParam.m_Cpin] );
result += "\n";
WritePinParasitics( result, aParam );
result += "Vku KU GND pwl ( 0 0 )\n";

View File

@ -327,6 +327,7 @@ public:
const std::pair<IbisWaveform*, IbisWaveform*>& aPair,
const KIBIS_PARAMETER& aParam );
std::string generateWF( const IbisWaveform& aWF, const KIBIS_PARAMETER& aParam );
/** @brief Copy a waveform, and substract the first value to all samples
*
@ -361,9 +362,13 @@ public:
KIBIS_COMPONENT* m_parent;
std::vector<double> m_t, m_Ku, m_Kd;
std::vector<double> m_tR, m_KuR, m_KdR;
std::vector<double> m_tF, m_KuF, m_KdF;
std::vector<KIBIS_MODEL*> m_models;
void WritePinParasitics( std::string& aDest, const KIBIS_PARAMETER& aParam );
bool writeSpiceDriver( std::string& aDest, const std::string& aName, KIBIS_MODEL& aModel,
const KIBIS_PARAMETER& aParam );
bool writeSpiceDiffDriver( std::string& aDest, const std::string& aName, KIBIS_MODEL& aModel,
@ -438,6 +443,7 @@ public:
* @param aSimul The simulation to run, multiline spice directives
*/
void getKuKdFromFile( const std::string& aSimul );
void getKuKd( KIBIS_MODEL& aModel, const KIBIS_PARAMETER& aParam );
KIBIS_PIN* m_complementaryPin = nullptr;

View File

@ -120,6 +120,7 @@ set( PCBNEW_DIALOGS
dialogs/dialog_outset_items_base.cpp
dialogs/dialog_pad_properties.cpp
dialogs/dialog_pad_properties_base.cpp
dialogs/dialog_fp_edit_pad_table.cpp
dialogs/dialog_plot.cpp
dialogs/dialog_plot_base.cpp
dialogs/dialog_pns_diff_pair_dimensions.cpp

View File

@ -0,0 +1,689 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "dialog_fp_edit_pad_table.h"
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/dcclient.h>
#include <pcb_shape.h>
#include <widgets/wx_grid.h>
#include <widgets/grid_text_helpers.h>
#include <widgets/grid_combobox.h>
#include <base_units.h>
#include <units_provider.h>
#include <board.h>
#include <footprint.h>
#include <footprint_edit_frame.h>
// Helper to map shape string to PAD_SHAPE
static PAD_SHAPE ShapeFromString( const wxString& shape )
{
if( shape == _( "Oval" ) ) return PAD_SHAPE::OVAL;
if( shape == _( "Rectangle" ) ) return PAD_SHAPE::RECTANGLE;
if( shape == _( "Trapezoid" ) ) return PAD_SHAPE::TRAPEZOID;
if( shape == _( "Rounded rectangle" ) ) return PAD_SHAPE::ROUNDRECT;
if( shape == _( "Chamfered rectangle" ) ) return PAD_SHAPE::CHAMFERED_RECT;
if( shape == _( "Custom shape" ) ) return PAD_SHAPE::CUSTOM;
return PAD_SHAPE::CIRCLE;
}
DIALOG_FP_EDIT_PAD_TABLE::DIALOG_FP_EDIT_PAD_TABLE( PCB_BASE_FRAME* aParent, FOOTPRINT* aFootprint ) :
DIALOG_SHIM( (wxWindow*)aParent, wxID_ANY, _( "Pad Table" ), wxDefaultPosition, wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
m_grid( nullptr ),
m_footprint( aFootprint ),
m_unitsProvider( std::make_unique<UNITS_PROVIDER>( pcbIUScale, GetUserUnits() ) )
{
wxBoxSizer* topSizer = new wxBoxSizer( wxVERTICAL );
m_grid = new WX_GRID( this, wxID_ANY );
m_grid->CreateGrid( 0, 11 );
m_grid->SetColLabelValue( COL_NUMBER, _( "Number" ) );
m_grid->SetColLabelValue( COL_TYPE, _( "Type" ) );
m_grid->SetColLabelValue( COL_SHAPE, _( "Shape" ) );
m_grid->SetColLabelValue( COL_POS_X, _( "X Position" ) );
m_grid->SetColLabelValue( COL_POS_Y, _( "Y Position" ) );
m_grid->SetColLabelValue( COL_SIZE_X, _( "Size X" ) );
m_grid->SetColLabelValue( COL_SIZE_Y, _( "Size Y" ) );
m_grid->SetColLabelValue( COL_DRILL_X, _( "Drill X" ) );
m_grid->SetColLabelValue( COL_DRILL_Y, _( "Drill Y" ) );
m_grid->SetColLabelValue( COL_P2D_LENGTH, _( "Pad->Die Length" ) );
m_grid->SetColLabelValue( COL_P2D_DELAY, _( "Pad->Die Delay" ) );
m_grid->EnableEditing( true );
wxGridCellAttr* attr;
// Type column editor (attribute)
attr = new wxGridCellAttr;
{
wxArrayString typeNames;
typeNames.push_back( _( "Through-hole" ) ); // PTH
typeNames.push_back( _( "SMD" ) ); // SMD
typeNames.push_back( _( "Connector" ) ); // CONN SMD? (use CONN?)
typeNames.push_back( _( "NPTH" ) ); // NPTH
typeNames.push_back( _( "Aperture" ) ); // inferred copper-less
attr->SetEditor( new GRID_CELL_COMBOBOX( typeNames ) );
}
m_grid->SetColAttr( COL_TYPE, attr );
attr = new wxGridCellAttr;
wxArrayString shapeNames;
shapeNames.push_back( _( "Circle" ) );
shapeNames.push_back( _( "Oval" ) );
shapeNames.push_back( _( "Rectangle" ) );
shapeNames.push_back( _( "Trapezoid" ) );
shapeNames.push_back( _( "Rounded rectangle" ) );
shapeNames.push_back( _( "Chamfered rectangle" ) );
shapeNames.push_back( _( "Custom shape" ) );
attr->SetEditor( new GRID_CELL_COMBOBOX( shapeNames ) );
m_grid->SetColAttr( COL_SHAPE, attr );
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_POS_X, attr );
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_POS_Y, attr );
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_SIZE_X, attr );
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_SIZE_Y, attr );
// Drill X
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_DRILL_X, attr );
// Drill Y
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_DRILL_Y, attr );
// Pad->Die Length
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_P2D_LENGTH, attr );
// Pad->Die Delay
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_TEXT_EDITOR() );
m_grid->SetColAttr( COL_P2D_DELAY, attr );
m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_X );
m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_POS_Y );
m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_X );
m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_SIZE_Y );
m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_X );
m_grid->SetUnitsProvider( m_unitsProvider.get(), COL_DRILL_Y );
m_grid->SetAutoEvalCols( { COL_POS_X, COL_POS_Y, COL_SIZE_X, COL_SIZE_Y, COL_DRILL_X, COL_DRILL_Y } );
topSizer->Add( m_grid, 1, wxEXPAND | wxALL, 5 );
wxStdDialogButtonSizer* buttons = new wxStdDialogButtonSizer();
buttons->AddButton( new wxButton( this, wxID_OK ) );
buttons->AddButton( new wxButton( this, wxID_CANCEL ) );
buttons->Realize();
topSizer->Add( buttons, 0, wxALIGN_RIGHT | wxALL, 5 );
SetSizerAndFit( topSizer );
CaptureOriginalPadState();
Populate();
// Bind cell change handlers for real-time updates
m_grid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_FP_EDIT_PAD_TABLE::OnCellChanged, this );
m_grid->Bind( wxEVT_GRID_SELECT_CELL, &DIALOG_FP_EDIT_PAD_TABLE::OnSelectCell, this );
// Listen for cancel
Bind(
wxEVT_BUTTON,
[this]( wxCommandEvent& aEvt )
{
m_cancelled = true;
aEvt.Skip();
},
wxID_CANCEL );
finishDialogSettings();
}
DIALOG_FP_EDIT_PAD_TABLE::~DIALOG_FP_EDIT_PAD_TABLE()
{
if( m_cancelled )
RestoreOriginalPadState();
}
void DIALOG_FP_EDIT_PAD_TABLE::Populate()
{
if( !m_footprint )
return;
int row = 0;
for( PAD* pad : m_footprint->Pads() )
{
m_grid->AppendRows( 1 );
m_grid->SetCellValue( row, COL_NUMBER, pad->GetNumber() );
// Pad attribute to string
wxString attrStr;
switch( pad->GetAttribute() )
{
case PAD_ATTRIB::PTH: attrStr = _( "Through-hole" ); break;
case PAD_ATTRIB::SMD: attrStr = _( "SMD" ); break;
case PAD_ATTRIB::CONN: attrStr = _( "Connector" ); break;
case PAD_ATTRIB::NPTH: attrStr = _( "NPTH" ); break;
default: attrStr = _( "Through-hole" ); break;
}
VECTOR2I size = pad->GetSize( PADSTACK::ALL_LAYERS );
if( pad->IsAperturePad() )
attrStr = _( "Aperture" );
m_grid->SetCellValue( row, COL_TYPE, attrStr );
m_grid->SetCellValue( row, COL_SHAPE, pad->ShowPadShape( PADSTACK::ALL_LAYERS ) );
m_grid->SetCellValue( row, COL_POS_X, m_unitsProvider->StringFromValue( pad->GetPosition().x, true ) );
m_grid->SetCellValue( row, COL_POS_Y, m_unitsProvider->StringFromValue( pad->GetPosition().y, true ) );
m_grid->SetCellValue( row, COL_SIZE_X, m_unitsProvider->StringFromValue( size.x, true ) );
m_grid->SetCellValue( row, COL_SIZE_Y, m_unitsProvider->StringFromValue( size.y, true ) );
// Drill values (only meaningful for PTH or NPTH). Leave empty otherwise.
if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
{
VECTOR2I drill = pad->GetDrillSize();
if( drill.x > 0 )
m_grid->SetCellValue( row, COL_DRILL_X, m_unitsProvider->StringFromValue( drill.x, true ) );
if( drill.y > 0 )
m_grid->SetCellValue( row, COL_DRILL_Y, m_unitsProvider->StringFromValue( drill.y, true ) );
}
else
m_grid->SetCellValue( row, COL_SIZE_X, m_unitsProvide
{
m_grid->SetReadOnly( row, COL_DRILL_X, true );
m_grid->SetReadOnly( row, COL_DRILL_Y, true );
}
// Pad to die metrics
if( pad->GetPadToDieLength() )
m_grid->SetCellValue( row, COL_P2D_LENGTH, m_unitsProvider->StringFromValue( pad->GetPadToDieLength(), true ) );
if( pad->GetPadToDieDelay() )
m_grid->SetCellValue( row, COL_P2D_DELAY, wxString::Format( "%d", pad->GetPadToDieDelay() ) );
row++;
}
// Hide row labels per requirements
m_grid->HideRowLabels();
// Auto size the data columns first to get reasonable initial widths
m_grid->AutoSizeColumns();
// Ensure the Shape column (index 1) is wide enough for the longest translated
// shape text plus the dropdown arrow / padding. We compute a max text width
// using a device context and add a platform neutral padding.
{
wxClientDC dc( m_grid );
dc.SetFont( m_grid->GetFont() );
wxArrayString shapeNames;
shapeNames.push_back( _( "Circle" ) );
shapeNames.push_back( _( "Oval" ) );
shapeNames.push_back( _( "Rectangle" ) );
shapeNames.push_back( _( "Trapezoid" ) );
shapeNames.push_back( _( "Rounded rectangle" ) );
shapeNames.push_back( _( "Chamfered rectangle" ) );
shapeNames.push_back( _( "Custom shape" ) );
int maxWidth = 0;
for( const wxString& str : shapeNames )
{
int w, h;
dc.GetTextExtent( str, &w, &h );
maxWidth = std::max( maxWidth, w );
}
// Add padding for internal cell margins + dropdown control.
int padding = FromDIP( 30 ); // heuristic: 2*margin + arrow button
m_grid->SetColSize( COL_SHAPE, maxWidth + padding );
}
// Record initial proportions for proportional resizing later.
InitColumnProportions();
Bind( wxEVT_SIZE, &DIALOG_FP_EDIT_PAD_TABLE::OnSize, this );
// Run an initial proportional resize using current client size so columns
// respect proportions immediately.
wxSizeEvent sizeEvt( GetSize(), GetId() );
CallAfter(
[this, sizeEvt]
{
wxSizeEvent evt( sizeEvt );
this->OnSize( evt );
} );
// If pads exist, select the first row to show initial highlight
if( m_grid->GetNumberRows() > 0 )
{
m_grid->SetGridCursor( 0, 0 );
// Construct event with required parameters (id, type, obj, row, col,...)
wxGridEvent ev( m_grid->GetId(), wxEVT_GRID_SELECT_CELL, m_grid, 0, 0, -1, -1, true );
OnSelectCell( ev );
}
}
void DIALOG_FP_EDIT_PAD_TABLE::CaptureOriginalPadState()
{
m_originalPads.clear();
if( !m_footprint )
return;
for( PAD* pad : m_footprint->Pads() )
{
PAD_SNAPSHOT snap;
snap.number = pad->GetNumber();
snap.shape = pad->GetShape( PADSTACK::ALL_LAYERS );
snap.position = pad->GetPosition();
snap.size = pad->GetSize( PADSTACK::ALL_LAYERS );
snap.attribute = pad->GetAttribute();
snap.drillSize = pad->GetDrillSize();
snap.padToDieLength= pad->GetPadToDieLength();
snap.padToDieDelay = pad->GetPadToDieDelay();
m_originalPads.push_back( snap );
}
}
void DIALOG_FP_EDIT_PAD_TABLE::RestoreOriginalPadState()
{
if( !m_footprint )
return;
size_t idx = 0;
PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
for( PAD* pad : m_footprint->Pads() )
{
if( idx >= m_originalPads.size() )
break;
const PAD_SNAPSHOT& snap = m_originalPads[idx++];
pad->SetNumber( snap.number );
pad->SetShape( PADSTACK::ALL_LAYERS, snap.shape );
pad->SetAttribute( snap.attribute );
pad->SetPosition( snap.position );
pad->SetSize( PADSTACK::ALL_LAYERS, snap.size );
pad->SetDrillSize( snap.drillSize );
pad->SetPadToDieLength( snap.padToDieLength );
pad->SetPadToDieDelay( snap.padToDieDelay );
pad->ClearBrightened();
if( canvas )
canvas->GetView()->Update( pad, KIGFX::REPAINT );
}
if( canvas )
{
canvas->GetView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
canvas->ForceRefresh();
}
}
bool DIALOG_FP_EDIT_PAD_TABLE::TransferDataFromWindow()
{
if( !m_grid->CommitPendingChanges() )
return false;
if( !m_footprint )
return true;
int row = 0;
for( PAD* pad : m_footprint->Pads() )
{
pad->SetNumber( m_grid->GetCellValue( row, COL_NUMBER ) );
wxString typeStr = m_grid->GetCellValue( row, COL_TYPE );
if( typeStr == _( "Through-hole" ) )
pad->SetAttribute( PAD_ATTRIB::PTH );
else if( typeStr == _( "SMD" ) )
pad->SetAttribute( PAD_ATTRIB::SMD );
else if( typeStr == _( "Connector" ) )
pad->SetAttribute( PAD_ATTRIB::CONN );
else if( typeStr == _( "NPTH" ) )
pad->SetAttribute( PAD_ATTRIB::NPTH );
// Aperture derived by copper-less layers; do not overwrite attribute here.
wxString shape = m_grid->GetCellValue( row, COL_SHAPE );
PAD_SHAPE newShape = ShapeFromString( shape );
pad->SetShape( PADSTACK::ALL_LAYERS, newShape );
VECTOR2I pos;
pos.x = m_grid->GetUnitValue( row, COL_POS_X );
pos.y = m_grid->GetUnitValue( row, COL_POS_Y );
pad->SetPosition( pos );
VECTOR2I size;
size.x = m_grid->GetUnitValue( row, COL_SIZE_X );
size.y = m_grid->GetUnitValue( row, COL_SIZE_Y );
pad->SetSize( PADSTACK::ALL_LAYERS, size );
// Drill sizes (only if attribute allows)
if( pad->GetAttribute() == PAD_ATTRIB::PTH || pad->GetAttribute() == PAD_ATTRIB::NPTH )
{
int dx = m_grid->GetUnitValue( row, COL_DRILL_X );
int dy = m_grid->GetUnitValue( row, COL_DRILL_Y );
if( dx > 0 || dy > 0 )
{
if( dx <= 0 ) dx = dy;
if( dy <= 0 ) dy = dx;
pad->SetDrillSize( { dx, dy } );
}
}
// Pad->Die
wxString delayStr = m_grid->GetCellValue( row, COL_P2D_DELAY );
wxString lenStr = m_grid->GetCellValue( row, COL_P2D_LENGTH );
if( !lenStr.IsEmpty() )
pad->SetPadToDieLength( m_grid->GetUnitValue( row, COL_P2D_LENGTH ) );
else
pad->SetPadToDieLength( 0 );
if( !delayStr.IsEmpty() )
{
long delayVal;
if( delayStr.ToLong( &delayVal ) )
pad->SetPadToDieDelay( (int) delayVal );
else
pad->SetPadToDieDelay( 0 );
}
else
pad->SetPadToDieDelay( 0 );
row++;
}
return true;
}
void DIALOG_FP_EDIT_PAD_TABLE::InitColumnProportions()
{
m_colProportions.clear();
m_minColWidths.clear();
if( !m_grid )
return;
// Only consider the actual data columns (all of them since row labels are hidden)
int cols = m_grid->GetNumberCols();
int total = 0;
std::vector<int> widths;
widths.reserve( cols );
for( int c = 0; c < cols; ++c )
{
int w = m_grid->GetColSize( c );
widths.push_back( w );
total += w;
}
if( total <= 0 )
return;
for( int w : widths )
{
m_colProportions.push_back( (double) w / (double) total );
m_minColWidths.push_back( w );
}
}
void DIALOG_FP_EDIT_PAD_TABLE::OnSize( wxSizeEvent& aEvent )
{
if( m_colProportions.empty() )
{
aEvent.Skip();
return;
}
// Compute available total width for columns and resize keeping proportions.
int cols = m_grid->GetNumberCols();
int available = 0;
for( int c = 0; c < cols; ++c )
available += m_grid->GetColSize( c );
// Use client size of grid minus scrollbar estimate to better distribute.
int clientW = m_grid->GetClientSize().x;
if( clientW > 0 )
available = clientW; // prefer actual client width
int used = 0;
for( int c = 0; c < cols; ++c )
{
int target = (int) std::round( m_colProportions[c] * available );
target = std::max( target, m_minColWidths[c] );
// Defer last column to absorb rounding diff.
if( c == cols - 1 )
target = std::max( available - used, m_minColWidths[c] );
m_grid->SetColSize( c, target );
used += target;
}
aEvent.Skip();
}
void DIALOG_FP_EDIT_PAD_TABLE::OnCellChanged( wxGridEvent& aEvent )
{
int row = aEvent.GetRow();
int col = aEvent.GetCol();
if( !m_footprint )
return;
// row -> pad mapping is current order of Pads() iteration; rebuild each time (pads count small)
int idx = 0;
PAD* target = nullptr;
for( PAD* pad : m_footprint->Pads() )
{
if( idx == row ) { target = pad; break; }
++idx;
}
if( !target )
return;
bool needCanvasRefresh = false;
switch( col )
{
case COL_NUMBER:
target->SetNumber( m_grid->GetCellValue( row, col ) );
break;
case COL_TYPE:
{
wxString typeStr = m_grid->GetCellValue( row, col );
PAD_ATTRIB newAttr = target->GetAttribute();
if( typeStr == _( "Through-hole" ) )
newAttr = PAD_ATTRIB::PTH;
else if( typeStr == _( "SMD" ) )
newAttr = PAD_ATTRIB::SMD;
else if( typeStr == _( "Connector" ) )
newAttr = PAD_ATTRIB::CONN;
else if( typeStr == _( "NPTH" ) )
newAttr = PAD_ATTRIB::NPTH;
if( newAttr != target->GetAttribute() )
{
target->SetAttribute( newAttr );
// Toggle drill columns read-only state dynamically.
bool drillsEditable = ( newAttr == PAD_ATTRIB::PTH || newAttr == PAD_ATTRIB::NPTH );
m_grid->SetReadOnly( row, COL_DRILL_X, !drillsEditable );
m_grid->SetReadOnly( row, COL_DRILL_Y, !drillsEditable );
needCanvasRefresh = true;
}
break;
}
case COL_SHAPE:
target->SetShape( PADSTACK::ALL_LAYERS, ShapeFromString( m_grid->GetCellValue( row, col ) ) );
needCanvasRefresh = true;
break;
case COL_POS_X:
case COL_POS_Y:
{
VECTOR2I pos = target->GetPosition();
if( col == COL_POS_X )
pos.x = m_grid->GetUnitValue( row, col );
else
pos.y = m_grid->GetUnitValue( row, col );
target->SetPosition( pos );
needCanvasRefresh = true;
break;
}
case COL_SIZE_X:
case COL_SIZE_Y:
{
VECTOR2I size = target->GetSize( PADSTACK::ALL_LAYERS );
if( col == COL_SIZE_X )
size.x = m_grid->GetUnitValue( row, col );
else
size.y = m_grid->GetUnitValue( row, col );
target->SetSize( PADSTACK::ALL_LAYERS, size );
needCanvasRefresh = true;
break;
}
case COL_DRILL_X:
case COL_DRILL_Y:
{
if( target->GetAttribute() == PAD_ATTRIB::PTH || target->GetAttribute() == PAD_ATTRIB::NPTH )
{
int dx = m_grid->GetUnitValue( row, COL_DRILL_X );
int dy = m_grid->GetUnitValue( row, COL_DRILL_Y );
if( dx > 0 || dy > 0 )
{
if( dx <= 0 ) dx = dy;
if( dy <= 0 ) dy = dx;
target->SetDrillSize( { dx, dy } );
needCanvasRefresh = true;
}
}
break;
}
case COL_P2D_LENGTH:
if( !m_grid->GetCellValue( row, col ).IsEmpty() )
target->SetPadToDieLength( m_grid->GetUnitValue( row, col ) );
break;
case COL_P2D_DELAY:
{
wxString d = m_grid->GetCellValue( row, col );
long val;
if( d.ToLong( &val ) )
target->SetPadToDieDelay( (int) val );
break;
}
default:
break;
}
// Request redraw (simple approach)
target->SetDirty();
if( needCanvasRefresh )
{
if( PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() ) )
base->GetCanvas()->ForceRefresh();
}
}
void DIALOG_FP_EDIT_PAD_TABLE::OnSelectCell( wxGridEvent& aEvent )
{
int row = aEvent.GetRow();
if( !m_footprint )
return;
PCB_BASE_FRAME* base = dynamic_cast<PCB_BASE_FRAME*>( GetParent() );
PCB_DRAW_PANEL_GAL* canvas = base ? base->GetCanvas() : nullptr;
// Clear existing pad selections
for( PAD* pad : m_footprint->Pads() )
{
if( pad->IsBrightened() )
{
pad->ClearBrightened();
if( canvas )
canvas->GetView()->Update( pad, KIGFX::REPAINT );
}
}
int idx = 0;
for( PAD* pad : m_footprint->Pads() )
{
if( idx == row )
{
pad->SetBrightened();
if( canvas )
{
canvas->GetView()->Update( pad, KIGFX::REPAINT );
canvas->ForceRefresh();
}
break;
}
++idx;
}
}

View File

@ -0,0 +1,59 @@
#pragma once
#include "dialog_shim.h"
#include <memory>
#include <vector>
#include <pad.h>
class PCB_BASE_FRAME;
class FOOTPRINT;
class WX_GRID;
class UNITS_PROVIDER;
class DIALOG_FP_EDIT_PAD_TABLE : public DIALOG_SHIM
{
public:
DIALOG_FP_EDIT_PAD_TABLE( PCB_BASE_FRAME* aParent, FOOTPRINT* aFootprint );
~DIALOG_FP_EDIT_PAD_TABLE() override;
bool TransferDataFromWindow() override;
private:
void Populate();
void OnSize( wxSizeEvent& aEvent );
// Proportional resize support
std::vector<double> m_colProportions; // relative widths captured after init
std::vector<int> m_minColWidths; // initial (minimum) widths
void InitColumnProportions();
struct PAD_SNAPSHOT
{
wxString number;
PAD_SHAPE shape;
VECTOR2I position;
VECTOR2I size;
PAD_ATTRIB attribute;
VECTOR2I drillSize;
int padToDieLength = 0;
int padToDieDelay = 0;
};
std::vector<PAD_SNAPSHOT> m_originalPads; // original pad data for cancel rollback
bool m_cancelled = false; // set if user hit cancel
void CaptureOriginalPadState();
void RestoreOriginalPadState();
void OnCellChanged( wxGridEvent& aEvent );
void OnSelectCell( wxGridEvent& aEvent );
// Column indices (after adding Type column)
enum COLS { COL_NUMBER = 0, COL_TYPE, COL_SHAPE, COL_POS_X, COL_POS_Y, COL_SIZE_X, COL_SIZE_Y,
COL_DRILL_X, COL_DRILL_Y, COL_P2D_LENGTH, COL_P2D_DELAY };
WX_GRID* m_grid;
FOOTPRINT* m_footprint;
std::unique_ptr<UNITS_PROVIDER> m_unitsProvider;
};

View File

@ -542,7 +542,8 @@ void DRC_ENGINE::compileRules()
}
if( error_semaphore.HasMessageOfSeverity( RPT_SEVERITY_ERROR ) )
THROW_PARSE_ERROR( wxT( "Parse error" ), rule->m_Name, rule->m_Condition->GetExpression(), 0, 0 );
THROW_PARSE_ERROR( wxT( "Parse error" ), rule->m_Name,
TO_UTF8( rule->m_Condition->GetExpression() ), 0, 0 );
for( const DRC_CONSTRAINT& constraint : rule->m_Constraints )
{

View File

@ -1421,6 +1421,7 @@ void FOOTPRINT_EDIT_FRAME::setupUIConditions()
mgr->SetConditions( PCB_ACTIONS::placeImportedGraphics, ENABLE( haveFootprintCond ) );
mgr->SetConditions( PCB_ACTIONS::footprintProperties, ENABLE( haveFootprintCond ) );
mgr->SetConditions( PCB_ACTIONS::padTable, ENABLE( haveFootprintCond ) );
mgr->SetConditions( PCB_ACTIONS::editTextAndGraphics, ENABLE( haveFootprintCond ) );
mgr->SetConditions( PCB_ACTIONS::checkFootprint, ENABLE( haveFootprintCond ) );
mgr->SetConditions( PCB_ACTIONS::repairFootprint, ENABLE( haveFootprintCond ) );

View File

@ -119,6 +119,7 @@ void FOOTPRINT_EDIT_FRAME::doReCreateMenuBar()
editMenu->AppendSeparator();
editMenu->Add( PCB_ACTIONS::editTextAndGraphics );
editMenu->Add( PCB_ACTIONS::padTable );
editMenu->Add( PCB_ACTIONS::defaultPadProperties );
editMenu->Add( PCB_ACTIONS::enumeratePads );
editMenu->Add( ACTIONS::gridOrigin );

View File

@ -166,6 +166,7 @@ std::optional<TOOLBAR_CONFIGURATION> FOOTPRINT_EDIT_TOOLBAR_SETTINGS::DefaultToo
config.AppendSeparator()
.AppendAction( PCB_ACTIONS::footprintProperties )
.AppendAction( PCB_ACTIONS::padTable )
.AppendAction( PCB_ACTIONS::defaultPadProperties )
.AppendAction( ACTIONS::showDatasheet )
.AppendAction( PCB_ACTIONS::checkFootprint );

View File

@ -37,6 +37,7 @@
#include <pcbnew_settings.h>
#include <board_commit.h>
#include <dialogs/dialog_push_pad_properties.h>
#include <dialogs/dialog_fp_edit_pad_table.h>
#include <tools/pcb_actions.h>
#include <tools/pcb_grid_helper.h>
#include <tools/pcb_selection_tool.h>
@ -109,6 +110,7 @@ bool PAD_TOOL::Init()
if( m_isFootprintEditor )
{
menu.AddItem( PCB_ACTIONS::padTable, SELECTION_CONDITIONS::ShowAlways, 400 );
menu.AddItem( PCB_ACTIONS::enumeratePads, SELECTION_CONDITIONS::ShowAlways, 400 );
menu.AddItem( PCB_ACTIONS::recombinePad, recombineCondition, 400 );
menu.AddItem( PCB_ACTIONS::explodePad, explodeCondition, 400 );
@ -911,6 +913,22 @@ std::vector<PCB_SHAPE*> PAD_TOOL::RecombinePad( PAD* aPad, bool aIsDryRun )
return aPad->Recombine( aIsDryRun, maxError );
}
int PAD_TOOL::PadTable( const TOOL_EVENT& aEvent )
{
if( !m_isFootprintEditor || InPadEditMode() )
return 0;
FOOTPRINT* footprint = board()->GetFirstFootprint();
if( !footprint )
return 0;
DIALOG_FP_EDIT_PAD_TABLE dlg( frame(), footprint );
dlg.ShowQuasiModal();
return 0;
}
void PAD_TOOL::setTransitions()
{
@ -920,6 +938,7 @@ void PAD_TOOL::setTransitions()
Go( &PAD_TOOL::PlacePad, PCB_ACTIONS::placePad.MakeEvent() );
Go( &PAD_TOOL::EnumeratePads, PCB_ACTIONS::enumeratePads.MakeEvent() );
Go( &PAD_TOOL::PadTable, PCB_ACTIONS::padTable.MakeEvent() );
Go( &PAD_TOOL::EditPad, PCB_ACTIONS::explodePad.MakeEvent() );
Go( &PAD_TOOL::EditPad, PCB_ACTIONS::recombinePad.MakeEvent() );

View File

@ -46,6 +46,8 @@ public:
*/
int EnumeratePads( const TOOL_EVENT& aEvent );
int PadTable( const TOOL_EVENT& aEvent );
/**
* Place a pad in footprint editor.
*/

View File

@ -899,6 +899,13 @@ TOOL_ACTION PCB_ACTIONS::footprintProperties( TOOL_ACTION_ARGS()
.FriendlyName( _( "Footprint Properties..." ) )
.Icon( BITMAPS::module_options ) );
TOOL_ACTION PCB_ACTIONS::padTable( TOOL_ACTION_ARGS()
.Name( "pcbnew.ModuleEditor.padTable" )
.Scope( AS_GLOBAL )
.FriendlyName( _( "Pad Table..." ) )
.Tooltip( _( "Displays pad table for bulk editing of pads" ) )
.Icon( BITMAPS::pin_table ) );
TOOL_ACTION PCB_ACTIONS::checkFootprint( TOOL_ACTION_ARGS()
.Name( "pcbnew.ModuleEditor.checkFootprint" )
.Scope( AS_GLOBAL )

View File

@ -488,6 +488,7 @@ public:
static TOOL_ACTION footprintProperties;
static TOOL_ACTION defaultPadProperties;
static TOOL_ACTION padTable;
static TOOL_ACTION checkFootprint;

View File

@ -0,0 +1,3 @@
include("${VCPKG_ROOT_DIR}/triplets/x64-windows.cmake")
set(VCPKG_DISABLE_COMPILER_TRACKING ON)