Compare commits

...

25 Commits

Author SHA1 Message Date
Daniel Treffenstädt
0e4b478041 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 03:00:44 +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
29 changed files with 10239 additions and 57 deletions

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

@ -281,6 +281,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

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

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

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

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

@ -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 ) );
}
}
}