Compare commits

...

35 Commits

Author SHA1 Message Date
Daniel Treffenstädt
f6784849aa Merge branch 'DRCPadFanout' into 'master'
DRC tests for fanout width to pad and fanout symmetry for footprints

See merge request kicad/code/kicad!2140
2025-09-12 16:54:38 +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
Daniel Treffenstädt
fe697261f2
Made mirror and point symmetry checks unitless. 2025-09-08 09:56:20 +02:00
Daniel Treffenstädt
5f4cffd934
Fixed post-rebase error with unit parsing. 2025-09-08 09:54:06 +02:00
Daniel Treffenstädt
42eb1dbff1
Fixed name for drc rules, force and torque. 2025-09-08 09:54:05 +02:00
Daniel Treffenstädt
dab925a778
Made the description of pad_fanout_ratio more specific 2025-09-08 09:53:25 +02:00
Daniel Treffenstädt
14ad71f4c7
Fixed formatting 2025-09-08 09:53:22 +02:00
Daniel Treffenstädt
4d9b96bf04
modified hasComponentClass function to also check for parent footprint of pad. 2025-09-08 09:50:17 +02:00
Daniel Treffenstädt
6d3185da81
Changed torque reference point to footprint position 2025-09-08 09:50:17 +02:00
Daniel Treffenstädt
af8e2d015d
Added QA tests for pad and footprint fanout checks 2025-09-08 09:50:16 +02:00
Daniel Treffenstädt
850c773f9d
Fixed bug where via violations were sometimes counted multiple times. 2025-09-08 09:49:41 +02:00
Daniel Treffenstädt
71224ca4bc
Added via distance check to run condition for drc check 2025-09-08 09:49:41 +02:00
Daniel Treffenstädt
b011ca8a75
Check for zone and pad bounding boxes, prevent spoke double counting. 2025-09-08 09:49:41 +02:00
Daniel Treffenstädt
7a6a701960
Added Pad via distance drc check 2025-09-08 09:49:40 +02:00
Daniel Treffenstädt
b836e55f7f
Made drc item message shorter. 2025-09-08 09:49:40 +02:00
Daniel Treffenstädt
443c1ebb4c
Renamed pad_fanout_ratio provider to fanout_checks 2025-09-08 09:49:40 +02:00
Daniel Treffenstädt
0f2b812c45
Implemented Fanout Force and Torque drc checks 2025-09-08 09:49:40 +02:00
Daniel Treffenstädt
b20b970826
Added: Footprint fanout force and torque constraints 2025-09-08 09:49:39 +02:00
Daniel Treffenstädt
1a4617f201
Added basic function for footprint symmetry 2025-09-08 09:49:09 +02:00
Daniel Treffenstädt
234a34dbf5
Fixed pad size determination 2025-09-08 09:49:09 +02:00
Daniel Treffenstädt
ea67c4318e
Added units to custom drc rules 2025-09-08 09:49:08 +02:00
Daniel Treffenstädt
924541036c
Felt cute, might revert later: Renamed DRC Error from width to ratio? 2025-09-08 09:48:34 +02:00
Daniel Treffenstädt
26e87986bb
Also renamed fanout_width class to fanout_ratio 2025-09-08 09:47:28 +02:00
Daniel Treffenstädt
8eec521482
Also renamed source file from width to ratio 2025-09-08 09:47:28 +02:00
Daniel Treffenstädt
68d3e19a69
Renamed constraint fanout_width to fanout_ratio 2025-09-08 09:47:27 +02:00
Daniel Treffenstädt
0580f57c71
Added basic pad fanout width drc test provider
This is only a rough implementation and is subject
to change.

At the moment it simply takes the bounding box of
the pad and applies the percentage value to the
minor axis. This might be okay for basic shapes,
but complex pad shapes need a better
implementation.
2025-09-08 09:46:12 +02:00
76 changed files with 11445 additions and 188 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

@ -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

@ -55,6 +55,10 @@ thermal_relief_gap
thermal_spoke_width
track
track_angle
pad_fanout_ratio
pad_fanout_via_distance
footprint_fanout_mirror_symmetry
footprint_fanout_point_symmetry
track_width
track_segment_length
version

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

@ -28,7 +28,7 @@
#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

@ -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

@ -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

@ -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
@ -281,6 +282,7 @@ set( PCBNEW_DRC_SRCS
drc/drc_test_provider_misc.cpp
drc/drc_test_provider_text_dims.cpp
drc/drc_test_provider_track_angle.cpp
drc/drc_test_provider_fanout_checks.cpp
drc/drc_test_provider_track_width.cpp
drc/drc_test_provider_track_segment_length.cpp
drc/drc_test_provider_zone_connections.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

@ -226,6 +226,7 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
STRUCT_REF
};
// clang-format off: suggestion is less maintainable
auto isDisallowToken =
[]( const wxString& token ) -> bool
{
@ -271,10 +272,15 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
|| token == wxT( "track_width" )
|| token == wxT( "track_angle" )
|| token == wxT( "track_segment_length" )
|| token == wxT( "pad_fanout_ratio" )
|| token == wxT( "pad_fanout_via_distance" )
|| token == wxT( "footprint_fanout_mirror_symmetry" )
|| token == wxT( "footprint_fanout_point_symmetry" )
|| token == wxT( "via_count" )
|| token == wxT( "via_diameter" )
|| token == wxT( "zone_connection" );
};
// clang-format on: suggestion is less maintainable
std::stack<wxString> sexprs;
wxString partial;
@ -492,6 +498,10 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent )
"track_width|"
"track_angle|"
"track_segment_length|"
"pad_fanout_ratio|"
"pad_fanout_via_distance|"
"footprint_fanout_mirror_symmetry|"
"footprint_fanout_point_symmetry|"
"via_count|"
"via_diameter|"
"zone_connection" );

View File

@ -27,6 +27,10 @@
| `track_width` | min/opt/max | Checks the width of track and arc segments. An error will be generated for each segment that has a width below the `min` value (if specified) or above the `max` value (if specified).<br> |
| `track_angle` | min/opt/max | Checks the angle between two connected track segments. An error will be generated for each connected pair with an angle below the `min` value (if specified) or above the `max` value (if specified).<br> |
| `track_segment_length` | min/max | Checks the length of track and arc segments. An error will be generated for each segment that has a length below the `min` value (if specified) or above the `max` value (if specified).<br> |
| `pad_fanout_ratio` | max | Checks the width of the fanout for a pad stated as a percentage of the pad minor axis length. An error will be generated for each connected track with a width above the `max` value (if specified).<br> |
| `pad_fanout_via_distance`| min | Checks the distance from a pad to the next via. Creates an error if the distance is below the min value. <br> |
| `footprint_fanout_mirror_symmetry` | max | Checks the mirror symmetry of the tracks and thermal spokes connected to the pads of a footprint. A maximum value can be used to fine-tune the allowed deviation from perfect symmetry. An error will be generated for the footprint if the deviation is above the `max` value (if specified). <br> |\n
| `footprint_fanout_point_symmetry` | max | Checks the point symmetry of the tracks and thermal spokes connected to the pads of a footprint. A maximum value can be used to fine-tune the allowed deviation from perfect symmetry. An error will be generated for the footprint if the deviation is above the `max` value (if specified). <br> |\n
| `via_count` | max | Counts the number of vias on every net matched by the rule condition. If that number exceeds the constraint `max` value on any matched net, an error will be generated for that net.<br> |
| `via_dangling` | | Checks for vias that are unconnected or connected on only one layer. This constraint does not take a min/opt/max value. In combination with a severity clause, this constraint can be used to allow or disallow dangling vias in various conditions.<br> |
| `zone_connection` | `solid`<br>`thermal_reliefs`<br>`none` | Specifies the connection to be made between a zone and a pad.<br> |

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

@ -129,6 +129,22 @@ DRC_ITEM DRC_ITEM::trackSegmentLength( DRCE_TRACK_SEGMENT_LENGTH,
_HKI( "Track segment length" ),
wxT( "track_segment_length" ) );
DRC_ITEM DRC_ITEM::padFanoutRatio( DRCE_PAD_FANOUT_RATIO,
_( "Pad fanout ratio" ),
wxT( "pad_fanout_ratio" ) );
DRC_ITEM DRC_ITEM::padFanoutViaDistance( DRCE_PAD_FANOUT_VIA_DISTANCE,
_( "Pad fanout via distance" ),
wxT( "pad_fanout_via_distance" ) );
DRC_ITEM DRC_ITEM::footprintFanoutMirrorSymmetry( DRCE_FOOTPRINT_FANOUT_MIRROR_SYMMETRY,
_( "Footprint fanout mirror symmetry" ),
wxT( "footprint_fanout_mirror_symmetry" ) );
DRC_ITEM DRC_ITEM::footprintFanoutPointSymmetry( DRCE_FOOTPRINT_FANOUT_POINT_SYMMETRY,
_( "Footprint fanout point symmetry" ),
wxT( "footprint_fanout_point_symmetry" ) );
DRC_ITEM DRC_ITEM::annularWidth( DRCE_ANNULAR_WIDTH,
_HKI( "Annular width" ),
wxT( "annular_width" ) );
@ -309,40 +325,44 @@ std::vector<std::reference_wrapper<RC_ITEM>> DRC_ITEM::allItemTypes(
DRC_ITEM::trackDangling,
DRC_ITEM::starvedThermal,
DRC_ITEM::heading_DFM,
DRC_ITEM::edgeClearance,
DRC_ITEM::holeClearance,
DRC_ITEM::holeNearHole,
DRC_ITEM::holesCoLocated,
DRC_ITEM::trackWidth,
DRC_ITEM::trackAngle,
DRC_ITEM::trackSegmentLength,
DRC_ITEM::annularWidth,
DRC_ITEM::drillTooSmall,
DRC_ITEM::microviaDrillTooSmall,
DRC_ITEM::courtyardsOverlap,
DRC_ITEM::missingCourtyard,
DRC_ITEM::malformedCourtyard,
DRC_ITEM::invalidOutline,
DRC_ITEM::copperSliver,
DRC_ITEM::solderMaskBridge,
DRC_ITEM::connectionWidth,
DRC_ITEM::heading_DFM,
DRC_ITEM::edgeClearance,
DRC_ITEM::holeClearance,
DRC_ITEM::holeNearHole,
DRC_ITEM::holesCoLocated,
DRC_ITEM::trackWidth,
DRC_ITEM::trackAngle,
DRC_ITEM::trackSegmentLength,
DRC_ITEM::padFanoutRatio,
DRC_ITEM::padFanoutViaDistance,
DRC_ITEM::footprintFanoutMirrorSymmetry,
DRC_ITEM::footprintFanoutPointSymmetry,
DRC_ITEM::annularWidth,
DRC_ITEM::drillTooSmall,
DRC_ITEM::microviaDrillTooSmall,
DRC_ITEM::courtyardsOverlap,
DRC_ITEM::missingCourtyard,
DRC_ITEM::malformedCourtyard,
DRC_ITEM::invalidOutline,
DRC_ITEM::copperSliver,
DRC_ITEM::solderMaskBridge,
DRC_ITEM::connectionWidth,
DRC_ITEM::heading_schematic_parity,
DRC_ITEM::duplicateFootprints,
DRC_ITEM::missingFootprint,
DRC_ITEM::extraFootprint,
DRC_ITEM::schematicParity,
DRC_ITEM::footprintFilters,
DRC_ITEM::netConflict,
DRC_ITEM::unconnectedItems,
DRC_ITEM::heading_schematic_parity,
DRC_ITEM::duplicateFootprints,
DRC_ITEM::missingFootprint,
DRC_ITEM::extraFootprint,
DRC_ITEM::schematicParity,
DRC_ITEM::footprintFilters,
DRC_ITEM::netConflict,
DRC_ITEM::unconnectedItems,
DRC_ITEM::heading_signal_integrity,
DRC_ITEM::lengthOutOfRange,
DRC_ITEM::skewOutOfRange,
DRC_ITEM::viaCountOutOfRange,
DRC_ITEM::diffPairGapOutOfRange,
DRC_ITEM::diffPairUncoupledLengthTooLong,
DRC_ITEM::heading_signal_integrity,
DRC_ITEM::lengthOutOfRange,
DRC_ITEM::skewOutOfRange,
DRC_ITEM::viaCountOutOfRange,
DRC_ITEM::diffPairGapOutOfRange,
DRC_ITEM::diffPairUncoupledLengthTooLong,
DRC_ITEM::heading_readability,
DRC_ITEM::silkOverlaps,
@ -380,6 +400,7 @@ std::vector<std::reference_wrapper<RC_ITEM>> DRC_ITEM::allItemTypes(
std::shared_ptr<DRC_ITEM> DRC_ITEM::Create( int aErrorCode )
{
// clang-format off: suggestion is less organised
switch( aErrorCode )
{
case DRCE_UNCONNECTED_ITEMS: return std::make_shared<DRC_ITEM>( unconnectedItems );
@ -402,6 +423,10 @@ std::shared_ptr<DRC_ITEM> DRC_ITEM::Create( int aErrorCode )
case DRCE_TRACK_WIDTH: return std::make_shared<DRC_ITEM>( trackWidth );
case DRCE_TRACK_ANGLE: return std::make_shared<DRC_ITEM>( trackAngle );
case DRCE_TRACK_SEGMENT_LENGTH: return std::make_shared<DRC_ITEM>( trackSegmentLength );
case DRCE_PAD_FANOUT_RATIO: return std::make_shared<DRC_ITEM>( padFanoutRatio );
case DRCE_PAD_FANOUT_VIA_DISTANCE: return std::make_shared<DRC_ITEM>( padFanoutViaDistance );
case DRCE_FOOTPRINT_FANOUT_MIRROR_SYMMETRY: return std::make_shared<DRC_ITEM>( footprintFanoutMirrorSymmetry );
case DRCE_FOOTPRINT_FANOUT_POINT_SYMMETRY: return std::make_shared<DRC_ITEM>( footprintFanoutPointSymmetry );
case DRCE_ANNULAR_WIDTH: return std::make_shared<DRC_ITEM>( annularWidth );
case DRCE_DRILL_OUT_OF_RANGE: return std::make_shared<DRC_ITEM>( drillTooSmall );
case DRCE_VIA_DIAMETER: return std::make_shared<DRC_ITEM>( viaDiameter );
@ -448,6 +473,7 @@ std::shared_ptr<DRC_ITEM> DRC_ITEM::Create( int aErrorCode )
wxFAIL_MSG( wxT( "Unknown DRC error code" ) );
return nullptr;
}
// clang-format on: suggestion is less organised
}

View File

@ -34,7 +34,9 @@ class DRC_TEST_PROVIDER;
class PCB_MARKER;
class BOARD;
enum PCB_DRC_CODE {
// clang-format off: suggestion is less organised
enum PCB_DRC_CODE
{
DRCE_FIRST = 1,
DRCE_UNCONNECTED_ITEMS = DRCE_FIRST, // items are unconnected
DRCE_SHORTING_ITEMS, // items short two nets but are not a net-tie
@ -55,6 +57,10 @@ enum PCB_DRC_CODE {
DRCE_TRACK_WIDTH, // Track width is too small or too large
DRCE_TRACK_ANGLE, // Angle between two connected tracks is too small or too large
DRCE_TRACK_SEGMENT_LENGTH, // Track segment is too short or too long
DRCE_PAD_FANOUT_RATIO, // The fanout width from a pad is too large as a percentage of pad width
DRCE_PAD_FANOUT_VIA_DISTANCE, // The distance from a pad to the nearest via connected by copper
DRCE_FOOTPRINT_FANOUT_MIRROR_SYMMETRY, // The fanout from a footprint should by symmetric.
DRCE_FOOTPRINT_FANOUT_POINT_SYMMETRY, // The fanout from a footprint should by symmetric.
DRCE_ANNULAR_WIDTH, // Via size and drill leave annular ring too small
DRCE_CONNECTION_WIDTH, // Net connection too small
DRCE_DRILL_OUT_OF_RANGE, // Too small via or pad drill
@ -111,6 +117,7 @@ enum PCB_DRC_CODE {
DRCE_LAST = DRCE_NONMIRRORED_TEXT_ON_BACK_LAYER
};
// clang-format on: suggestion is less organised
class DRC_ITEM : public RC_ITEM
@ -200,6 +207,10 @@ private:
static DRC_ITEM trackWidth;
static DRC_ITEM trackAngle;
static DRC_ITEM trackSegmentLength;
static DRC_ITEM padFanoutRatio;
static DRC_ITEM padFanoutViaDistance;
static DRC_ITEM footprintFanoutMirrorSymmetry;
static DRC_ITEM footprintFanoutPointSymmetry;
static DRC_ITEM annularWidth;
static DRC_ITEM drillTooSmall;
static DRC_ITEM viaDiameter;

View File

@ -79,7 +79,11 @@ enum DRC_CONSTRAINT_T
ASSERTION_CONSTRAINT,
CONNECTION_WIDTH_CONSTRAINT,
TRACK_ANGLE_CONSTRAINT,
VIA_DANGLING_CONSTRAINT
VIA_DANGLING_CONSTRAINT,
PAD_FANOUT_RATIO_CONSTRAINT,
PAD_FANOUT_VIA_DISTANCE_CONSTRAINT,
FOOTPRINT_FANOUT_MIRROR_SYMMETRY_CONSTRAINT,
FOOTPRINT_FANOUT_POINT_SYMMETRY_CONSTRAINT,
};

View File

@ -502,6 +502,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
return;
}
// clang-format off: suggestion is less organised
switch( token )
{
case T_assertion: c.m_Type = ASSERTION_CONSTRAINT; break;
@ -518,6 +519,10 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
case T_track_width: c.m_Type = TRACK_WIDTH_CONSTRAINT; break;
case T_track_angle: c.m_Type = TRACK_ANGLE_CONSTRAINT; break;
case T_track_segment_length: c.m_Type = TRACK_SEGMENT_LENGTH_CONSTRAINT; break;
case T_pad_fanout_ratio: c.m_Type = PAD_FANOUT_RATIO_CONSTRAINT; break;
case T_pad_fanout_via_distance: c.m_Type = PAD_FANOUT_VIA_DISTANCE_CONSTRAINT; break;
case T_footprint_fanout_mirror_symmetry: c.m_Type = FOOTPRINT_FANOUT_MIRROR_SYMMETRY_CONSTRAINT; break;
case T_footprint_fanout_point_symmetry: c.m_Type = FOOTPRINT_FANOUT_POINT_SYMMETRY_CONSTRAINT; break;
case T_connection_width: c.m_Type = CONNECTION_WIDTH_CONSTRAINT; break;
case T_annular_width: c.m_Type = ANNULAR_WIDTH_CONSTRAINT; break;
case T_via_diameter: c.m_Type = VIA_DIAMETER_CONSTRAINT; break;
@ -545,9 +550,10 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
"disallow, zone_connection, thermal_relief_gap, thermal_spoke_width, "
"min_resolved_spokes, solder_mask_expansion, solder_paste_abs_margin, "
"solder_paste_rel_margin, length, skew, via_count, via_dangling, via_diameter, "
"diff_pair_gap or diff_pair_uncoupled" ) );
"pad_fanout_ratio, diff_pair_gap or diff_pair_uncoupled" ) );
reportError( msg );
}
// clang-format on: suggestion is less organised
if( aRule->FindConstraint( c.m_Type ) )
{
@ -558,7 +564,10 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule )
bool unitless = c.m_Type == VIA_COUNT_CONSTRAINT
|| c.m_Type == MIN_RESOLVED_SPOKES_CONSTRAINT
|| c.m_Type == TRACK_ANGLE_CONSTRAINT
|| c.m_Type == VIA_DANGLING_CONSTRAINT;
|| c.m_Type == VIA_DANGLING_CONSTRAINT
|| c.m_Type == PAD_FANOUT_RATIO_CONSTRAINT
|| c.m_Type == FOOTPRINT_FANOUT_MIRROR_SYMMETRY_CONSTRAINT
|| c.m_Type == FOOTPRINT_FANOUT_POINT_SYMMETRY_CONSTRAINT;
allowsTimeDomain = c.m_Type == LENGTH_CONSTRAINT || c.m_Type == SKEW_CONSTRAINT;
@ -920,4 +929,4 @@ SEVERITY DRC_RULES_PARSER::parseSeverity()
reportError( _( "Missing ')'." ) );
return retVal;
}
}

View File

@ -0,0 +1,606 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2004-2025 KiCad Developers.
*
* 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 <cmath>
#include <thread_pool.h>
#include "base_units.h"
#include "core/typeinfo.h"
#include "geometry/shape_line_chain.h"
#include "geometry/shape_poly_set.h"
#include "layer_ids.h"
#include "math/vector2d.h"
#include "widgets/report_severity.h"
#include <pcb_track.h>
#include <drc/drc_engine.h>
#include <drc/drc_item.h>
#include <drc/drc_rule.h>
#include <drc/drc_test_provider.h>
#include <pad.h>
#include <footprint.h>
#include <math/box2.h>
#include <connectivity/connectivity_data.h>
/*
Pad fanout test. Checks the width of tracks connected to a pad.
Errors generated:
- DRCE_PAD_FANOUT_RATIO
- DRCE_PAD_FANOUT_VIA_DISTANCE
- DRCE_FOOTPRINT_FANOUT_MIRROR_SYMMETRY
- DRCE_FOOTPRINT_FANOUT_POINT_SYMMETRY
*/
class DRC_TEST_PROVIDER_FANOUT_CHECKS : public DRC_TEST_PROVIDER
{
public:
DRC_TEST_PROVIDER_FANOUT_CHECKS() {}
virtual ~DRC_TEST_PROVIDER_FANOUT_CHECKS() {}
virtual bool Run() override;
virtual const wxString GetName() const override { return wxT( "pad_fanout" ); };
};
bool DRC_TEST_PROVIDER_FANOUT_CHECKS::Run()
{
if( !m_drcEngine->HasRulesForConstraintType( PAD_FANOUT_RATIO_CONSTRAINT )
&& !m_drcEngine->HasRulesForConstraintType( FOOTPRINT_FANOUT_MIRROR_SYMMETRY_CONSTRAINT )
&& !m_drcEngine->HasRulesForConstraintType( FOOTPRINT_FANOUT_POINT_SYMMETRY_CONSTRAINT )
&& !m_drcEngine->HasRulesForConstraintType( PAD_FANOUT_VIA_DISTANCE_CONSTRAINT ) )
{
REPORT_AUX( wxT( "No fanout constraints found. Tests not run." ) );
return true; // continue with other tests
}
if( !reportPhase( _( "Checking pad fanouts..." ) ) )
return false; // DRC cancelled
auto checkPadViaDistance = [&]( const PAD* pad ) -> bool
{
if( m_drcEngine->IsCancelled() )
{
return false;
}
std::shared_ptr<CONNECTIVITY_DATA> connectivity =
m_drcEngine->GetBoard()->GetConnectivity();
std::set<PCB_TRACK*> visited;
std::queue<std::tuple<PCB_TRACK*, VECTOR2I, int>> next;
auto constraint = m_drcEngine->EvalRules( PAD_FANOUT_VIA_DISTANCE_CONSTRAINT, pad, nullptr,
pad->GetLayer() );
if( !constraint.m_Value.HasMin() || constraint.GetSeverity() == RPT_SEVERITY_IGNORE )
return false;
int length_limit{ constraint.m_Value.Min() };
for( auto track : connectivity->GetConnectedTracks( pad ) )
{
if( track->Type() == PCB_TRACE_T )
{
auto padOutline = pad->GetEffectivePolygon( track->GetLayer() )->Outline( 0 );
SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
padOutline.Intersect( SEG( track->GetStart(), track->GetEnd() ), intersections );
std::set<VECTOR2I> points;
for( auto intersection : intersections )
{
if( points.contains( intersection.p ) )
continue;
points.emplace( intersection.p );
}
for( VECTOR2I p : points )
{
next.emplace( track, p, 0 );
}
}
}
while( !next.empty() )
{
auto [current, point, length] = next.front();
next.pop();
visited.emplace( current );
for( auto track : connectivity->GetConnectedTracks( current ) )
{
std::vector<VECTOR2I> buffer;
if( track->Type() == PCB_VIA_T )
{
if( visited.contains( track ) )
continue;
PCB_VIA* via = static_cast<PCB_VIA*>( track );
int len = length + ( point - via->GetPosition() ).EuclideanNorm()
- via->GetDrill();
if( len < length_limit )
{
visited.emplace( track );
std::shared_ptr<DRC_ITEM> drcItem =
DRC_ITEM::Create( DRCE_PAD_FANOUT_VIA_DISTANCE );
wxString msg;
msg = formatMsg( _( "(%s min via distance %s; actual %s)" ),
constraint.GetName(), length_limit, len );
drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
drcItem->SetItems( pad, via );
drcItem->SetViolatingRule( constraint.GetParentRule() );
reportViolation( drcItem, pad->GetPosition(), pad->GetLayer() );
}
}
else if( track->Type() == PCB_ARC_T )
{
PCB_ARC* arc = static_cast<PCB_ARC*>( track );
SHAPE_ARC arc_shape{ arc->GetStart(), arc->GetMid(), arc->GetEnd(),
arc->GetWidth() };
if( current->Type() == PCB_ARC_T )
{
PCB_ARC* current_arc = static_cast<PCB_ARC*>( current );
SHAPE_ARC current_arc_shape{ current_arc->GetStart(), current_arc->GetMid(),
current_arc->GetEnd(),
current_arc->GetWidth() };
arc_shape.Intersect( current_arc_shape, &buffer );
}
else
{
arc_shape.IntersectLine( SEG( current->GetStart(), current->GetEnd() ),
&buffer );
}
}
else
{
SEG b{ track->GetStart(), track->GetEnd() };
if( current->Type() == PCB_ARC_T )
{
PCB_ARC* current_arc = static_cast<PCB_ARC*>( current );
SHAPE_ARC current_arc_shape{ current_arc->GetStart(), current_arc->GetMid(),
current_arc->GetEnd(),
current_arc->GetWidth() };
current_arc_shape.IntersectLine( b, &buffer );
}
else
{
SEG a{ current->GetStart(), current->GetEnd() };
OPT_VECTOR2I p = a.Intersect( b );
if( !p.has_value() )
continue;
buffer.emplace_back( *p );
}
}
if( buffer.empty() )
continue;
double shortest = -1;
VECTOR2I p;
for( VECTOR2I c : buffer )
{
if( shortest > 0 && c.Distance( point ) >= shortest )
continue;
shortest = c.Distance( point );
p = c;
}
int len = length + shortest;
if( len > length_limit * 2 )
continue;
if( visited.contains( track ) )
continue;
next.emplace( track, p, len );
}
}
return true;
};
auto checkPadFanoutRatio = [&]( const PAD* pad ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_PAD_FANOUT_RATIO ) )
{
return false;
}
if( m_drcEngine->IsCancelled() )
{
return false;
}
std::shared_ptr<CONNECTIVITY_DATA> connectivity =
m_drcEngine->GetBoard()->GetConnectivity();
for( BOARD_ITEM* other : connectivity->GetConnectedTracks( pad ) )
{
int actual;
VECTOR2I p0;
if( other->Type() == PCB_ARC_T )
{
PCB_ARC* arc = static_cast<PCB_ARC*>( other );
actual = arc->GetWidth();
p0 = arc->GetStart();
}
else if( other->Type() == PCB_TRACE_T )
{
PCB_TRACK* track = static_cast<PCB_TRACK*>( other );
actual = track->GetWidth();
p0 = ( track->GetStart() + track->GetEnd() ) / 2;
}
else
{
continue;
}
const auto& size_vec = pad->GetSize( other->GetLayer() );
int size = size_vec.x < size_vec.y ? size_vec.x : size_vec.y;
auto constraint = m_drcEngine->EvalRules( PAD_FANOUT_RATIO_CONSTRAINT, pad, other,
other->GetLayer() );
int constraintWidth = 0;
if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE )
{
if( constraint.Value().HasMax() && actual * 100 > constraint.Value().Max() * size )
{
constraintWidth =
static_cast<double>( constraint.Value().Max() ) / 100.0 * size;
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_PAD_FANOUT_RATIO );
wxString msg;
msg = formatMsg( _( "(%s max fanout width %s; actual %s)" ),
constraint.GetName(), constraintWidth, actual );
drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
drcItem->SetItems( pad, other );
drcItem->SetViolatingRule( constraint.GetParentRule() );
reportViolation( drcItem, p0, other->GetLayer() );
}
}
}
for( ZONE* zone : m_drcEngine->GetBoard()->Zones() )
{
for( PCB_LAYER_ID layer : pad->GetLayerSet() )
{
if( !zone->IsOnLayer( layer ) )
continue;
if( !zone->GetBoundingBox().Intersects( pad->GetBoundingBox() ) )
continue;
const std::shared_ptr<SHAPE_POLY_SET>& zoneFill = zone->GetFilledPolysList( layer );
if( zoneFill->IsEmpty() )
continue;
auto padOutline = pad->GetEffectivePolygon( layer )->Outline( 0 );
auto padBBox = pad->GetBoundingBox();
int spoke_width =
m_drcEngine->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, zone, layer )
.m_Value.Min();
int actual{ 0 };
for( int jj = 0; jj < zoneFill->OutlineCount(); ++jj )
{
std::vector<SHAPE_LINE_CHAIN::INTERSECTION> intersections;
// We only need to check for one spoke since the width will be the same for the same zone.
if( zoneFill->Outline( jj ).Intersect( padOutline, intersections, true,
&padBBox )
>= 2 )
{
actual = spoke_width;
break;
}
}
const auto& size_vec = pad->GetSize( layer );
int size = size_vec.x < size_vec.y ? size_vec.x : size_vec.y;
auto constraint =
m_drcEngine->EvalRules( PAD_FANOUT_RATIO_CONSTRAINT, pad, zone, layer );
int constraintWidth = 0;
if( constraint.GetSeverity() != RPT_SEVERITY_IGNORE )
{
if( constraint.Value().HasMax()
&& actual * 100 > constraint.Value().Max() * size )
{
constraintWidth =
static_cast<double>( constraint.Value().Max() ) / 100.0 * size;
std::shared_ptr<DRC_ITEM> drcItem =
DRC_ITEM::Create( DRCE_PAD_FANOUT_RATIO );
wxString msg;
msg = formatMsg( _( "(%s max fanout width %s; actual %s)" ),
constraint.GetName(), constraintWidth, actual );
drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
drcItem->SetItems( pad, zone );
drcItem->SetViolatingRule( constraint.GetParentRule() );
reportViolation( drcItem, pad->GetPosition(), layer );
}
}
}
}
return true;
};
auto checkFootprintSymmetry = [&]( const FOOTPRINT* footprint ) -> bool
{
if( m_drcEngine->IsErrorLimitExceeded( DRCE_FOOTPRINT_FANOUT_MIRROR_SYMMETRY )
&& m_drcEngine->IsErrorLimitExceeded( DRCE_FOOTPRINT_FANOUT_POINT_SYMMETRY ) )
{
return false;
}
if( m_drcEngine->IsCancelled() )
{
return false;
}
// pad fanout symmetry is easiest to be calculated using torque and net force.
// Both need to be net zero for the fanout to be symmetric.
// Force is calculated using connected width.
double torque{ 0 };
VECTOR2D force{ 0, 0 };
for( auto pad : footprint->Pads() )
{
std::shared_ptr<CONNECTIVITY_DATA> connectivity =
m_drcEngine->GetBoard()->GetConnectivity();
for( BOARD_ITEM* other : connectivity->GetConnectedTracks( pad ) )
{
if( other->Type() != PCB_TRACE_T )
continue;
PCB_TRACK* track = static_cast<PCB_TRACK*>( other );
const auto shape = pad->GetEffectiveShape( track->GetLayer() );
VECTOR2D direction = VECTOR2D( track->GetEnd() - track->GetStart() );
VECTOR2D p0 = track->GetStart();
if( !shape->PointInside( track->GetStart() ) )
{
if( shape->PointInside( track->GetEnd() ) )
{
p0 = track->GetEnd();
direction = VECTOR2D( track->GetStart() - track->GetEnd() );
}
}
SHAPE_LINE_CHAIN::INTERSECTIONS intersections;
auto outline = pad->GetEffectivePolygon( track->GetLayer() )->Outline( 0 );
if( outline.Intersect( SEG( track->GetStart(), track->GetEnd() ), intersections ) )
p0 = intersections[0].p;
p0 = p0 - footprint->GetPosition();
direction = direction.Resize( pcbIUScale.IUTomm( track->GetWidth() ) );
force += direction;
torque += pcbIUScale.IUTomm( p0.x ) * direction.y
- pcbIUScale.IUTomm( p0.y ) * direction.x;
}
for( ZONE* zone : m_drcEngine->GetBoard()->Zones() )
{
for( PCB_LAYER_ID layer : pad->GetLayerSet() )
{
if( !zone->IsOnLayer( layer ) )
continue;
if( !zone->GetBoundingBox().Intersects( pad->GetBoundingBox() ) )
continue;
const std::shared_ptr<SHAPE_POLY_SET>& zoneFill =
zone->GetFilledPolysList( layer );
if( zoneFill->IsEmpty() )
continue;
auto padOutline = pad->GetEffectivePolygon( layer )->Outline( 0 );
auto padBBox = pad->GetBoundingBox();
int spoke_half_width =
m_drcEngine
->EvalRules( THERMAL_SPOKE_WIDTH_CONSTRAINT, pad, zone, layer )
.m_Value.Min()
/ 2;
for( int jj = 0; jj < zoneFill->OutlineCount(); ++jj )
{
std::vector<SHAPE_LINE_CHAIN::INTERSECTION> intersections;
zoneFill->Outline( jj ).Intersect( padOutline, intersections, true,
&padBBox );
if( intersections.size() < 2 )
continue;
std::set<int> our_indices;
for( auto intersection : intersections )
{
// Don't double count spoke edges.
if( our_indices.contains( intersection.index_our ) )
continue;
our_indices.emplace( intersection.index_our );
SEG seg = zoneFill->Outline( jj ).Segment( intersection.index_our );
VECTOR2D direction;
if( padOutline.PointInside( seg.A ) )
direction = seg.B - intersection.p;
else
direction = seg.A - intersection.p;
direction = direction.Resize( pcbIUScale.IUTomm( spoke_half_width ) );
VECTOR2D p0 = intersection.p - footprint->GetPosition();
force += direction;
torque += pcbIUScale.IUTomm( p0.x ) * direction.y
- pcbIUScale.IUTomm( p0.y ) * direction.x;
}
}
}
}
}
// The "torque" and "force" values are quantities without a clear physical quantity attached.
// The factor 1000 is chosen experimentally.
int actual_torque = static_cast<int>( std::sqrt( std::abs( torque ) ) * 1000 );
int actual_force = static_cast<int>( force.EuclideanNorm() * 1000 );
auto force_constraint = m_drcEngine->EvalRules( FOOTPRINT_FANOUT_MIRROR_SYMMETRY_CONSTRAINT, footprint, nullptr,
footprint->GetLayer() );
auto torque_constraint = m_drcEngine->EvalRules( FOOTPRINT_FANOUT_POINT_SYMMETRY_CONSTRAINT, footprint, nullptr,
footprint->GetLayer() );
if( force_constraint.m_Value.HasMax() && force_constraint.GetSeverity() != RPT_SEVERITY_IGNORE
&& !m_drcEngine->IsErrorLimitExceeded( DRCE_FOOTPRINT_FANOUT_MIRROR_SYMMETRY ) )
{
if( actual_force > force_constraint.m_Value.Max() )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_FOOTPRINT_FANOUT_MIRROR_SYMMETRY );
wxString msg;
msg = wxString::Format( _( "(%s fanout mirror asymmetry exceeded %d; actual %d)" ),
force_constraint.GetName(), force_constraint.m_Value.Max(), actual_force );
drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
drcItem->SetItems( footprint, nullptr );
drcItem->SetViolatingRule( force_constraint.GetParentRule() );
reportViolation( drcItem, footprint->GetPosition(), footprint->GetLayer() );
}
}
if( torque_constraint.m_Value.HasMax() && torque_constraint.GetSeverity() != RPT_SEVERITY_IGNORE
&& !m_drcEngine->IsErrorLimitExceeded( DRCE_FOOTPRINT_FANOUT_POINT_SYMMETRY ) )
{
if( actual_torque > torque_constraint.m_Value.Max() )
{
std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( DRCE_FOOTPRINT_FANOUT_POINT_SYMMETRY );
wxString msg;
msg = wxString::Format( _( "(%s fanout point asymmetry exceeded %d; actual %d)" ),
torque_constraint.GetName(), torque_constraint.m_Value.Max(), actual_torque );
drcItem->SetErrorMessage( drcItem->GetErrorText() + wxS( " " ) + msg );
drcItem->SetItems( footprint, nullptr );
drcItem->SetViolatingRule( torque_constraint.GetParentRule() );
reportViolation( drcItem, footprint->GetPosition(), footprint->GetLayer() );
}
}
return true;
};
const int progressDelta = 250;
int ii = 0;
thread_pool& tp = GetKiCadThreadPool();
std::vector<std::future<bool>> returns;
returns.reserve( m_drcEngine->GetBoard()->GetPads().size()
+ m_drcEngine->GetBoard()->Footprints().size() );
for( PAD* item : m_drcEngine->GetBoard()->GetPads() )
{
returns.emplace_back( tp.submit( checkPadFanoutRatio, item ) );
returns.emplace_back( tp.submit( checkPadViaDistance, item ) );
}
for( FOOTPRINT* item : m_drcEngine->GetBoard()->Footprints() )
{
returns.emplace_back( tp.submit( checkFootprintSymmetry, item ) );
}
for( std::future<bool>& ret : returns )
{
std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
while( status != std::future_status::ready )
{
reportProgress( ii++, m_drcEngine->GetBoard()->Tracks().size(), progressDelta );
status = ret.wait_for( std::chrono::milliseconds( 250 ) );
}
}
return !m_drcEngine->IsCancelled();
}
namespace detail
{
static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_FANOUT_CHECKS> dummy;
}

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

@ -23,6 +23,7 @@
#include "pcbexpr_evaluator.h"
#include "eda_units.h"
#include <cstdio>
#include <memory>
#include <mutex>
@ -672,8 +673,9 @@ BOARD* PCBEXPR_CONTEXT::GetBoard() const
const std::vector<wxString>& PCBEXPR_UNIT_RESOLVER::GetSupportedUnits() const
{
static const std::vector<wxString> pcbUnits = { wxT( "mil" ), wxT( "mm" ), wxT( "in" ),
wxT( "deg" ), wxT( "fs" ), wxT( "ps" ) };
wxT( "fs" ), wxT( "ps" ),
wxT( "deg" ), wxT( "rad" ),
wxT( "%" ) };
return pcbUnits;
}
@ -681,14 +683,15 @@ const std::vector<wxString>& PCBEXPR_UNIT_RESOLVER::GetSupportedUnits() const
wxString PCBEXPR_UNIT_RESOLVER::GetSupportedUnitsMessage() const
{
return _( "must be mm, in, mil, deg, fs, or ps" );
return _( "must be mm, in, mil, fs, ps, deg, rad or %" );
}
const std::vector<EDA_UNITS>& PCBEXPR_UNIT_RESOLVER::GetSupportedUnitsTypes() const
{
static const std::vector<EDA_UNITS> pcbUnits = { EDA_UNITS::MILS, EDA_UNITS::MM, EDA_UNITS::INCH,
EDA_UNITS::DEGREES, EDA_UNITS::FS, EDA_UNITS::PS };
static const std::vector<EDA_UNITS> pcbUnits = { EDA_UNITS::MILS, EDA_UNITS::MM, EDA_UNITS::INCH,
EDA_UNITS::FS, EDA_UNITS::PS, EDA_UNITS::DEGREES,
EDA_UNITS::UNSCALED, EDA_UNITS::PERCENT };
return pcbUnits;
}
@ -703,9 +706,11 @@ double PCBEXPR_UNIT_RESOLVER::Convert( const wxString& aString, int unitId ) con
case 0: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::MILS, aString );
case 1: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::MM, aString );
case 2: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::INCH, aString );
case 3: return v;
case 4: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::FS, aString );
case 5: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::PS, aString );
case 3: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::FS, aString );
case 4: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::PS, aString );
case 5: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::DEGREES, aString );
case 6: return v;
case 7: return EDA_UNIT_UTILS::UI::DoubleValueFromString( pcbIUScale, EDA_UNITS::PERCENT, aString );
default: return v;
}
};

View File

@ -21,6 +21,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "core/typeinfo.h"
#include <algorithm>
#include <cstdio>
#include <memory>
@ -1367,7 +1368,12 @@ static void hasComponentClassFunc( LIBEVAL::CONTEXT* aCtx, void* self )
footprint = item->GetParentFootprint();
if( !footprint )
return 0.0;
{
if( item->Type() == PCB_PAD_T )
footprint = dynamic_cast<PAD*>( item )->GetParentFootprint();
else
return 0.0;
}
const COMPONENT_CLASS* compClass = footprint->GetComponentClass();

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

@ -21,6 +21,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "drc/drc_rule.h"
#include <bitmaps.h>
#include <pcb_group.h>
#include <tool/tool_manager.h>
@ -310,17 +311,18 @@ wxString BOARD_INSPECTION_TOOL::InspectDRCErrorMenuText( const std::shared_ptr<R
return menuDescription( PCB_ACTIONS::inspectClearance );
}
else if( aDRCItem->GetErrorCode() == DRCE_TEXT_HEIGHT
|| aDRCItem->GetErrorCode() == DRCE_TEXT_THICKNESS
|| aDRCItem->GetErrorCode() == DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG
|| aDRCItem->GetErrorCode() == DRCE_TRACK_WIDTH
|| aDRCItem->GetErrorCode() == DRCE_TRACK_ANGLE
|| aDRCItem->GetErrorCode() == DRCE_TRACK_SEGMENT_LENGTH
|| aDRCItem->GetErrorCode() == DRCE_VIA_DIAMETER
|| aDRCItem->GetErrorCode() == DRCE_ANNULAR_WIDTH
|| aDRCItem->GetErrorCode() == DRCE_DRILL_OUT_OF_RANGE
|| aDRCItem->GetErrorCode() == DRCE_MICROVIA_DRILL_OUT_OF_RANGE
|| aDRCItem->GetErrorCode() == DRCE_CONNECTION_WIDTH
|| aDRCItem->GetErrorCode() == DRCE_ASSERTION_FAILURE )
|| aDRCItem->GetErrorCode() == DRCE_TEXT_THICKNESS
|| aDRCItem->GetErrorCode() == DRCE_DIFF_PAIR_UNCOUPLED_LENGTH_TOO_LONG
|| aDRCItem->GetErrorCode() == DRCE_TRACK_WIDTH
|| aDRCItem->GetErrorCode() == DRCE_TRACK_ANGLE
|| aDRCItem->GetErrorCode() == DRCE_TRACK_SEGMENT_LENGTH
|| aDRCItem->GetErrorCode() == DRCE_PAD_FANOUT_RATIO
|| aDRCItem->GetErrorCode() == DRCE_VIA_DIAMETER
|| aDRCItem->GetErrorCode() == DRCE_ANNULAR_WIDTH
|| aDRCItem->GetErrorCode() == DRCE_DRILL_OUT_OF_RANGE
|| aDRCItem->GetErrorCode() == DRCE_MICROVIA_DRILL_OUT_OF_RANGE
|| aDRCItem->GetErrorCode() == DRCE_CONNECTION_WIDTH
|| aDRCItem->GetErrorCode() == DRCE_ASSERTION_FAILURE )
{
return menuDescription( PCB_ACTIONS::inspectConstraints );
}
@ -449,6 +451,21 @@ void BOARD_INSPECTION_TOOL::InspectDRCError( const std::shared_ptr<RC_ITEM>& aDR
reportMax( m_frame, constraint ) ) );
break;
case DRCE_PAD_FANOUT_RATIO:
r = dialog->AddHTMLPage( _( "Pad fanout ratio" ) );
reportHeader( _( "Pad fanout ratio resolution for:" ), a, r );
if( compileError )
reportCompileError( r );
constraint = drcEngine->EvalRules( PAD_FANOUT_RATIO_CONSTRAINT, a, b, layer, r );
r->Report( "" );
r->Report( wxString::Format( _( "Resolved fanout ratio constraints: min %s; max %s." ),
reportMin( m_frame, constraint ),
reportMax( m_frame, constraint ) ) );
break;
case DRCE_TRACK_ANGLE:
r = dialog->AddHTMLPage( _( "Track Angle" ) );
reportHeader( _( "Track Angle resolution for:" ), a, r );

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,9 @@
(version 1)
(rule "test footprint fanout point symmetry"
(constraint footprint_fanout_point_symmetry (max 100))
)
(rule "test footprint fanout mirror symmetry"
(constraint footprint_fanout_mirror_symmetry (max 100))
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 0.6,
"width": 1.575
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "ignore",
"clearance": "ignore",
"connection_width": "ignore",
"copper_edge_clearance": "ignore",
"copper_sliver": "ignore",
"courtyards_overlap": "ignore",
"creepage": "ignore",
"diff_pair_gap_out_of_range": "ignore",
"diff_pair_uncoupled_length_too_long": "ignore",
"drill_out_of_range": "ignore",
"duplicate_footprints": "ignore",
"extra_footprint": "ignore",
"footprint": "ignore",
"footprint_fanout_mirror_symmetry": "error",
"footprint_fanout_point_symmetry": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "ignore",
"footprint_type_mismatch": "ignore",
"hole_clearance": "ignore",
"hole_near_hole": "error",
"hole_to_hole": "ignore",
"holes_co_located": "ignore",
"invalid_outline": "ignore",
"isolated_copper": "ignore",
"item_on_disabled_layer": "ignore",
"items_not_allowed": "ignore",
"length_out_of_range": "ignore",
"lib_footprint_issues": "ignore",
"lib_footprint_mismatch": "ignore",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "ignore",
"mirrored_text_on_front_layer": "ignore",
"missing_courtyard": "ignore",
"missing_footprint": "ignore",
"net_conflict": "ignore",
"nonmirrored_text_on_back_layer": "ignore",
"npth_inside_courtyard": "ignore",
"pad_fanout_ratio": "ignore",
"pad_fanout_via_distance": "ignore",
"padstack": "ignore",
"pth_inside_courtyard": "ignore",
"shorting_items": "ignore",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "ignore",
"solder_mask_bridge": "ignore",
"starved_thermal": "ignore",
"text_height": "ignore",
"text_thickness": "ignore",
"through_hole_pad_without_hole": "ignore",
"too_many_vias": "ignore",
"track_angle": "ignore",
"track_dangling": "ignore",
"track_segment_length": "ignore",
"track_width": "ignore",
"tracks_crossing": "ignore",
"unconnected_items": "ignore",
"unresolved_variable": "ignore",
"via_dangling": "ignore",
"zones_intersect": "ignore"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 1,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "footprint_fanout_combined.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"legacy_lib_dir": "",
"legacy_lib_list": []
},
"sheets": [],
"text_variables": {}
}

View File

@ -0,0 +1,5 @@
(version 1)
(rule "test footprint fanout mirror symmetry"
(constraint footprint_fanout_mirror_symmetry (max 100))
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 1.4,
"width": 1.025
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "ignore",
"clearance": "ignore",
"connection_width": "ignore",
"copper_edge_clearance": "ignore",
"copper_sliver": "ignore",
"courtyards_overlap": "ignore",
"creepage": "ignore",
"diff_pair_gap_out_of_range": "ignore",
"diff_pair_uncoupled_length_too_long": "ignore",
"drill_out_of_range": "ignore",
"duplicate_footprints": "ignore",
"extra_footprint": "ignore",
"footprint": "ignore",
"footprint_fanout_mirror_symmetry": "error",
"footprint_fanout_torque": "ignore",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "ignore",
"footprint_type_mismatch": "ignore",
"hole_clearance": "ignore",
"hole_near_hole": "error",
"hole_to_hole": "ignore",
"holes_co_located": "ignore",
"invalid_outline": "ignore",
"isolated_copper": "ignore",
"item_on_disabled_layer": "ignore",
"items_not_allowed": "ignore",
"length_out_of_range": "ignore",
"lib_footprint_issues": "ignore",
"lib_footprint_mismatch": "ignore",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "ignore",
"mirrored_text_on_front_layer": "ignore",
"missing_courtyard": "ignore",
"missing_footprint": "ignore",
"net_conflict": "ignore",
"nonmirrored_text_on_back_layer": "ignore",
"npth_inside_courtyard": "ignore",
"pad_fanout_ratio": "ignore",
"pad_fanout_via_distance": "ignore",
"padstack": "ignore",
"pth_inside_courtyard": "ignore",
"shorting_items": "ignore",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "ignore",
"solder_mask_bridge": "ignore",
"starved_thermal": "ignore",
"text_height": "ignore",
"text_thickness": "ignore",
"through_hole_pad_without_hole": "ignore",
"too_many_vias": "ignore",
"track_angle": "ignore",
"track_dangling": "ignore",
"track_segment_length": "ignore",
"track_width": "ignore",
"tracks_crossing": "ignore",
"unconnected_items": "ignore",
"unresolved_variable": "ignore",
"via_dangling": "ignore",
"zones_intersect": "ignore"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 1,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "footprint_fanout_mirror_symmetry.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"legacy_lib_dir": "",
"legacy_lib_list": []
},
"sheets": [],
"text_variables": {}
}

View File

@ -0,0 +1,5 @@
(version 1)
(rule "test footprint fanout point symmetry"
(constraint footprint_fanout_point_symmetry (max 100))
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 1.4,
"width": 1.025
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "ignore",
"clearance": "ignore",
"connection_width": "ignore",
"copper_edge_clearance": "ignore",
"copper_sliver": "ignore",
"courtyards_overlap": "ignore",
"creepage": "ignore",
"diff_pair_gap_out_of_range": "ignore",
"diff_pair_uncoupled_length_too_long": "ignore",
"drill_out_of_range": "ignore",
"duplicate_footprints": "ignore",
"extra_footprint": "ignore",
"footprint": "ignore",
"footprint_fanout_mirror_symmetry": "ignore",
"footprint_fanout_point_symmetry": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "ignore",
"footprint_type_mismatch": "ignore",
"hole_clearance": "ignore",
"hole_near_hole": "error",
"hole_to_hole": "ignore",
"holes_co_located": "ignore",
"invalid_outline": "ignore",
"isolated_copper": "ignore",
"item_on_disabled_layer": "ignore",
"items_not_allowed": "ignore",
"length_out_of_range": "ignore",
"lib_footprint_issues": "ignore",
"lib_footprint_mismatch": "ignore",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "ignore",
"mirrored_text_on_front_layer": "ignore",
"missing_courtyard": "ignore",
"missing_footprint": "ignore",
"net_conflict": "ignore",
"nonmirrored_text_on_back_layer": "ignore",
"npth_inside_courtyard": "ignore",
"pad_fanout_ratio": "ignore",
"pad_fanout_via_distance": "ignore",
"padstack": "ignore",
"pth_inside_courtyard": "ignore",
"shorting_items": "ignore",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "ignore",
"solder_mask_bridge": "ignore",
"starved_thermal": "ignore",
"text_height": "ignore",
"text_thickness": "ignore",
"through_hole_pad_without_hole": "ignore",
"too_many_vias": "ignore",
"track_angle": "ignore",
"track_dangling": "ignore",
"track_segment_length": "ignore",
"track_width": "ignore",
"tracks_crossing": "ignore",
"unconnected_items": "ignore",
"unresolved_variable": "ignore",
"via_dangling": "ignore",
"zones_intersect": "ignore"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "footprint_fanout_point_symmetry.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"legacy_lib_dir": "",
"legacy_lib_list": []
},
"sheets": [],
"text_variables": {}
}

View File

@ -0,0 +1,5 @@
(version 1)
(rule "test pad fanout ratio"
(constraint pad_fanout_ratio (max 45%))
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,301 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 1.4,
"width": 1.025
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "ignore",
"clearance": "ignore",
"connection_width": "ignore",
"copper_edge_clearance": "ignore",
"copper_sliver": "ignore",
"courtyards_overlap": "ignore",
"creepage": "ignore",
"diff_pair_gap_out_of_range": "ignore",
"diff_pair_uncoupled_length_too_long": "ignore",
"drill_out_of_range": "ignore",
"duplicate_footprints": "ignore",
"extra_footprint": "ignore",
"footprint": "ignore",
"footprint_fanout_mirror_symmetry": "ignore",
"footprint_fanout_point_symmetry": "ignore",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "ignore",
"footprint_type_mismatch": "ignore",
"hole_clearance": "ignore",
"hole_near_hole": "error",
"hole_to_hole": "ignore",
"holes_co_located": "ignore",
"invalid_outline": "ignore",
"isolated_copper": "ignore",
"item_on_disabled_layer": "ignore",
"items_not_allowed": "ignore",
"length_out_of_range": "ignore",
"lib_footprint_issues": "ignore",
"lib_footprint_mismatch": "ignore",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "ignore",
"mirrored_text_on_front_layer": "ignore",
"missing_courtyard": "ignore",
"missing_footprint": "ignore",
"net_conflict": "ignore",
"nonmirrored_text_on_back_layer": "ignore",
"npth_inside_courtyard": "ignore",
"pad_fanout_ratio": "error",
"pad_fanout_via_distance": "ignore",
"padstack": "ignore",
"pth_inside_courtyard": "ignore",
"shorting_items": "ignore",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "ignore",
"solder_mask_bridge": "ignore",
"starved_thermal": "ignore",
"text_height": "ignore",
"text_thickness": "ignore",
"through_hole_pad_without_hole": "ignore",
"too_many_vias": "ignore",
"track_angle": "ignore",
"track_dangling": "ignore",
"track_segment_length": "ignore",
"track_width": "ignore",
"tracks_crossing": "ignore",
"unconnected_items": "ignore",
"unresolved_variable": "ignore",
"via_dangling": "ignore",
"zones_intersect": "ignore"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "pad_fanout_ratio.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"legacy_lib_dir": "",
"legacy_lib_list": []
},
"sheets": [],
"text_variables": {}
}

View File

@ -0,0 +1,5 @@
(version 1)
(rule "test pad fanout length"
(constraint pad_fanout_via_distance (min 1mm))
)

View File

@ -0,0 +1,741 @@
(kicad_pcb
(version 20241229)
(generator "pcbnew")
(generator_version "9.99")
(general
(thickness 1.6)
(legacy_teardrops no)
)
(paper "A4")
(layers
(0 "F.Cu" signal)
(2 "B.Cu" signal)
(9 "F.Adhes" user "F.Adhesive")
(11 "B.Adhes" user "B.Adhesive")
(13 "F.Paste" user)
(15 "B.Paste" user)
(5 "F.SilkS" user "F.Silkscreen")
(7 "B.SilkS" user "B.Silkscreen")
(1 "F.Mask" user)
(3 "B.Mask" user)
(17 "Dwgs.User" user "User.Drawings")
(19 "Cmts.User" user "User.Comments")
(21 "Eco1.User" user "User.Eco1")
(23 "Eco2.User" user "User.Eco2")
(25 "Edge.Cuts" user)
(27 "Margin" user)
(31 "F.CrtYd" user "F.Courtyard")
(29 "B.CrtYd" user "B.Courtyard")
(35 "F.Fab" user)
(33 "B.Fab" user)
(39 "User.1" user)
(41 "User.2" user)
(43 "User.3" user)
(45 "User.4" user)
(47 "User.5" user)
(49 "User.6" user)
(51 "User.7" user)
(53 "User.8" user)
(55 "User.9" user)
)
(setup
(stackup
(layer "F.SilkS"
(type "Top Silk Screen")
)
(layer "F.Paste"
(type "Top Solder Paste")
)
(layer "F.Mask"
(type "Top Solder Mask")
(thickness 0.01)
)
(layer "F.Cu"
(type "copper")
(thickness 0.035)
)
(layer "dielectric 1"
(type "core")
(thickness 1.51)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "B.Cu"
(type "copper")
(thickness 0.035)
)
(layer "B.Mask"
(type "Bottom Solder Mask")
(thickness 0.01)
)
(layer "B.Paste"
(type "Bottom Solder Paste")
)
(layer "B.SilkS"
(type "Bottom Silk Screen")
)
(copper_finish "None")
(dielectric_constraints no)
)
(pad_to_mask_clearance 0)
(allow_soldermask_bridges_in_footprints no)
(tenting front back)
(pcbplotparams
(layerselection 0x00000000_00000000_55555555_5755f5ff)
(plot_on_all_layers_selection 0x00000000_00000000_00000000_00000000)
(disableapertmacros no)
(usegerberextensions no)
(usegerberattributes yes)
(usegerberadvancedattributes yes)
(creategerberjobfile yes)
(dashed_line_dash_ratio 12.000000)
(dashed_line_gap_ratio 3.000000)
(svgprecision 4)
(plotframeref no)
(mode 1)
(useauxorigin no)
(hpglpennumber 1)
(hpglpenspeed 20)
(hpglpendiameter 15.000000)
(pdf_front_fp_property_popups yes)
(pdf_back_fp_property_popups yes)
(pdf_metadata yes)
(pdf_single_document no)
(dxfpolygonmode yes)
(dxfimperialunits yes)
(dxfusepcbnewfont yes)
(psnegative no)
(psa4output no)
(plot_black_and_white yes)
(plotinvisibletext no)
(sketchpadsonfab no)
(plotpadnumbers no)
(hidednponfab no)
(sketchdnponfab yes)
(crossoutdnponfab yes)
(subtractmaskfromsilk no)
(outputformat 1)
(mirror no)
(drillshape 1)
(scaleselection 1)
(outputdirectory "")
)
)
(net 0 "")
(net 1 "a")
(net 2 "b")
(footprint "Resistor_SMD:R_0805_2012Metric"
(layer "F.Cu")
(uuid "55007e31-7536-4ac1-a51a-ac7eeb872e88")
(at 115 120)
(descr "Resistor SMD 0805 (2012 Metric), square (rectangular) end terminal, IPC_7351 nominal, (Body size source: IPC-SM-782 page 72, https://www.pcb-3d.com/wordpress/wp-content/uploads/ipc-sm-782a_amendment_1_and_2.pdf), generated with kicad-footprint-generator")
(tags "resistor")
(property "Reference" "REF**"
(at 0 -1.65 0)
(layer "F.SilkS")
(hide yes)
(uuid "0d389c9c-6c2f-4a85-88bd-30f2195ee314")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "R_0805_2012Metric"
(at 0 1.65 0)
(layer "F.Fab")
(hide yes)
(uuid "4455d39f-6592-4606-8b7b-c7321190c608")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "77c46cdd-378a-4ce0-9fc3-e3f6ade6dd5a")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "dd3cb2ba-d3cf-42c5-9dc9-8584493f2be3")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr smd)
(fp_line
(start -0.227064 -0.735)
(end 0.227064 -0.735)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "35c8ef98-7fc4-438b-ac9a-2f27bb04a706")
)
(fp_line
(start -0.227064 0.735)
(end 0.227064 0.735)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "82668174-257f-4ca4-9f96-a3c7d2496d9c")
)
(fp_line
(start -1.68 -0.95)
(end 1.68 -0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "a87302b3-2663-4307-92c1-d92eb6d98aa9")
)
(fp_line
(start -1.68 0.95)
(end -1.68 -0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "c0ed33a2-bf27-4042-b401-0d9de40aec8e")
)
(fp_line
(start 1.68 -0.95)
(end 1.68 0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "d0504a37-2e09-4a0c-a7a3-786dbe2927d9")
)
(fp_line
(start 1.68 0.95)
(end -1.68 0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "05a1d88a-de00-48fc-b359-0fecc1792333")
)
(fp_line
(start -1 -0.625)
(end 1 -0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "121e6f91-d85b-4d25-8a8b-26c783dfa1b7")
)
(fp_line
(start -1 0.625)
(end -1 -0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "69f55891-668f-4208-a994-b424771602db")
)
(fp_line
(start 1 -0.625)
(end 1 0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "ea6d3488-6865-4d39-afac-46f42d90ed81")
)
(fp_line
(start 1 0.625)
(end -1 0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "a8c28a7d-e034-46f4-8f00-96dd8a4d5418")
)
(pad "1" smd roundrect
(at -0.9125 0)
(size 1.025 1.4)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.243902)
(net 2 "b")
(uuid "073f6d2f-5a52-4842-937e-38e4e0653d3b")
)
(pad "2" smd roundrect
(at 0.9125 0)
(size 1.025 1.4)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.243902)
(net 2 "b")
(uuid "c16a197f-ac4c-4484-84b9-8c132414a58f")
)
(embedded_fonts no)
(model "${KICAD8_3DMODEL_DIR}/Resistor_SMD.3dshapes/R_0805_2012Metric.wrl"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
)
(footprint "Resistor_SMD:R_0805_2012Metric"
(layer "F.Cu")
(uuid "95804b84-291a-4d91-b270-fc874932a705")
(at 115 115)
(descr "Resistor SMD 0805 (2012 Metric), square (rectangular) end terminal, IPC_7351 nominal, (Body size source: IPC-SM-782 page 72, https://www.pcb-3d.com/wordpress/wp-content/uploads/ipc-sm-782a_amendment_1_and_2.pdf), generated with kicad-footprint-generator")
(tags "resistor")
(property "Reference" "REF**"
(at 0 -1.65 0)
(layer "F.SilkS")
(hide yes)
(uuid "6a0e3bbc-f8b0-41e2-8f86-789fe308efe0")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Value" "R_0805_2012Metric"
(at 0 1.65 0)
(layer "F.Fab")
(hide yes)
(uuid "26aea571-1837-4046-bd81-939183c03ec8")
(effects
(font
(size 1 1)
(thickness 0.15)
)
)
)
(property "Datasheet" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "40b1a684-f250-404f-ab85-6751dfbb5fc6")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(property "Description" ""
(at 0 0 0)
(unlocked yes)
(layer "F.Fab")
(hide yes)
(uuid "299bf097-bcaa-4ac1-9572-fb06c0468900")
(effects
(font
(size 1.27 1.27)
(thickness 0.15)
)
)
)
(attr smd)
(fp_line
(start -0.227064 -0.735)
(end 0.227064 -0.735)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "271224a0-1752-4642-b149-bea22cdf700d")
)
(fp_line
(start -0.227064 0.735)
(end 0.227064 0.735)
(stroke
(width 0.12)
(type solid)
)
(layer "F.SilkS")
(uuid "ce20f34b-c24e-456f-b809-8b20267dd3a8")
)
(fp_line
(start -1.68 -0.95)
(end 1.68 -0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "1900ed80-cccd-485f-81cc-835ce0832f08")
)
(fp_line
(start -1.68 0.95)
(end -1.68 -0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "f453edd8-03e4-480a-83d6-90e9431eb819")
)
(fp_line
(start 1.68 -0.95)
(end 1.68 0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "c43227aa-d0b5-45f1-9762-773dbb694a49")
)
(fp_line
(start 1.68 0.95)
(end -1.68 0.95)
(stroke
(width 0.05)
(type solid)
)
(layer "F.CrtYd")
(uuid "468c8dc1-a4fa-479a-9b8d-4af6577c83bd")
)
(fp_line
(start -1 -0.625)
(end 1 -0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "79c7710d-9328-4bda-9b86-8d0a49ca028a")
)
(fp_line
(start -1 0.625)
(end -1 -0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "347a759d-80de-454a-b172-8866309c3410")
)
(fp_line
(start 1 -0.625)
(end 1 0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "bf746ac4-72fb-4b51-86c5-7a4da5951f62")
)
(fp_line
(start 1 0.625)
(end -1 0.625)
(stroke
(width 0.1)
(type solid)
)
(layer "F.Fab")
(uuid "f2d5b8c8-3103-4785-9eb1-28993c4bf298")
)
(pad "1" smd roundrect
(at -0.9125 0)
(size 1.025 1.4)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.243902)
(net 1 "a")
(uuid "bd2a90a2-d53e-4f5f-baea-886a12d2cdf9")
)
(pad "2" smd roundrect
(at 0.9125 0)
(size 1.025 1.4)
(layers "F.Cu" "F.Mask" "F.Paste")
(roundrect_rratio 0.243902)
(net 1 "a")
(uuid "a5178535-c334-4d2a-8f1a-37bb469733b1")
)
(embedded_fonts no)
(model "${KICAD8_3DMODEL_DIR}/Resistor_SMD.3dshapes/R_0805_2012Metric.wrl"
(offset
(xyz 0 0 0)
)
(scale
(xyz 1 1 1)
)
(rotate
(xyz 0 0 0)
)
)
)
(gr_line
(start 130 150)
(end 100 150)
(stroke
(width 0.05)
(type default)
)
(layer "Edge.Cuts")
(uuid "6fb7d833-f07b-4c6b-8cc4-4f516667c624")
)
(gr_line
(start 100 100)
(end 130 100)
(stroke
(width 0.05)
(type default)
)
(layer "Edge.Cuts")
(uuid "914b4d58-bf5e-4ae1-8193-402067f045c0")
)
(gr_line
(start 100 150)
(end 100 100)
(stroke
(width 0.05)
(type default)
)
(layer "Edge.Cuts")
(uuid "ab3b346f-73c0-45ab-a3c3-ed6c74a90ba3")
)
(gr_line
(start 130 100)
(end 130 150)
(stroke
(width 0.05)
(type default)
)
(layer "Edge.Cuts")
(uuid "c708431e-b75c-4c05-9d39-4f91e60551e1")
)
(segment
(start 117.5 114.5)
(end 117 115)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "1ab94135-e54e-405a-b68a-597cdd3f46d6")
)
(segment
(start 117 115)
(end 115.9125 115)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "29c9d040-1a6a-483e-83b0-bc96004b8329")
)
(segment
(start 114.0875 115)
(end 112.5 115)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "3617ebac-a977-4e5a-8951-0d91f0ba83fd")
)
(segment
(start 117.5 113.5)
(end 117.5 114.5)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "50d47861-8a94-43eb-a355-0e47495696e8")
)
(segment
(start 112 113.5)
(end 112.5 113)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "533f5c6d-6abf-486d-8ffc-65dae3c07f63")
)
(segment
(start 112.5 113)
(end 117 113)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "8b6d6222-04f2-410d-961b-06e93e036579")
)
(segment
(start 112 114.5)
(end 112 113.5)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "9999020a-eaf5-4bc1-87d0-600b9b9d5084")
)
(segment
(start 112.5 115)
(end 112 114.5)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "c88cea1d-9e11-474c-96bb-72d4c5776dfd")
)
(segment
(start 117 113)
(end 117.5 113.5)
(width 0.381)
(layer "F.Cu")
(net 1)
(uuid "e85c7449-766e-4a2e-a115-a33151e46fd0")
)
(via
(at 112.5 115)
(size 0.8)
(drill 0.3)
(layers "F.Cu" "B.Cu")
(net 1)
(uuid "c13854b5-a7f7-4dd2-89ad-e5844eb07619")
)
(via
(at 117.5 113.5)
(size 0.8)
(drill 0.3)
(layers "F.Cu" "B.Cu")
(net 1)
(uuid "ef54020d-b77a-433b-ae75-b627c2453261")
)
(segment
(start 115.5 118)
(end 112.5 118)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "0025cd6f-a163-4542-ba9e-cea34b6efec8")
)
(segment
(start 112.5 120)
(end 113 120)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "25f21657-7702-4f47-a531-0438429e4fe9")
)
(segment
(start 116.3 118.8)
(end 115.5 118)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "3e4310a9-4390-4154-834f-b9b5c6fbd6df")
)
(segment
(start 117.4 119.6)
(end 117 120)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "417954bb-d7cf-4e64-9488-318a1908e12c")
)
(segment
(start 116.3 118.8)
(end 117 118.8)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "4288a461-9a57-4618-b174-e6fe53510064")
)
(segment
(start 112.5 118)
(end 112 118.5)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "774e7827-f674-45a3-8860-b0d3f2ad1512")
)
(segment
(start 117.4 119.2)
(end 117.4 119.6)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "7ad041c8-4a55-46c0-a08d-46e60bd3a0b3")
)
(segment
(start 112 119.5)
(end 112.5 120)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "88535165-0121-40e7-b304-5564ff0118c3")
)
(segment
(start 112 118.5)
(end 112 119.5)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "a7331cf8-abda-43ff-b3f0-82174f38de35")
)
(segment
(start 117 120)
(end 115.9125 120)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "d208d1ec-24a1-4215-870d-6e15df337a94")
)
(segment
(start 114.0875 120)
(end 113 120)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "db08c018-1429-485a-a0f0-1cf257287e6c")
)
(segment
(start 117 118.8)
(end 117.4 119.2)
(width 0.381)
(layer "F.Cu")
(net 2)
(uuid "fd74fe00-db48-44ab-b69b-1e34506debb0")
)
(via
(at 116.3 118.8)
(size 0.8)
(drill 0.3)
(layers "F.Cu" "B.Cu")
(net 2)
(uuid "61d09800-fe0b-4c58-ae05-f6f9d7fd639a")
)
(via
(at 113 120)
(size 0.8)
(drill 0.3)
(layers "F.Cu" "B.Cu")
(net 2)
(uuid "8938687d-21dc-47dc-b99b-361f13186405")
)
(embedded_fonts no)
)

View File

@ -0,0 +1,288 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 1.4,
"width": 1.025
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "ignore",
"clearance": "ignore",
"connection_width": "ignore",
"copper_edge_clearance": "ignore",
"copper_sliver": "ignore",
"courtyards_overlap": "ignore",
"creepage": "ignore",
"diff_pair_gap_out_of_range": "ignore",
"diff_pair_uncoupled_length_too_long": "ignore",
"drill_out_of_range": "ignore",
"duplicate_footprints": "ignore",
"extra_footprint": "ignore",
"footprint": "ignore",
"footprint_fanout_mirror_symmetry": "ignore",
"footprint_fanout_point_symmetry": "ignore",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_mismatch": "ignore",
"footprint_type_mismatch": "ignore",
"hole_clearance": "ignore",
"hole_near_hole": "error",
"hole_to_hole": "ignore",
"holes_co_located": "ignore",
"invalid_outline": "ignore",
"isolated_copper": "ignore",
"item_on_disabled_layer": "ignore",
"items_not_allowed": "ignore",
"length_out_of_range": "ignore",
"lib_footprint_issues": "ignore",
"lib_footprint_mismatch": "ignore",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "ignore",
"mirrored_text_on_front_layer": "ignore",
"missing_courtyard": "ignore",
"missing_footprint": "ignore",
"net_conflict": "ignore",
"nonmirrored_text_on_back_layer": "ignore",
"npth_inside_courtyard": "ignore",
"pad_fanout_ratio": "ignore",
"pad_fanout_via_distance": "error",
"padstack": "ignore",
"pth_inside_courtyard": "ignore",
"shorting_items": "ignore",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "ignore",
"solder_mask_bridge": "ignore",
"starved_thermal": "ignore",
"text_height": "ignore",
"text_thickness": "ignore",
"through_hole_pad_without_hole": "ignore",
"too_many_vias": "ignore",
"track_angle": "ignore",
"track_dangling": "ignore",
"track_segment_length": "ignore",
"track_width": "ignore",
"tracks_crossing": "ignore",
"unconnected_items": "ignore",
"unresolved_variable": "ignore",
"via_dangling": "ignore",
"zones_intersect": "ignore"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.0,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "pad_fanout_via_distance.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
}
],
"meta": {
"version": 4
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"legacy_lib_dir": "",
"legacy_lib_list": []
},
"sheets": [],
"text_variables": {}
}

View File

@ -75,6 +75,7 @@ set( QA_PCBNEW_SRCS
drc/test_drc_lengths.cpp
drc/test_drc_unconnected_items_exclusion_loss.cpp
drc/test_drc_via_dangling.cpp
drc/test_drc_fanout_checks.cpp
pcb_io/altium/test_altium_rule_transformer.cpp
pcb_io/altium/test_altium_pcblib_import.cpp

View File

@ -0,0 +1,104 @@
/*
* 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 <qa_utils/wx_utils/unit_test_utils.h>
#include <pcbnew_utils/board_test_utils.h>
#include <board.h>
#include <board_design_settings.h>
#include <pad.h>
#include <pcb_track.h>
#include <pcb_marker.h>
#include <footprint.h>
#include <drc/drc_item.h>
#include <settings/settings_manager.h>
struct DRC_REGRESSION_TEST_FIXTURE
{
// clang-format off
DRC_REGRESSION_TEST_FIXTURE() :
m_settingsManager( true /* headless */ )
{ }
// clang-format on
SETTINGS_MANAGER m_settingsManager;
std::unique_ptr<BOARD> m_board;
};
BOOST_FIXTURE_TEST_CASE( DRCFanoutChecks, DRC_REGRESSION_TEST_FIXTURE )
{
// Check for errors in pad and footprint fanout.
// clang-format off
std::vector<std::pair<wxString, int>> tests =
{
{ "fanout_checks/pad_fanout_ratio", 4 },
{ "fanout_checks/footprint_fanout_combined", 8 },
{ "fanout_checks/footprint_fanout_point_symmetry", 4 },
{ "fanout_checks/footprint_fanout_mirror_symmetry", 4 },
{ "fanout_checks/pad_fanout_via_distance", 2 },
};
// clang-format on
for( const std::pair<wxString, int>& test : tests )
{
KI_TEST::LoadBoard( m_settingsManager, test.first, m_board );
KI_TEST::FillZones( m_board.get() );
std::vector<DRC_ITEM> violations;
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
bds.m_DRCEngine->SetViolationHandler(
[&]( const std::shared_ptr<DRC_ITEM>& aItem, VECTOR2I aPos, int aLayer,
DRC_CUSTOM_MARKER_HANDLER* aCustomHandler )
{
if( bds.GetSeverity( aItem->GetErrorCode() ) == SEVERITY::RPT_SEVERITY_ERROR )
violations.push_back( *aItem );
} );
bds.m_DRCEngine->RunTests( EDA_UNITS::MM, true, false );
if( violations.size() == test.second )
{
BOOST_CHECK_EQUAL( 1, 1 ); // quiet "did not check any assertions" warning
BOOST_TEST_MESSAGE( wxString::Format( "DRC skew: %s, passed", test.first ) );
}
else
{
UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCH );
std::map<KIID, EDA_ITEM*> itemMap;
m_board->FillItemMap( itemMap );
for( const DRC_ITEM& item : violations )
{
BOOST_TEST_MESSAGE(
item.ShowReport( &unitsProvider, RPT_SEVERITY_ERROR, itemMap ) );
}
BOOST_ERROR( wxString::Format( "DRC skew: %s, failed (violations found %d expected %d)",
test.first, (int) violations.size(), test.second ) );
}
}
}

View File

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