Compare commits

...

23 Commits

Author SHA1 Message Date
Dhineshkumar S
29ce09fae8 Merge branch 'fix-text-bold-thickness-v2-18975' into 'master'
Draft: Fix for ensuring thickness reverts only when the user manually sets it.

Closes #18975

See merge request kicad/code/kicad!2182
2025-09-13 14:38:56 +00:00
Jan Wichmann
36e38cd60a ADDED: Sort the templates alphabetically, leaving the default template at the top. 2025-09-13 03:15:44 -07:00
Seth Hillbrand
5fdfff2b9c Prevent lines from drawing on netclass layer
Only wires and busses get netclasses

Fixes https://gitlab.com/kicad/code/kicad/issues/21731
2025-09-13 02:51:00 -07:00
Seth Hillbrand
733a379ca3 Unify sheet name validators and apply to properties
Properties panel and properties dialog now share a validator with shared
strings

Fixes https://gitlab.com/kicad/code/kicad/issues/21689
2025-09-13 02:42:03 -07:00
Seth Hillbrand
497afffd48 Properly order netlist pages
We cannot reorder these without updating the defines.  So, keep the GUI
in line with the definitions

Fixes https://gitlab.com/kicad/code/kicad/issues/21730
2025-09-12 23:34:29 -07:00
Seth Hillbrand
89be756d1a Ensure that we are checking case differences
When comparing symbols, look for case differences in values since
netlists are indeed case sensitive
2025-09-12 23:27:06 -07:00
Seth Hillbrand
a6decf15b5 Don't assert on stderr messages
These can be things like missing library tables, directory creation, etc
2025-09-12 23:26:20 -07:00
Seth Hillbrand
4229b3cc76 Update cli golden files with new output format 2025-09-12 23:25:22 -07:00
Seth Hillbrand
32ee4ebdfa Fix QA.
QA was built with pin names outside but this was a bug.  For now, we
only want to test outside pin names.  The test can be expanded in the
future
2025-09-12 12:53:01 -07:00
Jeff Young
2c102a62e0 Auto-convert numeric values in fields when referencing them in expressions.
Fixes https://gitlab.com/kicad/code/kicad/-/issues/21723
2025-09-12 20:35:17 +01:00
jean-pierre charras
aa4de22ef4 EDA_3D_CANVAS: fix crash when used in dialogs and clicking in the 3D shape
It was due to the fact a message (using ExpressMail) was sent to the board
and schematic editor, but it is possible only if the manager of this canvas
is a EDA_3D_VIEWER_FRAME, because only this kind of frame has ExpressMail stuff

Fixes https://gitlab.com/kicad/code/kicad/-/issues/21728
2025-09-12 20:00:10 +02:00
jean-pierre charras
79a3b7ac5f Add missing header to compil on msys2 2025-09-12 19:44:19 +02:00
Seth Hillbrand
56ad08cdd8 Prevent backannotation from changing variables
Updating to PCB always resolves text variables from the schematic.  This
means that back annotation will always start from resolved variables if
they exist in the schematic.  So to avoid this, we prevent overwriting
any text variables that exist in the schematic.

This does prevent the user from updating a text variable in a pcbnew
field and backannotating this variable change into the schematic where
the schematic had a text variable already.  Text variables are forward
annotation only until we add an additional property to each field to
include the unresolved text

Fixes https://gitlab.com/kicad/code/kicad/issues/21724
2025-09-12 07:24:39 -07:00
Seth Hillbrand
accbee3c6e Move git stuff to kicommon to fix build
And do other cool stuff
2025-09-12 07:17:42 -07:00
Seth Hillbrand
29025882fc Fix typo in pad table 2025-09-12 06:55:31 -07: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
Dhineshkumar S
072c74caf2 Fix for ensuring thickness reverts only when the user manually sets it.
Fixes #18975
2025-03-05 00:01:23 +05:30
66 changed files with 1593 additions and 358 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

@ -1087,14 +1087,16 @@ void EDA_3D_CANVAS::OnLeftDown( wxMouseEvent& event )
if( footprint )
{
std::string command =
fmt::format( "$SELECT: 0,F{}",
EscapeString( footprint->GetReference(), CTX_IPC ).ToStdString() );
EDA_3D_VIEWER_FRAME* frame = static_cast<EDA_3D_VIEWER_FRAME*>( GetParent() );
// We send a message (by ExpressMail) to the board and schematic editor, but only
// if the manager of this canvas is a EDA_3D_VIEWER_FRAME, because only this
// kind of frame has ExpressMail stuff
EDA_3D_VIEWER_FRAME* frame = dynamic_cast<EDA_3D_VIEWER_FRAME*>( GetParent() );
if( frame )
{
std::string command = fmt::format( "$SELECT: 0,F{}",
EscapeString( footprint->GetReference(), CTX_IPC ).ToStdString() );
frame->Kiway().ExpressMail( FRAME_PCB_EDITOR, MAIL_SELECTION, command, frame );
frame->Kiway().ExpressMail( FRAME_SCH, MAIL_SELECTION, command, frame );
}

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

@ -67,6 +67,30 @@ set( KICOMMON_SRCS
# Gal
gal/color4d.cpp
gal/opengl/gl_context_mgr.cpp
# Git
git/git_add_to_index_handler.cpp
git/git_branch_handler.cpp
git/git_clone_handler.cpp
git/git_commit_handler.cpp
git/git_config_handler.cpp
git/git_compare_handler.cpp
git/git_init_handler.cpp
git/project_git_utils.cpp
git/git_pull_handler.cpp
git/git_push_handler.cpp
git/git_remove_from_index_handler.cpp
git/git_remove_vcs_handler.cpp
git/git_resolve_conflict_handler.cpp
git/git_revert_handler.cpp
git/git_status_handler.cpp
git/git_switch_branch_handler.cpp
git/git_sync_handler.cpp
git/kicad_git_common.cpp
git/kicad_git_errors.cpp
git/git_backend.cpp
git/libgit_backend.cpp
# Jobs
jobs/job.cpp
jobs/job_dispatcher.cpp
@ -616,26 +640,6 @@ set( COMMON_IMPORT_GFX_SRCS
import_gfx/svg_import_plugin.cpp
)
set( COMMON_GIT_SRCS
git/git_add_to_index_handler.cpp
git/git_branch_handler.cpp
git/git_clone_handler.cpp
git/git_commit_handler.cpp
git/git_config_handler.cpp
git/git_init_handler.cpp
git/git_pull_handler.cpp
git/git_push_handler.cpp
git/git_remove_from_index_handler.cpp
git/git_resolve_conflict_handler.cpp
git/git_revert_handler.cpp
git/git_status_handler.cpp
git/git_sync_handler.cpp
git/project_git_utils.cpp
git/kicad_git_common.cpp
git/kicad_git_errors.cpp
git/git_backend.cpp
git/libgit_backend.cpp
)
set( COMMON_SRCS
${LIB_KICAD_SRCS}
@ -648,7 +652,6 @@ set( COMMON_SRCS
${COMMON_IO_SRCS}
${FONT_SRCS}
${COMMON_IMPORT_GFX_SRCS}
${COMMON_GIT_SRCS}
${COMMON_TRANSLINE_CALCULATION_SRCS}
base_screen.cpp
bin_mod.cpp

View File

@ -285,6 +285,18 @@ void EDA_TEXT::CopyText( const EDA_TEXT& aSrc )
void EDA_TEXT::SetTextThickness( int aWidth )
{
m_attributes.m_StrokeWidth = aWidth;
const int size = std::min( m_attributes.m_Size.x, m_attributes.m_Size.y );
// Identifies whether the user manually entered a thickness value.
// If the value is manually set (not matching the system-calculated bold pen size), store it.
// Otherwise, set it to -1, indicating that no manual value should be retained when toggling bold/unbold.
if( ( aWidth < GetPenSizeForBold( size ) && aWidth != GetPenSizeForNormal( size ) )
|| aWidth > GetPenSizeForBold( size ) )
m_attributes.m_StoredStrokeWidth = aWidth;
else
m_attributes.m_StoredStrokeWidth = -1;
ClearRenderCache();
ClearBoundingBoxCache();
}
@ -346,23 +358,23 @@ void EDA_TEXT::SetBold( bool aBold )
if( aBold )
{
m_attributes.m_StoredStrokeWidth = m_attributes.m_StrokeWidth;
m_attributes.m_StrokeWidth = GetPenSizeForBold( size );
// If the user manually set a stroke width greater than the system-calculated bold pen size,
// use the manually set stroke width instead of the default bold stroke width.
if( m_attributes.m_StoredStrokeWidth > 0
&& m_attributes.m_StoredStrokeWidth > GetPenSizeForBold( size ) )
m_attributes.m_StrokeWidth = m_attributes.m_StoredStrokeWidth;
else
m_attributes.m_StrokeWidth = GetPenSizeForBold( size );
}
else
{
// Restore the original stroke width from `m_StoredStrokeWidth` if it was
// previously stored, resetting the width after unbolding.
if( m_attributes.m_StoredStrokeWidth )
// If the user manually set a stroke width smaller than the system-calculated bold pen size,
// use the manually set stroke width instead of the default unbold stroke width.
if( m_attributes.m_StoredStrokeWidth >= 0
&& m_attributes.m_StoredStrokeWidth < GetPenSizeForBold( size ) )
m_attributes.m_StrokeWidth = m_attributes.m_StoredStrokeWidth;
else
{
m_attributes.m_StrokeWidth = GetPenSizeForNormal( size );
// Sets `m_StrokeWidth` to the normal pen size and stores it in
// `m_StoredStrokeWidth` as the default, but only if the bold option was
// applied before this feature was implemented.
m_attributes.m_StoredStrokeWidth = m_attributes.m_StrokeWidth;
}
}
}
else

View File

@ -708,6 +708,104 @@ double EDA_UNIT_UTILS::UI::DoubleValueFromString( const EDA_IU_SCALE& aIuScale,
}
bool EDA_UNIT_UTILS::UI::DoubleValueFromString( const EDA_IU_SCALE& aIuScale, const wxString& aTextValue,
double& aDoubleValue )
{
double dtmp = 0;
// Acquire the 'right' decimal point separator
const struct lconv* lc = localeconv();
wxChar decimal_point = lc->decimal_point[0];
wxString buf( aTextValue.Strip( wxString::both ) );
// Convert any entered decimal point separators to the 'right' one
buf.Replace( wxT( "." ), wxString( decimal_point, 1 ) );
buf.Replace( wxT( "," ), wxString( decimal_point, 1 ) );
// Find the end of the numeric part
unsigned brk_point = 0;
while( brk_point < buf.Len() )
{
wxChar ch = buf[brk_point];
if( !( (ch >= '0' && ch <= '9') || (ch == decimal_point) || (ch == '-') || (ch == '+') ) )
break;
++brk_point;
}
if( brk_point == 0 )
return false;
// Extract the numeric part
buf.Left( brk_point ).ToDouble( &dtmp );
// Check the unit designator
wxString unit( buf.Mid( brk_point ).Strip( wxString::both ).Lower() );
EDA_UNITS units;
//check for um, μm (µ is MICRO SIGN) and µm (µ is GREEK SMALL LETTER MU) for micrometre
if( unit == wxT( "um" ) || unit == wxT( "\u00B5m" ) || unit == wxT( "\u03BCm" ) )
{
units = EDA_UNITS::UM;
}
else if( unit == wxT( "mm" ) )
{
units = EDA_UNITS::MM;
}
else if( unit == wxT( "cm" ) )
{
units = EDA_UNITS::CM;
}
else if( unit == wxT( "mil" ) || unit == wxT( "mils" ) || unit == wxT( "thou" ) )
{
units = EDA_UNITS::MILS;
}
else if( unit == wxT( "in" ) || unit == wxT( "\"" ) )
{
units = EDA_UNITS::INCH;
}
else if( unit == wxT( "oz" ) ) // 1 oz = 1.37 mils
{
units = EDA_UNITS::MILS;
dtmp *= 1.37;
}
else if( unit == wxT( "ra" ) ) // Radians
{
dtmp *= 180.0f / M_PI;
}
else if( unit == wxT( "fs" ) )
{
units = EDA_UNITS::FS;
}
else if( unit == wxT( "ps" ) )
{
units = EDA_UNITS::PS;
}
else if( unit == wxT( "ps/in" ) )
{
units = EDA_UNITS::PS_PER_INCH;
}
else if( unit == wxT( "ps/cm" ) )
{
units = EDA_UNITS::PS_PER_CM;
}
else if( unit == wxT( "ps/mm" ) )
{
units = EDA_UNITS::PS_PER_MM;
}
else
{
return false;
}
aDoubleValue = FromUserUnit( aIuScale, units, dtmp );
return true;
}
long long int EDA_UNIT_UTILS::UI::ValueFromString( const EDA_IU_SCALE& aIuScale, EDA_UNITS aUnits,
const wxString& aTextValue, EDA_DATA_TYPE aType )
{

View File

@ -25,12 +25,13 @@
#define GIT_ADD_TO_INDEX_HANDLER_H_
#include <git/kicad_git_common.h>
#include <import_export.h>
#include <vector>
#include <wx/string.h>
class LIBGIT_BACKEND;
class GIT_ADD_TO_INDEX_HANDLER : public KIGIT_COMMON
class APIEXPORT GIT_ADD_TO_INDEX_HANDLER : public KIGIT_COMMON
{
public:
GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository );

View File

@ -25,6 +25,7 @@
#define GIT_BACKEND_H_
#include <map>
#include <import_export.h>
#include <set>
#include <vector>
#include <wx/string.h>
@ -56,7 +57,7 @@ enum class CommitResult
Cancelled
};
class GIT_BACKEND
class APIEXPORT GIT_BACKEND
{
public:
virtual ~GIT_BACKEND() = default;
@ -121,7 +122,7 @@ public:
virtual void PerformRemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler ) = 0;
};
GIT_BACKEND* GetGitBackend();
void SetGitBackend( GIT_BACKEND* aBackend );
APIEXPORT GIT_BACKEND* GetGitBackend();
APIEXPORT void SetGitBackend( GIT_BACKEND* aBackend );
#endif

View File

@ -25,6 +25,7 @@
#define GIT_BRANCH_HANDLER_H
#include <git/git_repo_mixin.h>
#include <import_export.h>
#include <wx/string.h>
#include <vector>
@ -36,7 +37,7 @@ enum class BranchResult
Error
};
class GIT_BRANCH_HANDLER : public KIGIT_REPO_MIXIN
class APIEXPORT GIT_BRANCH_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_BRANCH_HANDLER( KIGIT_COMMON* aCommon );

View File

@ -25,10 +25,11 @@
#define GIT_CLONE_HANDLER_H_
#include "kicad_git_common.h"
#include <import_export.h>
#include "git_repo_mixin.h"
#include "git_progress.h"
class GIT_CLONE_HANDLER : public KIGIT_REPO_MIXIN
class APIEXPORT GIT_CLONE_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_CLONE_HANDLER( KIGIT_COMMON* aCommon );

View File

@ -27,6 +27,7 @@
// Define a class to handle git commit operations
#include <git/kicad_git_common.h>
#include <import_export.h>
#include "git_backend.h"
#include <string>
@ -35,7 +36,7 @@
class LIBGIT_BACKEND;
class GIT_COMMIT_HANDLER : public KIGIT_COMMON
class APIEXPORT GIT_COMMIT_HANDLER : public KIGIT_COMMON
{
public:
GIT_COMMIT_HANDLER( git_repository* aRepo );

View File

@ -25,6 +25,7 @@
#define GIT_CONFIG_HANDLER_H
#include <git/git_repo_mixin.h>
#include <import_export.h>
#include <wx/string.h>
struct GitUserConfig
@ -35,7 +36,7 @@ struct GitUserConfig
bool hasEmail = false;
};
class GIT_CONFIG_HANDLER : public KIGIT_REPO_MIXIN
class APIEXPORT GIT_CONFIG_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_CONFIG_HANDLER( KIGIT_COMMON* aCommon );

View File

@ -25,6 +25,7 @@
#define GIT_INIT_HANDLER_H
#include <git/git_repo_mixin.h>
#include <import_export.h>
#include <wx/string.h>
enum class InitResult
@ -43,7 +44,7 @@ struct RemoteConfig
KIGIT_COMMON::GIT_CONN_TYPE connType;
};
class GIT_INIT_HANDLER : public KIGIT_REPO_MIXIN
class APIEXPORT GIT_INIT_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_INIT_HANDLER( KIGIT_COMMON* aCommon );

View File

@ -25,10 +25,11 @@
#define GIT_PROGRESS_H_
#include <widgets/wx_progress_reporters.h>
#include <import_export.h>
#include <memory>
class GIT_PROGRESS
class APIEXPORT GIT_PROGRESS
{
public:
GIT_PROGRESS() :

View File

@ -25,6 +25,7 @@
#define _GIT_PULL_HANDLER_H_
#include <git/git_repo_mixin.h>
#include <import_export.h>
#include <vector>
#include <string>
@ -51,7 +52,7 @@ enum class PullResult : int
class LIBGIT_BACKEND;
class GIT_PULL_HANDLER : public KIGIT_REPO_MIXIN
class APIEXPORT GIT_PULL_HANDLER : public KIGIT_REPO_MIXIN
{
public:
friend class LIBGIT_BACKEND;

View File

@ -26,6 +26,7 @@
#include <git/git_progress.h>
#include <git/git_repo_mixin.h>
#include <import_export.h>
#include <git/kicad_git_errors.h>
#include <wx/string.h>
@ -37,7 +38,7 @@ enum class PushResult
Error
};
class GIT_PUSH_HANDLER : public KIGIT_REPO_MIXIN
class APIEXPORT GIT_PUSH_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_PUSH_HANDLER( KIGIT_COMMON* aCommon );

View File

@ -25,12 +25,13 @@
#define GIT_REMOVE_FROM_INDEX_HANDLER_H_
#include <git/kicad_git_common.h>
#include <import_export.h>
#include <vector>
#include <wx/string.h>
class LIBGIT_BACKEND;
class GIT_REMOVE_FROM_INDEX_HANDLER : public KIGIT_COMMON
class APIEXPORT GIT_REMOVE_FROM_INDEX_HANDLER : public KIGIT_COMMON
{
public:
GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository );

View File

@ -17,8 +17,9 @@
#include "kicad_git_common.h"
#include "kicad_git_errors.h"
#include "git_progress.h"
#include <import_export.h>
class KIGIT_REPO_MIXIN: public KIGIT_ERRORS, public GIT_PROGRESS
class APIEXPORT KIGIT_REPO_MIXIN: public KIGIT_ERRORS, public GIT_PROGRESS
{
public:
KIGIT_REPO_MIXIN( KIGIT_COMMON* aCommon ) : m_common( aCommon )

View File

@ -25,10 +25,11 @@
#define GIT_RESOLVE_CONFLICT_HANDLER_H
#include <git2.h>
#include <import_export.h>
class wxString;
class GIT_RESOLVE_CONFLICT_HANDLER
class APIEXPORT GIT_RESOLVE_CONFLICT_HANDLER
{
public:
GIT_RESOLVE_CONFLICT_HANDLER( git_repository* aRepository );

View File

@ -25,6 +25,7 @@
#define GIT_REVERT_HANDLER_H_
#include <git2.h>
#include <import_export.h>
#include <vector>
#include <wx/string.h>
// TEMPORARY HACKFIX INCLUDE FOR STD::VECTOR EXPORT OUT OF KICOMMON ON WINDOWS
@ -32,7 +33,7 @@
class LIBGIT_BACKEND;
class GIT_REVERT_HANDLER
class APIEXPORT GIT_REVERT_HANDLER
{
public:
GIT_REVERT_HANDLER( git_repository* aRepository );

View File

@ -25,6 +25,7 @@
#define GIT_STATUS_HANDLER_H
#include <git/git_repo_mixin.h>
#include <import_export.h>
#include <wx/string.h>
#include <map>
#include <set>
@ -38,7 +39,7 @@ struct FileStatus
unsigned int gitStatus; // Raw git status flags
};
class GIT_STATUS_HANDLER : public KIGIT_REPO_MIXIN
class APIEXPORT GIT_STATUS_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_STATUS_HANDLER( KIGIT_COMMON* aCommon );

View File

@ -25,10 +25,11 @@
#define GIT_SYNC_HANDLER_H_
#include <git2.h>
#include <import_export.h>
class wxString;
class GIT_SYNC_HANDLER
class APIEXPORT GIT_SYNC_HANDLER
{
public:
GIT_SYNC_HANDLER( git_repository* aRepository );

View File

@ -25,11 +25,12 @@
#include <istream>
#include <string>
#include <git2.h>
#include <import_export.h>
#include <richio.h>
class BLOB_BUFFER_STREAM : public std::streambuf
class APIEXPORT BLOB_BUFFER_STREAM : public std::streambuf
{
public:
BLOB_BUFFER_STREAM( git_blob* aBlob )
@ -57,7 +58,7 @@ public:
};
// Build a class that implements LINE_READER for git_blobs
class BLOB_READER : public LINE_READER
class APIEXPORT BLOB_READER : public LINE_READER
{
public:
BLOB_READER( git_blob* aBlob ) : m_blob( aBlob )

View File

@ -25,6 +25,7 @@
#define _GIT_COMMON_H_
#include <git/kicad_git_errors.h>
#include <import_export.h>
#include <git2.h>
#include <atomic>
@ -35,7 +36,7 @@
class LIBGIT_BACKEND;
class KIGIT_COMMON
class APIEXPORT KIGIT_COMMON
{
public:
@ -193,19 +194,19 @@ private:
static const unsigned KIGIT_CREDENTIAL_SSH_AGENT = 1 << sizeof( m_testedTypes - 1 );
};
extern "C" int progress_cb( const char* str, int len, void* data );
extern "C" void clone_progress_cb( const char* str, size_t len, size_t total, void* data );
extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload );
extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
void* aPayload );
extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal,
size_t aBytes, void* aPayload );
extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus,
void* aPayload );
extern "C" APIEXPORT int progress_cb( const char* str, int len, void* data );
extern "C" APIEXPORT void clone_progress_cb( const char* str, size_t len, size_t total, void* data );
extern "C" APIEXPORT int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload );
extern "C" APIEXPORT int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
void* aPayload );
extern "C" APIEXPORT int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal,
size_t aBytes, void* aPayload );
extern "C" APIEXPORT int push_update_reference_cb( const char* aRefname, const char* aStatus,
void* aPayload );
extern "C" int fetchhead_foreach_cb( const char*, const char*,
const git_oid* aOID, unsigned int aIsMerge, void* aPayload );
extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
unsigned int aAllowedTypes, void* aPayload );
extern "C" APIEXPORT int fetchhead_foreach_cb( const char*, const char*,
const git_oid* aOID, unsigned int aIsMerge, void* aPayload );
extern "C" APIEXPORT int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
unsigned int aAllowedTypes, void* aPayload );
#endif // _GIT_COMMON_H_

View File

@ -25,10 +25,11 @@
#define KICAD_GIT_ERRORS_H
#include <vector>
#include <import_export.h>
#include <wx/translation.h>
class KIGIT_ERRORS
class APIEXPORT KIGIT_ERRORS
{
public:

View File

@ -25,11 +25,12 @@
#define LIBGIT_BACKEND_H_
#include "git_backend.h"
#include <import_export.h>
// Forward declarations to avoid exposing libgit2 headers
struct git_annotated_commit;
class LIBGIT_BACKEND : public GIT_BACKEND
class APIEXPORT LIBGIT_BACKEND : public GIT_BACKEND
{
public:
void Init() override;

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

@ -26,12 +26,13 @@
#include <git2.h>
#include <wx/string.h>
#include <import_export.h>
namespace KIGIT
{
/** Utility class with helper functions for project level git operations. */
class PROJECT_GIT_UTILS
class APIEXPORT PROJECT_GIT_UTILS
{
public:
/**
@ -51,6 +52,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

@ -289,13 +289,14 @@ bool FIELD_VALIDATOR::Validate( wxWindow* aParent )
}
bool FIELD_VALIDATOR::DoValidate( const wxString& aValue, wxWindow* aParent )
wxString GetFieldValidationErrorMessage( FIELD_T aFieldId, const wxString& aValue )
{
wxString msg;
FIELD_VALIDATOR validator( aFieldId );
wxString msg;
if( HasFlag( wxFILTER_EMPTY ) && aValue.empty() )
if( validator.HasFlag( wxFILTER_EMPTY ) && aValue.empty() )
{
switch( m_fieldId )
switch( aFieldId )
{
case FIELD_T::SHEET_NAME: msg = _( "A sheet must have a name." ); break;
case FIELD_T::SHEET_FILENAME: msg = _( "A sheet must have a file specified." ); break;
@ -303,11 +304,11 @@ bool FIELD_VALIDATOR::DoValidate( const wxString& aValue, wxWindow* aParent )
}
}
if( HasFlag( wxFILTER_EXCLUDE_CHAR_LIST ) && ContainsExcludedCharacters( aValue ) )
if( msg.empty() && validator.HasFlag( wxFILTER_EXCLUDE_CHAR_LIST ) )
{
wxArrayString badCharsFound;
for( const wxUniCharRef& excludeChar : GetCharExcludes() )
for( const wxUniCharRef& excludeChar : validator.GetCharExcludes() )
{
if( aValue.Find( excludeChar ) != wxNOT_FOUND )
{
@ -324,68 +325,83 @@ bool FIELD_VALIDATOR::DoValidate( const wxString& aValue, wxWindow* aParent )
}
}
wxString badChars;
for( size_t i = 0; i < badCharsFound.GetCount(); i++ )
if( !badCharsFound.IsEmpty() )
{
if( !badChars.IsEmpty() )
wxString badChars;
for( size_t i = 0; i < badCharsFound.GetCount(); i++ )
{
if( badCharsFound.GetCount() == 2 )
if( !badChars.IsEmpty() )
{
badChars += _( " or " );
}
else
{
if( i < badCharsFound.GetCount() - 2 )
badChars += _( ", or " );
if( badCharsFound.GetCount() == 2 )
{
badChars += _( " or " );
}
else
badChars += wxT( ", " );
{
if( i < badCharsFound.GetCount() - 2 )
badChars += _( ", or " );
else
badChars += wxT( ", " );
}
}
badChars += badCharsFound.Item( i );
}
badChars += badCharsFound.Item( i );
switch( aFieldId )
{
case FIELD_T::REFERENCE:
msg.Printf( _( "The reference designator cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::VALUE:
msg.Printf( _( "The value field cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::FOOTPRINT:
msg.Printf( _( "The footprint field cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::DATASHEET:
msg.Printf( _( "The datasheet field cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::SHEET_NAME:
msg.Printf( _( "The sheet name cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::SHEET_FILENAME:
msg.Printf( _( "The sheet filename cannot contain %s character(s)." ), badChars );
break;
default:
msg.Printf( _( "The field cannot contain %s character(s)." ), badChars );
break;
};
}
}
switch( m_fieldId )
if( msg.empty() )
{
if( aFieldId == FIELD_T::REFERENCE && aValue.Contains( wxT( "${" ) ) )
{
case FIELD_T::REFERENCE:
msg.Printf( _( "The reference designator cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::VALUE:
msg.Printf( _( "The value field cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::FOOTPRINT:
msg.Printf( _( "The footprint field cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::DATASHEET:
msg.Printf( _( "The datasheet field cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::SHEET_NAME:
msg.Printf( _( "The sheet name cannot contain %s character(s)." ), badChars );
break;
case FIELD_T::SHEET_FILENAME:
msg.Printf( _( "The sheet filename cannot contain %s character(s)." ), badChars );
break;
default:
msg.Printf( _( "The field cannot contain %s character(s)." ), badChars );
break;
};
}
else if( m_fieldId == FIELD_T::REFERENCE && aValue.Contains( wxT( "${" ) ) )
{
msg.Printf( _( "The reference designator cannot contain text variable references" ) );
}
else if( m_fieldId == FIELD_T::REFERENCE && UTIL::GetRefDesPrefix( aValue ).IsEmpty() )
{
msg.Printf( _( "References must start with a letter." ) );
msg.Printf( _( "The reference designator cannot contain text variable references" ) );
}
else if( aFieldId == FIELD_T::REFERENCE && UTIL::GetRefDesPrefix( aValue ).IsEmpty() )
{
msg.Printf( _( "References must start with a letter." ) );
}
}
return msg;
}
bool FIELD_VALIDATOR::DoValidate( const wxString& aValue, wxWindow* aParent )
{
wxString msg = GetFieldValidationErrorMessage( m_fieldId, aValue );
if( !msg.empty() )
{
if( m_validatorWindow )

View File

@ -227,16 +227,16 @@ DIALOG_EXPORT_NETLIST::DIALOG_EXPORT_NETLIST( SCH_EDIT_FRAME* aEditFrame, wxWind
page->m_LeftBoxSizer->Add( label, 0, wxBOTTOM, 10 );
m_PanelNetType[PANELALLEGRO] = page;
page = new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "PADS" ), NET_TYPE_PADS, false );
label = new wxStaticText( page, wxID_ANY, _( "Export netlist in PADS format" ) );
page->m_LeftBoxSizer->Add( label, 0, wxBOTTOM, 10 );
m_PanelNetType[PANELPADS] = page;
page = new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "CadStar" ), NET_TYPE_CADSTAR, false );
label = new wxStaticText( page, wxID_ANY, _( "Export netlist in CadStar format" ) );
page->m_LeftBoxSizer->Add( label, 0, wxBOTTOM, 10 );
m_PanelNetType[PANELCADSTAR] = page;
page = new EXPORT_NETLIST_PAGE( m_NoteBook, wxT( "PADS" ), NET_TYPE_PADS, false );
label = new wxStaticText( page, wxID_ANY, _( "Export netlist in PADS format" ) );
page->m_LeftBoxSizer->Add( label, 0, wxBOTTOM, 10 );
m_PanelNetType[PANELPADS] = page;
InstallPageSpice();
InstallPageSpiceModel();

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,68 @@ 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 );
if( tmp )
{
retv = tmp;
REPORT( wxString::Format( _( "%s field differs." ), aField->GetName( false ) ) );
retv = 1;
REPORT( wxString::Format( _( "Extra field in schematic symbol: %s." ), ITEM_DESC( aField ) ) );
if( !aReporter )
return retv;
}
else
{
int tmp = 0;
// For EQUALITY comparison, we need to compare field content directly
// since SCH_ITEM::compare() returns 0 for EQUALITY flag
if( aCompareFlags & SCH_ITEM::COMPARE_FLAGS::EQUALITY )
{
// Compare field text content
tmp = aField->GetText().compare( bField->GetText() );
}
if( tmp == 0 )
{
// Fall back to base class comparison for other properties
tmp = aField->SCH_ITEM::compare( *bField, aCompareFlags );
}
if( tmp != 0 )
{
retv = tmp;
REPORT( wxString::Format( _( "Field '%s' differs: %s; %s." ),
aField->GetName( false ),
ITEM_DESC( aField ),
ITEM_DESC( bField ) ) );
if( !aReporter )
return retv;
}
}
}
for( const SCH_FIELD* bField : bFields )
{
const SCH_FIELD* aField = nullptr;
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>( aFields.size() - bFields.size() ) )
{
retv = tmp;
REPORT( _( "Field count differs." ) );
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

@ -750,7 +750,9 @@ std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNameInfo( int
info->m_Thickness = m_nameThickness;
info->m_Angle = ANGLE_HORIZONTAL;
if( m_pin.GetParentSymbol()->GetPinNameOffset() > 0 )
bool nameInside = m_pin.GetParentSymbol()->GetPinNameOffset() > 0;
if( nameInside )
{
// This means name inside the pin
VECTOR2I pos = { m_pin.GetLength() + m_pin.GetParentSymbol()->GetPinNameOffset(), 0 };
@ -760,6 +762,7 @@ std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNameInfo( int
info->m_TextPosition = pos + VECTOR2I{ thickOffset, 0 };
info->m_HAlign = GR_TEXT_H_ALIGN_LEFT;
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
transformTextForPin( *info );
}
else
{
@ -769,42 +772,44 @@ std::optional<PIN_LAYOUT_CACHE::TEXT_INFO> PIN_LAYOUT_CACHE::GetPinNameInfo( int
info->m_TextPosition = pos;
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
info->m_VAlign = GR_TEXT_V_ALIGN_BOTTOM;
}
// New policy: names follow same positioning semantics as numbers.
const SYMBOL* parentSym = m_pin.GetParentSymbol();
if( parentSym )
{
int maxHalfHeight = 0;
for( const SCH_PIN* p : parentSym->GetPins() )
// New policy: names follow same positioning semantics as numbers except when
// specified as inside. When names are inside, they should not overlap with the
// number position.
const SYMBOL* parentSym = m_pin.GetParentSymbol();
if( parentSym )
{
wxString n = p->GetShownName();
if( n.IsEmpty() )
continue;
maxHalfHeight = std::max( maxHalfHeight, p->GetNameTextSize() / 2 );
}
int clearance = getPinTextOffset() + schIUScale.MilsToIU( PIN_TEXT_MARGIN );
VECTOR2I pinPos = m_pin.GetPosition();
PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
int maxHalfHeight = 0;
for( const SCH_PIN* p : parentSym->GetPins() )
{
wxString n = p->GetShownName();
if( n.IsEmpty() )
continue;
maxHalfHeight = std::max( maxHalfHeight, p->GetNameTextSize() / 2 );
}
int clearance = getPinTextOffset() + schIUScale.MilsToIU( PIN_TEXT_MARGIN );
VECTOR2I pinPos = m_pin.GetPosition();
PIN_ORIENTATION orient = m_pin.PinDrawOrient( DefaultTransform );
bool verticalOrient = ( orient == PIN_ORIENTATION::PIN_UP || orient == PIN_ORIENTATION::PIN_DOWN );
if( verticalOrient )
{
// Vertical pins: name mirrors number placement (left + rotated) for visual consistency.
int boxWidth = info->m_TextSize * (int) info->m_Text.Length() * 0.6; // heuristic width
int centerX = pinPos.x - clearance - boxWidth / 2;
info->m_TextPosition = { centerX, pinPos.y };
info->m_Angle = ANGLE_VERTICAL;
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
}
else
{
// Horizontal pins: name above (negative Y) aligned to same Y offset logic as numbers.
info->m_TextPosition = { pinPos.x, pinPos.y - ( maxHalfHeight + clearance ) };
info->m_Angle = ANGLE_HORIZONTAL;
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
if( verticalOrient )
{
// Vertical pins: name mirrors number placement (left + rotated) for visual consistency.
int boxWidth = info->m_TextSize * (int) info->m_Text.Length() * 0.6; // heuristic width
int centerX = pinPos.x - clearance - boxWidth / 2;
info->m_TextPosition = { centerX, pinPos.y };
info->m_Angle = ANGLE_VERTICAL;
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
}
else
{
// Horizontal pins: name above (negative Y) aligned to same Y offset logic as numbers.
info->m_TextPosition = { pinPos.x, pinPos.y - ( maxHalfHeight + clearance ) };
info->m_Angle = ANGLE_HORIZONTAL;
info->m_HAlign = GR_TEXT_H_ALIGN_CENTER;
info->m_VAlign = GR_TEXT_V_ALIGN_CENTER;
}
}
}
return info;

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

@ -198,8 +198,11 @@ void SCH_LINE::Show( int nestLevel, std::ostream& os ) const
std::vector<int> SCH_LINE::ViewGetLayers() const
{
return { LAYER_DANGLING, m_layer, LAYER_SELECTION_SHADOWS, LAYER_NET_COLOR_HIGHLIGHT,
LAYER_OP_VOLTAGES };
if( IsWire() || IsBus() )
return { LAYER_DANGLING, m_layer, LAYER_SELECTION_SHADOWS, LAYER_NET_COLOR_HIGHLIGHT,
LAYER_OP_VOLTAGES };
return { LAYER_DANGLING, m_layer, LAYER_SELECTION_SHADOWS, LAYER_OP_VOLTAGES };
}

View File

@ -1825,6 +1825,9 @@ void SCH_PAINTER::draw( const SCH_LINE* aLine, int aLayer )
if( !highlightNetclassColors && drawingNetColorHighlights )
return;
if( drawingNetColorHighlights && !( aLine->IsWire() || aLine->IsBus() ) )
return;
if( m_schSettings.m_OverrideItemColors && drawingNetColorHighlights )
return;

View File

@ -46,6 +46,8 @@
#include <settings/color_settings.h>
#include <settings/settings_manager.h>
#include <trace_helpers.h>
#include <validators.h>
#include <properties/property_validators.h>
#include <pgm_base.h>
#include <wx/log.h>
@ -1169,7 +1171,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() ) );
}
@ -1657,7 +1659,21 @@ static struct SCH_SHEET_DESC
propMgr.InheritsAfter( TYPE_HASH( SCH_SHEET ), TYPE_HASH( SCH_ITEM ) );
propMgr.AddProperty( new PROPERTY<SCH_SHEET, wxString>( _HKI( "Sheet Name" ),
&SCH_SHEET::SetName, &SCH_SHEET::GetName ) );
&SCH_SHEET::SetName, &SCH_SHEET::GetName ) )
.SetValidator( []( const wxAny&& aValue, EDA_ITEM* ) -> VALIDATOR_RESULT
{
wxString value;
if( !aValue.GetAs( &value ) )
return {};
wxString msg = GetFieldValidationErrorMessage( FIELD_T::SHEET_NAME, value );
if( msg.empty() )
return {};
return std::make_unique<VALIDATION_ERROR_MSG>( msg );
} );
propMgr.AddProperty( new PROPERTY<SCH_SHEET, int>( _HKI( "Border Width" ),
&SCH_SHEET::SetBorderWidth, &SCH_SHEET::GetBorderWidth,

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

@ -384,7 +384,8 @@ void BACK_ANNOTATE::applyChangelist()
if( !m_dryRun )
commit.Modify( symbol, screen, RECURSE_MODE::NO_RECURSE );
if( m_processReferences && ref.GetRef() != fpData.m_ref && !skip )
if( m_processReferences && ref.GetRef() != fpData.m_ref && !skip
&& !symbol->GetField( FIELD_T::REFERENCE )->HasTextVars() )
{
++m_changesCount;
msg.Printf( _( "Change %s reference designator to '%s'." ),
@ -397,7 +398,8 @@ void BACK_ANNOTATE::applyChangelist()
m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
}
if( m_processFootprints && oldFootprint != fpData.m_footprint && !skip )
if( m_processFootprints && oldFootprint != fpData.m_footprint && !skip
&& !symbol->GetField( FIELD_T::FOOTPRINT )->HasTextVars() )
{
++m_changesCount;
msg.Printf( _( "Change %s footprint assignment from '%s' to '%s'." ),
@ -411,7 +413,8 @@ void BACK_ANNOTATE::applyChangelist()
m_reporter.ReportHead( msg, RPT_SEVERITY_ACTION );
}
if( m_processValues && oldValue != fpData.m_value && !skip )
if( m_processValues && oldValue != fpData.m_value && !skip
&& !symbol->GetField( FIELD_T::VALUE )->HasTextVars() )
{
++m_changesCount;
msg.Printf( _( "Change %s value from '%s' to '%s'." ),
@ -502,6 +505,7 @@ void BACK_ANNOTATE::applyChangelist()
// with all the variables resolved. The footprints field value gets the symbol's
// resolved value when the PCB is updated from the schematic.
if( symField
&& !symField->HasTextVars()
&& symField->GetShownText( &ref.GetSheetPath(), false ) != fpFieldValue )
{
m_changesCount++;

View File

@ -256,6 +256,9 @@ namespace EDA_UNIT_UTILS
KICOMMON_API double DoubleValueFromString( const wxString& aTextValue );
KICOMMON_API bool DoubleValueFromString( const EDA_IU_SCALE& aIuScale, const wxString& aTextValue,
double& aDoubleValue );
/**
* Convert \a aTextValue in \a aUnits to internal units used by the application.
*

View File

@ -162,5 +162,11 @@ private:
FIELD_T m_fieldId;
};
/**
* Return the error message if @a aValue is invalid for @a aFieldId.
* Returns an empty string when the value is valid.
*/
wxString GetFieldValidationErrorMessage( FIELD_T aFieldId, const wxString& aValue );
#endif // #ifndef VALIDATORS_H

View File

@ -54,6 +54,56 @@ void TEMPLATE_SELECTION_PANEL::AddTemplateWidget( TEMPLATE_WIDGET* aTemplateWidg
}
// Sort the widgets alphabetically, leaving Default at the top
void TEMPLATE_SELECTION_PANEL::SortAlphabetically()
{
std::vector<TEMPLATE_WIDGET*> sortedList;
TEMPLATE_WIDGET* default_temp = nullptr;
size_t count = m_SizerChoice->GetItemCount();
if( count <= 1 )
return;
for( size_t idx = 0; idx < count; idx++ )
{
wxSizerItem* item = m_SizerChoice->GetItem( idx );
if( item && item->IsWindow() )
{
TEMPLATE_WIDGET* temp = static_cast<TEMPLATE_WIDGET*>( item->GetWindow() );
const wxString title = *temp->GetTemplate()->GetTitle();
if( default_temp == nullptr && title.CmpNoCase( "default" ) == 0 )
default_temp = temp;
else
sortedList.push_back( temp );
}
}
std::sort(
sortedList.begin(), sortedList.end(),
[]( TEMPLATE_WIDGET* aWidgetA, TEMPLATE_WIDGET* aWidgetB ) -> bool
{
const wxString* a = aWidgetA->GetTemplate()->GetTitle();
const wxString* b = aWidgetB->GetTemplate()->GetTitle();
return ( *a ).CmpNoCase( *b ) < 0;
});
m_SizerChoice->Clear( false );
if( default_temp != nullptr )
m_SizerChoice->Add( default_temp );
for (TEMPLATE_WIDGET* temp : sortedList)
{
m_SizerChoice->Add( temp );
}
Layout();
}
TEMPLATE_WIDGET::TEMPLATE_WIDGET( wxWindow* aParent, DIALOG_TEMPLATE_SELECTOR* aDialog ) :
TEMPLATE_WIDGET_BASE( aParent )
{
@ -337,6 +387,7 @@ void DIALOG_TEMPLATE_SELECTOR::buildPageContent( const wxString& aPath, int aPag
}
}
m_panels[aPage]->SortAlphabetically();
Layout();
}

View File

@ -81,6 +81,8 @@ public:
void AddTemplateWidget( TEMPLATE_WIDGET* aTemplateWidget );
void SortAlphabetically();
protected:
wxNotebookPage* m_parent;
wxString m_templatesPath; ///< the path to access to the folder

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
{
// For non-PTH pads, drill columns are not applicable.
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

@ -447,28 +447,33 @@ LIBEVAL::VALUE* PCBEXPR_VAR_REF::GetValue( LIBEVAL::CONTEXT* aCtx )
if( it->second->Name() == wxT( "Pin Type" ) )
return new PCBEXPR_PINTYPE_VALUE( str );
else
return new LIBEVAL::VALUE( str );
// If it quacks like a duck, it is a duck
double doubleVal;
if( EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, str, doubleVal ) )
return new LIBEVAL::VALUE( doubleVal );
return new LIBEVAL::VALUE( str );
}
else
else if( it->second->Name() == wxT( "Layer" )
|| it->second->Name() == wxT( "Layer Top" )
|| it->second->Name() == wxT( "Layer Bottom" ) )
{
const wxAny& any = item->Get( it->second );
PCB_LAYER_ID layer;
if( it->second->Name() == wxT( "Layer" )
|| it->second->Name() == wxT( "Layer Top" )
|| it->second->Name() == wxT( "Layer Bottom" ) )
{
if( any.GetAs<PCB_LAYER_ID>( &layer ) )
return new PCBEXPR_LAYER_VALUE( layer );
else if( any.GetAs<wxString>( &str ) )
return new PCBEXPR_LAYER_VALUE( context->GetBoard()->GetLayerID( str ) );
}
else
{
if( any.GetAs<wxString>( &str ) )
return new LIBEVAL::VALUE( str );
}
if( any.GetAs<PCB_LAYER_ID>( &layer ) )
return new PCBEXPR_LAYER_VALUE( layer );
else if( any.GetAs<wxString>( &str ) )
return new PCBEXPR_LAYER_VALUE( context->GetBoard()->GetLayerID( str ) );
}
else
{
const wxAny& any = item->Get( it->second );
if( any.GetAs<wxString>( &str ) )
return new LIBEVAL::VALUE( str );
}
return new LIBEVAL::VALUE();

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

@ -1,7 +1,7 @@
{
"$schema": "https://schemas.kicad.org/erc.v1.json",
"coordinate_units": "mm",
"date": "2025-08-20T22:34:08+0000",
"date": "2025-09-12T21:52:37-0700",
"kicad_version": "9.99.0",
"sheets": [
{
@ -158,51 +158,6 @@
"severity": "warning",
"type": "footprint_link_issues"
},
{
"description": "Symbol 'GND' doesn't match copy in library 'power'",
"items": [
{
"description": "Symbol #PWR03 [GND]",
"pos": {
"x": 1.2192,
"y": 0.8382
},
"uuid": "5d9a8cf4-5c54-4c57-accb-dd6295272287"
}
],
"severity": "warning",
"type": "lib_symbol_mismatch"
},
{
"description": "Symbol 'GND' doesn't match copy in library 'power'",
"items": [
{
"description": "Symbol #PWR04 [GND]",
"pos": {
"x": 1.1176,
"y": 1.0033
},
"uuid": "a45b22f2-0929-43b6-b1fb-e2272cb68106"
}
],
"severity": "warning",
"type": "lib_symbol_mismatch"
},
{
"description": "Symbol 'GND' doesn't match copy in library 'power'",
"items": [
{
"description": "Symbol #PWR01 [GND]",
"pos": {
"x": 1.1938,
"y": 0.9652
},
"uuid": "19ba0538-2910-44b8-9336-edf1ff095912"
}
],
"severity": "warning",
"type": "lib_symbol_mismatch"
},
{
"description": "Footprint 'R_1206_3216Metric' not found in library 'Resistor_SMD'",
"items": [
@ -248,21 +203,6 @@
"severity": "warning",
"type": "lib_symbol_issues"
},
{
"description": "Symbol 'GND' doesn't match copy in library 'power'",
"items": [
{
"description": "Symbol #PWR02 [GND]",
"pos": {
"x": 1.4097,
"y": 0.8763
},
"uuid": "62fef766-bfdd-4a4a-a8fa-79183473dd3b"
}
],
"severity": "warning",
"type": "lib_symbol_mismatch"
},
{
"description": "Footprint 'R_1206_3216Metric' not found in library 'Resistor_SMD'",
"items": [

View File

@ -1,4 +1,4 @@
ERC report (2025-08-20T22:34:08+0000, Encoding UTF8)
ERC report (2025-09-12T21:52:36-0700, Encoding UTF8)
***** Sheet /
[power_pin_not_driven]: Input Power pin not driven by any Output Power pins
@ -31,15 +31,6 @@ ERC report (2025-08-20T22:34:08+0000, Encoding UTF8)
[footprint_link_issues]: Footprint 'C_1206_3216Metric' not found in library 'Capacitor_SMD'
; warning
@(121.92 mm, 80.01 mm): Symbol C1 [C_Small]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(121.92 mm, 83.82 mm): Symbol #PWR03 [GND]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(111.76 mm, 100.33 mm): Symbol #PWR04 [GND]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(119.38 mm, 96.52 mm): Symbol #PWR01 [GND]
[footprint_link_issues]: Footprint 'R_1206_3216Metric' not found in library 'Resistor_SMD'
; warning
@(127.00 mm, 93.98 mm): Symbol R1 [R_US]
@ -49,9 +40,6 @@ ERC report (2025-08-20T22:34:08+0000, Encoding UTF8)
[lib_symbol_issues]: The current configuration does not include the symbol library 'Amplifier_Operational'
; warning
@(143.51 mm, 78.74 mm): Symbol U1 [TLV2371DBV]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(140.97 mm, 87.63 mm): Symbol #PWR02 [GND]
[footprint_link_issues]: Footprint 'R_1206_3216Metric' not found in library 'Resistor_SMD'
; warning
@(149.86 mm, 93.98 mm): Symbol R2 [R_US]
@ -62,4 +50,4 @@ ERC report (2025-08-20T22:34:08+0000, Encoding UTF8)
; warning
@(177.80 mm, 78.74 mm): Symbol J3 [Conn_01x01_Pin]
** ERC messages: 20 Errors 2 Warnings 18
** ERC messages: 16 Errors 2 Warnings 14

View File

@ -1,4 +1,4 @@
ERC report (2025-08-20T22:34:09+0000, Encoding UTF8)
ERC report (2025-09-12T21:52:37-0700, Encoding UTF8)
***** Sheet /
[power_pin_not_driven]: Input Power pin not driven by any Output Power pins
@ -31,15 +31,6 @@ ERC report (2025-08-20T22:34:09+0000, Encoding UTF8)
[footprint_link_issues]: Footprint 'C_1206_3216Metric' not found in library 'Capacitor_SMD'
; warning
@(4.800 in, 3.150 in): Symbol C1 [C_Small]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(4.800 in, 3.300 in): Symbol #PWR03 [GND]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(4.400 in, 3.950 in): Symbol #PWR04 [GND]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(4.700 in, 3.800 in): Symbol #PWR01 [GND]
[footprint_link_issues]: Footprint 'R_1206_3216Metric' not found in library 'Resistor_SMD'
; warning
@(5.000 in, 3.700 in): Symbol R1 [R_US]
@ -49,9 +40,6 @@ ERC report (2025-08-20T22:34:09+0000, Encoding UTF8)
[lib_symbol_issues]: The current configuration does not include the symbol library 'Amplifier_Operational'
; warning
@(5.650 in, 3.100 in): Symbol U1 [TLV2371DBV]
[lib_symbol_mismatch]: Symbol 'GND' doesn't match copy in library 'power'
; warning
@(5.550 in, 3.450 in): Symbol #PWR02 [GND]
[footprint_link_issues]: Footprint 'R_1206_3216Metric' not found in library 'Resistor_SMD'
; warning
@(5.900 in, 3.700 in): Symbol R2 [R_US]
@ -62,4 +50,4 @@ ERC report (2025-08-20T22:34:09+0000, Encoding UTF8)
; warning
@(7.000 in, 3.100 in): Symbol J3 [Conn_01x01_Pin]
** ERC messages: 20 Errors 2 Warnings 18
** ERC messages: 16 Errors 2 Warnings 14

View File

@ -194,7 +194,6 @@ def test_sch_export_erc( kitest,
stdout, stderr, exitcode = utils.run_and_capture( command )
assert exitcode == expected_exit_code
assert stderr == ''
# some of our netlist formats are not cross platform so skip for now
if not skip_compare:

View File

@ -46,6 +46,9 @@ static std::unique_ptr<LIB_SYMBOL> createTestResistorSymbol()
{
auto symbol = std::make_unique<LIB_SYMBOL>( wxT( "TestResistor" ) );
// Set pin name offset to 0 so names are positioned outside (like numbers)
symbol->SetPinNameOffset( 0 );
// Create first pin with stacked numbers [1-5]
auto pin1 = std::make_unique<SCH_PIN>( symbol.get() );
pin1->SetPosition( VECTOR2I( 0, schIUScale.MilsToIU( 250 ) ) ); // top pin

View File

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